<%# app/views/admin/edicao_lancamentos/show.html.erb %>
<%# Card estilizado com foto da fachada, edição inline (click-to-edit, salva por
    campo direto na API do SimpliRoute) e histórico duplo (nosso AuditoriaLog +
    /history/ da API). Os dados do card chegam via fetch (endpoint buscar). %>
<div class="space-y-6" id="edicao-app"
     data-buscar-url="<%= buscar_admin_edicao_lancamento_path %>"
     data-carregar-url="<%= carregar_admin_edicao_lancamento_path %>"
     data-atualizar-url="<%= atualizar_admin_edicao_lancamento_path %>"
     data-historico-url="<%= historico_admin_edicao_lancamento_path %>"
     data-csrf="<%= form_authenticity_token %>"
     data-motivos="<%= @motivos.to_json %>">

  <%# Header %>
  <div>
    <h1 class="text-2xl font-bold text-white">Editar Lançamento</h1>
    <p class="text-gray-400 text-sm mt-0.5">Corrige status, motivo e dados de checkout de uma entrega direto no SimpliRoute</p>
  </div>

  <%# Flash %>
  <%= render 'shared/flash' %>

  <%# Aviso de sincronização %>
  <div class="p-4 bg-amber-900/20 border border-amber-500/40 rounded-xl text-amber-200/90 text-sm flex items-start gap-2">
    <%= icone :alerta, cor: nil, tamanho: 'w-5 h-5 mt-0.5', espaco: false %>
    <p>As alterações são gravadas no <strong>SimpliRoute na hora</strong> (cada campo salva ao confirmar <%= icone :confirmar, cor: nil, tamanho: 'w-3.5 h-3.5' %>),
       mas o painel e as consolidações só refletem <strong>após a próxima sincronização</strong> da base de
       rastreio. Toda alteração fica registrada na auditoria.</p>
  </div>

  <%# Busca por NF %>
  <div class="bg-[#111] border border-white/5 rounded-2xl p-5">
    <label class="block text-sm font-medium text-gray-300 mb-2">Buscar entrega pela NF</label>
    <div class="flex gap-2">
      <input id="campo-nf" type="text" inputmode="numeric" placeholder="Ex: 79774"
             class="flex-1 bg-[#1a1a1a] border border-white/10 rounded-xl px-4 py-3 text-white
                    placeholder-gray-600 focus:outline-none focus:border-orange-500 min-h-[48px]">
      <button id="btn-buscar" type="button"
              class="px-6 py-3 bg-[#f97316] hover:bg-orange-500 text-white font-semibold rounded-xl
                     transition-colors min-h-[48px] whitespace-nowrap">
        Buscar
      </button>
    </div>
    <%# Período — a API do SimpliRoute só filtra visita por UM dia, então achar
        uma NF exige saber em quais dias procurar. Sem período, procuramos nas
        datas que o painel já conhece; quando o plano foi duplicado para outra
        data, o painel não sabe dela e é preciso ampliar aqui. %>
    <div class="flex flex-wrap items-end gap-2 mt-3">
      <div>
        <label class="block text-xs text-gray-500 mb-1" for="campo-de">De</label>
        <input id="campo-de" type="date"
               class="bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-2 text-white text-sm
                      focus:outline-none focus:border-orange-500 min-h-[42px]">
      </div>
      <div>
        <label class="block text-xs text-gray-500 mb-1" for="campo-ate">Até</label>
        <input id="campo-ate" type="date"
               class="bg-[#1a1a1a] border border-white/10 rounded-xl px-3 py-2 text-white text-sm
                      focus:outline-none focus:border-orange-500 min-h-[42px]">
      </div>
      <button id="btn-30dias" type="button"
              class="px-4 py-2 bg-[#1a1a1a] hover:bg-[#252525] border border-white/10 text-gray-200
                     text-sm rounded-xl transition-colors min-h-[42px] whitespace-nowrap">
        Últimos 30 dias
      </button>
      <button id="btn-limpar-periodo" type="button"
              class="px-3 py-2 text-gray-500 hover:text-gray-300 text-sm min-h-[42px]">Limpar</button>
    </div>
    <p class="mt-2 text-xs text-gray-500">
      Sem período, procuramos só nas datas que o painel conhece. Se o plano foi duplicado para outra
      data, amplie o período — a API do SimpliRoute não busca NF por intervalo, então varremos dia a
      dia (máx. 62 dias).
    </p>
    <p id="busca-erro" class="hidden mt-2 text-sm text-red-400"></p>
    <p id="busca-loading" class="hidden mt-2 text-sm text-gray-400">Consultando SimpliRoute…</p>
    <p id="busca-periodo" class="hidden mt-2 text-xs text-gray-500"></p>
    <div id="busca-falhas" class="hidden mt-2 p-3 rounded-xl bg-red-900/20 border border-red-500/40 text-xs text-red-200 space-y-1"></div>
  </div>

  <%# Lançamentos da NF — só aparece quando há mais de um. Plano duplicado no
      SimpliRoute cria uma visita nova com a MESMA NF, e a antiga (a que tem o
      motorista) continua valendo: aqui o ADM vê as duas e escolhe qual editar. %>
  <div id="ocorrencias" class="hidden bg-[#111] border border-white/5 rounded-2xl p-5 space-y-3">
    <p class="text-sm text-gray-300">
      <span id="oc-contador" class="font-semibold text-white"></span> <span id="oc-dica"></span>
    </p>
    <div id="oc-lista" class="space-y-2"></div>
  </div>

  <%# Card do lançamento (preenchido via JS após a busca) %>
  <div id="card" class="hidden bg-[#111] border border-white/5 rounded-2xl overflow-hidden">

    <%# Header do card %>
    <div class="p-5 border-b border-white/5 flex flex-wrap items-start justify-between gap-3">
      <div class="min-w-0">
        <div class="flex items-center gap-3 flex-wrap">
          <h2 id="c-titulo" class="text-white font-bold text-lg truncate">—</h2>
          <span id="c-badge" class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold bg-gray-800 text-gray-400">—</span>
        </div>
        <p id="c-endereco" class="text-gray-400 text-sm mt-1">—</p>
        <div class="flex flex-wrap gap-2 mt-2 text-xs text-gray-500">
          <span class="px-2 py-0.5 rounded-full bg-white/5">NF <span id="c-nf">—</span></span>
          <span class="px-2 py-0.5 rounded-full bg-white/5">Visita <span id="c-id">—</span></span>
          <span class="px-2 py-0.5 rounded-full bg-white/5"><%= icone :caminhao %> <span id="c-motorista">—</span></span>
          <span class="px-2 py-0.5 rounded-full bg-white/5"><%= icone :veiculo %> <span id="c-veiculo">—</span></span>
          <span class="px-2 py-0.5 rounded-full bg-white/5"><%= icone :telefone %> <span id="c-telefone">—</span></span>
        </div>
      </div>
      <button id="btn-historico" type="button"
              class="px-4 py-2.5 bg-[#1a1a1a] hover:bg-[#252525] border border-white/10 text-gray-200
                     text-sm font-semibold rounded-xl transition-colors whitespace-nowrap">
        <%= icone :historico %> Histórico
      </button>
    </div>

    <div class="grid lg:grid-cols-3 gap-0">

      <%# Coluna esquerda — galeria com TODAS as fotos do lançamento %>
      <div class="p-5 border-b lg:border-b-0 lg:border-r border-white/5 space-y-3">
        <p class="text-xs font-semibold text-gray-500 uppercase tracking-wider">
          Fotos do lançamento <span id="c-fotos-contador" class="normal-case font-normal text-gray-600"></span>
        </p>
        <div id="c-fotos" class="space-y-3"></div>
      </div>

      <%# Coluna direita — campos click-to-edit %>
      <div class="lg:col-span-2 p-5">
        <p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3">
          Dados do lançamento <span class="normal-case font-normal text-gray-600">— clique num valor para editar</span>
        </p>
        <div id="c-campos" class="grid sm:grid-cols-2 gap-3"></div>
      </div>
    </div>
  </div>

  <%# Modal de histórico %>
  <div id="modal-hist" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4">
    <div id="modal-hist-overlay" class="absolute inset-0 bg-black/70"></div>
    <div class="relative bg-[#151515] border border-white/10 rounded-2xl w-full max-w-2xl max-h-[85vh] flex flex-col">
      <div class="flex items-center justify-between p-4 border-b border-white/5">
        <h3 class="text-white font-bold flex items-center gap-2"><%= icone :historico, espaco: false %> Histórico de alterações</h3>
        <button id="modal-hist-fechar" type="button"
                class="w-9 h-9 rounded-lg bg-white/5 hover:bg-white/10 text-gray-300" aria-label="Fechar"><%= icone :fechar, cor: nil, tamanho: 'w-5 h-5', espaco: false %></button>
      </div>
      <div id="modal-hist-corpo" class="p-4 overflow-y-auto space-y-5 text-sm">
        <p class="text-gray-400">Carregando…</p>
      </div>
    </div>
  </div>

  <%# Visualizador de foto — amplia e navega entre todas as imagens do lançamento %>
  <div id="modal-foto" class="hidden fixed inset-0 z-50 flex items-center justify-center p-4">
    <div id="modal-foto-overlay" class="absolute inset-0 bg-black/85"></div>
    <div class="relative w-full max-w-3xl flex flex-col gap-3">
      <div class="flex items-center justify-between gap-3">
        <p id="modal-foto-titulo" class="text-white font-semibold text-sm"></p>
        <div class="flex items-center gap-2">
          <a id="modal-foto-abrir" href="#" target="_blank" rel="noopener"
             class="px-3 py-2 rounded-lg bg-white/5 hover:bg-white/10 text-gray-300 text-xs inline-flex items-center">
            <%= icone :visualizar, cor: nil, tamanho: 'w-4 h-4' %> Tamanho real
          </a>
          <button id="modal-foto-fechar" type="button"
                  class="w-9 h-9 rounded-lg bg-white/5 hover:bg-white/10 text-gray-300 flex items-center justify-center"
                  aria-label="Fechar"><%= icone :fechar, cor: nil, tamanho: 'w-5 h-5', espaco: false %></button>
        </div>
      </div>
      <div class="relative bg-[#151515] border border-white/10 rounded-2xl overflow-hidden">
        <img id="modal-foto-img" src="" alt="" class="w-full max-h-[70vh] object-contain bg-black">
        <button id="modal-foto-ant" type="button"
                class="absolute left-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/70 hover:bg-black
                       text-white flex items-center justify-center" aria-label="Foto anterior">
          <%= icone :voltar, cor: nil, tamanho: 'w-5 h-5', espaco: false %>
        </button>
        <button id="modal-foto-prox" type="button"
                class="absolute right-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-black/70 hover:bg-black
                       text-white flex items-center justify-center" aria-label="Próxima foto">
          <%= icone :avancar, cor: nil, tamanho: 'w-5 h-5', espaco: false %>
        </button>
      </div>
    </div>
  </div>
