/* ───────────────────────────────────────────────────────────────
   Migração de apólices — site da corretora (POC)
   Aba 1 "Importar": upload .xlsx do concorrente → apólices.
   Aba 2 "Ajuste fino": completa dados de risco + de-para → equivalência Azos.
   Backend: Salles  /migracao/equivalencia  e  /migracao/professions.
   ─────────────────────────────────────────────────────────────── */
const { useState, useRef, useEffect } = React;
const {
  Button, Card, CardHeader, CardTitle, CardDescription, CardContent,
  Input, Label, Badge, Alert, AlertTitle, AlertDescription,
  Tabs, TabsList, TabsTrigger, TabsContent, Switch, Separator, CounterPill,
} = window;

/* ═══════════════════════════════════════════════════════════════
   ENGINE DE DEMO (portada do backend Salles — migracao/demo.py +
   migracao/equivalence.py). Roda 100% no browser: sem VPC, sem
   backend. Os pricing_factor abaixo foram capturados de uma chamada
   REAL ao Pricing Engine (stg) p/ um perfil masc., 41a, 80kg/1,75m,
   não-fumante, R$20k, engenheiro — servem só p/ demonstrar o fluxo.
   Em produção, o /equivalencia chama o motor de verdade e os números
   variam com o perfil.
   ═══════════════════════════════════════════════════════════════ */
const DEMO_PRICING = {
  coverages: [
    { code: "4010", coverage_type: "death_lifetime_term", group: "death", refusal_reason: null, min_capital: 33000.0, max_capital: 3000000.0, suggested_capital: 1000000.0, pricing_factor: 0.0018631993005405406, step: 1000 },
    { code: "4012", coverage_type: "accidental_death", group: "death", refusal_reason: null, min_capital: 53000.0, max_capital: 1000000.0, suggested_capital: 500000.0, pricing_factor: 0.0011394486486486486, step: 1000 },
    { code: "4025", coverage_type: "critical_diseases_30", group: "critical_diseases", refusal_reason: null, min_capital: 8000.0, max_capital: 1000000.0, suggested_capital: 500000.0, pricing_factor: 0.007772050366216217, step: 1000 },
    { code: "4015", coverage_type: "critical_diseases_13", group: "critical_diseases", refusal_reason: null, min_capital: 10000.0, max_capital: 1000000.0, suggested_capital: 500000.0, pricing_factor: 0.0065906925249729735, step: 1000 },
    { code: "4007", coverage_type: "temporary_hospitalization_income", group: "temporary_hospitalization_income", refusal_reason: null, min_capital: 10.0, max_capital: 660.0, suggested_capital: 250.0, pricing_factor: 10.301238230306055, step: 10 },
    { code: "4006", coverage_type: "daily_hospitalization_stipend", group: "daily_hospitalization_stipend", refusal_reason: null, min_capital: 90.0, max_capital: 1000.0, suggested_capital: 1000.0, pricing_factor: 0.7489833352702703, step: 10 },
    { code: "4013", coverage_type: "disability", group: "disability", refusal_reason: null, min_capital: 71000.0, max_capital: 2000000.0, suggested_capital: 2000000.0, pricing_factor: 0.0008452240167567568, step: 1000 },
    { code: "4019", coverage_type: "disability_permanent_total", group: "disability", refusal_reason: null, min_capital: 34000.0, max_capital: 2000000.0, suggested_capital: 2000000.0, pricing_factor: 0.0017989723783783786, step: 1000 },
    { code: "4017", coverage_type: "family_funeral", group: "funeral_assistance", refusal_reason: null, min_capital: 15000.0, max_capital: 15000.0, suggested_capital: 15000.0, pricing_factor: 0.0030708561591891886, step: 1000 },
    { code: "4016", coverage_type: "funeral_assistance_lifetime_term", group: "funeral_assistance", refusal_reason: null, min_capital: 15000.0, max_capital: 15000.0, suggested_capital: 15000.0, pricing_factor: 0.0018631993005405404, step: 1000 },
    { code: "4018", coverage_type: "surgery_no_franchise", group: "surgery", refusal_reason: null, min_capital: 14000.0, max_capital: 100000.0, suggested_capital: 100000.0, pricing_factor: 0.00455780354578294, step: 1000 },
    { code: "4028", coverage_type: "ruptures_and_fractures", group: "ruptures_and_fractures", refusal_reason: null, min_capital: 13000.0, max_capital: 100000.0, suggested_capital: 100000.0, pricing_factor: 0.0049366070108108106, step: 1000 },
  ],
};

/* de-para heurístico (por keyword do nome do concorrente): [rótulo, keywords, candidatos] */
const _RULES = [
  ["Perda de autonomia", ["autonomia"], ["disability_permanent_total"]],
  ["Invalidez total (IPT)", ["invalidez total", "permanente total", "invalidez permanente"], ["disability_permanent_total", "disability"]],
  ["Invalidez", ["invalidez", "incapacidade"], ["disability", "disability_permanent_total", "disability_major", "disability_extended"]],
  ["Assistência funeral", ["funeral"], ["family_funeral", "funeral_assistance", "funeral_assistance_lifetime_term"]],
  ["Doenças graves", ["doenca", "grave", "critica", "critical"], ["critical_diseases_30", "critical_diseases_13", "critical_diseases"]],
  ["Renda hospitalar", ["hospital", "diaria", "internacao", "internamento", "renda hosp"], ["daily_hospitalization_stipend", "temporary_hospitalization_income"]],
  ["Cirurgia", ["cirurgia", "cirurgic"], ["surgery", "surgery_no_franchise"]],
  ["Fraturas e ossos", ["osso", "fratura", "quebra", "ruptura"], ["ruptures_and_fractures"]],
  ["Morte acidental", ["acidental", "acidente"], ["accidental_death"]],
  ["Morte", ["vida", "morte", "saude", "falecimento", "vitalicia"], ["death_lifetime_term", "death"]],
];

