/*
  A loja inteira, do banco até o objeto que o modelo desenha.

  Lê o mesmo que a vitrine lê (produto ativo e marcado para a loja, na ordem
  da loja; categorias visíveis) com a chave de serviço, depois de a rota ter
  conferido quem pediu e de que empresa ele é. Nada aqui grava no banco.
*/

import type { SupabaseClient } from "@supabase/supabase-js";
import QRCode from "qrcode";
import { coresDaMarca, logosDaLoja } from "../marca.ts";
import { emDestaque, limiteDeProdutos, type OpcoesDoCatalogo } from "../opcoes.ts";
import {
  agruparPorCategoria,
  destaquesDaCapa,
  ID_DOS_OUTROS,
  instagramDaLoja,
  montarProduto,
  raizesDasCategorias,
  SELECT_DO_PRODUTO,
  whatsappDaLoja,
  type LinhaDaCategoria,
  type LinhaDoProduto,
  type LojaDoCatalogo,
  type ProdutoDoCatalogo,
} from "../produtos.ts";
import { miniatura } from "../../foto.ts";
import { emLotes, FotosDoCatalogo, prepararFoto } from "./fotos.ts";
import { fontesDaLoja, logoDaLoja } from "./identidade.ts";

/* Erro que a pessoa lê. O texto já vem pronto para a tela. */
export class ErroDoCatalogo extends Error {
  codigo: string;
  constructor(codigo: string, mensagem: string) {
    super(mensagem);
    this.codigo = codigo;
  }
}

const COLUNAS_DO_PERFIL =
  "id,slug,store_name,catalog_name,catalog_slogan,catalog_logo_url,logo_url,primary_color,company_whatsapp,whatsapp,social_instagram,address,catalog_font_heading,catalog_font_body,catalog_price_mode";

type LinhaDoPerfil = {
  id: string;
  slug: string | null;
  store_name: string | null;
  catalog_name: string | null;
  catalog_slogan: string | null;
  catalog_logo_url: string | null;
  logo_url: string | null;
  primary_color: string | null;
  company_whatsapp: string | null;
  whatsapp: string | null;
  social_instagram: string | null;
  address: string | null;
  catalog_font_heading: string | null;
  catalog_font_body: string | null;
  /* Regras da loja: public (todos veem o preço), hidden, clients. */
  catalog_price_mode: string | null;
};

export type EnderecosDaLoja = { urlDaLoja: string; urlCurta: string; urlDoProduto: (id: string) => string };

/* O endereço público: o domínio próprio da loja, quando verificado; senão
   ktalogu.com.br/<slug>. O mesmo critério dos links de pagamento. */