</div>

<script>
  (function () {
    const app  = document.getElementById('edicao-app');
    const URLS = {
      buscar:    app.dataset.buscarUrl,
      carregar:  app.dataset.carregarUrl,
      atualizar: app.dataset.atualizarUrl,
      historico: app.dataset.historicoUrl
    };
    const CSRF    = app.dataset.csrf;
    const MOTIVOS = JSON.parse(app.dataset.motivos || '[]');

    let visita      = null; // estado atual do lançamento carregado
    let ocorrencias = [];   // todos os lançamentos da NF buscada
    let selecionado = null; // tracking_id do lançamento aberto no card

    // ── Config dos campos editáveis ─────────────────────────────
    // Ícone do sprite (public/icons.svg). fill=currentColor -> herda a cor do badge.
    function ico(id, cls) {
      return '<svg class="' + (cls || 'w-3.5 h-3.5') + '" fill="currentColor" ' +
             'style="display:inline-block;vertical-align:-2px"><use href="/icons.svg#' + id + '"></use></svg>';
    }

    const STATUS = {
      completed: { label: 'Completa',  cls: 'bg-green-600 text-white',           icone: 'check-circle-fill' },
      failed:    { label: 'Falha',     cls: 'bg-red-600 text-white',             icone: 'x-circle-fill' },
      pending:   { label: 'Pendente',  cls: 'bg-yellow-500 text-black',          icone: 'clock-fill' },
      partial:   { label: 'Parcial',   cls: 'bg-blue-600 text-white',            icone: 'hourglass-split' },
      canceled:  { label: 'Cancelada', cls: 'bg-gray-700 text-gray-300',         icone: 'slash-circle' }
    };
    const CAMPOS = [
      { campo: 'status',               label: 'Status',               tipo: 'status' },
      { campo: 'checkout_observation', label: 'Motivo (insucesso)',   tipo: 'motivo' },
      { campo: 'checkout_comment',     label: 'Comentário do checkout', tipo: 'textarea', col2: true },
      { campo: 'notes',                label: 'Observações (notes)',  tipo: 'textarea', col2: true },
      { campo: 'checkout_time',        label: 'Data/hora do checkout', tipo: 'datetime' },
      { campo: 'checkout_latitude',    label: 'Latitude',             tipo: 'texto' },
      { campo: 'checkout_longitude',   label: 'Longitude',            tipo: 'texto' }
    ];

    const esc = s => String(s ?? '').replace(/[&<>"']/g,
      m => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[m]));

    const motivoLabel = uuid => (MOTIVOS.find(m => m.id === uuid) || {}).label || null;

    // ── Busca por NF ────────────────────────────────────────────
    const campoNf  = document.getElementById('campo-nf');
    const erroEl   = document.getElementById('busca-erro');
    const loadEl   = document.getElementById('busca-loading');
    const cardEl   = document.getElementById('card');

    const campoDe   = document.getElementById('campo-de');
    const campoAte  = document.getElementById('campo-ate');
    const periodoEl = document.getElementById('busca-periodo');
    const falhasEl  = document.getElementById('busca-falhas');

    async function buscar() {
      const nf = campoNf.value.trim();
      erroEl.classList.add('hidden');
      periodoEl.classList.add('hidden');
      falhasEl.classList.add('hidden');
      if (!nf) { mostrarErroBusca('Informe o número da NF.'); return; }

      loadEl.classList.remove('hidden');
      cardEl.classList.add('hidden');
      ocorrencias = [];
      selecionado = null;
      try {
        let url = URLS.buscar + '?nf=' + encodeURIComponent(nf);
        if (campoDe.value)  url += '&de='  + encodeURIComponent(campoDe.value);
        if (campoAte.value) url += '&ate=' + encodeURIComponent(campoAte.value);
        const resp = await fetch(url, { headers: { 'Accept': 'application/json' } });
        const data = await resp.json();
        if (!data.ok) { renderOcorrencias(); mostrarErroBusca(data.erro || 'Não foi possível localizar a entrega.'); return; }
        ocorrencias = data.ocorrencias || [];
        renderPeriodo(data.periodo, data.falhas);
        // Só veio o card pronto quando a NF tem um lançamento único; com mais de
        // um, o ADM escolhe na lista antes de abrir qualquer coisa.
        if (data.visita) {
          abrirCard(data.visita);
        } else {
          visita = null;
          renderOcorrencias();
          // Tela em branco é pior que erro: se não há card E a lista ficou
          // escondida, o ADM precisa saber por quê em vez de achar que a NF
          // não existe.
          if (ocEl.classList.contains('hidden')) {
            mostrarErroBusca(ocorrencias.length
              ? 'O lançamento foi localizado mas não pôde ser aberto — veja os avisos acima.'
              : 'Nenhum lançamento localizado para esta NF no período consultado.');
          }
        }
      } catch (e) {
        // Mostra o erro real: "Erro de conexão" genérico escondeu uma exceção de
        // renderização e deixou a tela muda.
        console.error('[edicao-lancamento] falha na busca', e);
        mostrarErroBusca('Falha ao processar a busca: ' + ((e && e.message) || e));
      } finally {
        loadEl.classList.add('hidden');
      }
    }
    function mostrarErroBusca(msg) { erroEl.textContent = msg; erroEl.classList.remove('hidden'); }

    // Dizer o que foi consultado e o que falhou. Uma busca que devolve menos do
    // que existe precisa admitir isso — senão parece que a NF não existe.
    function renderPeriodo(periodo, falhas) {
      if (periodo && periodo.dias) {
        periodoEl.textContent = 'Consultado: ' + fmtData(periodo.de) +
          (periodo.dias > 1 ? ' a ' + fmtData(periodo.ate) + ' (' + periodo.dias + ' dias)' : '') + '.';
        periodoEl.classList.remove('hidden');
      }
      if (falhas && falhas.length) {
        falhasEl.innerHTML = '<p class="font-semibold">A API do SimpliRoute falhou em ' + falhas.length +
          ' dia(s) — pode haver lançamento faltando na lista:</p>' +
          falhas.map(f => '<p>' + esc(f.data) + ' — ' + esc(f.erro) + '</p>').join('');
        falhasEl.classList.remove('hidden');
      }
    }

    document.getElementById('btn-buscar').addEventListener('click', buscar);
    campoNf.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); buscar(); } });

    document.getElementById('btn-30dias').addEventListener('click', () => {
      const hoje = new Date();
      const trinta = new Date(hoje.getTime() - 30 * 86400000);
      const iso = d => d.toISOString().slice(0, 10);
      campoDe.value  = iso(trinta);
      campoAte.value = iso(hoje);
      if (campoNf.value.trim()) buscar();
    });
    document.getElementById('btn-limpar-periodo').addEventListener('click', () => {
      campoDe.value = ''; campoAte.value = '';
    });

    // ── Lista de lançamentos da NF ──────────────────────────────
    const ocEl    = document.getElementById('ocorrencias');
    const ocLista = document.getElementById('oc-lista');
    const ocCont  = document.getElementById('oc-contador');
    const ocDica  = document.getElementById('oc-dica');

    // ⚠️ NÃO usar new Date('2026-07-15') para data pura: o JS lê como meia-noite
    // UTC e o toLocale em UTC-3 devolve o DIA ANTERIOR. Data planejada não é um
    // instante — formata direto do texto ISO.
    const fmtData = v => {
      if (!v) return '—';
      const s = String(v);
      const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})/);
      if (iso) return iso[3] + '/' + iso[2] + '/' + iso[1];
      const d = new Date(s);
      return isNaN(d) ? s.slice(0, 10) : d.toLocaleDateString('pt-BR');
    };
    const fmtHora = v => {
      if (!v) return null;
      const d = new Date(v);
      return isNaN(d) ? null : d.toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
    };

    // Com um lançamento editável só, a lista some — a tela fica igual ao que era
    // antes. Fica visível se ele não for editável, para explicar o porquê.
    function renderOcorrencias() {
      const n = ocorrencias.length;
      if (n === 0 || (n === 1 && ocorrencias[0].editavel)) {
        ocEl.classList.add('hidden'); ocLista.innerHTML = ''; return;
      }

      // O bloco nasce com `hidden` no ERB — sem tirar a classe aqui, a lista é
      // montada e fica invisível (a tela parecia não ter achado nada).
      ocEl.classList.remove('hidden');
      ocCont.textContent = n + (n === 1 ? ' lançamento' : ' lançamentos');
      ocDica.textContent = n > 1 ? 'para esta NF — escolha qual quer editar.' : 'para esta NF.';
      ocLista.innerHTML = ocorrencias.map((o, i) => {
        const cfg   = STATUS[o.status] || { label: o.status || '—', cls: 'bg-gray-800 text-gray-400', icone: 'question-circle' };
        const ativo = selecionado != null && String(selecionado) === String(o.tracking_id);
        return '' +
          '<div class="flex flex-wrap items-center justify-between gap-3 p-3 rounded-xl border ' +
               (ativo ? 'border-orange-500/60 bg-orange-500/10' : 'border-white/10 bg-[#1a1a1a]') + '">' +
            '<div class="min-w-0">' +
              '<div class="flex items-center gap-2 flex-wrap">' +
                '<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-bold ' + cfg.cls + '">' +
                  ico(cfg.icone) + ' ' + esc(cfg.label) + '</span>' +
                '<span class="text-white text-sm font-semibold">' + esc(fmtData(o.data)) + '</span>' +
                '<span class="text-xs text-gray-500">Visita ' + esc(o.id ?? '—') + '</span>' +
              '</div>' +
              '<p class="text-xs text-gray-400 mt-1">' +
                // "sem motorista" (o painel sabe e está vazio) é diferente de
                // "não identificado" (a visita nem está no painel ainda).
                (o.motorista ? esc(o.motorista)
                             : '<span class="text-gray-600">' + (o.no_painel ? 'sem motorista' : 'motorista não identificado') + '</span>') +
                ' · ' +
                (o.veiculo ? esc(o.veiculo) : '<span class="text-gray-600">' + (o.no_painel ? 'sem veículo' : 'veículo não identificado') + '</span>') +
                (fmtHora(o.checkout_time) ? ' · <span class="text-gray-300">checkout ' + esc(fmtHora(o.checkout_time)) + '</span>' : '') +
              '</p>' +
              (o.aviso ? '<p class="text-xs text-amber-400 mt-1">' + esc(o.aviso) + '</p>' : '') +
            '</div>' +
            (o.editavel
              ? '<button type="button" data-oc="' + i + '" class="px-4 py-2 text-sm font-semibold rounded-xl transition-colors ' +
                (ativo ? 'bg-white/5 text-gray-400 border border-white/10' : 'bg-[#f97316] hover:bg-orange-500 text-white') + '">' +
                (ativo ? 'Editando' : 'Editar') + '</button>'
              : '') +
          '</div>';
      }).join('');
    }

    ocLista.addEventListener('click', e => {
      const btn = e.target.closest('[data-oc]');
      if (btn) abrirOcorrencia(ocorrencias[Number(btn.dataset.oc)]);
    });

    async function abrirOcorrencia(o) {
      if (!o || !o.id) return;
      erroEl.classList.add('hidden');
      loadEl.classList.remove('hidden');
      cardEl.classList.add('hidden');
      try {
        const resp = await fetch(URLS.carregar + '?visit_id=' + encodeURIComponent(o.id) +
                                 (o.tracking_id ? '&tracking_id=' + encodeURIComponent(o.tracking_id) : ''),
                                 { headers: { 'Accept': 'application/json' } });
        const data = await resp.json();
        if (!data.ok) { mostrarErroBusca(data.erro || 'Não foi possível abrir este lançamento.'); return; }
        abrirCard(data.visita);
        cardEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
      } catch (e) {
        mostrarErroBusca('Erro de conexão ao abrir o lançamento.');
      } finally {
        loadEl.classList.add('hidden');
      }
    }

    function abrirCard(v) {
      visita      = v;
      selecionado = v.tracking_id;
      renderCard();
      renderOcorrencias(); // destaca na lista qual está aberto
      cardEl.classList.remove('hidden');
    }

    // ── Card ────────────────────────────────────────────────────
    function renderCard() {
      const v = visita;
      document.getElementById('c-titulo').textContent    = v.titulo || '—';
      document.getElementById('c-endereco').textContent  = v.endereco || '—';
      document.getElementById('c-nf').textContent        = v.nf ?? '—';
      document.getElementById('c-id').textContent        = v.id ?? '—';
      document.getElementById('c-motorista').textContent = v.motorista || '—';
      document.getElementById('c-veiculo').textContent   = v.veiculo || '—';
      document.getElementById('c-telefone').textContent  = v.telefone || '—';
      renderBadge();
      renderFotos();
      renderCampos();
    }

    function renderBadge() {
      const cfg = STATUS[visita.status] || { label: visita.status || '—', cls: 'bg-gray-800 text-gray-400', icone: 'question-circle' };
      const b = document.getElementById('c-badge');
      b.className = 'inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-xs font-bold ' + cfg.cls;
      b.innerHTML = ico(cfg.icone) + ' ' + esc(cfg.label);
    }

    // Etiqueta de cada tipo de imagem — o ADM precisa saber o que está olhando
    // para julgar se a foto está errada.
    // Cor da etiqueta por CATEGORIA (cat vem do servidor). O texto do rótulo
    // também vem pronto do servidor (f.rotulo), porque os campos de foto do
    // SimpliRoute variam por conta. `cls` = etiqueta translúcida (foto grande em
    // destaque); `txt` = só a cor do texto (miniatura, sobre fundo preto sólido —
    // a versão translúcida some sobre foto clara).
    const COR_CAT = {
      fachada:    { cls: 'bg-orange-500/20 text-orange-300 border-orange-500/40', txt: 'text-orange-300' },
      prova:      { cls: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/40', txt: 'text-emerald-300' },
      nf:         { cls: 'bg-blue-500/20 text-blue-300 border-blue-500/40',       txt: 'text-blue-300' },
      termo:      { cls: 'bg-amber-500/20 text-amber-300 border-amber-500/40',    txt: 'text-amber-300' },
      relatorio:  { cls: 'bg-cyan-500/20 text-cyan-300 border-cyan-500/40',       txt: 'text-cyan-300' },
      assinatura: { cls: 'bg-purple-500/20 text-purple-300 border-purple-500/40', txt: 'text-purple-300' },
      entrega:    { cls: 'bg-sky-500/20 text-sky-300 border-sky-500/40',          txt: 'text-sky-300' },
      extra:      { cls: 'bg-white/10 text-gray-300 border-white/20',             txt: 'text-gray-300' }
    };
    const COR_PADRAO = COR_CAT.extra;

    function renderFotos() {
      const fotos = visita.fotos || [];
      const wrap  = document.getElementById('c-fotos');
      const cont  = document.getElementById('c-fotos-contador');

      cont.textContent = fotos.length ? '— ' + fotos.length + (fotos.length > 1 ? ' imagens' : ' imagem') : '';

      if (!fotos.length) {
        wrap.innerHTML =
          '<div class="w-full h-52 rounded-xl border border-dashed border-white/10 flex items-center justify-center ' +
               'text-gray-600 text-sm">' + ico('camera-fill') + ' Nenhuma foto registrada neste lançamento</div>';
        return;
      }

      // A 1ª em destaque; as demais numa grade de 3. Clicar abre o visualizador.
      // Na miniatura a etiqueta é compacta e truncada (senão estoura a célula);
      // o rótulo completo + origem aparecem no visualizador.
      const figura = (f, i, destaque) => {
        const c = COR_CAT[f.cat] || COR_PADRAO;
        const etiqueta = destaque
          ? '<figcaption class="absolute top-1.5 left-1.5 px-1.5 py-0.5 rounded text-[10px] font-bold uppercase ' +
                                'tracking-wide border ' + c.cls + '">' + esc(f.rotulo) + '</figcaption>' +
            '<span class="absolute bottom-1.5 right-1.5 px-1.5 py-0.5 rounded bg-black/70 text-gray-300 text-[10px]">' +
              esc(f.origem) + '</span>'
          : '<figcaption class="absolute top-1 left-1 right-1 px-1 py-0.5 rounded text-[9px] font-bold uppercase ' +
                                'tracking-tight truncate bg-black/80 ' + c.txt + '">' + esc(f.rotulo) + '</figcaption>';

        return '<figure class="relative group cursor-zoom-in" data-foto-idx="' + i + '" ' +
                       'title="' + esc(f.rotulo + ' · ' + f.origem) + ' — clique para ampliar">' +
          '<img src="' + esc(f.url) + '" alt="' + esc(f.rotulo) + '" loading="lazy" ' +
               'class="w-full ' + (destaque ? 'h-52' : 'h-24') + ' object-cover rounded-xl border border-white/10 ' +
                      'group-hover:opacity-90 transition-opacity">' +
          etiqueta +
        '</figure>';
      };

      const resto = fotos.slice(1).map((f, i) => figura(f, i + 1, false)).join('');

      wrap.innerHTML = figura(fotos[0], 0, true) +
        (resto ? '<div class="grid grid-cols-3 gap-2">' + resto + '</div>' : '');

      wrap.querySelectorAll('[data-foto-idx]').forEach(el =>
        el.addEventListener('click', () => abrirVisualizador(+el.dataset.fotoIdx)));
    }

    // ── Visualizador (lightbox) — amplia e navega entre as fotos ──────────
    let fotoAtual = 0;

    function abrirVisualizador(i) {
      fotoAtual = i;
      document.getElementById('modal-foto').classList.remove('hidden');
      pintarVisualizador();
    }
    function fecharVisualizador() {
      document.getElementById('modal-foto').classList.add('hidden');
    }
    function navegarFoto(passo) {
      const fotos = visita.fotos || [];
      if (!fotos.length) return;
      fotoAtual = (fotoAtual + passo + fotos.length) % fotos.length;
      pintarVisualizador();
    }
    function pintarVisualizador() {
      const fotos = visita.fotos || [];
      const f = fotos[fotoAtual];
      if (!f) return;
      document.getElementById('modal-foto-img').src = f.url;
      document.getElementById('modal-foto-titulo').textContent =
        f.rotulo + ' · ' + f.origem + '  (' + (fotoAtual + 1) + '/' + fotos.length + ')';
      document.getElementById('modal-foto-abrir').href = f.url;
    }

    // ── Campos click-to-edit ────────────────────────────────────
    function valorExibicao(def) {
      const v = visita[def.campo];
      if (def.tipo === 'status')  return (STATUS[v] || {}).label || v || '—';
      if (def.tipo === 'motivo')  return v ? (motivoLabel(v) || v) : '— sem motivo —';
      if (def.tipo === 'datetime') {
        if (!v) return '—';
        const s = String(v);
        return s.slice(8, 10) + '/' + s.slice(5, 7) + '/' + s.slice(0, 4) + ' ' + s.slice(11, 16);
      }
      return (v === null || v === undefined || v === '') ? '—' : String(v);
    }

    function renderCampos() {
      const grid = document.getElementById('c-campos');
      grid.innerHTML = '';
      CAMPOS.forEach(def => {
        const div = document.createElement('div');
        div.className = 'campo group bg-[#1a1a1a] border border-white/5 rounded-xl px-4 py-3 cursor-pointer ' +
                        'hover:border-orange-500/50 transition-colors' + (def.col2 ? ' sm:col-span-2' : '');
        div.dataset.campo = def.campo;
        pintarExibicao(div, def);
        grid.appendChild(div);
      });
    }

    function pintarExibicao(div, def, flash) {
      div.innerHTML =
        '<p class="text-xs text-gray-500 mb-0.5">' + esc(def.label) + '</p>' +
        '<div class="flex items-start justify-between gap-2">' +
          '<p class="texto-valor text-sm text-white break-words min-h-[20px]">' + esc(valorExibicao(def)) + '</p>' +
          '<span class="text-gray-600 group-hover:text-orange-400" title="Editar">' + ico('pencil-fill') + '</span>' +
        '</div>';
      div.onclick = () => abrirEdicao(div, def);
      if (flash) {
        div.classList.add('ring-2', 'ring-green-500/70');
        setTimeout(() => div.classList.remove('ring-2', 'ring-green-500/70'), 1200);
      }
    }

    function abrirEdicao(div, def) {
      div.onclick = null;
      div.classList.remove('cursor-pointer');
      const atual = visita[def.campo];

      let editorHtml;
      const base = 'w-full bg-[#111] border border-orange-500/60 rounded-lg px-2.5 py-2 text-sm text-white focus:outline-none';
      if (def.tipo === 'status') {
        editorHtml = '<select class="ed ' + base + '">' +
          Object.entries(STATUS).map(([v, c]) =>
            '<option value="' + v + '"' + (v === atual ? ' selected' : '') + '>' + c.label + '</option>').join('') +
          '</select>';
      } else if (def.tipo === 'motivo') {
        editorHtml = '<select class="ed ' + base + '">' +
          '<option value="">— sem motivo —</option>' +
          MOTIVOS.map(m =>
            '<option value="' + esc(m.id) + '"' + (m.id === atual ? ' selected' : '') + '>' + esc(m.label) + '</option>').join('') +
          '</select>';
      } else if (def.tipo === 'textarea') {
        editorHtml = '<textarea rows="2" class="ed ' + base + '">' + esc(atual ?? '') + '</textarea>';
      } else if (def.tipo === 'datetime') {
        const valor = atual ? String(atual).slice(0, 16) : '';
        editorHtml = '<input type="datetime-local" value="' + esc(valor) + '" class="ed ' + base + '">';
      } else {
        editorHtml = '<input type="text" value="' + esc(atual ?? '') + '" class="ed ' + base + '">';
      }

      div.innerHTML =
        '<p class="text-xs text-orange-400 mb-1">' + esc(def.label) + '</p>' +
        editorHtml +
        '<div class="flex items-center gap-2 mt-2">' +
          '<button type="button" class="btn-ok px-3 py-1.5 bg-green-600 hover:bg-green-500 text-white text-xs font-bold rounded-lg">' + ico('check-lg', 'w-3 h-3') + ' Salvar</button>' +
          '<button type="button" class="btn-cancelar px-3 py-1.5 bg-white/5 hover:bg-white/10 text-gray-300 text-xs rounded-lg">' + ico('x-lg', 'w-3 h-3') + ' Cancelar</button>' +
          '<span class="msg-salvando hidden text-xs text-gray-400">Salvando…</span>' +
        '</div>' +
        '<p class="msg-erro hidden mt-1.5 text-xs text-red-400"></p>';

      const ed = div.querySelector('.ed');
      ed.focus();

      const cancelar = () => pintarExibicao(div, def) || div.classList.add('cursor-pointer');
      div.querySelector('.btn-cancelar').onclick = e => { e.stopPropagation(); cancelar(); };
      div.querySelector('.btn-ok').onclick       = e => { e.stopPropagation(); salvar(div, def, ed.value); };
      ed.addEventListener('keydown', e => {
        if (e.key === 'Escape') cancelar();
        if (e.key === 'Enter' && def.tipo !== 'textarea') { e.preventDefault(); salvar(div, def, ed.value); }
      });
    }

    async function salvar(div, def, valor) {
      const msgErro = div.querySelector('.msg-erro');
      const msgSalvando = div.querySelector('.msg-salvando');
      msgErro.classList.add('hidden');
      msgSalvando.classList.remove('hidden');
      try {
        const body = { visit_id: visita.id };
        body[def.campo] = valor;
        const resp = await fetch(URLS.atualizar, {
          method: 'PATCH',
          headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'X-CSRF-Token': CSRF
          },
          body: JSON.stringify(body)
        });
        const data = await resp.json();
        if (!data.ok) {
          msgErro.textContent = data.erro || 'Erro ao salvar.';
          msgErro.classList.remove('hidden');
          return;
        }
        // Atualiza o estado local com o que a API confirmou (ou mantém o atual
        // quando o valor não mudou — campos: []).
        if (data.campos && data.campos.length) Object.assign(visita, data.visita);
        pintarExibicao(div, def, data.campos && data.campos.length > 0);
        div.classList.add('cursor-pointer');
        if (def.campo === 'status') {
          renderBadge();
          // Mantém a lista coerente com o que acabou de ser gravado.
          const o = ocorrencias.find(x => String(x.tracking_id) === String(visita.tracking_id));
          if (o) { o.status = visita.status; renderOcorrencias(); }
        }
      } catch (e) {
        msgErro.textContent = 'Erro de conexão ao salvar.';
        msgErro.classList.remove('hidden');
      } finally {
        msgSalvando.classList.add('hidden');
      }
    }

    // ── Histórico (modal) ───────────────────────────────────────
    const modal = document.getElementById('modal-hist');
    const corpo = document.getElementById('modal-hist-corpo');

    function fecharModal() { modal.classList.add('hidden'); }
    document.getElementById('modal-hist-fechar').onclick  = fecharModal;
    document.getElementById('modal-hist-overlay').onclick = fecharModal;

    // ── Visualizador de foto ────────────────────────────────────
    document.getElementById('modal-foto-fechar').onclick  = fecharVisualizador;
    document.getElementById('modal-foto-overlay').onclick = fecharVisualizador;
    document.getElementById('modal-foto-ant').onclick     = () => navegarFoto(-1);
    document.getElementById('modal-foto-prox').onclick    = () => navegarFoto(1);

    document.addEventListener('keydown', e => {
      if (e.key === 'Escape') { fecharModal(); fecharVisualizador(); }
      // Setas só navegam com o visualizador aberto, senão atrapalham os campos.
      if (document.getElementById('modal-foto').classList.contains('hidden')) return;
      if (e.key === 'ArrowLeft')  navegarFoto(-1);
      if (e.key === 'ArrowRight') navegarFoto(1);
    });

    document.getElementById('btn-historico').addEventListener('click', async () => {
      if (!visita) return;
      modal.classList.remove('hidden');
      corpo.innerHTML = '<p class="text-gray-400">Carregando…</p>';
      try {
        const resp = await fetch(URLS.historico + '?visit_id=' + encodeURIComponent(visita.id),
                                 { headers: { 'Accept': 'application/json' } });
        const data = await resp.json();
        corpo.innerHTML = htmlHistorico(data);
      } catch (e) {
        corpo.innerHTML = '<p class="text-red-400">Erro ao carregar o histórico.</p>';
      }
    });

    function nomeCampo(campo) {
      const def = CAMPOS.find(c => c.campo === campo);
      return def ? def.label : campo;
    }
    function fmtValor(campo, valor) {
      if (valor === null || valor === undefined || valor === '') return '—';
      if (campo === 'checkout_observation') return motivoLabel(valor) || valor;
      if (campo === 'status') return (STATUS[valor] || {}).label || valor;
      return String(valor);
    }

    function htmlHistorico(data) {
      let html = '<div>' +
        '<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">' + ico('journal-text') + ' Nosso sistema (auditoria)</p>';
      const interno = (data.interno || []);
      if (!interno.length) {
        html += '<p class="text-gray-500">Nenhuma alteração registrada pelo sistema.</p>';
      } else {
        html += interno.map(l => {
          const mudancas = Object.keys(l.para || {}).map(campo =>
            '<li class="text-gray-300"><span class="text-gray-500">' + esc(nomeCampo(campo)) + ':</span> ' +
            '<span class="text-red-300/80 line-through">' + esc(fmtValor(campo, (l.de || {})[campo])) + '</span>' +
            ' → <span class="text-green-300">' + esc(fmtValor(campo, l.para[campo])) + '</span></li>').join('');
          return '<div class="bg-[#1a1a1a] border border-white/5 rounded-xl p-3 mb-2">' +
            '<div class="flex items-center justify-between text-xs text-gray-500 mb-1.5">' +
              '<span>' + ico('person-fill') + ' ' + esc(l.usuario) + '</span><span>' + esc(l.quando) + '</span>' +
            '</div>' +
            '<ul class="space-y-0.5 text-xs">' + (mudancas || '<li class="text-gray-500">—</li>') + '</ul>' +
          '</div>';
        }).join('');
      }
      html += '</div><div>' +
        '<p class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">' + ico('globe2') + ' SimpliRoute (API)</p>';
      const api = (data.api || []);
      if (!api.length) {
        html += '<p class="text-gray-500">Sem registros de histórico na API do SimpliRoute.</p>';
      } else {
        html += '<pre class="bg-[#1a1a1a] border border-white/5 rounded-xl p-3 text-xs text-gray-300 overflow-x-auto">' +
                esc(JSON.stringify(api, null, 2)) + '</pre>';
      }
      html += '</div>';
      return html;
    }
  })();
</script>