const _norm = (s) =>
  (s || "").normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();

function inferConcept(name) {
  const n = _norm(name);
  for (const [label, keywords, candidates] of _RULES) {
    if (keywords.some((k) => n.includes(k))) {
      if (label === "Morte acidental" && !["morte", "vida", "falecimento", "saude"].some((m) => n.includes(m))) continue;
      return [label, candidates];
    }
  }
  return [null, []];
}

function resolveType(candidates, available) {
  const ok = (ctype) => !((available[ctype] || {}).refusal_reason);
  for (const cand of candidates) if (available[cand] && ok(cand)) return cand;
  for (const cand of candidates) for (const ctype in available) if (ctype.includes(cand) && ok(ctype)) return ctype;
  for (const cand of candidates) for (const ctype in available) if (ctype.includes(cand)) return ctype;
  return null;
}

function ageFrom(birth_date) {
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(birth_date || "").trim());
  if (!m) return null;
  const [y, mo, d] = [Number(m[1]), Number(m[2]), Number(m[3])];
  const t = new Date();
  let age = t.getFullYear() - y;
  if (t.getMonth() + 1 < mo || (t.getMonth() + 1 === mo && t.getDate() < d)) age -= 1;
  return age;
}

const roundToStep = (v, step) => (!step || step <= 0 ? Math.round(v * 100) / 100 : Math.floor(v / step) * step);
const round2 = (v) => Math.round(v * 100) / 100;

function matchOne(cov, available) {
  const out = {
    competitor_name: cov.name, competitor_code: cov.code || null,
    competitor_capital: cov.capital ?? null, competitor_premium: cov.premium ?? null,
    azos_coverage_type: null, azos_code: null, status: "sem_equivalente",
    pricing_factor: null, min_capital: null, max_capital: null,
    modo_a_capital: null, modo_a_premium_mensal: null, modo_a_capital_capped: false,
    modo_b_premium_mensal: null, modo_b_capital: null, notes: [],
  };

  let target;
  if (cov.azos_coverage_type) {
    target = cov.azos_coverage_type;
    out.notes.push("De-para definido manualmente.");
  } else {
    const [label, candidates] = inferConcept(cov.name);
    if (label === null) { out.notes.push("Não consegui inferir o tipo — defina no ajuste fino."); return out; }
    target = resolveType(candidates, available);
    if (target === null) {
      out.status = "tipo_nao_encontrado";
      out.notes.push(`De-para apontou '${label}', mas a Azos não ofertou esse tipo p/ este perfil.`);
      return out;
    }
  }

  const azos = available[target];
  if (!azos) {
    out.status = "tipo_nao_encontrado";
    out.notes.push(`Tipo '${target}' não está disponível p/ este perfil.`);
    return out;
  }

  out.azos_coverage_type = target;
  out.azos_code = String(azos.code || "") || null;
  out.min_capital = azos.min_capital ?? null;
  out.max_capital = azos.max_capital ?? null;
  const factor = azos.pricing_factor;
  out.pricing_factor = factor ?? null;

  if (azos.refusal_reason || !factor) {
    out.status = "recusada";
    out.notes.push("Cobertura recusada pela Azos p/ este perfil.");
    return out;
  }

  out.status = "ok";

  // Modo A — mesmo capital → novo prêmio Azos.
  if (cov.capital != null) {
    let cap = cov.capital;
    const mx = out.max_capital, mn = out.min_capital;
    if (mx != null && cap > mx) { cap = mx; out.modo_a_capital_capped = true; out.notes.push(`Capital limitado ao máximo Azos (${brlCap(mx)}).`); }
    if (mn != null && cap < mn) { cap = mn; out.notes.push(`Capital elevado ao mínimo Azos (${brlCap(mn)}).`); }
    out.modo_a_capital = round2(cap);
    out.modo_a_premium_mensal = round2((cap * factor) / 12);
  } else {
    out.notes.push("Sem capital do concorrente → Modo A indisponível.");
  }

  // Modo B — mesmo prêmio → capital que a Azos entrega.
  if (cov.premium != null) {
    const rawCap = (cov.premium * 12) / factor;
    const mx = out.max_capital, mn = out.min_capital;
    let clamped = rawCap;
    if (mx != null) clamped = Math.min(clamped, mx);
    if (mn != null) clamped = Math.max(clamped, mn);
    clamped = roundToStep(clamped, azos.step);
    out.modo_b_capital = clamped;
    out.modo_b_premium_mensal = round2((clamped * factor) / 12);
    if (clamped < rawCap - 1) out.notes.push("Capital do Modo B limitado pelo teto/step — prêmio efetivo menor.");
  } else {
    out.notes.push("Sem prêmio do concorrente → Modo B indisponível.");
  }

  return out;
}

function missingFields(policy) {
  const p = policy.proponent, miss = [];
  if (p.weight == null) miss.push("peso");
  if (p.height == null) miss.push("altura");
  if (p.is_smoker == null) miss.push("fumante");
  if (p.monthly_income == null) miss.push("renda");
  return miss;
}

