/*
  O cabeçalho, o menu e o rodapé da loja e as páginas extras, do lado do
  painel: os tipos, a leitura (cópia da loja), o rascunho do editor, o que
  vai para o banco e o que impede publicar.

  Regras puras, sem "@/": o teste roda sozinho (navegacao-da-loja.test.ts).
  A loja tem a cópia das regras de leitura em apps/public/src/lib/navegacao-da-loja.ts
  (os dois apps não dividem pacote); mudou lá, muda aqui.

  Onde mora (migration 197): catalog_navegacao (uma linha por loja: cabecalho,
  menu, rodape em jsonb) e catalog_paginas. Quem grava a navegação é a função
  publicar_no_editor_da_loja, junto com as seções, numa transação só.

  A regra que manda: parte em null, ou igual ao padrão, a loja desenha
  exatamente como sempre desenhou.
*/

import { problemaDoEndereco } from "./pagina-inicial.ts";

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

export const LIMITE_DE_ITENS_NO_MENU = 12;
export const LIMITE_DE_FILHOS_NO_GRUPO = 12;
export const LIMITE_DE_COLUNAS_NO_RODAPE = 4;
export const LIMITE_DE_LINKS_NA_COLUNA = 10;
export const LIMITE_DO_SOBRE = 600;

/* -------------------------------------------------------------- tipos */

export type TipoDoItem = "inicio" | "categoria" | "produto" | "pagina" | "url" | "grupo";
export const TIPOS_DO_ITEM: readonly TipoDoItem[] = ["inicio", "categoria", "produto", "pagina", "url", "grupo"];

export type ItemDoMenu = {
  id: string;
  rotulo: string;
  tipo: TipoDoItem;
  categoria_id?: string;
  categoria_slug?: string;
  produto_id?: string;
  pagina_id?: string;
  pagina_slug?: string;
  url?: string;
  nova_aba?: boolean;
  /* O item que chama atenção no menu (pílula na cor da loja). */
  destaque?: boolean;
  /* Só no grupo: os links que descem dele (um nível). */
  filhos?: ItemDoMenu[];
};

export type MenuDaLoja = {
  itens: ItemDoMenu[];
  /* As categorias de sempre continuam no menu ("Todas as categorias" e a
     lista do celular), além dos itens próprios. */
  mostrar_categorias: boolean;
};

export type TamanhoDaLogo = "pequeno" | "normal" | "grande";

export type CabecalhoDaLoja = {
  mostrar_busca: boolean;
  tamanho_da_logo: TamanhoDaLogo;
  /* O cabeçalho acompanha a rolagem (como sempre foi) ou sobe com a página. */
  fixo: boolean;
};

export type ColunaDoRodape = { id: string; titulo: string; links: ItemDoMenu[] };

export type RodapeDaLoja = {
  colunas: ColunaDoRodape[];
  /* O texto "Sobre a loja", no lugar do slogan. */
  sobre: string;
  mostrar_contato: boolean;
  mostrar_redes: boolean;
  mostrar_pagamento: boolean;
  mostrar_categorias: boolean;
};

export type PaginaDaLoja = { id: string; titulo: string; slug: string };

export type NavegacaoDaLoja = {
  cabecalho: CabecalhoDaLoja | null;
  menu: MenuDaLoja | null;
  rodape: RodapeDaLoja | null;
  /* As páginas extras visíveis, para os links do menu e do rodapé. */
  paginas: PaginaDaLoja[];
};

export const CABECALHO_PADRAO: CabecalhoDaLoja = { mostrar_busca: true, tamanho_da_logo: "normal", fixo: true };
export const RODAPE_PADRAO: RodapeDaLoja = {
  colunas: [],
  sobre: "",
  mostrar_contato: true,
  mostrar_redes: true,
  mostrar_pagamento: true,
  mostrar_categorias: true,
};

/* ------------------------------------------------------------ leitura */

function texto(valor: unknown, maximo: number) {
  return String(valor ?? "").replace(/\s+/g, " ").trim().slice(0, maximo);
}

function objeto(valor: unknown): Record<string, unknown> | null {
  if (valor && typeof valor === "object" && !Array.isArray(valor)) return valor as Record<string, unknown>;
  if (typeof valor === "string") {
    try {
      const lido = JSON.parse(valor);
      return lido && typeof lido === "object" && !Array.isArray(lido) ? (lido as Record<string, unknown>) : null;
    } catch {
      return null;
    }
  }
  return null;
}

