/* ============================================================
   EDUCARE — Camada de banco (Supabase)

   ÚNICO lugar que conhece nomes de tabela e de coluna. O resto do
   app continua falando com window.PATIENTS, window.EVENTS etc.

   Regras:
   - owner_id é preenchido pelo próprio banco (default auth.uid()),
     e o RLS garante que cada conta só enxerga as próprias linhas.
   - Os ids são gerados aqui, no cliente, como UUID. Assim o objeto
     em memória e a linha no banco compartilham a mesma chave e não
     há reconciliação depois do insert.
   ============================================================ */
(function () {
  const sb = () => window.sb;

  function novoId() {
    if (window.crypto && window.crypto.randomUUID) return window.crypto.randomUUID();
    // Fallback para navegadores sem randomUUID (UUID v4 simples).
    return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
      const r = (Math.random() * 16) | 0;
      return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
    });
  }

  /* Toda falha de gravação passa por aqui.

     Quando a assinatura venceu, quem recusa é a RLS, e o PostgREST
     devolve "new row violates row-level security policy" — jargão de
     Postgres que não pode chegar na tela de uma fonoaudióloga. O mesmo
     vale para o teto de pacientes, que vem do gatilho com código
     próprio. Traduzir num lugar só garante que nenhuma tela invente a
     própria versão da mensagem.

     Recebe o PREFIXO da mensagem, e não a mensagem pronta. Montar a
     frase aqui dentro é o que garante que error.message só seja lido
     quando existe erro: com a frase pronta, o argumento era avaliado
     ANTES da chamada, e toda gravação bem-sucedida estourava em
     "Cannot read properties of null" — tarja vermelha de "não salvo"
     em cima de dado que tinha sido salvo direitinho. */
  function falhaDeGravacao(error, oQueFalhou) {
    if (!error) return;
    const tratado = window.EducareBilling && window.EducareBilling.traduzirErro(error);
    if (tratado && tratado.name === "ErroDeAssinatura") throw tratado;
    throw new Error(`${oQueFalhou}: ${error.message}`);
  }

  const txt = (v, vazio = "—") => (v == null || v === "" ? vazio : v);
  const ouNulo = (v) => (v === "" || v === "—" || v == null ? null : v);

  /* ---------------- mapeamento: linha do banco → objeto do app ---------------- */

  function pacienteDaLinha(r) {
    return {
      id: r.id,
      name: r.name,
      initials: r.initials || "",
      color: r.color || "a",
      birth: r.birth || "",
      age: window.ageFromBirth ? window.ageFromBirth(r.birth) : "—",
      grade: txt(r.grade),
      school: txt(r.school),
      escola: r.school_type || "",
      guardian: txt(r.guardian),
      guardianRel: r.guardian_rel || "Responsável",
      phone: txt(r.phone),
      status: r.status || "avaliacao",
      tags: r.tags || [],
      complaint: txt(r.complaint),
      referredBy: txt(r.referred_by),
      meds: txt(r.meds),
      anamnese: !!r.has_anamnese,
      attendance: Number(r.attendance) || 0,
      fee: Number(r.fee) || 0,
      billMode: r.bill_mode || "sessao",
      /* Paciente ativo é o não arquivado: é o que o plano conta, e o
         que educare_pacientes_ativos mede do lado do banco. Nada a ver
         com "status", que é a situação clínica do acompanhamento. */
      arquivado: !!r.arquivado,
      sessions: 0, lastSession: "—", nextSession: "—",
      moodSeries: [], goals: [],
      _user: true, _prof: r.professional_id || "",
    };
  }

  const pacienteParaLinha = (p) => ({
    id: p.id,
    professional_id: ouNulo(p._prof),
    name: p.name,
    initials: p.initials,
    color: p.color,
    birth: ouNulo(p.birth),
    grade: ouNulo(p.grade),
    school: ouNulo(p.school),
    school_type: ouNulo(p.escola),
    guardian: ouNulo(p.guardian),
    guardian_rel: ouNulo(p.guardianRel),
    phone: ouNulo(p.phone),
    status: p.status || "avaliacao",
    tags: p.tags || [],
    complaint: ouNulo(p.complaint),
    referred_by: ouNulo(p.referredBy),
    meds: ouNulo(p.meds),
    has_anamnese: !!p.anamnese,
    attendance: Number(p.attendance) || 0,
    fee: Number(p.fee) || 0,
    bill_mode: p.billMode || "sessao",
    arquivado: !!p.arquivado,
  });

  /* Cobrança: no app vive como mapa charges[pid+data]; no banco, uma
     linha por paciente/dia (índice único charges_patient_date_key). */
  const cobrancaDaLinha = (r) => ({
    pid: r.patient_id, dateISO: r.charge_date,
    value: Number(r.value) || 0, kind: r.kind || "realizado",
    paid: Number(r.paid) || 0, method: r.method || "",
    payDate: r.pay_date || "", log: r.log || [],
  });

  const cobrancaParaLinha = (c) => ({
    patient_id: c.pid, charge_date: c.dateISO,
    value: Number(c.value) || 0, kind: c.kind || "realizado",
    paid: Number(c.paid) || 0, method: ouNulo(c.method),
    pay_date: ouNulo(c.payDate), log: c.log || [],
  });

  const eventoDaLinha = (r) => {
    const ev = {
      id: r.id, date: r.event_date,
      start: (r.start_time || "").slice(0, 5),
      end: (r.end_time || "").slice(0, 5),
      title: r.title, type: r.type, _user: true,
    };
    if (r.patient_id) ev.pid = r.patient_id;
    if (r.all_day) ev.allDay = true;
    return ev;
  };

  const eventoParaLinha = (e) => ({
    id: e.id, patient_id: ouNulo(e.pid), event_date: e.date,
    start_time: ouNulo(e.start), end_time: ouNulo(e.end),
    title: e.title, type: e.type, all_day: !!e.allDay,
  });

  /* Atendimento: no app é o "rec" que alimenta ATEND e SESSIONS[pid]. */
  const atendimentoDaLinha = (r) => ({
    id: r.id, pid: r.patient_id, dateIso: r.session_date,
    dur: r.duration_min, mood: r.mood, focus: txt(r.focus, "Atendimento"),
    status: r.status || "realizado",
    activities: r.activities || "", notes: r.notes || "", next: r.next_steps || "",
  });

  const atendimentoParaLinha = (a) => ({
    id: a.id, patient_id: a.pid, session_date: a.dateIso,
    duration_min: a.dur, mood: a.mood, focus: ouNulo(a.focus),
    status: a.status || "realizado",
    activities: ouNulo(a.activities), notes: ouNulo(a.notes), next_steps: ouNulo(a.next),
  });

  const planoDaLinha = (r) => ({
    pid: r.patient_id,
    entry: {
      id: r.id, title: r.title, level: r.level,
      sessions: r.sessions_count, freq: r.freq,
      created: window.fmtLongDate ? window.fmtLongDate(String(r.created_at).slice(0, 10)) : String(r.created_at).slice(0, 10),
      status: r.status || "Em andamento", progress: r.progress || 0, goal: r.goal || "",
    },
  });

  const planoParaLinha = (pid, e) => ({
    id: e.id, patient_id: pid, title: e.title, level: ouNulo(e.level),
    sessions_count: e.sessions, freq: ouNulo(e.freq),
    status: e.status, progress: e.progress || 0, goal: ouNulo(e.goal),
  });

  const avaliacaoDaLinha = (r) => ({
    id: r.id, pid: r.patient_id, name: r.name, dateIso: r.applied_on,
    result: r.result || "Concluída", note: r.note || "", sheet: r.sheet || null,
  });

  const avaliacaoParaLinha = (a) => ({
    id: a.id, patient_id: a.pid, name: a.name, applied_on: a.dateIso,
    result: a.result, status: "done", note: ouNulo(a.note), sheet: a.sheet || null,
  });

  const pagamentoDaLinha = (r) => ({
    id: r.id, pid: r.patient_id, date: r.paid_on, desc: r.description,
    value: Number(r.amount) || 0, status: r.status, method: r.method || "",
  });
  const pagamentoParaLinha = (p) => ({
    id: p.id, patient_id: ouNulo(p.pid), paid_on: p.date, description: p.desc,
    amount: p.value, status: p.status, method: ouNulo(p.method),
  });

  const despesaDaLinha = (r) => ({
    id: r.id, date: r.spent_on, desc: r.description, cat: r.category,
    value: Number(r.amount) || 0, method: r.method || "",
  });
  const despesaParaLinha = (d) => ({
    id: d.id, spent_on: d.date, description: d.desc, category: d.cat,
    amount: d.value, method: ouNulo(d.method),
  });

  const profissionalDaLinha = (r) => ({
    id: r.id, name: r.name, role: r.role || "", reg: r.reg || "",
    initials: r.initials || "", color: r.color || "a", owner: !!r.is_owner,
  });

  /* ---------------- carga inicial ---------------- */

  async function carregar() {
    if (!sb()) throw new Error("Supabase não configurado.");

    const tabelas = [
      "professionals", "patients", "appointments", "sessions",
      "plans", "assessments", "payments", "expenses", "charges",
    ];

    const respostas = await Promise.all(tabelas.map((t) => sb().from(t).select("*")));

    const falhou = respostas.findIndex((r) => r.error);
    if (falhou >= 0) {
      throw new Error(`Falha ao carregar "${tabelas[falhou]}": ${respostas[falhou].error.message}`);
    }

    const [profs, pacs, evs, sess, plans, avals, pays, exps, cobs] = respostas.map((r) => r.data || []);
    const porData = (campo) => (a, b) => String(a[campo]).localeCompare(String(b[campo]));

    return {
      profissionais: profs.map(profissionalDaLinha),
      pacientes: pacs.map(pacienteDaLinha),
      eventos: evs.map(eventoDaLinha),
      atendimentos: sess.slice().sort(porData("session_date")).map(atendimentoDaLinha),
      planos: plans.map(planoDaLinha),
      avaliacoes: avals.slice().sort(porData("applied_on")).map(avaliacaoDaLinha),
      pagamentos: pays.map(pagamentoDaLinha),
      despesas: exps.map(despesaDaLinha),
      cobrancas: cobs.map(cobrancaDaLinha),
    };
  }

  /* ---------------- escrita ----------------
     Cada função devolve uma Promise. Quem chama já inseriu o registro
     em memória (escrita otimista); aqui só confirmamos no banco. */

  function gravar(tabela, linha) {
    return sb().from(tabela).insert(linha).then(({ error }) => {
      falhaDeGravacao(error, `Não foi possível salvar em "${tabela}"`);
    });
  }

  const salvarPaciente    = (p)      => gravar("patients", pacienteParaLinha(p));
  const salvarEvento      = (e)      => gravar("appointments", eventoParaLinha(e));
  const salvarAtendimento = (a)      => gravar("sessions", atendimentoParaLinha(a));
  const salvarPlano       = (pid, e) => gravar("plans", planoParaLinha(pid, e));
  const salvarAvaliacao   = (a)      => gravar("assessments", avaliacaoParaLinha(a));
  const salvarPagamento   = (p)      => gravar("payments", pagamentoParaLinha(p));
  const salvarDespesa     = (d)      => gravar("expenses", despesaParaLinha(d));

  function atualizarEvento(e) {
    const linha = eventoParaLinha(e);
    delete linha.id;
    return sb().from("appointments").update(linha).eq("id", e.id).then(({ error }) => {
      falhaDeGravacao(error, `Não foi possível atualizar o agendamento`);
    });
  }

  /* Reescreve uma avaliação já gravada. Usado quando o laudo da IA é gerado
     (ou gerado de novo) depois que a avaliação foi salva no prontuário. */
  function atualizarAvaliacao(a) {
    const linha = avaliacaoParaLinha(a);
    delete linha.id;
    return sb().from("assessments").update(linha).eq("id", a.id).then(({ error }) => {
      falhaDeGravacao(error, `Não foi possível atualizar a avaliação`);
    });
  }

  function apagarEvento(id) {
    return sb().from("appointments").delete().eq("id", id).then(({ error }) => {
      falhaDeGravacao(error, `Não foi possível remover o agendamento`);
    });
  }

  function atualizarStatusAtendimento(id, status) {
    return sb().from("sessions").update({ status }).eq("id", id).then(({ error }) => {
      falhaDeGravacao(error, `Não foi possível atualizar o atendimento`);
    });
  }

  function atualizarPaciente(p) {
    const linha = pacienteParaLinha(p);
    delete linha.id;
    return sb().from("patients").update(linha).eq("id", p.id).then(({ error }) => {
      falhaDeGravacao(error, `Não foi possível atualizar o paciente`);
    });
  }

  /* Arquivar e desarquivar mexem SÓ nessa coluna, e não na linha
     inteira: arquivar é uma decisão sobre a vaga do plano, não uma
     edição do cadastro. Mandar a linha toda arriscaria sobrescrever
     com o que esta aba tem em memória algo que foi alterado noutro
     lugar. Nada é apagado aqui: o prontuário continua inteiro.

     Desarquivar acima do teto é barrado pelo gatilho do banco, que
     devolve mensagem própria (EDU01). */
  function arquivarPaciente(id, arquivado) {
    return sb().from("patients").update({ arquivado: !!arquivado }).eq("id", id)
      .then(({ error }) => {
        falhaDeGravacao(error, arquivado
          ? "Não foi possível arquivar o paciente"
          : "Não foi possível reativar o paciente");
      });
  }

  /* Cobrança usa upsert: o app reescreve a mesma cobrança (paciente+data)
     a cada pagamento registrado. O índice único resolve o conflito. */
  function salvarCobranca(c) {
    return sb().from("charges")
      .upsert(cobrancaParaLinha(c), { onConflict: "patient_id,charge_date" })
      .then(({ error }) => {
        falhaDeGravacao(error, `Não foi possível salvar a cobrança`);
      });
  }

  function apagarCobranca(pid, dateISO) {
    return sb().from("charges").delete().eq("patient_id", pid).eq("charge_date", dateISO)
      .then(({ error }) => {
        falhaDeGravacao(error, `Não foi possível remover a cobrança`);
      });
  }

  window.EducareDB = {
    novoId, carregar,
    salvarPaciente, atualizarPaciente, arquivarPaciente,
    salvarEvento, salvarAtendimento, salvarPlano, salvarAvaliacao, atualizarAvaliacao,
    salvarPagamento, salvarDespesa, atualizarStatusAtendimento,
    salvarCobranca, apagarCobranca,
    atualizarEvento, apagarEvento,
  };
})();
