/* ============================================================
   EDUCARE — Assinatura (ÚNICO ponto de verdade no cliente)

   Espelha as funções educare_* do banco. O que este arquivo faz é
   INTERFACE: esconder botão, explicar por que algo está barrado, mostrar
   a faixa do teste. Não é segurança.

   A barreira real é a RLS. O Educare é um app estático: o navegador
   fala direto com o PostgREST usando o JWT da conta, então não existe
   rota de API onde interceptar a escrita. Quem diz "não" de verdade é a
   política do banco — este módulo só evita que a pessoa clique num
   botão que vai falhar.

   Correspondência com o desenho pedido no projeto:
     getSubscriptionState   -> estado()
     assertCanWrite         -> exigirEscrita()
     assertCanCreatePatient -> exigirNovoPaciente()
     assertCanUseAI         -> exigirIA()

   Uso:
     await window.EducareBilling.carregar();   // no boot, após o login
     window.EducareBilling.estado()            // { status, pacientesAtivos, ... }
     window.EducareBilling.exigirEscrita()     // lança ErroDeAssinatura
   ============================================================ */
(function () {
  const { useState, useEffect } = React;

  const EVENTO = "educare-assinatura-mudou";

  /* Erro único de cobrança. O 402 não trafega de verdade (não há API
     HTTP nossa no caminho), mas manter o código torna o contrato
     explícito para quem ler o app depois. */
  class ErroDeAssinatura extends Error {
    constructor(motivo, mensagem) {
      super(mensagem);
      this.name = "ErroDeAssinatura";
      this.motivo = motivo;   // "assinatura" | "limite_pacientes" | "ia"
      this.http = 402;
    }
  }

  const MENSAGENS = {
    assinatura:
      "Sua conta está em modo leitura. Assine para voltar a registrar.",
    limite_pacientes:
      "Você atingiu o limite de pacientes ativos do seu plano. " +
      "Arquive um paciente para abrir vaga ou mude de plano.",
    /* Uso justo não é venda: quem estourou o teto técnico é caso de
       suporte, não de upgrade. A mensagem não oferece plano. */
    ia:
      "Detectamos volume de uso muito acima do normal nesta conta. " +
      "Fale com o suporte para liberar.",
  };

  const VAZIO = {
    status: "blocked",
    statusCobranca: null,
    plano: null,
    planoNome: null,
    ciclo: null,
    cancelaNoFim: false,
    periodoTerminaEm: null,
    testeTerminaEm: null,
    carenciaTerminaEm: null,
    podeEscrever: false,
    podeCriarPaciente: false,
    pacientesAtivos: 0,
    limitePacientes: null,
    diasDeTeste: null,
    carregado: false,
  };

  let atual = { ...VAZIO };
  let ehAdminAtual = false;
  let tentativas = 0;      // releituras depois de falha no boot
  let lidoEm = 0;          // quando a assinatura foi lida pela última vez

  /* Voltou para a aba (depois do checkout, por exemplo): relê a
     assinatura. Antes o plano recém-pago só aparecia depois de recarregar
     a página (auditoria de 25/09/2026). No máximo uma vez a cada 30 s. */
  if (typeof document !== "undefined" && document.addEventListener) document.addEventListener("visibilitychange", () => {
    if (document.visibilityState !== "visible" || !atual.carregado) return;
    if (Date.now() - lidoEm < 30000) return;
    carregar();
  });

  const data = (v) => (v ? new Date(v) : null);

  function avisar() {
    window.dispatchEvent(new CustomEvent(EVENTO));
  }

  /* ---------------- carga ----------------
     Uma chamada só, e todo o cálculo de vencimento acontece no servidor.
     O relógio do navegador não decide nada: adiantar a data da máquina
     não estende teste nenhum. */
  async function carregar() {
    if (!window.sb) return estado();

    const { data: linha, error } = await window.sb.rpc("educare_estado_assinatura");
    if (error || !linha) {
      console.error("[EducareBilling] falha ao ler a assinatura:", error);
      /* Falhar fechado aqui só esconderia botões de quem está em dia.
         Mantemos o estado anterior e deixamos a RLS decidir na hora da
         escrita — ela é a barreira real. Mas sem avisar, a tela ficava
         em modo leitura calado (auditoria de 25/09/2026): agora avisa e
         tenta de novo algumas vezes. */
      if (!atual.carregado) {
        window.dispatchEvent(new CustomEvent("educare-db-erro", {
          detail: "Não foi possível verificar sua assinatura agora. Tentando de novo…",
        }));
        if (tentativas < 3) { tentativas += 1; setTimeout(carregar, 4000 * tentativas); }
      }
      return estado();
    }
    tentativas = 0;
    lidoEm = Date.now();

    /* Quem é administradora vem do servidor, pela mesma função que a
       RLS usa. Ler isso de um campo do perfil convidaria a "editar" o
       próprio papel; aqui o navegador só pergunta, e o que ele faz com
       a resposta é esconder um item de menu. As telas de administração
       são barradas no banco, não aqui. */
    window.sb.rpc("educare_is_admin").then(({ data }) => {
      ehAdminAtual = data === true;
      avisar();
    }).catch(() => {});

    atual = {
      status: linha.status,
      statusCobranca: linha.status_cobranca,
      plano: linha.plano,
      planoNome: linha.plano_nome,
      ciclo: linha.ciclo,
      cancelaNoFim: !!linha.cancela_no_fim,
      periodoTerminaEm: data(linha.periodo_termina_em),
      testeTerminaEm: data(linha.teste_termina_em),
      carenciaTerminaEm: data(linha.carencia_termina_em),
      podeEscrever: !!linha.pode_escrever,
      podeCriarPaciente: !!linha.pode_criar_paciente,
      pacientesAtivos: Number(linha.pacientes_ativos) || 0,
      limitePacientes: linha.limite_pacientes,
      diasDeTeste: linha.dias_de_teste,
      carregado: true,
    };
    avisar();
    return estado();
  }

  const estado = () => ({ ...atual });

  /* ---------------- perguntas ---------------- */

  /* emTeste() saiu com o teste grátis. Ninguém a chamava, e mantida ela
     responderia sempre false — pergunta cuja resposta não muda mais não
     é pergunta. O status 'trialing' continua existindo no banco por
     causa de contas antigas; o app só não decide nada por ele. */
  const emCarencia  = () => atual.status === "past_due";
  const modoLeitura = () => atual.carregado && !atual.podeEscrever;

  /* Quanto ainda cabe. null = ilimitado. */
  function vagasDePaciente() {
    if (atual.limitePacientes == null) return null;
    return Math.max(0, atual.limitePacientes - atual.pacientesAtivos);
  }

  /* ---------------- exigências ----------------
     Lançam para interromper o fluxo antes de tentar gravar. */

  function exigirEscrita() {
    if (!atual.podeEscrever) {
      throw new ErroDeAssinatura("assinatura", MENSAGENS.assinatura);
    }
  }

  function exigirNovoPaciente() {
    exigirEscrita();
    if (!atual.podeCriarPaciente) {
      throw new ErroDeAssinatura("limite_pacientes", MENSAGENS.limite_pacientes);
    }
  }

  /* O teto de IA vive só no servidor — de propósito, para não virar
     contador na tela. Aqui só barramos o que já sabemos: conta sem
     direito de escrita não gera rascunho. O estouro de uso justo chega
     como erro da Edge Function. */
  function exigirIA() {
    exigirEscrita();
  }

  /* ---------------- tradução do erro do banco ----------------
     Quando a RLS barra, o PostgREST devolve 42501 / "new row violates
     row-level security policy". Isso não pode chegar assim na tela: a
     profissional não tem que aprender jargão de Postgres para descobrir
     que a assinatura venceu. */
  const CODIGOS_DE_RLS = ["42501", "PGRST301"];

  /* Códigos que o gatilho do banco levanta. Existem para separar duas
     situações que a RLS sozinha confundiria: "sua assinatura venceu" e
     "seu plano encheu". Quem está no teto de pacientes tem a assinatura
     em dia — mandar essa pessoa assinar de novo seria cobrar duas vezes
     pelo mesmo problema. */
  const LIMITE_DE_PACIENTES = "EDU01";
  const MODO_LEITURA = "EDU02";

  function ehBloqueioDeAssinatura(erro) {
    if (!erro) return false;
    const codigo = String(erro.code || "");
    const texto = String(erro.message || "").toLowerCase();
    return CODIGOS_DE_RLS.includes(codigo)
      || codigo === MODO_LEITURA
      || codigo === LIMITE_DE_PACIENTES
      || texto.includes("row-level security")
      || texto.includes("row level security");
  }

  function traduzirErro(erro) {
    if (erro instanceof ErroDeAssinatura) return erro;
    if (!ehBloqueioDeAssinatura(erro)) return erro;

    /* Recarrega em segundo plano: se a assinatura venceu enquanto a
       aba estava aberta, a interface precisa saber disso agora. */
    carregar().catch(() => {});

    if (String(erro.code || "") === LIMITE_DE_PACIENTES) {
      /* O gatilho já escreve uma frase boa e específica (inclusive o
         caso de desarquivar). Preferimos a dele à genérica daqui. */
      return new ErroDeAssinatura(
        "limite_pacientes", erro.message || MENSAGENS.limite_pacientes);
    }
    return new ErroDeAssinatura("assinatura", MENSAGENS.assinatura);
  }

  /* ---------------- hook de interface ---------------- */

  function usarAssinatura() {
    const [, setTick] = useState(0);
    useEffect(() => {
      const recarregar = () => setTick((t) => t + 1);
      window.addEventListener(EVENTO, recarregar);
      return () => window.removeEventListener(EVENTO, recarregar);
    }, []);
    return estado();
  }

  window.EducareBilling = {
    carregar, estado, usarAssinatura, ehAdmin: () => ehAdminAtual,
    emCarencia, modoLeitura, vagasDePaciente,
    exigirEscrita, exigirNovoPaciente, exigirIA,
    traduzirErro, ehBloqueioDeAssinatura,
    ErroDeAssinatura, MENSAGENS, EVENTO,
  };
  window.usarAssinatura = usarAssinatura;
})();