/** Endereço de página da loja: minúsculas, números e hífens. */
export function slugDePagina(valor: unknown): string {
  return String(valor ?? "")
    .normalize("NFD")
    .replace(/[̀-ͯ]/g, "")
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 80)
    .replace(/-+$/g, "");
}

export function lerItem(valor: unknown, comoFilho = false, indice = 0): ItemDoMenu | null {
  const bruto = objeto(valor);
  if (!bruto) return null;
  const tipo = (TIPOS_DO_ITEM as readonly string[]).includes(String(bruto.tipo)) ? (bruto.tipo as TipoDoItem) : null;
  if (!tipo || (comoFilho && tipo === "grupo")) return null;
  const id = texto(bruto.id, 80) || `item-${indice}`;
  const item: ItemDoMenu = { id, rotulo: texto(bruto.rotulo, 60), tipo };
  if (bruto.nova_aba === true) item.nova_aba = true;
  if (bruto.destaque === true) item.destaque = true;
  if (tipo === "categoria") {
    item.categoria_id = texto(bruto.categoria_id, 80);
    item.categoria_slug = texto(bruto.categoria_slug, 200);
  }
  if (tipo === "produto") item.produto_id = texto(bruto.produto_id, 80);
  if (tipo === "pagina") {
    item.pagina_id = texto(bruto.pagina_id, 80);
    item.pagina_slug = slugDePagina(bruto.pagina_slug);
  }
  if (tipo === "url") item.url = texto(bruto.url, 500);
  if (tipo === "grupo") {
    item.filhos = (Array.isArray(bruto.filhos) ? bruto.filhos : [])
      .map((filho, i) => lerItem(filho, true, i))
      .filter((filho): filho is ItemDoMenu => Boolean(filho))
      .slice(0, LIMITE_DE_FILHOS_NO_GRUPO);
  }
  return item;
}

/** O menu salvo; null quando não muda nada (sem itens e com as categorias). */
export function lerMenu(valor: unknown): MenuDaLoja | null {
  const bruto = objeto(valor);
  if (!bruto) return null;
  const itens = (Array.isArray(bruto.itens) ? bruto.itens : [])
    .map((item, i) => lerItem(item, false, i))
    .filter((item): item is ItemDoMenu => Boolean(item))
    .slice(0, LIMITE_DE_ITENS_NO_MENU);
  const menu: MenuDaLoja = { itens, mostrar_categorias: bruto.mostrar_categorias !== false };
  if (!menu.itens.length && menu.mostrar_categorias) return null;
  return menu;
}

export function lerCabecalho(valor: unknown): CabecalhoDaLoja | null {
  const bruto = objeto(valor);
  if (!bruto) return null;
  const tamanho = bruto.tamanho_da_logo === "pequeno" || bruto.tamanho_da_logo === "grande" ? bruto.tamanho_da_logo : "normal";
  const cabecalho: CabecalhoDaLoja = {
    mostrar_busca: bruto.mostrar_busca !== false,
    tamanho_da_logo: tamanho,
    fixo: bruto.fixo !== false,
  };
  if (cabecalho.mostrar_busca && cabecalho.tamanho_da_logo === "normal" && cabecalho.fixo) return null;
  return cabecalho;
}

export function lerRodape(valor: unknown): RodapeDaLoja | null {
  const bruto = objeto(valor);
  if (!bruto) return null;
  const colunas = (Array.isArray(bruto.colunas) ? bruto.colunas : [])
    .map((coluna) => objeto(coluna))
    .filter((coluna): coluna is Record<string, unknown> => Boolean(coluna))
    .slice(0, LIMITE_DE_COLUNAS_NO_RODAPE)
    .map((coluna, indice) => ({
      id: texto(coluna.id, 80) || `coluna-${indice}`,
      titulo: texto(coluna.titulo, 60),
      links: (Array.isArray(coluna.links) ? coluna.links : [])
        .map((link, i) => lerItem(link, true, i))
        .filter((link): link is ItemDoMenu => Boolean(link))
        .slice(0, LIMITE_DE_LINKS_NA_COLUNA),
    }));
  const rodape: RodapeDaLoja = {
    colunas,
    sobre: String(bruto.sobre ?? "").replace(/\r\n/g, "\n").trim().slice(0, LIMITE_DO_SOBRE),
    mostrar_contato: bruto.mostrar_contato !== false,
    mostrar_redes: bruto.mostrar_redes !== false,
    mostrar_pagamento: bruto.mostrar_pagamento !== false,
    mostrar_categorias: bruto.mostrar_categorias !== false,
  };
  const igualAoPadrao =
    !rodape.colunas.length &&
    !rodape.sobre &&
    rodape.mostrar_contato &&
    rodape.mostrar_redes &&
    rodape.mostrar_pagamento &&
    rodape.mostrar_categorias;
  return igualAoPadrao ? null : rodape;
}

