import { lerLinkDeVideo } from "@/lib/digitais/curso";
import { lerOpcoesDoVideo, OPCOES_PADRAO } from "@/lib/opcoes-do-video";

import type { ContextoDoAssistente } from "../contexto";
import { arquivoDoCaminho, ehEnvioDigital } from "../envio-digital";
import type { Campos } from "../esquema";
import { recusar } from "./comum";
import type { AtributoDaFicha } from "./produto-completo-regras";
import type { Mudanca } from "./regras-do-produto";

/*
 * Os campos da ficha do produto que o cadastro pelo assistente ganhou em
 * 15/10/2026 (pedido do Natan: "tudo o que a tela do produto tem"): ativo,
 * frete, dados fiscais, controle de estoque, vídeo, gabaritos e "exclusivo do
 * catálogo". Valem para criar_produto e atualizar_produto. Cada campo vira o
 * mesmo pedaço do PATCH que a tela manda; quem confere é a rota (a mesma
 * função), aqui só se traduz e se monta o antes e depois.
 */

const ESTOQUE_POR = { produto: "product", "opção": "option", "combinação": "variant" } as const;
const NOME_DO_ESTOQUE: Record<string, string> = { product: "produto", option: "opção", variant: "combinação" };
const TIPO_FISCAL = { mercadoria: "mercadoria", "serviço": "servico", nenhum: null } as const;
const FORMATOS_DE_GABARITO = ["canva", "pdf", "ai", "cdr"] as const;

