/*
  Como uma seção da página aparece (vale para QUALQUER tipo de seção, nos três
  modelos de loja), guardado na própria configuração da seção:

  - config.agendamento   { inicio, fim } na hora de Brasília ("AAAA-MM-DDTHH:MM").
                         Fora da janela a seção não sai na loja. A decisão é
                         do servidor, a cada visita, sobre os dados lembrados
                         (lib/loja/dados-da-loja.ts guarda as seções por dois
                         minutos, nunca a decisão): a seção entra e sai no
                         minuto marcado, sem esperar a lembrança vencer.
  - config.dispositivo   "celular" | "computador" (ausente = em ambos). Feito
                         com as medidas de tela de cada modelo (Padrão e
                         Vitrine trocam em 768 px; Vendor, em 1024 px), só com
                         CSS: nada pula na tela e o servidor e o navegador
                         desenham igual.
  - config.fundo_da_secao { tipo: nenhum | cor | gradiente | imagem, cor, cor_2,
                         angulo, imagem, escurecer (0 a 80%), largura: inteira |
                         contida, acima e abaixo: "" | p | m | g, texto_claro:
                         true | false (ausente = automático: claro sobre
                         imagem escurecida ou cor escura) }.

  Seção sem nada disso sai exatamente como sempre saiu (nenhuma caixa a mais
  em volta dela).

  Regras puras, sem "@/": o teste roda sozinho (exibicao-da-secao.test.ts). O
  painel tem a cópia em apps/admin/src/lib/exibicao-da-secao.ts; mudou aqui,
  muda lá.
*/

import { instanteDeBrasilia } from "./widgets-da-home.ts";

export type Dispositivo = "todos" | "celular" | "computador";
export type TipoDeFundo = "nenhum" | "cor" | "gradiente" | "imagem";
export type Espaco = "" | "p" | "m" | "g";

export type Agendamento = { inicio: number | null; fim: number | null; inicioTexto: string; fimTexto: string };

export type FundoDaSecao = {
  tipo: TipoDeFundo;
  cor: string;
  cor2: string;
  angulo: number;
  imagem: string;
  escurecer: number;
  largura: "inteira" | "contida";
  acima: Espaco;
  abaixo: Espaco;
  /* Os textos da seção ficam brancos (fundo escuro). */
  textoClaro: boolean;
};

export type ExibicaoDaSecao = {
  agendamento: Agendamento | null;
  dispositivo: Dispositivo;
  fundo: FundoDaSecao | null;
};

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

function cor(valor: unknown, padrao: string) {
  const texto = String(valor ?? "").trim();
  return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(texto) ? texto : padrao;
}

function urlDeImagem(valor: unknown): string {
  const bruto = String(valor ?? "").trim();
  if (!bruto || bruto.length > 2000) return "";
  if (/^https:\/\//i.test(bruto)) return bruto;
  if (bruto.startsWith("/") && !bruto.startsWith("//")) return bruto;
  return "";
}

/** Luminância relativa (0 = preto, 1 = branco) de #abc ou #aabbcc. */
export function luminancia(hex: string): number {
  let h = hex.replace("#", "");
  if (h.length === 3) h = h.split("").map((c) => c + c).join("");
  const canais = [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16) / 255).map((c) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4));
  return 0.2126 * canais[0] + 0.7152 * canais[1] + 0.0722 * canais[2];
}

function espaco(valor: unknown): Espaco {
  return valor === "p" || valor === "m" || valor === "g" ? valor : "";
}

const DATA_E_HORA = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/;

/** A janela da seção. Sem início nem fim válidos: null (sem agendamento). */
export function lerAgendamento(config: Record<string, unknown> | null | undefined): Agendamento | null {
  const bruto = objeto(config?.agendamento);
  if (!bruto) return null;
  const inicioTexto = DATA_E_HORA.test(String(bruto.inicio ?? "")) ? String(bruto.inicio) : "";
  const fimTexto = DATA_E_HORA.test(String(bruto.fim ?? "")) ? String(bruto.fim) : "";
  const inicio = inicioTexto ? instanteDeBrasilia(inicioTexto) : null;
  const fim = fimTexto ? instanteDeBrasilia(fimTexto) : null;
  if (inicio === null && fim === null) return null;
  return { inicio, fim, inicioTexto: inicio === null ? "" : inicioTexto, fimTexto: fim === null ? "" : fimTexto };
}

export function lerDispositivo(config: Record<string, unknown> | null | undefined): Dispositivo {
  const valor = config?.dispositivo;
  return valor === "celular" || valor === "computador" ? valor : "todos";
}

/** O fundo da seção. Sem nada que mude o desenho (nenhum fundo e nenhum espaço): null. */
export function lerFundo(config: Record<string, unknown> | null | undefined): FundoDaSecao | null {
  const bruto = objeto(config?.fundo_da_secao);
  if (!bruto) return null;
  const tipoPedido = String(bruto.tipo ?? "");
  let tipo: TipoDeFundo = tipoPedido === "cor" || tipoPedido === "gradiente" || tipoPedido === "imagem" ? tipoPedido : "nenhum";
  const imagem = urlDeImagem(bruto.imagem);
  if (tipo === "imagem" && !imagem) tipo = "nenhum";
  const angulo = Math.round(Number(bruto.angulo));
  const escurecer = Math.round(Number(bruto.escurecer));
  const fundo: FundoDaSecao = {
    tipo,
    cor: cor(bruto.cor, "#f4f0ff"),
    cor2: cor(bruto.cor_2, "#ffffff"),
    angulo: Number.isFinite(angulo) ? ((angulo % 360) + 360) % 360 : 135,
    imagem: tipo === "imagem" ? imagem : "",
    escurecer: Number.isFinite(escurecer) ? Math.min(80, Math.max(0, escurecer)) : 35,
    largura: bruto.largura === "contida" ? "contida" : "inteira",
    acima: espaco(bruto.acima),
    abaixo: espaco(bruto.abaixo),
    textoClaro: false,
  };
  if (fundo.tipo === "nenhum" && !fundo.acima && !fundo.abaixo) return null;
  fundo.textoClaro =
    fundo.tipo === "nenhum"
      ? false
      : typeof bruto.texto_claro === "boolean"
        ? bruto.texto_claro
        : fundo.tipo === "imagem"
          ? fundo.escurecer >= 35
          : fundo.tipo === "gradiente"
            ? (luminancia(fundo.cor) + luminancia(fundo.cor2)) / 2 < 0.4
            : luminancia(fundo.cor) < 0.4;
  return fundo;
}

