import { Controller } from "@hotwired/stimulus"

// Editor do romaneio: salva cada campo assim que ele muda e reatualiza a prévia
// (que é o PDF de verdade, num iframe).
//
// Por que salvar POR CAMPO e não com um submit de formulário: `campos_editados`
// precisa registrar exatamente o campo que um humano tocou. Um submit em bloco não
// distingue "redigitei o mesmo valor" de "nunca olhei" — marcaria os 5 campos das 29
// linhas como editados, e a reimportação seguinte preservaria endereços velhos no
// plano inteiro. A regra de reconciliação (Romaneios::Importador) só funciona com
// escrita por campo.
export default class extends Controller {
  // `overlay` é o <details> da prévia (nome mantido para não renomear em vão);
  // `caixaPreview` saiu junto com o modal.
  static targets = ["celula", "preview", "itemVeiculo", "motorista", "rotulo", "status", "linha", "busca",
                    "contador", "overlay", "tituloPreview"]
  static values = { url: String, pdfUrl: String, veiculo: String }

  // Recarregar o PDF a cada tecla travaria a tela; 600ms depois do último save é o
  // suficiente para parecer imediato sem regerar o documento à toa.
  static ATRASO_PREVIEW = 600

  connect() {
    this.timerPreview = null
    // A prévia nasce fechada e DESATUALIZADA: o PDF só é gerado na primeira vez
    // que alguém pede para ver. Antes o iframe era montado a cada carregamento da
    // tela e gerava um PDF que muitas vezes ninguém olhava.
    this.previewDesatualizado = true
    // Com 72 carros a lista rola; sem isto, ao trocar de veículo o selecionado
    // pode nascer fora da área visível e parece que a seleção não pegou.
    const atual = this.itemVeiculoTargets.find((a) => a.dataset.atual === "1")
    if (atual) atual.scrollIntoView({ block: "nearest", inline: "nearest" })
  }

  disconnect() {
    if (this.timerPreview) clearTimeout(this.timerPreview)
  }

  // ── Edição ──────────────────────────────────────────────────────────────
  salvarCelula(event) {
    const input = event.currentTarget
    // Sair do campo sem mudar nada não é edição — marcar como editado aqui
    // congelaria o valor contra a próxima reimportação sem o operador querer.
    if (input.value === input.dataset.original) return

    this.gravar(input.dataset.linhaId, input.dataset.campo, input.value, input)
  }

  restaurar(event) {
    const botao = event.currentTarget
    const { linhaId, campo } = botao.dataset
    const input = this.celulaTargets.find(
      (c) => c.dataset.linhaId === linhaId && c.dataset.campo === campo
    )
    // valor null no corpo = "devolve o que o plano trouxe e destrava o campo"
    this.gravar(linhaId, campo, null, input, botao)
  }

  alternarAparelho(event) {
    const botao = event.currentTarget
    const novo = botao.dataset.valor === "SIM" ? "" : "SIM"
    this.gravar(botao.dataset.linhaId, "aparelho", novo, null, null, (dados) => {
      botao.dataset.valor = dados.valor
      botao.textContent = dados.valor || "—"
      const estilo = dados.valor === "SIM"
        ? "bg-green-600/20 text-green-300 border-green-600/40"
        : "bg-[#1a1a1a] text-gray-500 border-white/10"
      botao.className = `${estilo} w-full min-h-[40px] rounded-lg border font-semibold text-xs hover:border-orange-500 transition-colors`
      this.marcar(botao.dataset.linhaId, "aparelho", dados.editado)
    })
  }

  salvarRotulo() {
    this.enviar(`${this.urlValue}/atualizar_rotulo`, { rotulo_plano: this.rotuloTarget.value })
      .then(() => this.agendarPreview())
  }

  salvarMotorista() {
    // Não mexe na prévia: o nome do motorista não sai impresso — a linha do
    // rodapé é preenchida à mão na retirada.
    this.enviar(`${this.urlValue}/atualizar_veiculo`, {
      veiculo: this.veiculoValue,
      motorista: this.motoristaTarget.value
    })
  }

  // ── Navegação e filtro ──────────────────────────────────────────────────
  // A lista de veículos são LINKS de verdade (o veículo vive na query string), então
  // clicar já navega sem JS. As setas só precisam achar o vizinho na mesma lista —
  // é o que o operador faz o dia inteiro: percorrer carro a carro.
  anterior() { this.mover(-1) }
  proximo() { this.mover(1) }

