/**
 * As regras da vitrine pelo assistente (banners, tarja de vantagens, selos,
 * faixa de anúncio, prova social e stories), sem banco.
 *
 * Só regra, sem importar nada do app: o teste ao lado (loja-vitrine.test.ts)
 * roda sozinho com `node --test`.
 */

/* ------------------------------------------------------------------ limites */

export const MAXIMO_DE_BANNERS = 20;
export const MAXIMO_DE_GRUPOS_DE_STORIES = 30;
export const MAXIMO_DE_STORIES_POR_GRUPO = 30;

/* Os mesmos limites da tela Marketing do Catálogo (e da migration 50). */
export const LIMITES_DA_PROVA_SOCIAL = {
  intervalo: { min: 5, max: 120, padrao: 18 },
  duracao: { min: 2, max: 20, padrao: 5 },
  maximo_visivel: { min: 1, max: 5, padrao: 2 },
} as const;

/** O número dentro da faixa; sem número, o padrão. Igual ao `clamp` da tela. */
export function limitar(valor: unknown, min: number, max: number, padrao: number): number {
  const numero = Math.round(Number(valor));
  if (!Number.isFinite(numero) || numero === 0) return Math.min(max, Math.max(min, padrao));
  return Math.min(max, Math.max(min, numero));
}

/* -------------------------------------------------------------------- ícones */

/* A lista da tela Banners e Informações (INFO_ICON_OPTIONS), com o nome que a
   pessoa vê. A chave em inglês é o que o banco guarda e a loja desenha. */
export const ICONES = [
  { chave: "truck", nome: "Caminhão" },
  { chave: "creditCard", nome: "Cartão" },
  { chave: "shieldCheck", nome: "Segurança" },
  { chave: "package", nome: "Pacote" },
  { chave: "clock", nome: "Relógio" },
  { chave: "badgeCheck", nome: "Garantia" },
  { chave: "mapPin", nome: "Localização" },
  { chave: "headphones", nome: "Suporte" },
] as const;

export type ChaveDoIcone = (typeof ICONES)[number]["chave"];

function semAcento(valor: string) {
  return valor.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
}

/* Outros jeitos de pedir o mesmo ícone. */
const SINONIMOS: Record<string, ChaveDoIcone> = {
  caminhao: "truck",
  entrega: "truck",
  frete: "truck",
  envio: "truck",
  cartao: "creditCard",
  "cartao de credito": "creditCard",
  pagamento: "creditCard",
  parcelamento: "creditCard",
  seguranca: "shieldCheck",
  escudo: "shieldCheck",
  "compra segura": "shieldCheck",
  pacote: "package",
  caixa: "package",
  embalagem: "package",
  relogio: "clock",
  tempo: "clock",
  prazo: "clock",
  garantia: "badgeCheck",
  selo: "badgeCheck",
  qualidade: "badgeCheck",
  localizacao: "mapPin",
  local: "mapPin",
  endereco: "mapPin",
  "retirada": "mapPin",
  suporte: "headphones",
  atendimento: "headphones",
  "fone": "headphones",
};

/** "caminhão", "Caminhao", "truck" → "truck". Desconhecido → null. */
export function iconePeloNome(valor: unknown): ChaveDoIcone | null {
  const pedido = semAcento(String(valor ?? "")).replace(/[_-]+/g, " ").replace(/\s+/g, " ");
  if (!pedido) return null;
  for (const icone of ICONES) {
    if (semAcento(icone.chave) === pedido.replace(/\s/g, "") || semAcento(icone.nome) === pedido) return icone.chave;
  }
  return SINONIMOS[pedido] ?? null;
}

export function nomeDoIcone(chave: unknown): string {
  return ICONES.find((icone) => icone.chave === chave)?.nome ?? String(chave ?? "");
}

export const NOMES_DOS_ICONES = ICONES.map((icone) => icone.nome).join(", ");

/* ---------------------------------------------- tarja de vantagens e selos */

/* Os padrões da tela, quando o banco ainda não tem nada gravado. */
export const TARJA_PADRAO = [
  { icon: "truck", title: "Envio Nacional e Internacional", subtitle: "Frete econômico para todo Brasil" },
  { icon: "creditCard", title: "Parcele suas compras", subtitle: "Em até 3x sem juros no cartão" },
  { icon: "shieldCheck", title: "Compra 100% Segura", subtitle: "Seus dados protegidos" },
];

export const SELOS_PADRAO = [
  { icon: "truck", title: "Entrega Rápida e Segura" },
  { icon: "shieldCheck", title: "Garantia de Qualidade" },
];

export type ItemDaTarja = { icon: string; title: string; subtitle: string };
export type SeloDoProduto = { icon: string; title: string };