export function lerExibicao(config: Record<string, unknown> | null | undefined): ExibicaoDaSecao {
  return { agendamento: lerAgendamento(config), dispositivo: lerDispositivo(config), fundo: lerFundo(config) };
}

/** A seção muda o desenho de volta dela (precisa da caixa em volta)? */
export function temCaixaPropria(config: Record<string, unknown> | null | undefined) {
  return lerDispositivo(config) !== "todos" || lerFundo(config) !== null;
}

/** A seção está no ar agora (dentro da janela, ou sem agendamento)? */
export function estaNoAr(config: Record<string, unknown> | null | undefined, agora: number): boolean {
  const agenda = lerAgendamento(config);
  if (!agenda) return true;
  if (agenda.inicio !== null && agora < agenda.inicio) return false;
  if (agenda.fim !== null && agora >= agenda.fim) return false;
  return true;
}

function configDe(secao: { config?: unknown }): Record<string, unknown> | null {
  const bruto = secao.config;
  if (typeof bruto === "string") {
    try {
      return objeto(JSON.parse(bruto));
    } catch {
      return null;
    }
  }
  return objeto(bruto);
}

/** As seções que a loja mostra agora (as agendadas fora da janela saem). Sem agendamento nenhum, a mesma lista. */
export function secoesNoAr<S extends { config?: unknown }>(secoes: S[], agora: number): S[] {
  if (!secoes.some((secao) => configDe(secao)?.agendamento)) return secoes;
  return secoes.filter((secao) => estaNoAr(configDe(secao), agora));
}

/* --------------------------------------------------------- textos */

function quando(texto: string) {
  const [data, hora] = texto.split("T");
  const [ano, mes, dia] = data.split("-");
  void ano;
  return `${dia}/${mes} às ${hora}`;
}

/** "Agendada: aparece de 20/10 às 08:00 até 25/10 às 23:59". */
export function textoDoAgendamento(agenda: Agendamento | null): string {
  if (!agenda) return "";
  if (agenda.inicioTexto && agenda.fimTexto) return `Agendada: aparece de ${quando(agenda.inicioTexto)} até ${quando(agenda.fimTexto)}`;
  if (agenda.inicioTexto) return `Agendada: aparece a partir de ${quando(agenda.inicioTexto)}`;
  return `Agendada: aparece até ${quando(agenda.fimTexto)}`;
}

/** Onde a seção está em relação à janela: antes, no ar ou depois. */
export function momentoDoAgendamento(agenda: Agendamento | null, agora: number): "sem" | "antes" | "no_ar" | "depois" {
  if (!agenda) return "sem";
  if (agenda.inicio !== null && agora < agenda.inicio) return "antes";
  if (agenda.fim !== null && agora >= agenda.fim) return "depois";
  return "no_ar";
}

/* ------------------------------------------------------- desenho */

/** O fundo como CSS (cor, degradê ou imagem com a camada escura por cima). */
export function fundoEmCss(fundo: FundoDaSecao): { background?: string; backgroundImage?: string; backgroundSize?: string; backgroundPosition?: string } {
  if (fundo.tipo === "cor") return { background: fundo.cor };
  if (fundo.tipo === "gradiente") return { background: `linear-gradient(${fundo.angulo}deg, ${fundo.cor} 0%, ${fundo.cor2} 100%)` };
  if (fundo.tipo === "imagem") {
    const camada = fundo.escurecer > 0 ? `linear-gradient(rgba(0,0,0,${(fundo.escurecer / 100).toFixed(2)}), rgba(0,0,0,${(fundo.escurecer / 100).toFixed(2)})), ` : "";
    return {
      backgroundImage: `${camada}url("${fundo.imagem.replace(/["\\\n\r]/g, "")}")`,
      backgroundSize: "cover",
      backgroundPosition: "center",
    };
  }
  return {};
}

export const ESPACO_ACIMA: Record<Exclude<Espaco, "">, string> = { p: "pt-3 md:pt-4", m: "pt-6 md:pt-8", g: "pt-10 md:pt-14" };
export const ESPACO_ABAIXO: Record<Exclude<Espaco, "">, string> = { p: "pb-3 md:pb-4", m: "pb-6 md:pb-8", g: "pb-10 md:pb-14" };

/** As classes que escondem a seção no aparelho que não é o dela. Padrão/Vitrine: 768 px; Vendor e Boutique: 1024 px. */
export function classesDoDispositivo(dispositivo: Dispositivo, familia: "padrao" | "vendor" | "boutique"): string {
  const noLg = familia === "vendor" || familia === "boutique";
  if (dispositivo === "celular") return noLg ? "lg:hidden" : "md:hidden";
  if (dispositivo === "computador") return noLg ? "hidden lg:block" : "hidden md:block";
  return "";
}