function computeEquivalence(policy, pricing, insurer) {
  const raw = pricing.coverages || [];
  const available = {};
  const offers = [];
  for (const c of raw) {
    const ctype = c.coverage_type || "";
    if (ctype && (!(ctype in available) || !c.refusal_reason)) available[ctype] = c;
    offers.push({
      code: String(c.code || ""), coverage_type: ctype, group: c.group ?? null,
      pricing_factor: c.pricing_factor ?? null, min_capital: c.min_capital ?? null,
      max_capital: c.max_capital ?? null, suggested_capital: c.suggested_capital ?? null,
      step: c.step ?? null, refused: !!c.refusal_reason, refusal_reason: c.refusal_reason ?? null,
    });
  }

  const results = policy.coverages.map((cov) => matchOne(cov, available));

  const modoATotal = results.reduce((s, r) => s + (r.status === "ok" ? (r.modo_a_premium_mensal || 0) : 0), 0);
  let competitorTotal = policy.total_premium;
  if (competitorTotal == null) {
    const prems = policy.coverages.map((c) => c.premium).filter((x) => x != null);
    competitorTotal = prems.length ? prems.reduce((a, b) => a + b, 0) : null;
  }

  const totals = {
    competitor_premium_mensal: competitorTotal,
    modo_a_premium_mensal: round2(modoATotal),
    modo_a_economia_mensal: competitorTotal != null ? round2(competitorTotal - modoATotal) : null,
    n_matched: results.filter((r) => r.status === "ok").length,
    n_total: results.length,
  };

  const age = ageFrom(policy.proponent.birth_date);
  const missing = missingFields(policy);
  const warnings = [];
  if (missing.length) warnings.push("Sem " + missing.join(", ") + " a cotação sai estimada (sem agravo) — o preço Azos tende a ficar otimista.");
  if (age != null && !(age >= 18 && age <= 65)) warnings.push(`Idade ${age} fora da faixa 18–65; a Azos pode recusar a apólice.`);
  warnings.push("Cotação inicial (sem DPS/EV): agravos de saúde/estilo de vida não aplicados.");

  const quotable = offers.some((o) => !o.refused && o.pricing_factor);

  return {
    insured_name: policy.insured_name, policy_number: policy.policy_number, insurer,
    age, quotable, coverages: results, totals, azos_available: offers,
    warnings, missing_fields: missing,
  };
}

/* Lista estática de profissões p/ o autocomplete (no backend real vem do
   Quotation Service). No modo demo o profession_id não altera o pricing. */
const PROFESSIONS = [
  { id: "6154a50bf7f3c1f30aa4c03b", name: "Engenheira(o) mecânico" },
  { id: "demo-eng-civil", name: "Engenheira(o) civil" },
  { id: "demo-eng-software", name: "Engenheira(o) de software" },
  { id: "demo-medico", name: "Médica(o)" },
  { id: "demo-enfermeiro", name: "Enfermeira(o)" },
  { id: "demo-advogado", name: "Advogada(o)" },
  { id: "demo-contador", name: "Contador(a)" },
  { id: "demo-administrador", name: "Administrador(a) de empresas" },
  { id: "demo-professor", name: "Professor(a)" },
  { id: "demo-arquiteto", name: "Arquiteta(o)" },
  { id: "demo-dentista", name: "Dentista" },
  { id: "demo-analista", name: "Analista de sistemas" },
  { id: "demo-vendedor", name: "Vendedor(a)" },
  { id: "demo-empresario", name: "Empresária(o)" },
  { id: "demo-corretor", name: "Corretor(a) de seguros" },
  { id: "demo-designer", name: "Designer" },
  { id: "demo-psicologo", name: "Psicóloga(o)" },
  { id: "demo-farmaceutico", name: "Farmacêutica(o)" },
  { id: "demo-fisioterapeuta", name: "Fisioterapeuta" },
  { id: "demo-motorista", name: "Motorista" },
  { id: "demo-autonomo", name: "Autônoma(o)" },
  { id: "demo-comerciante", name: "Comerciante" },
  { id: "demo-servidor", name: "Servidor(a) público" },
  { id: "demo-bancario", name: "Bancária(o)" },
  { id: "demo-jornalista", name: "Jornalista" },
];

function searchProfessions(name) {
  const q = _norm(name);
  return PROFESSIONS.filter((p) => _norm(p.name).includes(q)).slice(0, 20);
}