/** O que está gravado, completado com o padrão: sempre 3 itens (como a tela lê). */
export function tarjaGravada(bruto: unknown): ItemDaTarja[] {
  const lista = (Array.isArray(bruto) ? bruto : [])
    .map((x) => {
      const item = (x ?? {}) as Record<string, unknown>;
      return {
        icon: typeof item.icon === "string" ? item.icon : "",
        title: typeof item.title === "string" ? item.title : "",
        subtitle: typeof item.subtitle === "string" ? item.subtitle : "",
      };
    })
    .filter((x) => x.icon || x.title || x.subtitle);
  while (lista.length < 3) lista.push(TARJA_PADRAO[lista.length] ?? TARJA_PADRAO[0]);
  return lista.slice(0, 3);
}

/** O mesmo para os 2 selos da página do produto. */
export function selosGravados(bruto: unknown): SeloDoProduto[] {
  const lista = (Array.isArray(bruto) ? bruto : [])
    .map((x) => {
      const item = (x ?? {}) as Record<string, unknown>;
      return { icon: typeof item.icon === "string" ? item.icon : "", title: typeof item.title === "string" ? item.title : "" };
    })
    .filter((x) => x.icon || x.title);
  while (lista.length < 2) lista.push(SELOS_PADRAO[lista.length] ?? SELOS_PADRAO[0]);
  return lista.slice(0, 2);
}

/** Texto curto que o cliente lê: sem HTML, sem quebra, sem espaço sobrando. */
export function textoCurto(valor: unknown, maximo: number): string {
  return String(valor ?? "")
    .replace(/<[^>]*>/g, " ")
    .replace(/\s+/g, " ")
    .trim()
    .slice(0, maximo);
}

type Lido<T> = { ok: true; valor: T } | { ok: false; erro: string };

/** Os itens pedidos pelo assistente, conferidos e no formato do banco. */
export function montarTarja(itens: Array<Record<string, unknown>>): Lido<ItemDaTarja[]> {
  if (itens.length !== 3) return { ok: false, erro: "A tarja de vantagens tem exatamente 3 itens. Mande os 3 (repita os que não mudam; veja em ver_vitrine_da_loja)." };
  const saida: ItemDaTarja[] = [];
  for (let i = 0; i < itens.length; i += 1) {
    const icone = iconePeloNome(itens[i].icone);
    if (!icone) return { ok: false, erro: `Item ${i + 1}: ícone "${String(itens[i].icone ?? "")}" não existe. Use um destes: ${NOMES_DOS_ICONES}.` };
    const titulo = textoCurto(itens[i].titulo, 60);
    if (!titulo) return { ok: false, erro: `Item ${i + 1}: falta o título.` };
    saida.push({ icon: icone, title: titulo, subtitle: textoCurto(itens[i].subtitulo, 80) });
  }
  return { ok: true, valor: saida };
}

export function montarSelos(itens: Array<Record<string, unknown>>): Lido<SeloDoProduto[]> {
  if (itens.length !== 2) return { ok: false, erro: "Os selos da página do produto são exatamente 2. Mande os 2 (repita o que não muda; veja em ver_vitrine_da_loja)." };
  const saida: SeloDoProduto[] = [];
  for (let i = 0; i < itens.length; i += 1) {
    const icone = iconePeloNome(itens[i].icone);
    if (!icone) return { ok: false, erro: `Selo ${i + 1}: ícone "${String(itens[i].icone ?? "")}" não existe. Use um destes: ${NOMES_DOS_ICONES}.` };
    const titulo = textoCurto(itens[i].titulo, 60);
    if (!titulo) return { ok: false, erro: `Selo ${i + 1}: falta o título.` };
    saida.push({ icon: icone, title: titulo });
  }
  return { ok: true, valor: saida };
}

/* ---------------------------------------------------------- cores e links */

