"use client";

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ChevronLeft, ChevronRight, Download, FileImage, FileText, Film, ImageIcon, Loader2, Pause, Play, Volume2, VolumeX, X } from "lucide-react";
import { audiosDoProjeto, criarRelogio, duracaoDaPagina, paginaNoTempo, temposDoProjeto } from "@/lib/banner-editor/cena";
import { cssDasFontesDaMarca, fontesDaMarca, fontesDoDocumento, garantirFontes, htmlDasPaginas, numeracaoDosPosts, paginasDoDocumento, registrarFontesDaMarca, type AudioDoProjeto, type DocumentoDoBanner } from "@/lib/banner-editor/documento";
import { zipar } from "@/lib/banner-editor/zip";
import { CenaNoPalco } from "@/components/pagina-inicial/editor-de-banner/cena-no-palco";
import { TocadorDeAudios } from "@/components/pagina-inicial/editor-de-banner/tocador-de-audios";

/*
 * A vista do link público da arte: a arte no meio (parada, como o download;
 * tocando, como o play do editor, com o som), as páginas do carrossel e o
 * Baixar (PNG, JPG, PDF e, se tem animação ou som, MP4). Os arquivos saem do
 * servidor pela arte guardada (nunca por um documento mandado daqui); o MP4 é
 * montado neste navegador, com as camadas do servidor.
 */

type Formato = "png" | "jpg" | "pdf" | "mp4";
type Empresa = { nome: string | null; logo: string | null; cor: string | null };
type Andamento = { texto: string; fracao: number } | null;

const SEM_AUDIOS: AudioDoProjeto[] = [];

function baseDoNome(nome: string) {
  return (
    nome
      .normalize("NFD")
      .replace(/[̀-ͯ]/g, "")
      .replace(/[^a-zA-Z0-9 _-]/g, "")
      .trim()
      .replace(/\s+/g, "-")
      .slice(0, 60) || "arte"
  );
}

function entregar(arquivo: Blob, nome: string) {
  const url = URL.createObjectURL(arquivo);
  const a = document.createElement("a");
  a.href = url;
  a.download = nome;
  document.body.appendChild(a);
  a.click();
  a.remove();
  window.setTimeout(() => URL.revokeObjectURL(url), 60_000);
}

const relogioEmTexto = (s: number) => `${Math.floor(s / 60)}:${String(Math.floor(s % 60)).padStart(2, "0")}`;

/** Uma página parada: o mesmo HTML do editor e do download, num iframe no tamanho da arte. */
function PaginaParada({ pagina, escala }: { pagina: DocumentoDoBanner; escala: number }) {
  const html = useMemo(() => {
    const base = htmlDasPaginas([pagina], (p) => p.src);
    const fontes = cssDasFontesDaMarca(fontesDaMarca().map((f) => f.chave));
    return fontes ? base.replace("<style>", `<style>${fontes}`) : base;
  }, [pagina]);
  return <iframe title="Arte" srcDoc={html} sandbox="allow-scripts" className="absolute left-0 top-0 origin-top-left border-0" style={{ width: pagina.largura, height: pagina.altura, transform: `scale(${escala})` }} />;
}