export function lerPaginas(valor: unknown): PaginaDaLoja[] {
  if (!Array.isArray(valor)) return [];
  return valor
    .map((linha) => objeto(linha))
    .filter((linha): linha is Record<string, unknown> => Boolean(linha))
    .map((linha) => ({ id: texto(linha.id, 80), titulo: texto(linha.titulo, 120), slug: slugDePagina(linha.slug) }))
    .filter((pagina) => pagina.id && pagina.slug);
}

/**
 * A navegação da loja como os templates usam. Devolve null quando nada muda
 * (sem linha, ou tudo igual ao padrão): o template segue o caminho de sempre.
 */
export function lerNavegacao(linha: unknown, paginas: unknown): NavegacaoDaLoja | null {
  const bruto = objeto(linha);
  const lista = lerPaginas(paginas);
  const cabecalho = bruto ? lerCabecalho(bruto.cabecalho) : null;
  const menu = bruto ? lerMenu(bruto.menu) : null;
  const rodape = bruto ? lerRodape(bruto.rodape) : null;
  if (!cabecalho && !menu && !rodape) return null;
  return { cabecalho, menu, rodape, paginas: lista };
}

/* ---------------------------------------------------- o rascunho */

/**
 * O rascunho que o editor mexe. `menu` e `rodape` em null = os de sempre do
 * template ("Usar menu próprio" desligado). O cabeçalho é sempre um valor
 * (o padrão é o de sempre).
 */
export type RascunhoDaNavegacao = {
  cabecalho: CabecalhoDaLoja;
  menu: MenuDaLoja | null;
  rodape: RodapeDaLoja | null;
};

export function rascunhoDaNavegacao(linha: unknown): RascunhoDaNavegacao {
  const bruto = objeto(linha);
  const menuBruto = bruto ? objeto(bruto.menu) : null;
  const rodapeBruto = bruto ? objeto(bruto.rodape) : null;
  return {
    cabecalho: (bruto && lerCabecalho(bruto.cabecalho)) || { ...CABECALHO_PADRAO },
    /* O que foi salvo volta como estava, mesmo igual ao padrão: a pessoa
       ligou "menu próprio" e ainda não pôs item. */
    menu: menuBruto ? lerMenu(menuBruto) ?? { itens: [], mostrar_categorias: true } : null,
    rodape: rodapeBruto ? lerRodape(rodapeBruto) ?? { ...RODAPE_PADRAO, colunas: [] } : null,
  };
}

/** O que vai para publicar_no_editor_da_loja (cada parte em null = a de sempre). */
export function navegacaoParaGravar(rascunho: RascunhoDaNavegacao) {
  return {
    cabecalho: lerCabecalho(rascunho.cabecalho),
    menu: rascunho.menu ? lerMenu(rascunho.menu) : null,
    rodape: rascunho.rodape ? lerRodape(rascunho.rodape) : null,
  };
}

export function mudouANavegacao(a: RascunhoDaNavegacao, b: RascunhoDaNavegacao) {
  return JSON.stringify(navegacaoParaGravar(a)) !== JSON.stringify(navegacaoParaGravar(b));
}

/** A navegação que a loja desenha no rascunho (o mesmo formato da linha do banco). */
export function navegacaoParaALoja(rascunho: RascunhoDaNavegacao) {
  return {
    cabecalho: rascunho.cabecalho,
    menu: rascunho.menu,
    rodape: rascunho.rodape,
  };
}

let contador = 0;
export function idNovo(prefixo = "item") {
  contador += 1;
  return `${prefixo}-${Date.now().toString(36)}${contador.toString(36)}${Math.random().toString(36).slice(2, 5)}`;
}

