/*
 * Configurações dos produtos digitais de uma loja (tela Configurações da área
 * "Produtos digitais"). Ficam num jsonb em `digital_settings.config`; tudo o
 * que entra passa por `lerConfiguracoes`, que completa com o padrão e corta o
 * que estiver fora da faixa. Assim a tela, a área de membros e o e-mail leem
 * sempre um objeto inteiro e válido.
 */

import { lerAparencia, type FormatoDasCapas, type IdDaFonte, type IdDoFundo } from "./aparencia.ts";

export type Aparencia = "escura" | "clara";
export type QuandoAparecem = "na_hora" | "aprovacao";

export type ConfiguracoesDigitais = {
  area: {
    /* null: usa o nome da loja. */
    nome: string | null;
    aparencia: Aparencia;
    /* null: usa a cor da loja. */
    cor: string | null;
    banner: string | null;
    /* A aparência da área (aparencia.ts): letra dos títulos, fundo e capas. "aparencia" segue o fundo. */
    fonte: IdDaFonte;
    fundo: IdDoFundo;
    capas: FormatoDasCapas;
  };
  entrega: {
    assunto: string;
    mensagem: string;
    reenviarEm24h: boolean;
    botaoWhatsapp: boolean;
  };
  acesso: {
    /* null: para sempre. */
    prazoDias: number | null;
    aparelhos: number;
    /* null: freio desligado. */
    freioLugares: number | null;
    marcaDagua: boolean;
  };
  player: {
    continuar: boolean;
    percentualAssistida: number;
    velocidades: number[];
    proximaSozinha: boolean;
  };
  comentarios: {
    quando: QuandoAparecem;
    avisar: boolean;
  };
  certificado: {
    emitir: boolean;
    assinatura: string | null;
  };
  lembretes: {
    /* "Continue de onde parou" para quem não estuda há 30 dias (um por mês). */
    alunosParados: boolean;
  };
  fiscal: {
    /* A NFS-e sai sozinha nas vendas de produto digital da loja virtual. */
    nfseNaLoja: boolean;
  };
};

export const VELOCIDADES_POSSIVEIS = [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2] as const;
export const PALAVRAS_DO_EMAIL = ["primeiro_nome", "produto", "link_de_acesso", "loja"] as const;

export const CONFIGURACOES_PADRAO: ConfiguracoesDigitais = {
  area: { nome: null, aparencia: "escura", cor: null, banner: null, fonte: "cinema", fundo: "preto", capas: "em_pe" },
  entrega: {
    assunto: "Seu acesso chegou: {{produto}}",
    mensagem:
      "Oi, {{primeiro_nome}}! Obrigado pela compra. Seu acesso a {{produto}} já está liberado. É só entrar pelo botão abaixo com este e-mail.",
    reenviarEm24h: true,
    botaoWhatsapp: true,
  },
  acesso: { prazoDias: null, aparelhos: 2, freioLugares: 5, marcaDagua: false },
  player: { continuar: true, percentualAssistida: 90, velocidades: [0.75, 1, 1.25, 1.5, 2], proximaSozinha: true },
  comentarios: { quando: "na_hora", avisar: true },
  certificado: { emitir: true, assinatura: null },
  lembretes: { alunosParados: true },
  fiscal: { nfseNaLoja: false },
};

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

function texto(valor: unknown, padrao: string, teto: number): string {
  if (typeof valor !== "string") return padrao;
  const limpo = valor.replace(/\u0000/g, "").trim();
  return limpo ? limpo.slice(0, teto) : padrao;
}

function textoOuNulo(valor: unknown, teto: number): string | null {
  if (typeof valor !== "string") return null;
  const limpo = valor.replace(/\u0000/g, "").trim();
  return limpo ? limpo.slice(0, teto) : null;
}

function logico(valor: unknown, padrao: boolean): boolean {
  return typeof valor === "boolean" ? valor : padrao;
}

function inteiro(valor: unknown, padrao: number, minimo: number, maximo: number): number {
  const numero = typeof valor === "number" ? valor : typeof valor === "string" && valor.trim() ? Number(valor) : NaN;
  if (!Number.isFinite(numero)) return padrao;
  return Math.min(maximo, Math.max(minimo, Math.round(numero)));
}

function inteiroOuNulo(valor: unknown, padrao: number | null, minimo: number, maximo: number): number | null {
  if (valor === null) return null;
  if (valor === undefined) return padrao;
  const numero = typeof valor === "number" ? valor : typeof valor === "string" && valor.trim() ? Number(valor) : NaN;
  if (!Number.isFinite(numero)) return padrao;
  return Math.min(maximo, Math.max(minimo, Math.round(numero)));
}