export function VistaDaArte({ codigo, nome, empresa, documento }: { codigo: string; nome: string; empresa: Empresa; documento: DocumentoDoBanner }) {
  const paginas = useMemo(() => {
    const lista = paginasDoDocumento(documento);
    const contagens = numeracaoDosPosts(lista);
    return lista.map((p, i) => ({ ...p, numeracao: p.numeracao ?? contagens[i] }));
  }, [documento]);
  const audios = useMemo(() => audiosDoProjeto(paginas), [paginas]);
  const { inicios, total } = useMemo(() => temposDoProjeto(paginas), [paginas]);
  /* Tem o que tocar: animação, vídeo, transição entre cenas ou som. */
  const animada = useMemo(
    () => audios.length > 0 || paginas.some((p, i) => Boolean(p.animacao) || (i > 0 && Boolean(p.transicao)) || p.pecas.some((x) => Boolean(x.animacao) || (x.tipo === "imagem" && Boolean(x.video)))),
    [paginas, audios],
  );
  const relogio = useMemo(() => criarRelogio(), []);
  /*
   * A capa de cada cena na arte animada: um quadro de verdade do vídeo (as
   * entradas já feitas, uma legenda por vez), não a página com tudo junto.
   * A arte parada mostra o mesmo desenho do PNG.
   */
  const capaDa = useCallback((i: number) => (inicios[i] ?? 0) + Math.min(1.5, duracaoDaPagina(paginas[i] ?? paginas[0]) * 0.5), [inicios, paginas]);
  const naCapa = useRef(true);

  const [atual, setAtual] = useState(0);
  const [modo, setModo] = useState<"parada" | "tocando" | "pausada">(animada ? "pausada" : "parada");
  const [mudo, setMudo] = useState(false);
  const [painel, setPainel] = useState(false);
  const [formato, setFormato] = useState<Formato>(animada ? "mp4" : "png");
  const [todas, setTodas] = useState(false);
  const [grande, setGrande] = useState(true);
  const [andamento, setAndamento] = useState<Andamento>(null);
  const [erro, setErro] = useState<string | null>(null);
  const [palco, setPalco] = useState({ w: 0, h: 0 });
  const palcoRef = useRef<HTMLDivElement | null>(null);
  const quadro = useRef(0);
  const barra = useRef<HTMLDivElement | null>(null);
  const tempo = useRef<HTMLSpanElement | null>(null);
  const painelRef = useRef<HTMLDivElement | null>(null);
  const atualRef = useRef(0);
  atualRef.current = atual;

  /* As fontes da arte já começam a carregar (o play usa o desenho da página, fora do iframe). */
  useEffect(() => {
    for (const p of paginas) registrarFontesDaMarca(p.fontesProprias);
    garantirFontes(fontesDoDocumento(paginas));
  }, [paginas]);

  useEffect(() => {
    const el = palcoRef.current;
    if (!el) return;
    const medir = () => setPalco({ w: el.clientWidth, h: el.clientHeight });
    medir();
    const obs = new ResizeObserver(medir);
    obs.observe(el);
    return () => obs.disconnect();
  }, []);

  const pagina = paginas[Math.min(atual, paginas.length - 1)];
  const folga = palco.w < 640 ? 12 : 32;
  const escala = pagina && palco.w ? Math.max(0.05, Math.min((palco.w - folga * 2) / pagina.largura, (palco.h - folga * 2) / pagina.altura)) : 0;

  /* A barra do tempo anda pelo relógio, sem desenhar a página de novo a cada quadro; as bolinhas seguem a cena. */
  useEffect(
    () =>
      relogio.assinar((t) => {
        if (barra.current) barra.current.style.width = `${Math.min(100, (t / Math.max(0.01, total)) * 100)}%`;
        if (tempo.current) tempo.current.textContent = `${relogioEmTexto(t)} / ${relogioEmTexto(total)}`;
        const { indice } = paginaNoTempo(paginas, t);
        if (animada && indice !== atualRef.current) setAtual(indice);
      }),
    [relogio, total, paginas, animada],
  );
  useEffect(() => {
    if (animada) relogio.mudar(capaDa(0));
  }, [animada, relogio, capaDa]);

  const parar = useCallback(() => {
    cancelAnimationFrame(quadro.current);
    relogio.tocando = false;
  }, [relogio]);
  useEffect(() => parar, [parar]);

  const tocar = useCallback(
    (de: number) => {
      parar();
      const comeco = performance.now() - de * 1000;
      relogio.tocando = true;
      relogio.mudar(de);
      setModo("tocando");
      const passo = () => {
        const t = (performance.now() - comeco) / 1000;
        if (t >= total) {
          parar();
          naCapa.current = true;
          relogio.mudar(capaDa(0));
          setModo("pausada");
          return;
        }
        relogio.mudar(t);
        quadro.current = requestAnimationFrame(passo);
      };
      quadro.current = requestAnimationFrame(passo);
    },
    [parar, relogio, total, capaDa],
  );
  /* Do quadro de capa, o play começa do início da cena; pausado no meio, continua dali. */
  const alternar = useCallback(() => {
    if (modo === "tocando") {
      parar();
      naCapa.current = false;
      setModo("pausada");
    } else tocar(naCapa.current ? (inicios[atual] ?? 0) : relogio.tempo());
  }, [modo, parar, tocar, relogio, inicios, atual]);
  const irPara = useCallback(
    (i: number) => {
      const k = Math.max(0, Math.min(paginas.length - 1, i));
      parar();
      setAtual(k);
      if (animada) {
        naCapa.current = true;
        relogio.mudar(capaDa(k));
        setModo("pausada");
      } else setModo("parada");
    },
    [parar, paginas.length, animada, relogio, capaDa],
  );

  useEffect(() => {
    const tecla = (e: KeyboardEvent) => {
      if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
      if (e.key === "Escape") setPainel(false);
      else if (e.key === "ArrowRight" && !painel) irPara(atual + 1);
      else if (e.key === "ArrowLeft" && !painel) irPara(atual - 1);
      else if (e.key === " " && animada && !painel) {
        e.preventDefault();
        alternar();
      }
    };
    window.addEventListener("keydown", tecla);
    return () => window.removeEventListener("keydown", tecla);
  }, [atual, painel, animada, alternar, irPara]);

  useEffect(() => {
    if (!painel) return;
    const fora = (e: PointerEvent) => {
      if (painelRef.current && !painelRef.current.contains(e.target as Node) && !(e.target as HTMLElement).closest("[data-abrir-baixar]")) setPainel(false);
    };
    window.addEventListener("pointerdown", fora);
    return () => window.removeEventListener("pointerdown", fora);
  }, [painel]);

  const pedirArquivo = async (corpo: Record<string, unknown>) => {
    const r = await fetch(`/api/artes/publica/${encodeURIComponent(codigo)}/baixar`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(corpo) });
    if (!r.ok) throw new Error(((await r.json().catch(() => ({}))) as { error?: string }).error || "Não deu para baixar agora. Tente de novo em instantes.");
    return r.blob();
  };

  const baixar = async () => {
    if (andamento) return;
    setErro(null);
    const base = baseDoNome(nome);
    const escalaDoArquivo = grande ? 2 : 1;
    try {
      if (formato === "mp4") {
        if (modo === "tocando") {
          parar();
          setModo("pausada");
        }
        setAndamento({ texto: "Preparando o vídeo…", fracao: 0 });
        const { gravarVideo } = await import("@/lib/banner-editor/video");
        const video = await gravarVideo(paginas, { linkPublico: codigo, aoAndar: (texto, fracao) => setAndamento({ texto, fracao }) });
        entregar(video, `${base}.mp4`);
      } else if (formato === "pdf") {
        setAndamento({ texto: paginas.length > 1 ? `Montando o PDF com ${paginas.length} páginas…` : "Montando o PDF…", fracao: 0.3 });
        entregar(await pedirArquivo({ formato: "pdf" }), `${base}.pdf`);
      } else if (todas && paginas.length > 1) {
        const arquivos: Array<{ nome: string; dados: Uint8Array }> = [];
        for (let i = 0; i < paginas.length; i++) {
          setAndamento({ texto: `Página ${i + 1} de ${paginas.length}…`, fracao: i / paginas.length });
          const blob = await pedirArquivo({ formato, pagina: i, escala: escalaDoArquivo });
          arquivos.push({ nome: `${base}-${String(i + 1).padStart(2, "0")}.${formato}`, dados: new Uint8Array(await blob.arrayBuffer()) });
        }
        entregar(zipar(arquivos), `${base}.zip`);
      } else {
        setAndamento({ texto: "Gerando a imagem…", fracao: 0.4 });
        entregar(await pedirArquivo({ formato, pagina: atual, escala: escalaDoArquivo }), paginas.length > 1 ? `${base}-${String(atual + 1).padStart(2, "0")}.${formato}` : `${base}.${formato}`);
      }
      setPainel(false);
    } catch (e) {
      const texto = e instanceof Error ? e.message : String(e);
      setErro(texto || "Não deu para baixar agora. Tente de novo em instantes.");
    } finally {
      setAndamento(null);
    }
  };

  const opcoes: Array<{ chave: Formato; nome: string; detalhe: string; Icone: typeof ImageIcon }> = [
    ...(animada ? [{ chave: "mp4" as const, nome: "Vídeo MP4", detalhe: "Com a animação e o som", Icone: Film }] : []),
    { chave: "png", nome: "PNG", detalhe: "Imagem de alta qualidade", Icone: ImageIcon },
    { chave: "jpg", nome: "JPG", detalhe: "Imagem mais leve, para enviar", Icone: FileImage },
    { chave: "pdf", nome: "PDF", detalhe: paginas.length > 1 ? `Todas as ${paginas.length} páginas num arquivo` : "Para imprimir ou enviar", Icone: FileText },
  ];
  const cssDaMarca = useMemo(() => cssDasFontesDaMarca(fontesDaMarca().map((f) => f.chave)), []);

  return (
    <main className="flex h-[100dvh] flex-col overflow-hidden bg-[#F1EFF6] text-[#1F1B2E]">
      {cssDaMarca ? <style dangerouslySetInnerHTML={{ __html: cssDaMarca }} /> : null}
      <header className="relative z-20 flex h-16 shrink-0 items-center gap-3 border-b border-black/5 bg-white px-3 sm:px-5">
        {empresa.logo ? (
          // eslint-disable-next-line @next/next/no-img-element
          <img src={empresa.logo} alt="" className="h-9 w-auto max-w-[120px] shrink-0 object-contain" />
        ) : (
          <span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-[10px] text-[15px] font-extrabold text-white" style={{ background: empresa.cor || "#7C3AED" }}>
            {(empresa.nome || nome).trim().charAt(0).toUpperCase() || "K"}
          </span>
        )}
        <div className="min-w-0 flex-1">
          <h1 className="truncate text-[15px] font-bold leading-tight">{nome}</h1>
          {empresa.nome ? <p className="truncate text-[12.5px] text-[#6B6584]">{empresa.nome}</p> : null}
        </div>
        <button
          type="button"
          data-abrir-baixar
          onClick={() => setPainel((v) => !v)}
          className="flex h-10 shrink-0 items-center gap-2 rounded-[12px] bg-[#7C3AED] px-4 text-[14px] font-bold text-white shadow-[0_6px_18px_rgba(124,58,237,0.35)] transition hover:bg-[#6D28D9]"
        >
          <Download size={17} /> Baixar
        </button>

        {painel ? (
          <div ref={painelRef} className="absolute left-3 right-3 top-[calc(100%+8px)] rounded-[18px] bg-white p-4 shadow-[0_20px_60px_rgba(20,11,43,0.22)] ring-1 ring-black/5 sm:left-auto sm:right-5 sm:w-[360px]">
            <div className="mb-3 flex items-center justify-between">
              <p className="text-[15px] font-bold">Baixar a arte</p>
              <button type="button" aria-label="Fechar" onClick={() => setPainel(false)} className="flex h-8 w-8 items-center justify-center rounded-full text-[#6B6584] hover:bg-[#F1EFF6]">
                <X size={17} />
              </button>
            </div>
            <div role="radiogroup" aria-label="Tipo de arquivo">
              {opcoes.map(({ chave, nome: rotulo, detalhe, Icone }) => (
                <button
                  key={chave}
                  type="button"
                  role="radio"
                  aria-checked={formato === chave}
                  onClick={() => setFormato(chave)}
                  className={`flex w-full items-center gap-3 rounded-[12px] px-2.5 py-2.5 text-left transition ${formato === chave ? "bg-[#7C3AED]/[0.08]" : "hover:bg-[#F7F6FA]"}`}
                >
                  <span className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-[10px] ${formato === chave ? "bg-[#7C3AED] text-white" : "bg-[#F1EFF6] text-[#7C3AED]"}`}>
                    <Icone size={18} />
                  </span>
                  <span className="min-w-0 flex-1">
                    <span className="block text-[14px] font-bold">{rotulo}</span>
                    <span className="block text-[12.5px] text-[#6B6584]">{detalhe}</span>
                  </span>
                  <span className={`h-4 w-4 shrink-0 rounded-full border-2 ${formato === chave ? "border-[#7C3AED] bg-[#7C3AED] shadow-[inset_0_0_0_3px_white]" : "border-[#D4D0DF]"}`} />
                </button>
              ))}
            </div>

            {formato === "png" || formato === "jpg" ? (
              <div className="mt-3 space-y-2 border-t border-slate-100/80 pt-3">
                {paginas.length > 1 ? (
                  <div className="flex gap-1 rounded-[12px] bg-[#F1EFF6] p-1 text-[13px] font-semibold">
                    <button type="button" onClick={() => setTodas(false)} className={`h-8 flex-1 rounded-[9px] ${!todas ? "bg-white shadow-sm" : "text-[#6B6584]"}`}>
                      Página {atual + 1}
                    </button>
                    <button type="button" onClick={() => setTodas(true)} className={`h-8 flex-1 rounded-[9px] ${todas ? "bg-white shadow-sm" : "text-[#6B6584]"}`}>
                      Todas ({paginas.length}, em .zip)
                    </button>
                  </div>
                ) : null}
                <div className="flex gap-1 rounded-[12px] bg-[#F1EFF6] p-1 text-[13px] font-semibold">
                  <button type="button" onClick={() => setGrande(false)} className={`h-8 flex-1 rounded-[9px] ${!grande ? "bg-white shadow-sm" : "text-[#6B6584]"}`}>
                    Normal ({pagina?.largura} px)
                  </button>
                  <button type="button" onClick={() => setGrande(true)} className={`h-8 flex-1 rounded-[9px] ${grande ? "bg-white shadow-sm" : "text-[#6B6584]"}`}>
                    Dobro da nitidez
                  </button>
                </div>
              </div>
            ) : null}
            {formato === "mp4" ? <p className="mt-3 border-t border-slate-100/80 pt-3 text-[12.5px] leading-relaxed text-[#6B6584]">O vídeo é montado aqui no seu navegador: deixe esta aba aberta até terminar. Funciona no Chrome, no Edge e no Safari atualizados.</p> : null}

            {andamento ? (
              <div className="mt-3">
                <div className="h-2 overflow-hidden rounded-full bg-[#F1EFF6]">
                  <div className="h-full rounded-full bg-[#7C3AED] transition-[width] duration-300" style={{ width: `${Math.max(4, Math.round(andamento.fracao * 100))}%` }} />
                </div>
                <p className="mt-1.5 text-[12.5px] font-semibold text-[#6B6584]">{andamento.texto}</p>
              </div>
            ) : null}
            {erro ? <p className="mt-3 text-[13px] font-semibold text-[#BE123C]">{erro}</p> : null}
            <button
              type="button"
              disabled={Boolean(andamento)}
              onClick={() => void baixar()}
              className="mt-3 flex h-11 w-full items-center justify-center gap-2 rounded-[12px] bg-[#7C3AED] text-[14px] font-bold text-white transition hover:bg-[#6D28D9] disabled:opacity-70"
            >
              {andamento ? <Loader2 size={17} className="animate-spin" /> : <Download size={17} />} {andamento ? "Preparando…" : "Baixar"}
            </button>
          </div>
        ) : null}
      </header>

      <div ref={palcoRef} className="relative min-h-0 flex-1">
        {pagina && escala ? (
          <div className="absolute inset-0 flex items-center justify-center">
            <div className="relative overflow-hidden rounded-[6px] bg-white shadow-[0_18px_50px_rgba(20,11,43,0.18)]" style={{ width: pagina.largura * escala, height: pagina.altura * escala }}>
              {modo === "parada" ? (
                <PaginaParada pagina={pagina} escala={escala} />
              ) : (
                <div className="absolute left-0 top-0 origin-top-left" style={{ width: pagina.largura, height: pagina.altura, transform: `scale(${escala})` }}>
                  <CenaNoPalco paginas={paginas} relogio={relogio} largura={pagina.largura} altura={pagina.altura} />
                </div>
              )}
              {animada ? (
                <button type="button" aria-label={modo === "tocando" ? "Pausar" : "Tocar"} onClick={alternar} className="group absolute inset-0 z-10 flex items-center justify-center">
                  {modo !== "tocando" ? (
                    <span className="flex h-16 w-16 items-center justify-center rounded-full bg-[#7C3AED] text-white shadow-[0_10px_30px_rgba(124,58,237,0.45)] transition group-hover:scale-105">
                      <Play size={28} className="ml-1" fill="currentColor" />
                    </span>
                  ) : null}
                </button>
              ) : null}
            </div>
          </div>
        ) : null}
        {paginas.length > 1 && modo !== "tocando" ? (
          <>
            <button type="button" aria-label="Página anterior" disabled={atual === 0} onClick={() => irPara(atual - 1)} className="absolute left-2 top-1/2 z-10 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full bg-white text-[#1F1B2E] shadow-[0_6px_20px_rgba(20,11,43,0.16)] disabled:opacity-0 sm:left-5">
              <ChevronLeft size={22} />
            </button>
            <button type="button" aria-label="Próxima página" disabled={atual === paginas.length - 1} onClick={() => irPara(atual + 1)} className="absolute right-2 top-1/2 z-10 flex h-11 w-11 -translate-y-1/2 items-center justify-center rounded-full bg-white text-[#1F1B2E] shadow-[0_6px_20px_rgba(20,11,43,0.16)] disabled:opacity-0 sm:right-5">
              <ChevronRight size={22} />
            </button>
          </>
        ) : null}
      </div>

      <footer className="flex shrink-0 flex-col items-center gap-2 px-4 pb-3 pt-1">
        {animada ? (
          <div className="flex w-full max-w-[560px] items-center gap-3 rounded-full bg-white px-3 py-1.5 shadow-[0_6px_20px_rgba(20,11,43,0.08)]">
            <button type="button" aria-label={modo === "tocando" ? "Pausar" : "Tocar"} onClick={alternar} className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-[#7C3AED] text-white">
              {modo === "tocando" ? <Pause size={15} fill="currentColor" /> : <Play size={15} className="ml-0.5" fill="currentColor" />}
            </button>
            <div
              className="relative h-1.5 flex-1 cursor-pointer rounded-full bg-[#E6E3EE]"
              onPointerDown={(e) => {
                const caixa = e.currentTarget.getBoundingClientRect();
                const t = Math.max(0, Math.min(total - 0.01, ((e.clientX - caixa.left) / caixa.width) * total));
                if (modo === "tocando") tocar(t);
                else {
                  naCapa.current = false;
                  relogio.mudar(t);
                  setModo("pausada");
                }
              }}
            >
              <div ref={barra} className="pointer-events-none absolute inset-y-0 left-0 rounded-full bg-[#7C3AED]" style={{ width: 0 }} />
            </div>
            <span ref={tempo} className="w-[78px] shrink-0 text-right text-[12px] font-semibold tabular-nums text-[#6B6584]">
              0:00 / {relogioEmTexto(total)}
            </span>
            {audios.length ? (
              <button type="button" aria-label={mudo ? "Ligar o som" : "Tirar o som"} onClick={() => setMudo((v) => !v)} className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-[#6B6584] hover:bg-[#F1EFF6]">
                {mudo ? <VolumeX size={17} /> : <Volume2 size={17} />}
              </button>
            ) : null}
          </div>
        ) : null}
        {paginas.length > 1 ? (
          <div className="flex items-center gap-1.5" aria-label={`Página ${atual + 1} de ${paginas.length}`}>
            {paginas.map((_, i) => (
              <button key={i} type="button" aria-label={`Página ${i + 1}`} onClick={() => irPara(i)} className={`h-2 rounded-full transition-all ${i === atual ? "w-6 bg-[#7C3AED]" : "w-2 bg-[#D4D0DF] hover:bg-[#B9B3C9]"}`} />
            ))}
          </div>
        ) : null}
        <a href="https://ktalogu.com.br" target="_blank" rel="noopener noreferrer" className="text-[11.5px] font-semibold text-[#8A849E] hover:text-[#7C3AED]">
          Feito com KtaloguDesign · Ktalogu
        </a>
      </footer>
      {/* Sempre montado: o primeiro play acontece dentro do clique (o Safari só deixa o som começar assim). */}
      {audios.length ? <TocadorDeAudios audios={mudo ? SEM_AUDIOS : audios} relogio={relogio} /> : null}
    </main>
  );
}