/** "#1f6feb", "1F6FEB" ou "#fff" → "#1F6FEB". Outra coisa → null. */
export function corHex(valor: unknown): string | null {
  const limpo = String(valor ?? "").trim().replace(/^#/, "").toUpperCase();
  if (/^[0-9A-F]{6}$/.test(limpo)) return `#${limpo}`;
  if (/^[0-9A-F]{3}$/.test(limpo)) return `#${limpo.split("").map((letra) => letra + letra).join("")}`;
  return null;
}

/** Link de fora: só https://, endereço de verdade, sem espaço. Devolve o link limpo ou null. */
export function linkHttps(valor: unknown): string | null {
  const texto = String(valor ?? "").trim();
  if (!texto || texto.length > 2000 || /\s/.test(texto)) return null;
  if (!/^https:\/\//i.test(texto)) return null;
  try {
    const url = new URL(texto);
    if (url.protocol !== "https:" || !url.hostname.includes(".") || url.username || url.password) return null;
    return url.toString();
  } catch {
    return null;
  }
}

/** Slug de categoria como a loja usa: letras minúsculas, números e hífen. */
export function slugValido(valor: unknown): string | null {
  const texto = String(valor ?? "").trim().toLowerCase();
  return /^[a-z0-9][a-z0-9-]{0,119}$/.test(texto) ? texto : null;
}

/** O destino do botão da faixa, em palavras: para ver_vitrine_da_loja e a prévia. */
export function descreverDestino(url: string | null | undefined): { tipo: "nenhum" | "produto" | "categoria" | "subcategoria" | "link"; valor: string | null } {
  const texto = String(url ?? "").trim();
  if (!texto) return { tipo: "nenhum", valor: null };
  const produto = /^catalog:\/\/product\/([^/?#]+)/i.exec(texto);
  if (produto) return { tipo: "produto", valor: decodeURIComponent(produto[1]) };
  const categoria = /^catalog:\/\/(category|subcategory)\/([^/?#]+)/i.exec(texto);
  if (categoria) return { tipo: categoria[1].toLowerCase() === "category" ? "categoria" : "subcategoria", valor: decodeURIComponent(categoria[2]) };
  return { tipo: "link", valor: texto };
}

/* ------------------------------------------------------------------- datas */

/**
 * Dia e hora de Brasília viram um instante (ISO, UTC).
 * O Brasil não tem horário de verão desde 2019: sempre -03:00.
 */
export function instanteEmBrasilia(data: string, hora = "23:59"): string | null {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(data) || !/^([01]\d|2[0-3]):[0-5]\d$/.test(hora)) return null;
  const instante = new Date(`${data}T${hora}:00-03:00`);
  return Number.isNaN(instante.getTime()) ? null : instante.toISOString();
}

/** Um instante gravado, lido no horário de Brasília: "31/10/2026 23:59". */
export function instanteBr(iso: string | null | undefined): string | null {
  if (!iso) return null;
  const data = new Date(iso);
  if (Number.isNaN(data.getTime())) return null;
  const partes = new Intl.DateTimeFormat("pt-BR", {
    timeZone: "America/Sao_Paulo",
    day: "2-digit",
    month: "2-digit",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
  }).format(data);
  return partes.replace(",", "");
}

/* ------------------------------------------------------------------- ordem */

/**
 * A nova ordem pedida. A lista pode trazer só alguns ids: eles vão para a
 * frente, na ordem dada, e os outros seguem atrás na ordem de antes.
 */
export function novaOrdem(atuais: string[], pedida: string[]): Lido<string[]> {
  const conhecidos = new Set(atuais);
  const vistos = new Set<string>();
  for (const id of pedida) {
    if (!conhecidos.has(id)) return { ok: false, erro: `O id ${id} não está nesta lista. Use os ids de ver_vitrine_da_loja.` };
    if (vistos.has(id)) return { ok: false, erro: `O id ${id} aparece duas vezes na ordem.` };
    vistos.add(id);
  }
  return { ok: true, valor: [...pedida, ...atuais.filter((id) => !vistos.has(id))] };
}

/** Move um id para a posição (1 = primeiro). Posição além do fim vai para o fim. */
export function moverPara(atuais: string[], id: string, posicao: number): string[] {
  const resto = atuais.filter((atual) => atual !== id);
  const indice = Math.min(Math.max(Math.round(posicao) - 1, 0), resto.length);
  resto.splice(indice, 0, id);
  return resto;
}

/** O que muda de posição para a lista ficar na ordem: só as linhas que precisam. */
export function posicoesQueMudam(
  linhas: Array<{ id: string; position: number | null }>,
  ordem: string[],
  base: 0 | 1
): Array<{ id: string; position: number }> {
  const atual = new Map(linhas.map((linha) => [linha.id, linha.position]));
  return ordem
    .map((id, indice) => ({ id, position: indice + base }))
    .filter((linha) => atual.get(linha.id) !== linha.position);
}

/* ------------------------------------------------------------- arquivos */

/**
 * O caminho do arquivo no depósito `products`, a partir do endereço público.
 * Só devolve caminhos dentro da pasta permitida (ex.: "stories/"): nunca
 * apaga a foto de um produto que por acaso foi usada como imagem.
 */
export function caminhoNoDeposito(endereco: unknown, pastaPermitida: string): string | null {
  const texto = String(endereco ?? "");
  const marca = "/object/public/products/";
  const onde = texto.indexOf(marca);
  if (onde < 0) return null;
  let caminho: string;
  try {
    caminho = decodeURIComponent(texto.slice(onde + marca.length).split(/[?#]/)[0]);
  } catch {
    return null;
  }
  const pasta = pastaPermitida.replace(/\/+$/, "") + "/";
  if (!caminho.startsWith(pasta) || caminho.includes("..")) return null;
  return caminho;
}