/* ── helpers ────────────────────────────────────────────────── */
const brl = (n) =>
  n == null || isNaN(n) ? "—"
  : "R$ " + Number(n).toLocaleString("pt-BR", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const brlCap = (n) =>
  n == null || isNaN(n) ? "—"
  : "R$ " + Number(n).toLocaleString("pt-BR", { maximumFractionDigits: 0 });

const numBR = (v) => {
  if (typeof v === "number") return v;
  if (v == null || v === "") return null;
  const s = String(v).trim().replace(/\s/g, "").replace(/\./g, "").replace(",", ".");
  const n = parseFloat(s);
  return isNaN(n) ? null : n;
};
const toGender = (v) => {
  const s = String(v || "").trim().toLowerCase();
  if (["m", "masculino", "male", "homem"].includes(s)) return "male";
  if (["f", "feminino", "female", "mulher"].includes(s)) return "female";
  return s || null;
};
const toBool = (v) => {
  if (v == null || v === "") return null;
  const s = String(v).trim().toLowerCase();
  if (["s", "sim", "true", "1", "y", "yes"].includes(s)) return true;
  if (["n", "nao", "não", "false", "0", "no"].includes(s)) return false;
  return null;
};
const STATUS_BADGE = {
  ok: ["success", "Equivalente"],
  recusada: ["destructive", "Recusada"],
  sem_equivalente: ["neutral", "Sem equivalente"],
  tipo_nao_encontrado: ["warning", "Mapear manualmente"],
};

/* ── modelo do template (long: 1 linha por cobertura) ─────────── */
const TEMPLATE_COLS = [
  "numero_apolice", "segurado_nome", "data_nascimento", "sexo", "profissao", "cpf",
  "peso_kg", "altura_m", "fumante", "renda_mensal", "seguradora_origem",
  "cobertura_codigo", "cobertura_nome", "capital_segurado", "premio_cobertura", "premio_total_mensal",
];

/* premissas de risco padrão (definidas pelo Gio) — usadas quando a apólice do
   concorrente não traz peso/altura/fumante/renda (o que é o caso comum). */
const DEFAULT_RISK = { weight: 80, height: 1.75, is_smoker: false, monthly_income: 20000 };

/* apólice do Ivan (PDF anexo) já parseada, p/ demo sem arquivo */
const EXAMPLE_POLICY = {
  insurer_origin: "Prudential",
  policy_number: "001805207",
  insured_name: "IVAN TIMOXENCO MORAES DE OLIVEIRA",
  total_premium: 1094.12,
  proponent: {
    birth_date: "1984-06-30", gender: "male",
    profession_id: "6154a50bf7f3c1f30aa4c03b", profession_label: "Engenheira(o) mecânico",
    cpf: "", ...DEFAULT_RISK,
    sale_channel: "b2b", broker_azos_plus_level: null,
  },
  coverages: [
    { code: "WV10G", name: "Vida e Saúde por 10 anos G", capital: 210680.0, premium: 821.43, azos_coverage_type: "" },
    { code: "DDP5G", name: "Doenças Graves Plus por 5 anos G", capital: 325467.59, premium: 115.42, azos_coverage_type: "" },
    { code: "HC05G", name: "Renda Hospitalar por 5 anos G", capital: 326.55, premium: 24.11, azos_coverage_type: "" },
    { code: "PI05G", name: "Invalidez Total e Parcial por Acidente", capital: 821652.0, premium: 48.08, azos_coverage_type: "" },
    { code: "AFC5G", name: "Assist Funeral Familiar I 5-G", capital: 12640.8, premium: 28.63, azos_coverage_type: "" },
    { code: "CIB5G", name: "Cirurgia por 5 anos G", capital: 10534.0, premium: 18.48, azos_coverage_type: "" },
    { code: "BRB5G", name: "Quebra de Ossos por 5 anos G", capital: 105340.0, premium: 33.83, azos_coverage_type: "" },
  ],
};

const emptyProponent = () => ({
  birth_date: "", gender: "", profession_id: "", profession_label: "", cpf: "",
  weight: null, height: null, is_smoker: null, monthly_income: null,
  sale_channel: "b2b", broker_azos_plus_level: null,
});

/* ── parsing do .xlsx ─────────────────────────────────────────── */
function parseWorkbook(rows) {
  // agrupa por numero_apolice; proponente vem da 1ª linha do grupo
  const groups = {};
  rows.forEach((r) => {
    const key = String(r.numero_apolice ?? r.apolice ?? "sem-numero").trim();
    (groups[key] = groups[key] || []).push(r);
  });
  return Object.entries(groups).map(([num, rs]) => {
    const f = rs[0];
    return {
      insurer_origin: f.seguradora_origem || "",
      policy_number: num,
      insured_name: f.segurado_nome || f.segurado || "",
      total_premium: numBR(f.premio_total_mensal),
      proponent: {
        ...emptyProponent(),
        birth_date: String(f.data_nascimento || "").trim(),
        gender: toGender(f.sexo),
        profession_label: f.profissao || "",
        cpf: String(f.cpf || "").replace(/\D/g, ""),
        // faltando na apólice → premissa padrão do Gio
        weight: numBR(f.peso_kg) ?? DEFAULT_RISK.weight,
        height: numBR(f.altura_m) ?? DEFAULT_RISK.height,
        is_smoker: toBool(f.fumante) ?? DEFAULT_RISK.is_smoker,
        monthly_income: numBR(f.renda_mensal) ?? DEFAULT_RISK.monthly_income,
      },
      coverages: rs.map((r) => ({
        code: String(r.cobertura_codigo || "").trim(),
        name: String(r.cobertura_nome || "").trim(),
        capital: numBR(r.capital_segurado),
        premium: numBR(r.premio_cobertura),
        azos_coverage_type: "",
      })),
    };
  });
}

function downloadTemplate() {
  const header = TEMPLATE_COLS;
  const sample = EXAMPLE_POLICY.coverages.slice(0, 3).map((c) => ({
    numero_apolice: EXAMPLE_POLICY.policy_number,
    segurado_nome: EXAMPLE_POLICY.insured_name,
    data_nascimento: EXAMPLE_POLICY.proponent.birth_date,
    sexo: "M", profissao: "Engenheiro mecânico", cpf: "",
    peso_kg: "", altura_m: "", fumante: "", renda_mensal: "",
    seguradora_origem: EXAMPLE_POLICY.insurer_origin,
    cobertura_codigo: c.code, cobertura_nome: c.name,
    capital_segurado: c.capital, premio_cobertura: c.premium,
    premio_total_mensal: EXAMPLE_POLICY.total_premium,
  }));
  const ws = XLSX.utils.json_to_sheet(sample, { header });
  const wb = XLSX.utils.book_new();
  XLSX.utils.book_append_sheet(wb, ws, "apolices");
  XLSX.writeFile(wb, "modelo-migracao-azos.xlsx");
}

/* ── cotação (engine local de demo) ───────────────────────────── */
function quote(policy) {
  const payload = JSON.parse(JSON.stringify(policy));
  payload.coverages = payload.coverages.map(({ azos_coverage_type, ...c }) => ({
    ...c, azos_coverage_type: azos_coverage_type || null,
  }));
  delete payload.proponent.profession_label;
  return computeEquivalence(payload, DEMO_PRICING, "azos");
}

/* ════════════════════════ APP ════════════════════════════════ */
function App() {
  const [tab, setTab] = useState("importar");
  const [policies, setPolicies] = useState([]);
  const [selected, setSelected] = useState(0);
  const [demo, setDemo] = useState(true);

  const goFineTune = (idx) => { setSelected(idx); setTab("ajuste"); };

  return (
    <div className="min-h-screen">
      <Header />
      <div className="max-w-[1200px] mx-auto px-6 py-6">
        <Tabs value={tab} onValueChange={setTab}>
          <TabsList>
            <TabsTrigger value="importar">
              Importar {policies.length > 0 && <CounterPill className="ml-2">{policies.length}</CounterPill>}
            </TabsTrigger>
            <TabsTrigger value="ajuste">Ajuste fino</TabsTrigger>
          </TabsList>

          <TabsContent value="importar">
            <ImportTab policies={policies} setPolicies={setPolicies} goFineTune={goFineTune} />
          </TabsContent>

          <TabsContent value="ajuste">
            <FineTuneTab
              policies={policies} setPolicies={setPolicies}
              selected={selected} setSelected={setSelected} demo={demo}
            />
          </TabsContent>
        </Tabs>
      </div>
    </div>
  );
}

function Header() {
  return (
    <div className="bg-white border-b-2 border-[#F3F4F6]">
      <div className="max-w-[1200px] mx-auto px-6 h-16 flex items-center justify-between">
        <div className="flex items-center gap-3">
          <img src="vendor/ds/assets/azos-logotype.svg" alt="Azos" className="h-6" />
          <Separator orientation="vertical" className="h-6" />
          <span className="text-[16px] font-bold font-sans text-[rgb(9,10,11)]">Migração de apólices</span>
        </div>
        <span title="Ambiente de demonstração: pricing fake embutido, roda 100% no navegador (sem cotação real).">
          <Badge variant="info" dot>Modo demonstração</Badge>
        </span>
      </div>
    </div>
  );
}

/* ════════════════════════ ABA 1 — IMPORTAR ═══════════════════ */
function ImportTab({ policies, setPolicies, goFineTune }) {
  const [error, setError] = useState("");
  const fileRef = useRef(null);

  const onFile = (file) => {
    setError("");
    const reader = new FileReader();
    reader.onload = (e) => {
      try {
        const wb = XLSX.read(e.target.result, { type: "array" });
        const rows = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]], { defval: "" });
        const parsed = parseWorkbook(rows);
        if (!parsed.length) throw new Error("Nenhuma apólice encontrada na planilha.");
        setPolicies(parsed);
      } catch (err) { setError(err.message || "Falha ao ler o arquivo."); }
    };
    reader.readAsArrayBuffer(file);
  };

  return (
    <div className="grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-6">
      <div className="flex flex-col gap-4">
        <div
          onDragOver={(e) => e.preventDefault()}
          onDrop={(e) => { e.preventDefault(); if (e.dataTransfer.files[0]) onFile(e.dataTransfer.files[0]); }}
          onClick={() => fileRef.current?.click()}
          className="bg-white border-2 border-dashed border-[rgb(208,212,220)] p-10 text-center cursor-pointer hover:border-[rgb(0,176,0)] transition-colors"
        >
          <UploadIcon />
          <p className="text-[16px] font-bold font-sans text-[rgb(9,10,11)] mt-3">Arraste a planilha de apólices aqui</p>
          <p className="text-[12px] font-medium font-sans text-[rgb(80,89,105)] mt-1">ou clique para escolher um arquivo .xlsx</p>
          <input ref={fileRef} type="file" accept=".xlsx,.xls,.csv" className="hidden"
            onChange={(e) => e.target.files[0] && onFile(e.target.files[0])} />
        </div>

        {error && <Alert variant="destructive"><AlertDescription>{error}</AlertDescription></Alert>}

        {policies.length > 0 && <PolicyImportTable policies={policies} goFineTune={goFineTune} />}
      </div>

      <div className="flex flex-col gap-4">
        <Card>
          <CardHeader><CardTitle>Como preparar a planilha</CardTitle>
            <CardDescription>Uma linha por cobertura. O número da apólice agrupa as coberturas do mesmo segurado.</CardDescription>
          </CardHeader>
          <CardContent className="flex flex-col gap-3">
            <Button variant="soft" onClick={downloadTemplate}>Baixar modelo .xlsx</Button>
            <Button variant="outline" onClick={() => setPolicies([EXAMPLE_POLICY])}>Carregar exemplo (Ivan)</Button>
            <Separator />
            <p className="text-[12px] font-bold font-sans text-[rgb(9,10,11)]">Campos que vêm da apólice</p>
            <p className="text-[12px] font-medium font-sans text-[rgb(80,89,105)] leading-[1.6]">
              segurado, nascimento, sexo, profissão, coberturas, capitais e prêmios.
            </p>
            <p className="text-[12px] font-bold font-sans text-[rgb(95,72,0)]">Faltam na apólice (ajuste fino)</p>
            <p className="text-[12px] font-medium font-sans text-[rgb(80,89,105)] leading-[1.6]">
              peso, altura, fumante e renda — o motor precisa pra precificar.
            </p>
          </CardContent>
        </Card>
      </div>
    </div>
  );
}