  mover(passo) {
    const itens = this.itemVeiculoTargets
    if (itens.length === 0) return

    const atual = itens.findIndex((a) => a.dataset.atual === "1")
    const destino = (atual === -1 ? 0 : atual) + passo
    // Sem dar a volta de propósito: chegar na ponta e voltar ao primeiro carro faz
    // o operador reimprimir folha que já separou, sem perceber.
    if (destino < 0 || destino >= itens.length) return

    window.location.href = itens[destino].href
  }

  filtrar() {
    const termo = this.hasBuscaTarget ? this.buscaTarget.value.trim().toLowerCase() : ""
    let visiveis = 0

    this.linhaTargets.forEach((linha) => {
      const casa = termo === "" || linha.dataset.busca.includes(termo)
      linha.hidden = !casa
      if (casa) visiveis += 1
    })

    // Sem o contador, uma busca que não casa com nada deixa a tabela vazia e
    // parece plano vazio — o operador acha que perdeu o romaneio.
    if (!this.hasContadorTarget) return
    const total = this.linhaTargets.length
    const plural = visiveis === 1 ? "parada" : "paradas"
    this.contadorTarget.textContent = termo === ""
      ? `${total} ${total === 1 ? "parada" : "paradas"} nesta folha`
      : `${visiveis} de ${total} ${plural} — filtrando por "${termo}"`
  }

  limparBusca() {
    if (!this.hasBuscaTarget) return
    this.buscaTarget.value = ""
    this.filtrar()
    this.buscaTarget.focus()
  }

  // ── Prévia (pop-up; estado na URL, ?previa=1) ───────────────────────────
  // Abrir, fechar e trocar de folha são LINKS comuns — nada disso depende deste
  // controller, e é o que mantém a prévia viva quando o JS não carrega. O
  // overlay só existe no DOM quando a prévia está aberta, então a presença do
  // alvo já é a resposta. O que sobra aqui é recarregar a folha após uma edição.
  previewAberta() {
    return this.hasOverlayTarget
  }

  carregarPreview() {
    if (!this.hasPreviewTarget) return

    // Cache-bust em vez de reload(): reload reinicializa o visualizador de PDF do
    // navegador e a tela pisca a cada salvamento.
    this.previewTarget.src =
      `${this.pdfUrlValue}?veiculo=${encodeURIComponent(this.veiculoValue)}&t=${Date.now()}`
    this.previewDesatualizado = false
  }

  // Chamada depois de cada gravação. Com o pop-up FECHADO não gera PDF nenhum —
  // o `loading="lazy"` do iframe já garante isso, e recarregar o src de um
  // iframe escondido forçaria o servidor a montar um PDF que ninguém vai ver.
  atualizarPreview() {
    if (!this.previewAberta()) {
      this.previewDesatualizado = true
      return
    }
    this.carregarPreview()
  }


  // ── Infra ───────────────────────────────────────────────────────────────
  gravar(linhaId, campo, valor, input, botao, aoConcluir) {
    this.status("Salvando…")

    this.enviar(`${this.urlValue}/atualizar_linha`, { linha_id: linhaId, campo, valor })
      .then((dados) => {
        if (!dados || !dados.ok) return
        if (input) {
          // Escrevemos de volta o valor QUE O SERVIDOR GRAVOU: a escrita é
          // last-write-wins, e assim uma sobreposição de duas abas fica visível em
          // vez de silenciosa.
          input.value = dados.valor
          input.dataset.original = dados.valor
        }
        this.marcar(linhaId, campo, dados.editado)
        if (aoConcluir) aoConcluir(dados)
        this.status("Salvo")
        this.agendarPreview()
      })
      .catch(() => this.status("Não deu para salvar — tente de novo"))
  }

  marcar(linhaId, campo, editado) {
    const alvo = this.element.querySelector(`[data-romaneio-marcador="${linhaId}-${campo}"]`)
    if (!alvo) return

    if (!editado) {
      alvo.innerHTML = ""
      return
    }
    alvo.innerHTML =
      `<button type="button" title="Editado — não será sobrescrito na reimportação. Clique para restaurar o valor do plano." ` +
      `data-action="romaneio#restaurar" data-linha-id="${linhaId}" data-campo="${campo}" ` +
      `class="text-orange-400 hover:text-orange-200 text-xs leading-none">●</button>`
  }

  agendarPreview() {
    if (this.timerPreview) clearTimeout(this.timerPreview)
    this.timerPreview = setTimeout(() => this.atualizarPreview(), this.constructor.ATRASO_PREVIEW)
  }

  enviar(url, corpo) {
    return fetch(url, {
      method: "PATCH",
      headers: {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]')?.content
      },
      body: JSON.stringify(corpo)
    }).then((r) => (r.ok ? r.json() : Promise.reject(r)))
  }

  status(texto) {
    if (this.hasStatusTarget) this.statusTarget.textContent = texto
  }
}