export function corValida(valor: unknown): string | null {
  if (typeof valor !== "string") return null;
  const cor = valor.trim();
  return /^#[0-9a-fA-F]{6}$/.test(cor) ? cor.toUpperCase() : null;
}

function enderecoDeImagem(valor: unknown): string | null {
  const bruto = textoOuNulo(valor, 1000);
  if (!bruto) return null;
  try {
    const url = new URL(bruto);
    return url.protocol === "https:" ? url.toString() : null;
  } catch {
    return null;
  }
}

export function lerConfiguracoes(bruto: unknown): ConfiguracoesDigitais {
  const raiz = objeto(bruto);
  const area = objeto(raiz.area);
  const entrega = objeto(raiz.entrega);
  const acesso = objeto(raiz.acesso);
  const player = objeto(raiz.player);
  const comentarios = objeto(raiz.comentarios);
  const certificado = objeto(raiz.certificado);
  const lembretes = objeto(raiz.lembretes);
  const fiscal = objeto(raiz.fiscal);
  const P = CONFIGURACOES_PADRAO;

  const velocidades = Array.isArray(player.velocidades)
    ? VELOCIDADES_POSSIVEIS.filter((v) => (player.velocidades as unknown[]).some((x) => Number(x) === v))
    : P.player.velocidades;

  const aparencia = lerAparencia(area);
  return {
    area: {
      nome: textoOuNulo(area.nome, 60),
      aparencia: aparencia.fundo === "claro" ? "clara" : "escura",
      cor: corValida(area.cor),
      banner: enderecoDeImagem(area.banner),
      ...aparencia,
    },
    entrega: {
      assunto: texto(entrega.assunto, P.entrega.assunto, 140),
      mensagem: texto(entrega.mensagem, P.entrega.mensagem, 2000),
      reenviarEm24h: logico(entrega.reenviarEm24h, P.entrega.reenviarEm24h),
      botaoWhatsapp: logico(entrega.botaoWhatsapp, P.entrega.botaoWhatsapp),
    },
    acesso: {
      prazoDias: inteiroOuNulo(acesso.prazoDias, P.acesso.prazoDias, 1, 3650),
      aparelhos: inteiro(acesso.aparelhos, P.acesso.aparelhos, 1, 10),
      freioLugares: inteiroOuNulo(acesso.freioLugares, P.acesso.freioLugares, 2, 50),
      marcaDagua: logico(acesso.marcaDagua, P.acesso.marcaDagua),
    },
    player: {
      continuar: logico(player.continuar, P.player.continuar),
      percentualAssistida: inteiro(player.percentualAssistida, P.player.percentualAssistida, 50, 100),
      velocidades: velocidades.length ? [...velocidades] : [1],
      proximaSozinha: logico(player.proximaSozinha, P.player.proximaSozinha),
    },
    comentarios: {
      quando: comentarios.quando === "aprovacao" ? "aprovacao" : "na_hora",
      avisar: logico(comentarios.avisar, P.comentarios.avisar),
    },
    certificado: {
      emitir: logico(certificado.emitir, P.certificado.emitir),
      assinatura: textoOuNulo(certificado.assinatura, 80),
    },
    lembretes: {
      alunosParados: logico(lembretes.alunosParados, P.lembretes.alunosParados),
    },
    fiscal: {
      nfseNaLoja: logico(fiscal.nfseNaLoja, P.fiscal.nfseNaLoja),
    },
  };
}

/* Troca {{palavra}} pelo valor; palavra desconhecida some. */
export function preencherMensagem(modelo: string, valores: Partial<Record<(typeof PALAVRAS_DO_EMAIL)[number], string>>): string {
  return modelo.replace(/\{\{\s*([a-z_]{1,30})\s*\}\}/g, (_inteiro, palavra: string) => {
    const valor = (valores as Record<string, string | undefined>)[palavra];
    return typeof valor === "string" ? valor : "";
  });
}

/* Quais seções diferem do padrão (o ponto violeta do índice). */
export function secoesMexidas(config: ConfiguracoesDigitais): Set<keyof ConfiguracoesDigitais> {
  const mexidas = new Set<keyof ConfiguracoesDigitais>();
  for (const secao of Object.keys(CONFIGURACOES_PADRAO) as Array<keyof ConfiguracoesDigitais>) {
    if (JSON.stringify(config[secao]) !== JSON.stringify(CONFIGURACOES_PADRAO[secao])) mexidas.add(secao);
  }
  return mexidas;
}
