/*
 * As regras puras dos cursos, iguais no painel e na loja. Esta é a cópia da
 * loja; a original fica em apps/admin/src/lib/digitais/curso.ts (mudou lá,
 * copia aqui).
 *
 * - links de vídeo do YouTube, Vimeo e Panda Video viram um endereço de
 *   incorporação conhecido (nada de colar HTML de fora);
 * - quando cada módulo e cada aula abrem para o aluno;
 * - durações em português;
 * - o resumo público do conteúdo (a página de venda mostra módulos e aulas,
 *   nunca o endereço de um vídeo);
 * - a parte da página de venda que é só do curso.
 */

export type ProvedorDeVideo = "youtube" | "vimeo" | "panda";
export type LinkDeVideo = { provedor: ProvedorDeVideo; id: string; url: string };

const ID_DO_YOUTUBE = /^[A-Za-z0-9_-]{11}$/;

/** O endereço que o aluno colou (YouTube, Vimeo ou Panda) → o vídeo e como incorporar. */
export function lerLinkDeVideo(bruto: unknown): LinkDeVideo | null {
  if (typeof bruto !== "string") return null;
  const texto = bruto.trim();
  if (!texto || texto.length > 2000) return null;
  let url: URL;
  try {
    url = new URL(/^https?:\/\//i.test(texto) ? texto : `https://${texto}`);
  } catch {
    return null;
  }
  if (url.protocol !== "https:" && url.protocol !== "http:") return null;
  const host = url.hostname.toLowerCase().replace(/^www\./, "").replace(/^m\./, "");
  const partes = url.pathname.split("/").filter(Boolean);

  if (host === "youtu.be") {
    const id = partes[0] ?? "";
    return ID_DO_YOUTUBE.test(id) ? { provedor: "youtube", id, url: `https://www.youtube-nocookie.com/embed/${id}` } : null;
  }
  if (host === "youtube.com" || host === "youtube-nocookie.com" || host === "music.youtube.com") {
    let id = url.searchParams.get("v") ?? "";
    if (!id && ["embed", "shorts", "live", "v"].includes(partes[0] ?? "")) id = partes[1] ?? "";
    return ID_DO_YOUTUBE.test(id) ? { provedor: "youtube", id, url: `https://www.youtube-nocookie.com/embed/${id}` } : null;
  }
  if (host === "vimeo.com" || host === "player.vimeo.com") {
    const numeros = partes.filter((p) => /^\d{5,12}$/.test(p));
    const id = numeros[0] ?? "";
    if (!id) return null;
    /* Vídeo não listado: o código depois do id (vimeo.com/123/abc ou ?h=abc). */
    const depois = partes[partes.indexOf(id) + 1];
    const chave = url.searchParams.get("h") || (depois && /^[0-9a-f]{6,20}$/i.test(depois) ? depois : "");
    return {
      provedor: "vimeo",
      id: chave ? `${id}:${chave}` : id,
      url: `https://player.vimeo.com/video/${id}${chave ? `?h=${chave}` : ""}`,
    };
  }
  if (host.endsWith(".tv.pandavideo.com.br") || host.endsWith(".pandavideo.com.br")) {
    const id = url.searchParams.get("v") ?? "";
    const subdominio = host.endsWith(".tv.pandavideo.com.br") ? host : "";
    if (!/^[0-9a-f-]{36}$/i.test(id) || !subdominio) return null;
    return { provedor: "panda", id, url: `https://${subdominio}/embed/?v=${id}` };
  }
  return null;
}

/* Confere um link guardado (o jsonb da aula) antes de mostrar. */
export function linkGuardado(bruto: unknown): LinkDeVideo | null {
  if (!bruto || typeof bruto !== "object" || Array.isArray(bruto)) return null;
  const valor = bruto as Record<string, unknown>;
  return typeof valor.url === "string" ? lerLinkDeVideo(valor.url) : null;
}

export type LiberacaoDoModulo = { liberacao: "imediata" | "dias" | "data"; dias: number | null; em: string | null };
export type LiberacaoDaAula = { liberacao: "modulo" | "dias" | "data"; dias: number | null; em: string | null };

const DIA_MS = 24 * 60 * 60 * 1000;

function instante(iso: string | null): number | null {
  if (!iso) return null;
  const t = new Date(iso).getTime();
  return Number.isFinite(t) ? t : null;
}

/**
 * Quando o módulo e a aula abrem para quem começou em `inicio` (a liberação do
 * acesso). Uma aula nunca abre antes do módulo dela.
 */
export function quandoAbre(params: {
  inicio: string | null;
  modulo: LiberacaoDoModulo;
  aula?: LiberacaoDaAula | null;
  agora?: number;
}): { aberta: boolean; abreEm: string | null } {
  const agora = params.agora ?? Date.now();
  const comeco = instante(params.inicio) ?? agora;
  const doModulo =
    params.modulo.liberacao === "dias" && params.modulo.dias
      ? comeco + params.modulo.dias * DIA_MS
      : params.modulo.liberacao === "data"
        ? instante(params.modulo.em) ?? comeco
        : comeco;
  let quando = doModulo;
  const aula = params.aula;
  if (aula && aula.liberacao === "dias" && aula.dias) quando = Math.max(doModulo, comeco + aula.dias * DIA_MS);
  else if (aula && aula.liberacao === "data") quando = Math.max(doModulo, instante(aula.em) ?? doModulo);
  return quando <= agora ? { aberta: true, abreEm: null } : { aberta: false, abreEm: new Date(quando).toISOString() };
}

/* O dia do calendário em Brasília, como número (para contar "amanhã" e "em 3 dias"). */
function diaEmBrasilia(t: number): number {
  const partes = new Intl.DateTimeFormat("en-CA", { year: "numeric", month: "2-digit", day: "2-digit", timeZone: "America/Sao_Paulo" }).format(new Date(t));
  const [ano, mes, dia] = partes.split("-").map(Number);
  return Math.round(Date.UTC(ano, mes - 1, dia) / DIA_MS);
}

/* "abre hoje às 19:00", "abre amanhã", "abre em 3 dias" (dias do calendário de Brasília). */
export function rotuloDeQuandoAbre(abreEm: string | null, agora = Date.now()): string | null {
  const t = instante(abreEm);
  if (t === null || t <= agora) return null;
  const dias = diaEmBrasilia(t) - diaEmBrasilia(agora);
  if (dias <= 0) {
    const hora = new Intl.DateTimeFormat("pt-BR", { hour: "2-digit", minute: "2-digit", timeZone: "America/Sao_Paulo" }).format(new Date(t));
    return `abre hoje às ${hora}`;
  }
  return dias === 1 ? "abre amanhã" : `abre em ${dias} dias`;
}

/* Para a página de venda e o editor: a regra, sem depender de quem compra. */
export function rotuloDaRegra(regra: { liberacao: string; dias: number | null; em: string | null }): string | null {
  if (regra.liberacao === "dias" && regra.dias) return regra.dias === 1 ? "abre 1 dia depois da compra" : `abre ${regra.dias} dias depois da compra`;
  if (regra.liberacao === "data" && regra.em) {
    const t = instante(regra.em);
    if (t === null) return null;
    return `abre em ${new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric", timeZone: "America/Sao_Paulo" }).format(new Date(t))}`;
  }
  return null;
}

/* 1102 → "18:22"; 4380 → "1:13:00". */
export function relogio(segundos: number | null | undefined): string {
  const total = Math.max(0, Math.round(Number(segundos) || 0));
  const h = Math.floor(total / 3600);
  const m = Math.floor((total % 3600) / 60);
  const s = total % 60;
  return h > 0 ? `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}` : `${m}:${String(s).padStart(2, "0")}`;
}

/* 24000 → "6h 40min"; 840 → "14 min"; 3600 → "1h". */
export function duracaoLegivel(segundos: number | null | undefined): string {
  const minutos = Math.round(Math.max(0, Number(segundos) || 0) / 60);
  if (minutos < 60) return `${minutos} min`;
  const h = Math.floor(minutos / 60);
  const m = minutos % 60;
  return m ? `${h}h ${m}min` : `${h}h`;
}

export type TipoDeAula = "video" | "texto" | "materiais" | "ao_vivo";

/* O conteúdo público do curso (página de venda): sem endereço nenhum. */
export type AulaPublica = { id: string; titulo: string; tipo: TipoDeAula; duracaoS: number | null; previaGratis: boolean };
export type ModuloPublico = { id: string; titulo: string; descricao: string | null; abre: string | null; aulas: AulaPublica[] };
export type ConteudoPublico = { modulos: ModuloPublico[]; aulas: number; duracaoS: number; materiais: number };

export function resumoDoConteudo(
  modulos: Array<{ id: string; titulo: string; descricao: string | null; liberacao: string; dias: number | null; em: string | null }>,
  aulas: Array<{ id: string; moduloId: string; titulo: string; tipo: TipoDeAula; duracaoS: number | null; previaGratis: boolean; publicada: boolean }>,
  materiais: number
): ConteudoPublico {
  const publicadas = aulas.filter((a) => a.publicada);
  const lista: ModuloPublico[] = modulos
    .map((m) => ({
      id: m.id,
      titulo: m.titulo,
      descricao: m.descricao,
      abre: rotuloDaRegra(m),
      aulas: publicadas
        .filter((a) => a.moduloId === m.id)
        .map((a) => ({ id: a.id, titulo: a.titulo, tipo: a.tipo, duracaoS: a.duracaoS, previaGratis: a.previaGratis })),
    }))
    .filter((m) => m.aulas.length > 0);
  const contadas = lista.flatMap((m) => m.aulas);
  return {
    modulos: lista,
    aulas: contadas.length,
    duracaoS: contadas.reduce((soma, a) => soma + (a.tipo === "video" ? Number(a.duracaoS) || 0 : 0), 0),
    materiais,
  };
}

/* A parte da página de venda que só o curso tem (fica em digital_config.curso). */
export type ProfessorDoCurso = { nome: string; foto: string | null; destaque: string | null; bio: string | null };
export type ApresentacaoDoCurso = { arquivoId: string } | { link: LinkDeVideo } | null;
export type ConfigDoCurso = {
  aprendizados: string[];
  professor: ProfessorDoCurso | null;
  apresentacao: ApresentacaoDoCurso;
  garantiaDias: number | null;
  conteudo: ConteudoPublico;
  /* O certificado deste curso (vale com o das Configurações ligado). A carga
     horária null é a soma dos vídeos. */
  certificado: { emitir: boolean; cargaHoras: number | null };
};

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

function texto(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 imagemHttps(valor: unknown): string | null {
  const bruto = texto(valor, 1000);
  if (!bruto) return null;
  try {
    return new URL(bruto).protocol === "https:" ? bruto : null;
  } catch {
    return null;
  }
}

const VAZIO: ConteudoPublico = { modulos: [], aulas: 0, duracaoS: 0, materiais: 0 };

function lerConteudo(bruto: unknown): ConteudoPublico {
  const raiz = objeto(bruto);
  const tipos: TipoDeAula[] = ["video", "texto", "materiais", "ao_vivo"];
  const modulos = (Array.isArray(raiz.modulos) ? raiz.modulos : []).slice(0, 100).flatMap((m): ModuloPublico[] => {
    const o = objeto(m);
    const id = texto(o.id, 40);
    const titulo = texto(o.titulo, 160);
    if (!id || !titulo) return [];
    const aulas = (Array.isArray(o.aulas) ? o.aulas : []).slice(0, 300).flatMap((a): AulaPublica[] => {
      const x = objeto(a);
      const idDaAula = texto(x.id, 40);
      const tituloDaAula = texto(x.titulo, 160);
      if (!idDaAula || !tituloDaAula) return [];
      const duracao = Number(x.duracaoS);
      return [
        {
          id: idDaAula,
          titulo: tituloDaAula,
          tipo: tipos.includes(x.tipo as TipoDeAula) ? (x.tipo as TipoDeAula) : "video",
          duracaoS: Number.isFinite(duracao) && duracao > 0 ? Math.round(duracao) : null,
          previaGratis: x.previaGratis === true,
        },
      ];
    });
    return [{ id, titulo, descricao: texto(o.descricao, 1000), abre: texto(o.abre, 60), aulas }];
  });
  const numero = (v: unknown) => (Number.isFinite(Number(v)) && Number(v) > 0 ? Math.round(Number(v)) : 0);
  return { modulos, aulas: numero(raiz.aulas), duracaoS: numero(raiz.duracaoS), materiais: numero(raiz.materiais) };
}

export function lerConfigDoCurso(bruto: unknown): ConfigDoCurso {
  const raiz = objeto(bruto);
  const professor = objeto(raiz.professor);
  const nomeDoProfessor = texto(professor.nome, 80);
  const apresentacao = objeto(raiz.apresentacao);
  const arquivoId = texto(apresentacao.arquivoId, 40);
  const link = linkGuardado(apresentacao.link);
  const garantia = Number(raiz.garantiaDias);
  const certificado = objeto(raiz.certificado);
  const carga = Number(certificado.cargaHoras);
  return {
    aprendizados: (Array.isArray(raiz.aprendizados) ? raiz.aprendizados : [])
      .map((item) => texto(item, 140))
      .filter((item): item is string => Boolean(item))
      .slice(0, 12),
    professor: nomeDoProfessor
      ? { nome: nomeDoProfessor, foto: imagemHttps(professor.foto), destaque: texto(professor.destaque, 120), bio: texto(professor.bio, 1200) }
      : null,
    apresentacao: arquivoId && /^[0-9a-f-]{36}$/i.test(arquivoId) ? { arquivoId } : link ? { link } : null,
    garantiaDias: Number.isFinite(garantia) && garantia >= 1 ? Math.min(90, Math.round(garantia)) : null,
    conteudo: raiz.conteudo === undefined ? VAZIO : lerConteudo(raiz.conteudo),
    certificado: {
      emitir: certificado.emitir !== false,
      cargaHoras: certificado.cargaHoras !== null && Number.isFinite(carga) && carga >= 1 ? Math.min(2000, Math.round(carga)) : null,
    },
  };
}

/* Os capítulos de uma aula: a linha do tempo do vídeo com nome em cada parte. */
export type Capitulo = { inicioS: number; titulo: string };

/* "1:02:03" → 3723; "12:40" → 760; "45" → 45; o resto → null. */
export function segundosDoRelogio(texto: string): number | null {
  const partes = texto.trim().split(":");
  if (!partes.length || partes.length > 3 || partes.some((p) => !/^\d{1,3}$/.test(p))) return null;
  const numeros = partes.map(Number);
  if (numeros.slice(1).some((n) => n > 59)) return null;
  return numeros.reduce((total, n) => total * 60 + n, 0);
}

/* O guardado no banco, conferido: em ordem, sem repetir o mesmo segundo, até 100. */
export function lerCapitulos(valor: unknown): Capitulo[] {
  if (!Array.isArray(valor)) return [];
  const vistos = new Set<number>();
  return valor
    .map((item) => {
      if (!item || typeof item !== "object") return null;
      const o = item as Record<string, unknown>;
      const inicio = Math.round(Number(o.inicioS));
      const titulo = typeof o.titulo === "string" ? o.titulo.replace(/\u0000/g, "").trim().slice(0, 120) : "";
      if (!Number.isFinite(inicio) || inicio < 0 || inicio > 86400 || !titulo) return null;
      return { inicioS: inicio, titulo };
    })
    .filter((c): c is Capitulo => c !== null)
    .sort((a, b) => a.inicioS - b.inicioS)
    .filter((c) => (vistos.has(c.inicioS) ? false : (vistos.add(c.inicioS), true)))
    .slice(0, 100);
}

/* O texto do editor ("00:00 Abertura", um por linha) vira a lista; linha sem tempo é ignorada. */
export function capitulosDoTexto(texto: string): { capitulos: Capitulo[]; ignoradas: number } {
  let ignoradas = 0;
  const lidos: Capitulo[] = [];
  for (const linha of String(texto || "").split(/\r?\n/)) {
    const limpa = linha.trim();
    if (!limpa) continue;
    const achado = /^(\d{1,3}(?::\d{1,2}){0,2})\s*[-–—:]?\s*(.+)$/.exec(limpa);
    const inicio = achado ? segundosDoRelogio(achado[1]) : null;
    if (!achado || inicio === null) {
      ignoradas += 1;
      continue;
    }
    lidos.push({ inicioS: inicio, titulo: achado[2] });
  }
  return { capitulos: lerCapitulos(lidos), ignoradas };
}

/* A lista de volta para o texto do editor. */
export function textoDosCapitulos(capitulos: Capitulo[]): string {
  return capitulos.map((c) => `${relogio(c.inicioS)} ${c.titulo}`).join("\n");
}

/* O capítulo em que o vídeo está agora. */
export function capituloNoTempo(capitulos: Capitulo[], segundos: number): Capitulo | null {
  let atual: Capitulo | null = null;
  for (const capitulo of capitulos) {
    if (capitulo.inicioS <= segundos) atual = capitulo;
    else break;
  }
  return atual;
}

/* ------------------------------------------------------------ certificado */

/* A carga horária do certificado, em minutos: a que a loja informou ou a soma
   dos vídeos do curso (no mínimo 1 minuto). */
export function cargaHorariaEmMinutos(certificado: { cargaHoras: number | null }, duracaoDosVideosS: number): number {
  if (certificado.cargaHoras) return certificado.cargaHoras * 60;
  return Math.max(1, Math.ceil(Math.max(0, Number(duracaoDosVideosS) || 0) / 60));
}

/* "20 horas", "1 hora", "6h 40min", "45 min". */
export function textoDaCargaHoraria(minutos: number): string {
  const total = Math.max(0, Math.round(minutos));
  if (total < 60) return `${total} min`;
  const horas = Math.floor(total / 60);
  const resto = total % 60;
  if (!resto) return `${horas} ${horas === 1 ? "hora" : "horas"}`;
  return `${horas}h ${resto}min`;
}