export const CAMPOS_DA_FICHA: Campos = {
  ativo: { tipo: "booleano", descricao: "Produto ativo. Falso tira o produto das vendas e da loja sem apagar nada (como o botão Ativo da tela)." },
  exclusivo_do_catalogo: {
    tipo: "booleano",
    descricao: 'Marca o produto como "exclusivo do catálogo" (o selo da lista de produtos da loja virtual, para o que foi cadastrado pela tela do catálogo). Raramente necessário.',
  },
  frete: {
    tipo: "objeto",
    descricao: "Medidas e peso da embalagem, para calcular o frete. Mande só o que muda; zero apaga o valor.",
    campos: {
      peso_kg: { tipo: "numero", descricao: "Peso em kg (ex.: 0,350).", min: 0, max: 10_000 },
      largura_cm: { tipo: "numero", descricao: "Largura em cm.", min: 0, max: 10_000 },
      altura_cm: { tipo: "numero", descricao: "Altura em cm.", min: 0, max: 10_000 },
      comprimento_cm: { tipo: "numero", descricao: "Comprimento em cm.", min: 0, max: 10_000 },
    },
  },
  fiscal: {
    tipo: "objeto",
    descricao: "Dados da nota fiscal do produto. Nunca invente NCM, CEST ou tributação: confirme com a pessoa (ou com o contador dela).",
    campos: {
      tipo: { tipo: "opcao", descricao: "Mercadoria (NF-e) ou serviço (NFS-e). nenhum = segue o padrão da empresa.", opcoes: Object.keys(TIPO_FISCAL) },
      ncm: { tipo: "texto", descricao: "NCM com 8 números. \"nenhum\" apaga.", max: 12 },
      cest: { tipo: "texto", descricao: "CEST com 7 números. \"nenhum\" apaga.", max: 12 },
      origem: { tipo: "inteiro", descricao: "Origem da mercadoria, de 0 a 8 (0 = nacional). -1 apaga.", min: -1, max: 8 },
      tributacao_id: { tipo: "texto", descricao: "Id da tributação (listar_tributacoes_fiscais). \"padrão\" volta para a tributação padrão da empresa.", max: 40 },
    },
  },
  estoque: {
    tipo: "objeto",
    descricao:
      'Como o estoque deste produto é controlado (o card Estoque da tela). O saldo em si muda com ajustar_estoque_do_produto. Por opção: diga qual atributo controla (ex.: "Cor"). Por combinação: cada atributo precisa aceitar uma escolha só.',
    campos: {
      controlar: { tipo: "booleano", descricao: "Controlar o estoque deste produto." },
      por: { tipo: "opcao", descricao: "produto (um saldo só), opção (um saldo por opção de um atributo) ou combinação (um saldo por combinação de opções).", opcoes: Object.keys(ESTOQUE_POR) },
      atributo: { tipo: "texto", descricao: 'Com estoque por opção: o nome do atributo que controla o estoque (ex.: "Cor").', max: 80 },
      vender_sem_estoque: { tipo: "booleano", descricao: "Deixa vender mesmo com o saldo zerado (estoque negativo)." },
    },
  },
  video: {
    tipo: "objeto",
    descricao:
      'O vídeo do produto, que entra como primeiro item da galeria da loja: um link do YouTube ou do Vimeo, ou um vídeo dos arquivos da empresa (id do arquivo ou o código "envio:..." de pedir_video). Vídeo enviado: até 1 minuto, 50 MB e 10 produtos por empresa. Mande remover: true para tirar.',
    campos: {
      link: { tipo: "texto", descricao: "Link do YouTube ou do Vimeo.", max: 2000 },
      arquivo: { tipo: "texto", descricao: 'Id de um vídeo dos arquivos da empresa, ou "envio:..." de pedir_video.', max: 200 },
      remover: { tipo: "booleano", descricao: "Tira o vídeo do produto." },
      flutuante: { tipo: "booleano", descricao: "Abre sozinho, sem som, numa janelinha no canto da página do produto." },
      comecar_sozinho: { tipo: "booleano", descricao: "Na galeria, começa a tocar ao abrir a página." },
      com_som: { tipo: "booleano", descricao: "Na galeria, começa com som (só junto de comecar_sozinho)." },
      repetir: { tipo: "booleano", descricao: "Na galeria, repete ao terminar." },
      controles: { tipo: "booleano", descricao: "Na galeria, mostra os controles do vídeo." },
      janelinha_com_som: { tipo: "booleano", descricao: "Na janelinha flutuante, toca com som." },
      janelinha_repetir: { tipo: "booleano", descricao: "Na janelinha flutuante, repete ao terminar." },
    },
  },
  gabaritos: {
    tipo: "lista",
    descricao:
      "Os gabaritos de produção (aba Produção): o link do Canva e, se a pessoa tiver o arquivo publicado, o link do PDF, AI ou CDR. Substitui a lista inteira; lista vazia tira todos. Um por formato.",
    maxItens: 4,
    itens: {
      formato: { tipo: "opcao", descricao: "canva, pdf, ai ou cdr.", opcoes: FORMATOS_DE_GABARITO, obrigatorio: true },
      link: { tipo: "texto", descricao: "O link https:// do gabarito.", obrigatorio: true, max: 2000 },
      nome: { tipo: "texto", descricao: "Nome do gabarito (padrão: Gabarito, ou Canva).", max: 120 },
    },
  },
};

export type PedacoDaFicha = {
  patch: Record<string, unknown>;
  mudancas: Mudanca[];
  avisos: string[];
  /* O pedaço muda o controle de estoque por opção ou combinação: a tela manda
     os atributos junto, para o saldo por opção e as combinações acompanharem. */
  estoquePorPartes: boolean;
  soEstoque: boolean;
  tiraDaLoja: boolean;
};

const sim = (valor: unknown) => (valor ? "sim" : "não");
const numeroOuNada = (valor: unknown) => (valor === null || valor === undefined || valor === "" ? null : Number(valor));

/** O vídeo vindo de um envio do assistente (pedir_video) vira o id do arquivo na biblioteca. */
async function arquivoDoVideo(ctx: ContextoDoAssistente, valor: string): Promise<string> {
  const texto = valor.trim();
  const codigo = /^envio:([A-Za-z0-9]{12})$/.exec(texto)?.[1];
  if (!codigo) {
    if (!/^[0-9a-f-]{36}$/i.test(texto)) recusar('O vídeo precisa ser o id de um arquivo de vídeo da empresa ou o código "envio:..." de pedir_video.');
    return texto.toLowerCase();
  }
  const { data } = await ctx.admin.from("assistentes_envios").select("tipo,caminho,recebido_em").eq("codigo", codigo).eq("company_id", ctx.chave.companyId).maybeSingle();
  const envio = data as { tipo: string | null; caminho: string | null; recebido_em: string | null } | null;
  if (!envio || !ehEnvioDigital(envio.tipo)) recusar("Este código de envio não é de um vídeo. Chame pedir_video para gerar o link de envio do vídeo.");
  const id = arquivoDoCaminho(envio.caminho);
  if (!envio.recebido_em || !id) recusar("O vídeo ainda não terminou de chegar por esse link. Peça à pessoa para concluir o envio e tente de novo.");
  return id;
}

