import sharp from "sharp";
import { createClient } from "@/lib/supabase-server";
import { canAnyCompany, getCompanyContext, resolveCompanyPublicProfileId } from "@/lib/company-context";
import { buscar } from "@/lib/assistentes/importacao/rede";
import { fonteDaChave, fontesDosTrechos, htmlDasPaginas, registrarFontesDaMarca, type DocumentoDoBanner, type FonteDaMarca, type PecaDeImagem } from "./documento";
import { RECADO_DO_BETA, designLiberado } from "./liberacao";
import { guardarVetor, svgComCores, trocasDoVetor } from "./vetores";

/*
 * O lado do servidor do editor de artes, comum às duas rotas: a do banner da
 * loja (/api/loja/editor/banner/documento) e a do download
 * (/api/artes/exportar).
 *
 * - quemPode: só empresa com o KtaloguDesign liberado (beta, liberacao.ts) e
 *   quem cria banners (catalog.banners.write) ou cuida do marketing
 *   (marketing.manage);
 * - as imagens só vêm do depósito desta Ktalogu e entram embutidas (data:), no
 *   tamanho em que aparecem vezes a escala: o Chrome não busca nada de fora.
 */

export class ImagemRecusada extends Error {}

export async function quemPodeCriarArtes() {
  const supabase = await createClient();
  const {
    data: { user },
  } = await supabase.auth.getUser();
  if (!user) return { falha: { texto: "Entre no painel de novo.", status: 401 } } as const;
  const ctx = await getCompanyContext(supabase, user);
  if (!designLiberado([ctx.companyId, ctx.ownerId])) return { falha: { texto: RECADO_DO_BETA, status: 403 } } as const;
  if (!canAnyCompany(ctx, user.id, ["catalog.banners.write", "marketing.manage"])) {
    return { falha: { texto: "Sem permissão para criar artes. Quem libera é o dono da empresa, em Funcionários.", status: 403 } } as const;
  }
  return { supabase, dono: resolveCompanyPublicProfileId(ctx, user.id), usuario: user.id } as const;
}

/* Só o depósito desta instalação da Ktalogu. */
export function doDeposito(endereco: string) {
  const base = String(process.env.NEXT_PUBLIC_SUPABASE_URL || "").replace(/\/$/, "");
  return Boolean(base) && endereco.startsWith(`${base}/storage/v1/object/public/`);
}

export async function baixarDoDeposito(endereco: string): Promise<Buffer | null> {
  if (!doDeposito(endereco)) return null;
  try {
    const resposta = await buscar(endereco, { limite: 15 * 1024 * 1024, tempoMs: 20_000, aceita: "image/*" });
    return resposta.status === 200 ? resposta.corpo : null;
  } catch {
    return null;
  }
}

/** O arquivo de uma fonte da marca, embutido (o Chrome do servidor não busca nada de fora). */
async function fonteEmbutida(f: FonteDaMarca): Promise<string | null> {
  if (!doDeposito(f.url)) return null;
  try {
    const resposta = await buscar(f.url, { limite: 5 * 1024 * 1024, tempoMs: 20_000, aceita: "*/*" });
    if (resposta.status !== 200) return null;
    const extensao = (/\.(woff2?|ttf|otf)$/i.exec(f.url)?.[1] || "ttf").toLowerCase();
    const tipo = extensao === "woff2" ? "font/woff2" : extensao === "woff" ? "font/woff" : extensao === "otf" ? "font/otf" : "font/ttf";
    return `data:${tipo};base64,${resposta.corpo.toString("base64")}`;
  } catch {
    return null;
  }
}

/** A imagem embutida, no tamanho em que aparece (vezes a escala, no mínimo o dobro), sem perder o fundo transparente. */
async function embutir(conteudo: Buffer, peca: PecaDeImagem, escala: number): Promise<string | null> {
  try {
    const lado = Math.round(Math.min(4000, Math.max(peca.w, peca.h) * Math.max(2, escala)));
    const imagem = sharp(conteudo, { limitInputPixels: 80_000_000, animated: false }).rotate().resize(lado, lado, { fit: "inside", withoutEnlargement: true });
    const { hasAlpha } = await sharp(conteudo, { limitInputPixels: 80_000_000 }).metadata();
    const saida = hasAlpha ? await imagem.png({ compressionLevel: 8 }).toBuffer() : await imagem.jpeg({ quality: 92 }).toBuffer();
    return `data:image/${hasAlpha ? "png" : "jpeg"};base64,${saida.toString("base64")}`;
  } catch {
    return null;
  }
}

/** O HTML pronto para o Chrome, com as imagens já dentro. Imagem que não abre vira ImagemRecusada. */
export async function htmlComAsImagens(documento: DocumentoDoBanner | DocumentoDoBanner[], opcoes: { escala?: number; semFundo?: boolean } = {}) {
  const paginas = Array.isArray(documento) ? documento : [documento];
  const embutidas = new Map<string, string>();
  const baixadas = new Map<string, Buffer | null>();
  for (const peca of paginas.flatMap((p) => p.pecas)) {
    if (peca.tipo !== "imagem" || peca.oculta) continue;
    if (!baixadas.has(peca.src)) baixadas.set(peca.src, await baixarDoDeposito(peca.src));
    const bytes = baixadas.get(peca.src);
    if (!bytes) throw new ImagemRecusada("Uma das imagens da arte não abriu. Troque por outra e tente de novo.");
    /* Elemento em SVG: entra como SVG (fica nítido em qualquer tamanho), com as cores que a pessoa trocou. */
    if (peca.vetor && /^\s*(<\?xml[^>]*>\s*)?<svg[\s>]/i.test(bytes.subarray(0, 400).toString("utf8"))) {
      const svg = svgComCores(bytes.toString("utf8"), trocasDoVetor(peca.vetor));
      guardarVetor(peca.src, bytes.toString("utf8"));
      embutidas.set(peca.id, `data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}`);
      continue;
    }
    const dados = await embutir(bytes, peca, opcoes.escala || 1);
    if (!dados) throw new ImagemRecusada("Uma das imagens da arte não pôde ser usada. Troque por outra (PNG, JPG ou WebP).");
    embutidas.set(peca.id, dados);
  }
  /* As fontes da marca que as páginas usam, embutidas (fonte que não abre cai na fonte padrão). */
  const arquivos = new Map<string, string | null>();
  for (const pagina of paginas) {
    registrarFontesDaMarca(pagina.fontesProprias);
    for (const f of pagina.fontesProprias || []) {
      const usada = pagina.pecas.some((p) => (p.tipo === "texto" && (p.fonte === f.chave || fontesDosTrechos(p.html).includes(f.chave))) || ((p.tipo === "tabela" || p.tipo === "grafico") && p.fonte === f.chave));
      if (usada && !arquivos.has(f.chave) && fonteDaChave(f.chave).propria) arquivos.set(f.chave, await fonteEmbutida(f));
    }
  }
  return htmlDasPaginas(paginas, (peca) => embutidas.get(peca.id) ?? null, { semFundo: opcoes.semFundo, arquivoDaFonte: (f) => arquivos.get(f.chave) ?? null });
}