async function enderecosDaLoja(admin: SupabaseClient, perfil: LinhaDoPerfil, origemPublica: string): Promise<EnderecosDaLoja> {
  const { data } = await admin
    .from("profile_custom_domains")
    .select("domain")
    .eq("profile_id", perfil.id)
    .eq("verified", true)
    .limit(1)
    .maybeSingle();
  const dominio = String((data as { domain?: string | null } | null)?.domain || "")
    .trim()
    .toLowerCase()
    .replace(/^https?:\/\//, "")
    .split("/")[0]
    ?.split(":")[0];
  const urlDaLoja = dominio ? `https://${dominio}` : `${origemPublica.replace(/\/+$/, "")}/${perfil.slug}`;
  return {
    urlDaLoja,
    urlCurta: urlDaLoja.replace(/^https?:\/\//, ""),
    urlDoProduto: (id: string) => `${urlDaLoja}/p/${id}`,
  };
}

async function lerPerfil(admin: SupabaseClient, donoId: string) {
  const { data, error } = await admin.from("profiles").select(COLUNAS_DO_PERFIL).eq("id", donoId).maybeSingle();
  if (error) throw new ErroDoCatalogo("banco", "Não foi possível ler a sua loja agora. Tente de novo em instantes.");
  const perfil = data as LinhaDoPerfil | null;
  if (!perfil?.slug) {
    throw new ErroDoCatalogo("sem-loja", "Sua loja virtual ainda não tem endereço. Configure a loja em Catálogo › Personalização e volte aqui.");
  }
  return perfil;
}

async function lerCategoriasEProdutos(admin: SupabaseClient, donoId: string) {
  const [categorias, produtos] = await Promise.all([
    admin.from("categories").select("id,name,parent_id").eq("user_id", donoId).eq("is_visible", true).order("name", { ascending: true }),
    admin
      .from("products")
      .select(SELECT_DO_PRODUTO)
      .eq("user_id", donoId)
      .neq("product_kind", "digital")
      .eq("active", true)
      .eq("show_in_catalog", true)
      .order("catalog_position", { ascending: true, nullsFirst: false })
      .order("created_at", { ascending: false }),
  ]);
  if (categorias.error || produtos.error) {
    throw new ErroDoCatalogo("banco", "Não foi possível ler os produtos da loja agora. Tente de novo em instantes.");
  }
  return {
    categorias: (categorias.data || []) as LinhaDaCategoria[],
    produtos: (produtos.data || []) as unknown as LinhaDoProduto[],
  };
}

function whatsappDoPerfil(perfil: LinhaDoPerfil) {
  return whatsappDaLoja(perfil.company_whatsapp) || whatsappDaLoja(perfil.whatsapp);
}

/* ------------------------------------------------------- o resumo da tela */

export type ResumoDaLoja = {
  nome: string;
  urlDaLoja: string;
  urlCurta: string;
  /* O número como sai no catálogo; `null` = a loja não tem WhatsApp. */
  whatsapp: string | null;
  slogan: string;
  /* A loja mostra os preços a todo mundo? Escondidos na loja (Regras da
     loja), o "Mostrar os preços" do PDF nasce desligado. */
  precosNaLoja: boolean;
  cores: { marca: string; tinta: string; tintaNaMarca: string; sobreTinta: string };
  logo: { url: string; placa: "branca" | "tinta" | null } | null;
  /* A fonte do título e as fotos da capa, para a prévia da tela. */
  fonte: { titulo: string; folhas: string[] };
  capa: string[];
  categorias: { id: string; nome: string; produtos: number; comFoto: number }[];
  totais: { produtos: number; comFoto: number };
  limites: { grade: number; destaque: number };
};

/** O que a tela precisa para montar as escolhas: categorias, contagens e a cara da loja. */
export async function resumoDaLoja(admin: SupabaseClient, donoId: string, origemPublica: string): Promise<ResumoDaLoja> {
  const perfil = await lerPerfil(admin, donoId);
  const [enderecos, { categorias, produtos }, logo, fontes] = await Promise.all([
    enderecosDaLoja(admin, perfil, origemPublica),
    lerCategoriasEProdutos(admin, donoId),
    logoDaLoja(perfil.catalog_logo_url || perfil.logo_url || null),
    fontesDaLoja(perfil.catalog_font_heading, perfil.catalog_font_body),
  ]);
  const raiz = raizesDasCategorias(categorias);
  const contagem = new Map<string, { produtos: number; comFoto: number }>();
  const fotosPorCategoria = new Map<string, string[]>();
  for (const linha of produtos) {
    const id = raiz(linha.category_id)?.id || ID_DOS_OUTROS;
    const atual = contagem.get(id) || { produtos: 0, comFoto: 0 };
    atual.produtos += 1;
    const principal = montarProduto(linha, { urlDoProduto: enderecos.urlDoProduto, whatsapp: null, categoriaId: id }).fotosOriginais[0];
    if (principal) {
      atual.comFoto += 1;
      fotosPorCategoria.set(id, [...(fotosPorCategoria.get(id) || []), principal]);
    }
    contagem.set(id, atual);
  }
  const lista: ResumoDaLoja["categorias"] = [];
  for (const cat of categorias) {
    if (cat.parent_id && categorias.some((outra) => outra.id === cat.parent_id)) continue;
    const numeros = contagem.get(cat.id);
    if (numeros) lista.push({ id: cat.id, nome: String(cat.name || "").trim() || "Produtos", ...numeros });
  }
  const outros = contagem.get(ID_DOS_OUTROS);
  if (outros) lista.push({ id: ID_DOS_OUTROS, nome: "Outros produtos", ...outros });
  /* A mesma escolha da capa de verdade: uma foto de cada categoria, em rodízio. */
  const capa: string[] = [];
  for (let volta = 0; capa.length < 6 && volta < 10; volta += 1) {
    for (const cat of lista) {
      const foto = fotosPorCategoria.get(cat.id)?.[volta];
      if (foto && capa.length < 6) capa.push(miniatura(foto, 240));
    }
  }
  const cores = coresDaMarca(perfil.primary_color, logo?.analise ?? null);
  const logos = logosDaLoja(logo ? perfil.catalog_logo_url || perfil.logo_url || null : null, logo?.analise ?? null, cores);
  return {
    nome: perfil.catalog_name || perfil.store_name || String(perfil.slug),
    urlDaLoja: enderecos.urlDaLoja,
    urlCurta: enderecos.urlCurta,
    whatsapp: whatsappDoPerfil(perfil)?.rotulo ?? null,
    slogan: perfil.catalog_slogan || "",
    precosNaLoja: (perfil.catalog_price_mode || "public") === "public",
    cores: { marca: cores.marca, tinta: cores.tinta, tintaNaMarca: cores.tintaNaMarca, sobreTinta: cores.sobreTinta },
    logo: logos.naMarca ? { url: miniatura(logos.naMarca.url, 480), placa: logos.naMarca.placa } : null,
    fonte: { titulo: fontes.titulo, folhas: fontes.folhas },
    capa,
    categorias: lista,
    totais: {
      produtos: lista.reduce((soma, cat) => soma + cat.produtos, 0),
      comFoto: lista.reduce((soma, cat) => soma + cat.comFoto, 0),
    },
    limites: { grade: limiteDeProdutos({ formato: "celular", layout: "vitrine" }), destaque: limiteDeProdutos({ formato: "celular", layout: "destaque" }) },
  };
}

/* ------------------------------------------------------- a loja inteira */

export type Progresso = (etapa: "loja" | "fotos", feito: number, total: number) => void;

export async function carregarLoja(params: {
  admin: SupabaseClient;
  donoId: string;
  origemPublica: string;
  opcoes: OpcoesDoCatalogo;
  agora?: Date;
  aoProgredir?: Progresso;
}): Promise<{ loja: LojaDoCatalogo; fotos: FotosDoCatalogo }> {
  const { admin, donoId, opcoes, aoProgredir } = params;
  aoProgredir?.("loja", 0, 1);
  const perfil = await lerPerfil(admin, donoId);
  const logoUrl = perfil.catalog_logo_url || perfil.logo_url || null;
  const [enderecos, { categorias, produtos: linhas }, logo, fontes] = await Promise.all([
    enderecosDaLoja(admin, perfil, params.origemPublica),
    lerCategoriasEProdutos(admin, donoId),
    logoDaLoja(logoUrl),
    fontesDaLoja(perfil.catalog_font_heading, perfil.catalog_font_body),
  ]);
  aoProgredir?.("loja", 1, 1);

  const whatsapp = whatsappDoPerfil(perfil);
  const raiz = raizesDasCategorias(categorias);
  const escolhidas = opcoes.categorias ? new Set(opcoes.categorias) : null;
  let produtos: ProdutoDoCatalogo[] = linhas
    .map((linha) => montarProduto(linha, { urlDoProduto: enderecos.urlDoProduto, whatsapp, categoriaId: raiz(linha.category_id)?.id || ID_DOS_OUTROS }))
    .filter((produto) => !escolhidas || escolhidas.has(produto.categoriaId));
  if (opcoes.soComFoto) produtos = produtos.filter((produto) => produto.fotosOriginais.length > 0);

  if (!produtos.length) {
    throw new ErroDoCatalogo(
      "sem-produtos",
      escolhidas || opcoes.soComFoto
        ? "Nenhum produto da loja entra nessas escolhas. Marque outras categorias ou inclua os produtos sem foto."
        : "Sua loja ainda não tem produto à venda. Coloque produtos na loja em Catálogo › Produtos e volte aqui."
    );
  }
  const limite = limiteDeProdutos(opcoes);
  if (produtos.length > limite) {
    throw new ErroDoCatalogo(
      "muitos-produtos",
      `São ${produtos.length} produtos, e este formato aceita até ${limite}. Escolha algumas categorias para caber num PDF que ainda se manda pelo WhatsApp.`
    );
  }

  /* As fotos: a principal de cada produto e, no destaque, mais duas. */
  const fotos = new FotosDoCatalogo();
  const comExtras = emDestaque(opcoes);
  const total = produtos.reduce((soma, p) => soma + Math.min(p.fotosOriginais.length, comExtras ? 3 : 1), 0);
  let feitas = 0;
  const contar = () => {
    feitas += 1;
    aoProgredir?.("fotos", feitas, total);
  };
  aoProgredir?.("fotos", 0, total);
  await emLotes(produtos, 6, async (p) => {
    const url = p.fotosOriginais[0];
    const principal = url ? await prepararFoto(url) : null;
    if (url) contar();
    if (url && principal) {
      p.recorte = principal.recorte;
      p.fotoCartao = fotos.marcador(url, principal, "cartao");
      p.fotoGrande = fotos.marcador(url, principal, "grande");
      p.fotoCapa = fotos.marcador(url, principal, "capa");
      p.fotoMini = fotos.marcador(url, principal, "mini");
    }
    if (comExtras) {
      for (const extraUrl of p.fotosOriginais.slice(1, 3)) {
        const extra = await prepararFoto(extraUrl);
        contar();
        if (extra) p.fotosExtras.push(fotos.marcador(extraUrl, extra, "extra"));
      }
    }
  });
  /* "Só com foto" vale pela foto que chegou, não pela que está cadastrada. */
  if (opcoes.soComFoto) {
    produtos = produtos.filter((produto) => produto.fotoCartao);
    if (!produtos.length) {
      throw new ErroDoCatalogo("sem-produtos", "As fotos desses produtos não abriram agora. Tente de novo em instantes ou inclua os produtos sem foto.");
    }
  }

  const grupos = agruparPorCategoria(produtos, categorias);
  const cores = coresDaMarca(perfil.primary_color, logo?.analise ?? null);
  const qrSvg = await QRCode.toString(enderecos.urlDaLoja, {
    type: "svg",
    margin: 0,
    errorCorrectionLevel: "M",
    color: { dark: cores.tinta, light: "#FFFFFF00" },
  });
  const agora = params.agora ?? new Date();

  const loja: LojaDoCatalogo = {
    nome: perfil.catalog_name || perfil.store_name || String(perfil.slug),
    slogan: perfil.catalog_slogan || "",
    urlDaLoja: enderecos.urlDaLoja,
    urlCurta: enderecos.urlCurta,
    whatsapp,
    instagram: instagramDaLoja(perfil.social_instagram),
    endereco: perfil.address || "",
    cores,
    logo: logosDaLoja(logo?.embutida ?? null, logo?.analise ?? null, cores),
    fontes,
    categorias: grupos,
    totais: { produtos: produtos.length, categorias: grupos.length },
    destaquesDaCapa: destaquesDaCapa(grupos),
    qrSvg,
    data: new Intl.DateTimeFormat("pt-BR", { timeZone: "America/Sao_Paulo" }).format(agora),
  };
  return { loja, fotos };
}