/**
 * Traduz os campos da ficha pedidos pelo assistente para o pedaço do PATCH da
 * tela. `atual` é a linha do produto (ou nada, no cadastro novo).
 */
export async function pedacoDaFicha(
  ctx: ContextoDoAssistente,
  args: Record<string, unknown>,
  atual: Record<string, unknown> | null,
  atributos: AtributoDaFicha[]
): Promise<PedacoDaFicha> {
  const patch: Record<string, unknown> = {};
  const mudancas: Mudanca[] = [];
  const avisos: string[] = [];
  let estoquePorPartes = false;
  const antes = atual ?? {};

  if (args.ativo !== undefined) {
    patch.active = args.ativo === true;
    mudancas.push({ campo: "ativo", antes: sim(antes.active !== false), depois: sim(args.ativo === true) });
  }
  if (args.exclusivo_do_catalogo !== undefined) {
    patch.is_catalog_exclusive = args.exclusivo_do_catalogo === true;
    mudancas.push({ campo: "exclusivo do catálogo", antes: sim(antes.is_catalog_exclusive), depois: sim(args.exclusivo_do_catalogo) });
  }

  const frete = args.frete as Record<string, number> | undefined;
  if (frete) {
    const colunas: Array<[string, string, string, number]> = [
      ["peso_kg", "shipping_weight_kg", "peso (kg)", 3],
      ["largura_cm", "shipping_width_cm", "largura (cm)", 2],
      ["altura_cm", "shipping_height_cm", "altura (cm)", 2],
      ["comprimento_cm", "shipping_length_cm", "comprimento (cm)", 2],
    ];
    for (const [nome, coluna, rotulo, casas] of colunas) {
      if (frete[nome] === undefined) continue;
      /* Como parseShippingNumberInput da tela: zero ou vazio = sem valor. */
      const valor = frete[nome] > 0 ? Number(frete[nome].toFixed(casas)) : null;
      patch[coluna] = valor;
      mudancas.push({ campo: rotulo, antes: numeroOuNada(antes[coluna])?.toString() ?? null, depois: valor?.toString() ?? null });
    }
  }

  const fiscal = args.fiscal as Record<string, unknown> | undefined;
  if (fiscal) {
    if (fiscal.tipo !== undefined) {
      patch.fiscal_tipo = TIPO_FISCAL[fiscal.tipo as keyof typeof TIPO_FISCAL];
      mudancas.push({ campo: "tipo na nota", antes: (antes.fiscal_tipo as string) ?? null, depois: (patch.fiscal_tipo as string) ?? null });
    }
    if (fiscal.ncm !== undefined) {
      const ncm = String(fiscal.ncm).replace(/\D/g, "");
      if (ncm && ncm.length !== 8) recusar("O NCM tem 8 números.");
      patch.ncm = ncm || null;
      mudancas.push({ campo: "NCM", antes: (antes.ncm as string) ?? null, depois: ncm || null });
    }
    if (fiscal.cest !== undefined) {
      const cest = String(fiscal.cest).replace(/\D/g, "");
      if (cest && cest.length !== 7) recusar("O CEST tem 7 números.");
      patch.cest = cest || null;
      mudancas.push({ campo: "CEST", antes: (antes.cest as string) ?? null, depois: cest || null });
    }
    if (fiscal.origem !== undefined) {
      const origem = Number(fiscal.origem) < 0 ? null : Number(fiscal.origem);
      patch.fiscal_origem = origem;
      mudancas.push({ campo: "origem da mercadoria", antes: numeroOuNada(antes.fiscal_origem)?.toString() ?? null, depois: origem?.toString() ?? null });
    }
    if (fiscal.tributacao_id !== undefined) {
      const pedido = String(fiscal.tributacao_id).trim();
      const id = /^padr[aã]o$/i.test(pedido) ? "" : pedido;
      patch.fiscal_perfil_id = id || null;
      mudancas.push({ campo: "tributação", antes: (antes.fiscal_perfil_id as string) ?? "padrão da empresa", depois: id || "padrão da empresa" });
    }
  }

  const estoque = args.estoque as Record<string, unknown> | undefined;
  if (estoque) {
    const controlarAntes = antes.track_inventory === true;
    const porAntes = String(antes.inventory_scope || "product");
    const controlar = estoque.controlar === undefined ? controlarAntes : estoque.controlar === true;
    const por = estoque.por === undefined ? porAntes : ESTOQUE_POR[estoque.por as keyof typeof ESTOQUE_POR];
    let atributo = (antes.inventory_attribute_name as string | null) ?? null;
    if (estoque.atributo !== undefined) {
      const pedido = String(estoque.atributo).trim().toLocaleLowerCase("pt-BR");
      const achado = atributos.find((item) => item.name.trim().toLocaleLowerCase("pt-BR") === pedido);
      if (!achado) recusar(`O produto não tem o atributo "${String(estoque.atributo)}". Atributos: ${atributos.map((item) => item.name).join(", ") || "nenhum"}.`);
      atributo = achado.name;
    }
    if (controlar && por === "option" && !atributo) recusar('Com estoque por opção, diga qual atributo controla o estoque (campo atributo, ex.: "Cor").');
    if (controlar && por === "variant") {
      const multiplos = atributos.filter((item) => item.allow_multiple).map((item) => item.name || "atributo");
      if (multiplos.length) recusar(`Com estoque por combinação, cada atributo aceita uma escolha só. Desligue a múltipla escolha em: ${multiplos.join(", ")} (alterar_variacoes_do_produto).`);
    }
    if (estoque.controlar !== undefined) {
      patch.track_inventory = controlar;
      mudancas.push({ campo: "controlar estoque", antes: sim(controlarAntes), depois: sim(controlar) });
    }
    if (estoque.por !== undefined || estoque.atributo !== undefined) {
      patch.inventory_scope = por;
      patch.inventory_attribute_name = por === "option" ? atributo : null;
      mudancas.push({
        campo: "estoque por",
        antes: `${NOME_DO_ESTOQUE[porAntes] ?? porAntes}${porAntes === "option" && antes.inventory_attribute_name ? ` (${String(antes.inventory_attribute_name)})` : ""}`,
        depois: `${NOME_DO_ESTOQUE[por] ?? por}${por === "option" && atributo ? ` (${atributo})` : ""}`,
      });
    }
    if (estoque.vender_sem_estoque !== undefined) {
      patch.allow_negative_stock = estoque.vender_sem_estoque === true;
      mudancas.push({ campo: "vender sem estoque", antes: sim(antes.allow_negative_stock), depois: sim(estoque.vender_sem_estoque) });
    }
    estoquePorPartes = controlar && (por === "option" || por === "variant") && (controlar !== controlarAntes || por !== porAntes || atributo !== (antes.inventory_attribute_name ?? null));
    if (estoquePorPartes) avisos.push("O saldo passa a ser contado por parte (opção ou combinação). As partes novas começam em zero: ajuste com ajustar_estoque_do_produto.");
  }

  const video = args.video as Record<string, unknown> | undefined;
  if (video) {
    const antesTexto = antes.video_file_id ? "vídeo enviado" : antes.video_link ? "link de vídeo" : "sem vídeo";
    if (video.remover === true) {
      if (video.link !== undefined || video.arquivo !== undefined) recusar("Escolha: remover o vídeo, ou mandar um link/arquivo novo.");
      Object.assign(patch, { video_file_id: null, video_link: null, video_flutuante: false });
      mudancas.push({ campo: "vídeo", antes: antesTexto, depois: "sem vídeo" });
    } else {
      if (video.link !== undefined && video.arquivo !== undefined) recusar("O vídeo é um link OU um arquivo, não os dois.");
      if (video.link !== undefined) {
        const link = lerLinkDeVideo(String(video.link));
        if (!link || link.provedor === "panda") recusar("Mande o link de um vídeo do YouTube ou do Vimeo.");
        patch.video_link = String(video.link).trim();
        patch.video_file_id = null;
        mudancas.push({ campo: "vídeo", antes: antesTexto, depois: `link do ${link.provedor === "youtube" ? "YouTube" : "Vimeo"}` });
      }
      if (video.arquivo !== undefined) {
        patch.video_file_id = await arquivoDoVideo(ctx, String(video.arquivo));
        patch.video_link = null;
        mudancas.push({ campo: "vídeo", antes: antesTexto, depois: "vídeo enviado" });
      }
      const temVideo = Boolean(patch.video_file_id ?? patch.video_link ?? antes.video_file_id ?? antes.video_link);
      if (video.flutuante !== undefined) {
        if (video.flutuante === true && !temVideo) recusar("O produto ainda não tem vídeo: mande o link ou o arquivo junto.");
        patch.video_flutuante = video.flutuante === true && temVideo;
        mudancas.push({ campo: "vídeo flutuante", antes: sim(antes.video_flutuante), depois: sim(patch.video_flutuante) });
      }
      const chaves: Array<[string, "galeria" | "flutuante", string]> = [
        ["comecar_sozinho", "galeria", "sozinho"],
        ["com_som", "galeria", "comSom"],
        ["repetir", "galeria", "repetir"],
        ["controles", "galeria", "controles"],
        ["janelinha_com_som", "flutuante", "comSom"],
        ["janelinha_repetir", "flutuante", "repetir"],
      ];
      if (chaves.some(([nome]) => video[nome] !== undefined)) {
        const opcoes = lerOpcoesDoVideo(antes.video_opcoes ?? OPCOES_PADRAO) as unknown as Record<string, Record<string, boolean>>;
        for (const [nome, grupo, chave] of chaves) if (video[nome] !== undefined) opcoes[grupo][chave] = video[nome] === true;
        patch.video_opcoes = lerOpcoesDoVideo(opcoes);
        mudancas.push({ campo: "como o vídeo toca", antes: null, depois: "ajustado" });
      }
    }
  }

  if (args.gabaritos !== undefined) {
    const lista = args.gabaritos as Array<{ formato: string; link: string; nome?: string }>;
    const vistos = new Set<string>();
    const gabaritos = lista.map((item) => {
      if (vistos.has(item.formato)) recusar(`Mande um gabarito por formato (${item.formato} repetido).`);
      vistos.add(item.formato);
      if (!/^https:\/\//i.test(item.link.trim())) recusar(`O link do gabarito ${item.formato} precisa começar com https://.`);
      return { format: item.formato, url: item.link.trim(), name: item.nome?.trim() || (item.formato === "canva" ? "Canva" : "Gabarito") };
    });
    /* Como o Salvar da tela: a lista nova e o gabarito antigo (pdf, ai ou cdr) espelhado. */
    const preferido = gabaritos.find((item) => item.format === "pdf") || gabaritos.find((item) => item.format === "ai") || gabaritos.find((item) => item.format === "cdr");
    patch.production_templates = gabaritos;
    patch.production_template_url = preferido?.url || null;
    patch.production_template_name = preferido?.name || null;
    patch.production_template_format = preferido?.format || null;
    const contar = (valor: unknown) => (Array.isArray(valor) ? valor.length : 0);
    mudancas.push({ campo: "gabaritos", antes: `${contar(antes.production_templates)}`, depois: gabaritos.map((item) => item.format).join(", ") || "nenhum" });
  }

  const ESTOQUE = new Set(["track_inventory", "inventory_scope", "inventory_attribute_name", "allow_negative_stock"]);
  const soEstoque = Object.keys(patch).length > 0 && Object.keys(patch).every((campo) => ESTOQUE.has(campo));
  return { patch, mudancas, avisos, estoquePorPartes, soEstoque, tiraDaLoja: patch.active === false };
}
