/*
  O cabeçalho, o menu e o rodapé que a loja montou no editor do painel, e as
  páginas extras (/<slug>/pagina/<pagina>).

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

  Onde mora (migration 197): catalog_navegacao, uma linha por loja, com
  cabecalho, menu e rodape em jsonb. A regra que manda em tudo daqui:

    SEM LINHA, OU COM A PARTE EM NULL, OU COM A PARTE IGUAL AO PADRÃO, A LOJA
    DESENHA EXATAMENTE COMO SEMPRE DESENHOU.

  Por isso cada leitura devolve null quando a parte não muda nada: o template
  só troca de desenho quando recebe um valor.
*/

import { destinoDoLink } from "./destino-do-link.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 };
}

/* -------------------------------------------------------------- links */

export function caminhoDaPagina(slugDaLoja: string, slugDaPagina: string) {
  return `/${slugDaLoja}/pagina/${encodeURIComponent(slugDaPagina)}`;
}

export type Destino = { href: string; externo: boolean; novaAba: boolean };

type Contexto = {
  slug: string;
  categorias?: Array<{ id: string; slug?: string | null; name?: string | null }>;
  paginas?: PaginaDaLoja[];
};

/**
 * Para onde o item leva. Categoria e página são achadas pelo id (renomear a
 * categoria ou mudar o endereço da página não quebra o menu); página
 * escondida ou apagada some do menu. O endereço digitado passa pela regra do
 * banner (destinoDoLink): javascript: e data: não viram link.
 */
export function destinoDoItem(item: ItemDoMenu, ctx: Contexto): Destino | null {
  const novaAba = item.nova_aba === true;
  if (item.tipo === "inicio") return { href: `/${ctx.slug}`, externo: false, novaAba };
  if (item.tipo === "categoria") {
    const daLista = item.categoria_id ? ctx.categorias?.find((c) => c.id === item.categoria_id) : null;
    if (item.categoria_id && ctx.categorias && !daLista) return null;
    const caminho = daLista?.slug || item.categoria_slug || "";
    return caminho ? { href: `/${ctx.slug}/c/${encodeURIComponent(caminho)}`, externo: false, novaAba } : null;
  }
  if (item.tipo === "produto") {
    return item.produto_id ? { href: `/${ctx.slug}/p/${encodeURIComponent(item.produto_id)}`, externo: false, novaAba } : null;
  }
  if (item.tipo === "pagina") {
    const pagina = ctx.paginas?.find((p) => p.id === item.pagina_id);
    return pagina ? { href: caminhoDaPagina(ctx.slug, pagina.slug), externo: false, novaAba } : null;
  }
  if (item.tipo === "url") {
    const destino = destinoDoLink(item.url);
    return destino ? { ...destino, novaAba } : null;
  }
  return null;
}

export type ItemPronto = {
  id: string;
  rotulo: string;
  destaque: boolean;
  destino: Destino | null;
  filhos: Array<{ id: string; rotulo: string; destino: Destino }>;
};

function rotuloDoItem(item: ItemDoMenu, ctx: Contexto & { nomesDasCategorias?: Map<string, string> }) {
  if (item.rotulo) return item.rotulo;
  if (item.tipo === "inicio") return "Início";
  if (item.tipo === "pagina") return ctx.paginas?.find((p) => p.id === item.pagina_id)?.titulo || "Página";
  if (item.tipo === "categoria" && item.categoria_id) return ctx.nomesDasCategorias?.get(item.categoria_id) || "Categoria";
  return "";
}

/**
 * Os itens do menu prontos para desenhar: com o endereço resolvido, sem os
 * que perderam o destino (categoria apagada, página escondida) e sem grupo
 * vazio.
 */
export function itensProntos(itens: ItemDoMenu[], ctx: Contexto): ItemPronto[] {
  const nomesDasCategorias = new Map((ctx.categorias || []).map((c) => [c.id, String(c.name || "")]));
  const comNomes = { ...ctx, nomesDasCategorias };
  const prontos: ItemPronto[] = [];
  for (const item of itens) {
    if (item.tipo === "grupo") {
      const filhos = (item.filhos || [])
        .map((filho) => {
          const destino = destinoDoItem(filho, ctx);
          const rotulo = rotuloDoItem(filho, comNomes);
          return destino && rotulo ? { id: filho.id, rotulo, destino } : null;
        })
        .filter((filho): filho is { id: string; rotulo: string; destino: Destino } => Boolean(filho));
      if (!filhos.length || !item.rotulo) continue;
      prontos.push({ id: item.id, rotulo: item.rotulo, destaque: item.destaque === true, destino: null, filhos });
      continue;
    }
    const destino = destinoDoItem(item, ctx);
    const rotulo = rotuloDoItem(item, comNomes);
    if (!destino || !rotulo) continue;
    prontos.push({ id: item.id, rotulo, destaque: item.destaque === true, destino, filhos: [] });
  }
  return prontos;
}

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

/** A navegação que o painel manda no rascunho (o editor confere e lê de novo). */
export function lerNavegacaoDoRascunho(valor: unknown, paginas: PaginaDaLoja[]): NavegacaoDaLoja | "padrao" | null {
  const bruto = objeto(valor);
  if (!bruto) return null;
  const lida = lerNavegacao(bruto, paginas);
  return lida ?? "padrao";
}