function PolicyImportTable({ policies, goFineTune }) {
  return (
    <Card variant="plain" className="overflow-hidden border-2 border-[#F3F4F6]">
      <table className="w-full text-left">
        <thead className="bg-[#F3F4F6]">
          <tr className="text-[12px] font-bold font-sans text-[rgb(80,89,105)]">
            <th className="px-4 py-3">Segurado</th>
            <th className="px-4 py-3">Apólice</th>
            <th className="px-4 py-3 text-center">Coberturas</th>
            <th className="px-4 py-3 text-right">Prêmio atual</th>
            <th className="px-4 py-3 text-center">Dados de risco</th>
            <th className="px-4 py-3"></th>
          </tr>
        </thead>
        <tbody>
          {policies.map((p, i) => {
            const miss = missingRisk(p.proponent);
            return (
              <tr key={i} className="border-t border-[#F3F4F6] text-[14px] font-medium font-sans text-[rgb(9,10,11)]">
                <td className="px-4 py-3">{p.insured_name || "—"}</td>
                <td className="px-4 py-3 text-[rgb(80,89,105)]">{p.policy_number}</td>
                <td className="px-4 py-3 text-center">{p.coverages.length}</td>
                <td className="px-4 py-3 text-right">{brl(p.total_premium)}</td>
                <td className="px-4 py-3 text-center">
                  {miss.length === 0
                    ? <Badge variant="success" dot>completo</Badge>
                    : <Badge variant="warning" dot>{`faltam ${miss.length}`}</Badge>}
                </td>
                <td className="px-4 py-3 text-right">
                  <Button size="sm" variant="link" onClick={() => goFineTune(i)}>Ajustar →</Button>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </Card>
  );
}

/* ════════════════════════ ABA 2 — AJUSTE FINO ════════════════ */
function missingRisk(p) {
  const m = [];
  if (p.weight == null) m.push("peso");
  if (p.height == null) m.push("altura");
  if (p.is_smoker == null) m.push("fumante");
  if (p.monthly_income == null) m.push("renda");
  return m;
}

function FineTuneTab({ policies, setPolicies, selected, setSelected, demo }) {
  if (policies.length === 0) {
    return (
      <Card><CardContent className="py-10 text-center">
        <p className="text-[14px] font-medium font-sans text-[rgb(80,89,105)]">
          Importe apólices na aba <b>Importar</b> primeiro.
        </p>
      </CardContent></Card>
    );
  }
  const policy = policies[selected] || policies[0];
  const update = (mut) => {
    setPolicies((prev) => prev.map((p, i) => (i === selected ? mut(structuredClone(p)) : p)));
  };

  return (
    <div className="grid grid-cols-1 lg:grid-cols-[260px_1fr] gap-6">
      <Card variant="plain" className="border-2 border-[#F3F4F6] h-fit">
        {policies.map((p, i) => (
          <div key={i} onClick={() => setSelected(i)}
            className={"px-4 py-3 cursor-pointer border-l-2 " +
              (i === selected ? "border-[rgb(0,176,0)] bg-[rgb(237,249,237)]" : "border-transparent hover:bg-[#F3F4F6]")}>
            <p className="text-[14px] font-bold font-sans text-[rgb(9,10,11)] truncate">{p.insured_name || p.policy_number}</p>
            <p className="text-[12px] font-medium font-sans text-[rgb(80,89,105)]">{p.coverages.length} coberturas · {brl(p.total_premium)}</p>
          </div>
        ))}
      </Card>

      <PolicyDetail key={selected} policy={policy} update={update} demo={demo} />
    </div>
  );
}

function PolicyDetail({ policy, update, demo }) {
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const p = policy.proponent;
  const setProp = (k, v) => update((pol) => { pol.proponent[k] = v; return pol; });

  const calcular = async () => {
    setLoading(true); setError(""); setResult(null);
    try { setResult(quote(policy)); }
    catch (e) { setError(String(e.message || e)); }
    finally { setLoading(false); }
  };

  const azosTypes = result ? result.azos_available.map((a) => a.coverage_type) : [];

  return (
    <div className="flex flex-col gap-6">
      <Alert variant="info">
        <AlertDescription>
          Peso, altura, fumante e renda usam a <b>premissa padrão (Gio)</b>: 80&nbsp;kg · 1,75&nbsp;m · não-fumante · R$&nbsp;20.000/mês. Ajuste se tiver o dado real do segurado.
        </AlertDescription>
      </Alert>

      {/* dados de risco */}
      <Card>
        <CardHeader><CardTitle>Dados de risco do segurado</CardTitle>
          <CardDescription>Complete o que não vem na apólice — o motor precisa pra cotar.</CardDescription>
        </CardHeader>
        <CardContent className="grid grid-cols-2 md:grid-cols-3 gap-4">
          <Field label="Nascimento"><Input value={p.birth_date} placeholder="AAAA-MM-DD" onChange={(e) => setProp("birth_date", e.target.value)} /></Field>
          <Field label="Sexo">
            <Select value={p.gender} onChange={(v) => setProp("gender", v)} options={[["male", "Masculino"], ["female", "Feminino"]]} />
          </Field>
          <ProfessionField value={p.profession_label} onPick={(id, label) => update((pol) => { pol.proponent.profession_id = id; pol.proponent.profession_label = label; return pol; })} />
          <Field label="Peso (kg)" missing={p.weight == null}><Input type="number" value={p.weight ?? ""} onChange={(e) => setProp("weight", e.target.value === "" ? null : Number(e.target.value))} /></Field>
          <Field label="Altura (m)" missing={p.height == null}><Input type="number" step="0.01" value={p.height ?? ""} onChange={(e) => setProp("height", e.target.value === "" ? null : Number(e.target.value))} /></Field>
          <Field label="Renda mensal" missing={p.monthly_income == null}><Input type="number" value={p.monthly_income ?? ""} onChange={(e) => setProp("monthly_income", e.target.value === "" ? null : Number(e.target.value))} /></Field>
          <Field label="Fumante" missing={p.is_smoker == null}>
            <div className="flex items-center gap-2 h-12">
              <Switch checked={!!p.is_smoker} onCheckedChange={(v) => setProp("is_smoker", v)} />
              <span className="text-[14px] font-medium font-sans text-[rgb(80,89,105)]">{p.is_smoker == null ? "não informado" : p.is_smoker ? "sim" : "não"}</span>
            </div>
          </Field>
          <Field label="Canal">
            <Select value={p.sale_channel} onChange={(v) => setProp("sale_channel", v)} options={[["b2b", "B2B (corretora)"], ["b2c", "B2C"]]} />
          </Field>
        </CardContent>
      </Card>

      {/* coberturas + de-para */}
      <Card>
        <CardHeader><CardTitle>Coberturas e de-para</CardTitle>
          <CardDescription>O tipo Azos é inferido pelo nome; ajuste aqui se precisar (opções aparecem após calcular).</CardDescription>
        </CardHeader>
        <CardContent className="p-0">
          <table className="w-full text-left">
            <thead className="bg-[#F3F4F6] text-[12px] font-bold font-sans text-[rgb(80,89,105)]">
              <tr>
                <th className="px-4 py-3">Cobertura ({policy.insurer_origin || "concorrente"})</th>
                <th className="px-4 py-3 text-right">Capital</th>
                <th className="px-4 py-3 text-right">Prêmio</th>
                <th className="px-4 py-3">Tipo Azos (de-para)</th>
              </tr>
            </thead>
            <tbody>
              {policy.coverages.map((c, i) => (
                <tr key={i} className="border-t border-[#F3F4F6] text-[14px] font-medium font-sans text-[rgb(9,10,11)]">
                  <td className="px-4 py-3">{c.name}{c.code && <span className="text-[rgb(163,170,185)]"> · {c.code}</span>}</td>
                  <td className="px-4 py-3 text-right">{brlCap(c.capital)}</td>
                  <td className="px-4 py-3 text-right">{brl(c.premium)}</td>
                  <td className="px-4 py-3">
                    <Select
                      value={c.azos_coverage_type}
                      onChange={(v) => update((pol) => { pol.coverages[i].azos_coverage_type = v; return pol; })}
                      options={[["", "auto (inferir)"], ...azosTypes.map((t) => [t, t])]}
                    />
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </CardContent>
      </Card>

      <div className="flex items-center gap-3">
        <Button onClick={calcular} disabled={loading}>{loading ? "Calculando…" : "Calcular equivalência"}</Button>
        {demo && <Badge variant="info" dot>modo demo</Badge>}
        {error && <span className="text-[14px] font-medium font-sans text-[rgb(255,68,68)]">{error}</span>}
      </div>

      {result && <ResultsPanel result={result} />}
    </div>
  );
}

/* ── painel de resultados ─────────────────────────────────────── */
function ResultsPanel({ result }) {
  const t = result.totals;
  const okCovs = result.coverages.filter((c) => c.status === "ok");
  const modoBcapital = okCovs.reduce((s, c) => s + (c.modo_b_capital || 0), 0);
  const economia = t.modo_a_economia_mensal;

  return (
    <div className="flex flex-col gap-4">
      {result.warnings.length > 0 && (
        <Alert variant="warning">
          <AlertTitle>Atenção</AlertTitle>
          <AlertDescription>
            <ul className="list-disc pl-4 mt-1">{result.warnings.map((w, i) => <li key={i}>{w}</li>)}</ul>
          </AlertDescription>
        </Alert>
      )}

      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <Card variant="plain" className="border-2 border-[rgb(0,176,0)] p-5">
          <p className="text-[12px] font-bold font-sans text-[rgb(0,87,0)]">MODO A · mesma proteção</p>
          <p className="text-[32px] font-bold font-sans text-[rgb(9,10,11)] leading-tight mt-1">{brl(t.modo_a_premium_mensal)}<span className="text-[14px] text-[rgb(80,89,105)]">/mês</span></p>
          <p className="text-[12px] font-medium font-sans text-[rgb(80,89,105)] mt-1">replicando os capitais do concorrente</p>
          {economia != null && (
            <div className="mt-3">
              <Badge variant={economia >= 0 ? "success" : "destructive"} dot>
                {economia >= 0 ? "economia de " : "acréscimo de "}{brl(Math.abs(economia))}/mês
              </Badge>
              <span className="text-[12px] font-medium font-sans text-[rgb(80,89,105)] ml-2">vs {brl(t.competitor_premium_mensal)} hoje</span>
            </div>
          )}
        </Card>

        <Card variant="plain" className="border-2 border-[#F3F4F6] p-5">
          <p className="text-[12px] font-bold font-sans text-[rgb(80,89,105)]">MODO B · mesmo bolso</p>
          <p className="text-[32px] font-bold font-sans text-[rgb(9,10,11)] leading-tight mt-1">{brlCap(modoBcapital)}</p>
          <p className="text-[12px] font-medium font-sans text-[rgb(80,89,105)] mt-1">capital total que a Azos entrega pelo mesmo prêmio</p>
          <div className="mt-3"><Badge variant="neutral">{t.n_matched} de {t.n_total} coberturas equivalentes</Badge></div>
        </Card>
      </div>

      <Card>
        <CardHeader><CardTitle>Detalhe por cobertura</CardTitle></CardHeader>
        <CardContent className="p-0">
          <table className="w-full text-left">
            <thead className="bg-[#F3F4F6] text-[12px] font-bold font-sans text-[rgb(80,89,105)]">
              <tr>
                <th className="px-4 py-3">Cobertura</th>
                <th className="px-4 py-3">Status</th>
                <th className="px-4 py-3 text-right">Capital atual</th>
                <th className="px-4 py-3 text-right">A · prêmio Azos</th>
                <th className="px-4 py-3 text-right">B · capital Azos</th>
              </tr>
            </thead>
            <tbody>
              {result.coverages.map((c, i) => {
                const [variant, label] = STATUS_BADGE[c.status] || ["neutral", c.status];
                return (
                  <tr key={i} className="border-t border-[#F3F4F6] text-[14px] font-medium font-sans text-[rgb(9,10,11)] align-top">
                    <td className="px-4 py-3">
                      {c.competitor_name}
                      {c.azos_coverage_type && <span className="block text-[12px] text-[rgb(163,170,185)]">→ {c.azos_coverage_type}</span>}
                      {c.notes.length > 0 && <span className="block text-[12px] text-[rgb(95,72,0)] mt-1">{c.notes.join(" ")}</span>}
                    </td>
                    <td className="px-4 py-3"><Badge variant={variant} dot>{label}</Badge></td>
                    <td className="px-4 py-3 text-right">
                      {brlCap(c.competitor_capital)}
                      {c.competitor_premium != null && <span className="block text-[12px] text-[rgb(163,170,185)]">{brl(c.competitor_premium)}/mês</span>}
                    </td>
                    <td className="px-4 py-3 text-right">
                      {c.modo_a_premium_mensal != null ? brl(c.modo_a_premium_mensal) : "—"}
                      {c.modo_a_capital != null && <span className="block text-[12px] text-[rgb(163,170,185)]">por {brlCap(c.modo_a_capital)}</span>}
                    </td>
                    <td className="px-4 py-3 text-right">
                      {c.modo_b_capital != null ? brlCap(c.modo_b_capital) : "—"}
                      {c.modo_b_premium_mensal != null && <span className="block text-[12px] text-[rgb(163,170,185)]">por {brl(c.modo_b_premium_mensal)}/mês</span>}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </CardContent>
      </Card>
    </div>
  );
}

/* ── inputs auxiliares ────────────────────────────────────────── */
function Field({ label, missing, children }) {
  return (
    <div className="flex flex-col gap-1.5">
      <Label>{label}{missing && <span className="text-[rgb(255,104,76)]"> *</span>}</Label>
      {children}
    </div>
  );
}

function Select({ value, onChange, options }) {
  return (
    <select value={value ?? ""} onChange={(e) => onChange(e.target.value)}
      className="flex h-12 w-full rounded-none border border-[rgb(208,212,220)] bg-white px-3 text-[14px] font-medium font-sans text-[rgb(9,10,11)] focus-visible:outline-none focus-visible:border-[rgb(0,176,0)]">
      {options.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
    </select>
  );
}

function ProfessionField({ value, onPick }) {
  const [q, setQ] = useState(value || "");
  const [opts, setOpts] = useState([]);
  const [open, setOpen] = useState(false);
  const timer = useRef(null);

  useEffect(() => { setQ(value || ""); }, [value]);

  const onType = (txt) => {
    setQ(txt); setOpen(true);
    if (txt.trim().length < 2) { setOpts([]); return; }
    setOpts(searchProfessions(txt.trim()));
  };

  return (
    <div className="flex flex-col gap-1.5 relative">
      <Label>Profissão</Label>
      <Input value={q} placeholder="digite p/ buscar…" onChange={(e) => onType(e.target.value)} onFocus={() => setOpen(true)} />
      {open && opts.length > 0 && (
        <div className="absolute top-full left-0 right-0 z-20 bg-white border border-[rgb(208,212,220)] max-h-56 overflow-auto shadow-az-2">
          {opts.map((o) => (
            <div key={o.id} onClick={() => { onPick(o.id, o.name); setQ(o.name); setOpen(false); }}
              className="px-3 py-2 text-[14px] font-medium font-sans text-[rgb(9,10,11)] cursor-pointer hover:bg-[rgb(237,249,237)]">
              {o.name}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function UploadIcon() {
  return (
    <svg className="mx-auto" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="rgb(0,176,0)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
      <polyline points="17 8 12 3 7 8" /><line x1="12" y1="3" x2="12" y2="15" />
    </svg>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