export const NOME_DO_TIPO: Record<TipoDoItem, string> = {
  inicio: "Início",
  categoria: "Categoria",
  produto: "Produto",
  pagina: "Página",
  url: "Endereço",
  grupo: "Grupo",
};

export function itemNovo(tipo: TipoDoItem): ItemDoMenu {
  const item: ItemDoMenu = { id: idNovo(), rotulo: tipo === "grupo" ? "Novo grupo" : tipo === "url" ? "Novo link" : "", tipo };
  if (tipo === "grupo") item.filhos = [];
  return item;
}

export function colunaNova(): ColunaDoRodape {
  return { id: idNovo("coluna"), titulo: "Nova coluna", links: [] };
}

export type ProblemaDaNavegacao = { regiao: "cabecalho" | "rodape"; texto: string };

function problemaDoItem(item: ItemDoMenu): string | null {
  const nome = item.rotulo ? `"${item.rotulo}"` : `O item (${NOME_DO_TIPO[item.tipo]})`;
  if (item.tipo === "categoria" && !item.categoria_id) return `${nome}: escolha a categoria.`;
  if (item.tipo === "produto" && !item.produto_id) return `${nome}: escolha o produto.`;
  if (item.tipo === "pagina" && !item.pagina_id) return `${nome}: escolha a página.`;
  if (item.tipo === "url") {
    if (!item.rotulo) return "Um link do menu está sem nome.";
    const erro = problemaDoEndereco(item.url);
    if (erro) return `${nome}: ${erro}`;
  }
  if (item.tipo === "grupo") {
    if (!item.rotulo) return "Um grupo do menu está sem nome.";
    if (!(item.filhos || []).length) return `${nome}: o grupo precisa de ao menos um link.`;
    for (const filho of item.filhos || []) {
      const erro = problemaDoItem(filho);
      if (erro) return erro;
    }
  }
  return null;
}

/** O que impede publicar o cabeçalho, o menu e o rodapé. */
export function problemasDaNavegacao(rascunho: RascunhoDaNavegacao): ProblemaDaNavegacao[] {
  const problemas: ProblemaDaNavegacao[] = [];
  for (const item of rascunho.menu?.itens || []) {
    const erro = problemaDoItem(item);
    if (erro) problemas.push({ regiao: "cabecalho", texto: `Menu: ${erro}` });
  }
  for (const coluna of rascunho.rodape?.colunas || []) {
    for (const link of coluna.links) {
      const erro = problemaDoItem(link);
      if (erro) problemas.push({ regiao: "rodape", texto: `Rodapé, "${coluna.titulo || "coluna"}": ${erro}` });
    }
  }
  return problemas;
}

/* ----------------------------------------------- protocolo do editor */

export const CANAL_DA_NAVEGACAO = "ktalogu:editor-da-loja";

/** A região que a loja diz que foi clicada (cabeçalho ou rodapé). */
export function lerRegiaoDaLoja(dados: unknown): "cabecalho" | "rodape" | null {
  const m = objeto(dados);
  if (!m || m.canal !== CANAL_DA_NAVEGACAO || m.tipo !== "regiao") return null;
  return m.id === "cabecalho" || m.id === "rodape" ? m.id : null;
}

/* ------------------------------------------------- páginas extras */

export function problemaDoSlugDaPagina(slug: string, existentes: string[]): string | null {
  if (!slug) return "Dê um endereço para a página.";
  if (slug.length > 80) return "O endereço passou de 80 letras.";
  if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(slug)) return "Use só letras minúsculas, números e hífen.";
  if (existentes.includes(slug)) return "Já existe uma página com esse endereço.";
  return null;
}

/** Um endereço livre a partir do título ("Sobre nós" vira "sobre-nos", depois "sobre-nos-2"). */
export function slugLivre(titulo: string, existentes: string[]): string {
  const base = slugDePagina(titulo) || "pagina";
  if (!existentes.includes(base)) return base;
  for (let n = 2; n < 500; n += 1) {
    const candidato = `${base.slice(0, 76)}-${n}`;
    if (!existentes.includes(candidato)) return candidato;
  }
  return `${base.slice(0, 70)}-${Date.now().toString(36)}`;
}
