/* SkyOps — Crew certifications: catalogue, compliance rules, register, matrix, CRUD, renewals, reminders.
   Certs stay as [name, expiry, meta] tuples on resStore.people so Calendar / Alerts / Argus keep working. */

const { useState: uSCR, useEffect: uECR, useMemo: uMCR } = React;
const CR_T = () => window.SKY_TODAY;
const crDays = (d) => d ? Math.round((Date.parse(d + "T00:00:00Z") - Date.parse(CR_T() + "T00:00:00Z")) / 864e5) : Infinity;
const crAddMonths = (d, n) => { const t = new Date((d || CR_T()) + "T00:00:00Z"); t.setUTCMonth(t.getUTCMonth() + n); return t.toISOString().slice(0, 10); };
const crToast = (m) => window.dispatchEvent(new CustomEvent("skyops:toast", { detail: m }));
const crId = () => "CRT-" + Math.random().toString(36).slice(2, 8).toUpperCase();

const CR_TYPES = [
  { id: "easa-a1a3", n: "EASA A1/A3 Open category", cat: "Regulatory", mo: 60, m: /^EASA A1/ },
  { id: "easa-a2", n: "EASA A2 CofC", cat: "Regulatory", mo: 60, m: /^EASA A2/ },
  { id: "sts01", n: "STS-01 operational", cat: "Regulatory", mo: 24, m: /^STS-01/ },
  { id: "sts02", n: "STS-02 operational", cat: "Regulatory", mo: 24, m: /^STS-02/ },
  { id: "th1", n: "Thermography Level 1", cat: "Technical", mo: 60, m: /^Thermography Level 1/ },
  { id: "th2", n: "Thermography Level 2 (ISO 18436-7)", cat: "Technical", mo: 60, m: /^Thermography Level 2/ },
  { id: "th3", n: "Thermography Level 3", cat: "Technical", mo: 60, m: /^Thermography Level 3/ },
  { id: "hv", n: "HV authorisation", cat: "Safety", mo: 36, m: /^HV/ },
  { id: "wah", n: "Working at height", cat: "Safety", mo: 36, m: /^Working at height/ },
  { id: "fa", n: "First aid", cat: "Safety", mo: 24, m: /^First aid/ },
  { id: "mod", n: "Module replacement", cat: "OEM", mo: 24, m: /^Module replacement/ },
  { id: "trk", n: "Tracker systems (NEXTracker)", cat: "OEM", mo: 24, m: /^Tracker systems/ },
];
const CR_CATS = ["Regulatory", "Technical", "Safety", "OEM", "Other"];
const crType = (name) => CR_TYPES.find(t => t.m.test(name || ""));
const crTypeById = (id) => CR_TYPES.find(t => t.id === id);
/* Requirements per skill: each inner array is an any-of group */
const CR_REQ = { Pilot: [["easa-a2"], ["sts01", "sts02"]], Thermography: [["th1", "th2", "th3"]], Electrical: [["hv"], ["wah"]], "Module replace": [["mod"], ["wah"]], Tracker: [["trk"]] };
const crGroupLabel = (g) => g.length === 1 ? crTypeById(g[0]).n : g.map(id => crTypeById(id).n.split(" (")[0].replace(" operational", "")).join(" or ");
const CR_ST = { expired: "Expired", suspended: "Suspended", missing: "Missing", crit: "≤ 30 days", soon: "≤ 90 days", ok: "Valid" };
const CR_RANK = { expired: 0, suspended: 1, missing: 2, crit: 3, soon: 4, ok: 5 };
const crMeta = (c) => c[2] || {};
const crKey = (c) => crMeta(c).id || "N:" + c[0];
function crState(c) { if (crMeta(c).suspended) return "suspended"; const d = crDays(c[1]); return d < 0 ? "expired" : d <= 30 ? "crit" : d <= 90 ? "soon" : "ok"; }
function crIssuer(p, t) {
  if (!t) return "—";
  if (t.cat === "Regulatory") return /^\+351/.test(p.phone || "") ? "ANAC Portugal" : "AESA España";
  if (t.cat === "Technical") return "Infrared Training Center";
  if (t.id === "trk") return "NEXTracker Academy";
  if (t.id === "mod") return "JinkoSolar O&M Academy";
  return p.kind === "contractor" ? p.org : "ISQ · Safety training";
}
function crRequired(p) { const seen = new Set(); return (p.skills || []).flatMap(s => CR_REQ[s] || []).filter(g => { const k = g.join(); if (seen.has(k)) return false; seen.add(k); return true; }); }
function crGroupBest(p, g) { const held = (p.certs || []).filter(c => { const t = crType(c[0]); return t && g.includes(t.id); }); return held.sort((a, b) => CR_RANK[crState(b)] - CR_RANK[crState(a)] || (b[1] || "").localeCompare(a[1] || ""))[0]; }
function crCompliance(p) {
  const groups = crRequired(p).map(g => { const c = crGroupBest(p, g); return { g, label: crGroupLabel(g), c, st: c ? crState(c) : "missing" }; });
  const blockers = groups.filter(x => ["expired", "suspended", "missing"].includes(x.st));
  const warn = groups.filter(x => x.st === "crit");
  return { groups, blockers, warn, ok: blockers.length === 0, pilot: (p.skills || []).includes("Pilot") };
}
function crIssues(people) {
  return people.flatMap(p => [
    ...(p.certs || []).map(c => ({ p, c, st: crState(c), days: crDays(c[1]) })).filter(x => x.st !== "ok" && x.st !== "soon"),
    ...crCompliance(p).groups.filter(g => g.st === "missing").map(g => ({ p, missing: g, st: "missing", days: -Infinity })),
  ]).sort((a, b) => CR_RANK[a.st] - CR_RANK[b.st] || a.days - b.days);
}
const crInspFor = (p) => (window.INSPECTIONS || []).filter(i => i.lead === p.name && !/closed|archiv|delivered/i.test(i.status || ""));
function crUpdate(pid, fn) { window.resStore.set(x => ({ ...x, people: x.people.map(p => p.id === pid ? { ...p, certs: fn(p.certs || []) } : p) })); }
const crHist = (m, a) => [{ d: CR_T(), a, by: "You" }, ...(m.history || [])];
function crRemind(p, c) {
  crUpdate(p.id, cs => cs.map(x => crKey(x) === crKey(c) ? [x[0], x[1], { ...crMeta(x), reminded: CR_T(), history: crHist(crMeta(x), "Renewal reminder sent") }] : x));
  crToast(`Reminder sent to ${p.name} · ${c[0]}`);
}
function crExportCSV(people) {
  const esc = (v) => `"${String(v ?? "").replace(/"/g, '""')}"`;
  const rows = [["Holder", "Role", "Organisation", "Certificate", "Category", "Number", "Issuer", "Issued", "Expires", "Days left", "Status"]];
  people.forEach(p => { (p.certs || []).forEach(c => { const m = crMeta(c); rows.push([p.name, p.role, p.org, c[0], m.cat || crType(c[0])?.cat || "Other", m.num, m.issuer, m.issued, c[1], crDays(c[1]), CR_ST[crState(c)]]); });
    crCompliance(p).groups.filter(g => g.st === "missing").forEach(g => rows.push([p.name, p.role, p.org, g.label, "", "", "", "", "", "", "Missing"])); });
  const csv = rows.map(r => r.map(esc).join(",")).join("\n");
  const name = `skyops-certifications-${CR_T()}.csv`;
  if (window.skyDownload) window.skyDownload(name, csv, "text/csv"); else { const a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" })); a.download = name; a.click(); }
}
const crOpen = (detail) => window.dispatchEvent(new CustomEvent("skyops:cert-modal", { detail }));

/* One-time enrichment of seed certificates */
(function crMigrate() {
  const R = window.resStore; if (!R || localStorage.getItem("skyops_certs_v1")) return;
  const A = window.skyAdd;
  const OV = { "J. Castaño": { "EASA A2": A(12) }, "L. Ferreira": { "EASA A2": A(51) }, "T. Aragón": { "Thermography Level 2": A(78) }, "S. Lima": { "Tracker systems": A(24) } };
  const ADD = { "R. Pardal": [["First aid", A(-9)]] };
  R.set(x => ({ ...x, people: x.people.map(p => {
    const src = [...(p.certs || []), ...(ADD[p.name] || [])];
    const certs = src.map((c, i) => {
      if (c[2]) return c;
      const ov = OV[p.name] && Object.entries(OV[p.name]).find(([k]) => c[0].startsWith(k));
      const exp = ov ? ov[1] : c[1]; const t = crType(c[0]);
      const h = (p.id + c[0]).split("").reduce((a, ch) => (a * 31 + ch.charCodeAt(0)) % 99991, 7);
      const pre = t?.cat === "Regulatory" ? (/^\+351/.test(p.phone || "") ? "PRT-RP" : "ESP-RP") : t?.cat === "Technical" ? "ITC" : "CRT";
      return [c[0], exp, { id: crId(), cat: t?.cat || "Other", num: `${pre}-${String(h).padStart(5, "0")}`, issuer: crIssuer(p, t), issued: crAddMonths(exp, -(t?.mo || 24)), doc: { name: `${c[0].split(" (")[0].replace(/[^\w]+/g, "_")}_${p.name.replace(/[^\w]+/g, "")}.pdf`, size: 180000 + h * 3 }, history: [{ d: crAddMonths(exp, -(t?.mo || 24)), a: "Added", by: "Import" }] }];
    });
    return { ...p, certs };
  }) }));
  localStorage.setItem("skyops_certs_v1", "1");
})();

function CrPill({ st }) { return <span className={`cr-pill cr-pill-${st}`}><i/>{CR_ST[st]}</span>; }
function CrBar({ c }) {
  const m = crMeta(c), d = crDays(c[1]), st = crState(c);
  const total = Math.max(1, crDays(c[1]) - crDays(m.issued || crAddMonths(c[1], -24)));
  const pct = Math.max(0, Math.min(100, d / total * 100));
  return <div className={`cr-bar cr-bar-${st}`}><span><i style={{ width: pct + "%" }}/></span><b className="mono">{d < 0 ? `${-d} d ago` : `${d} d`}</b></div>;
}

/* Alert strip on Fleet & crew */
function CrewCertAlerts({ people, onView, onPerson }) {
  const iss = crIssues(people);
  if (!iss.length) return null;
  const top = iss.slice(0, 4);
  return (
    <div className="cr-alerts">
      <div className="cr-alerts-hd"><Icon name="shield" size={13}/><b>{iss.length} certification issue{iss.length > 1 ? "s" : ""}</b>
        <span className="sec">{iss.filter(x => x.st === "expired").length} expired · {iss.filter(x => x.st === "crit").length} expire ≤ 30 d · {iss.filter(x => x.st === "missing").length} missing required</span>
        <span className="grow"/><button className="btn btn-ghost btn-xs" onClick={onView}>Open register<Icon name="arrow_right" size={11}/></button></div>
      <div className="cr-alerts-l">{top.map((x, i) => {
        const ins = crInspFor(x.p);
        return <div key={i} className={`cr-al cr-al-${x.st}`}>
          <CrPill st={x.st}/>
          <button className="cr-al-who" onClick={() => onPerson(x.p.id)}><b>{x.p.name}</b><span>{x.missing ? x.missing.label : x.c[0]}</span></button>
          <span className="mono cr-al-d">{x.missing ? "required for " + (x.p.skills || []).find(s => (CR_REQ[s] || []).some(g => g.join() === x.missing.g.join())) : x.st === "suspended" ? "suspended" : x.days < 0 ? `expired ${x.c[1]}` : `expires ${x.c[1]} · ${x.days} d`}</span>
          {ins.length > 0 && x.st !== "crit" ? <span className="cr-al-imp"><Icon name="alert" size={10}/>Lead on {ins[0].id}{ins.length > 1 ? ` +${ins.length - 1}` : ""}</span> : <span/>}
          <div className="cr-al-act">
            {!x.missing && x.st !== "suspended" && <button className="btn btn-ghost btn-xs" disabled={crMeta(x.c).reminded === CR_T()} onClick={() => crRemind(x.p, x.c)}><Icon name="bell" size={10}/>{crMeta(x.c).reminded === CR_T() ? "Reminded" : "Remind"}</button>}
            <button className="btn btn-secondary btn-xs" onClick={() => x.missing ? crOpen({ mode: "add", pid: x.p.id, typeId: x.missing.g[0] }) : crOpen({ mode: "renew", pid: x.p.id, cid: crKey(x.c) })}>{x.missing ? <><Icon name="plus" size={10}/>Add</> : <><Icon name="refresh" size={10}/>Renew</>}</button>
          </div>
        </div>; })}</div>
    </div>
  );
}

/* Compliance cell for crew table */
function CrewCertCell({ p }) {
  const c = crCompliance(p), states = (p.certs || []).map(crState);
  const valid = states.filter(s => s === "ok" || s === "soon").length;
  const worst = states.sort((a, b) => CR_RANK[a] - CR_RANK[b])[0] || "ok";
  const st = c.blockers.length ? (c.blockers.some(b => b.st === "expired") ? "expired" : c.blockers[0].st) : worst;
  const bad = states.filter(s => s === worst).length;
  const label = c.blockers.length ? `${c.blockers.length} gap${c.blockers.length > 1 ? "s" : ""}` : st === "ok" ? `${valid} valid` : st === "soon" ? `${valid} valid · ${bad} ≤ 90 d` : `${bad} ${CR_ST[st].toLowerCase()}`;
  const tip = [...c.blockers.map(b => `${b.label}: ${CR_ST[b.st]}`), ...(p.certs || []).filter(x => crState(x) !== "ok").map(x => `${x[0]}: ${CR_ST[crState(x)]} · ${x[1]}`)];
  return <span className={`cr-cell cr-cell-${st}`} title={[...new Set(tip)].join("\n") || "All certifications valid"}><i/>{label}</span>;
}

/* Drawer section */
function CrewPersonCerts({ person: p }) {
  const c = crCompliance(p);
  const certs = [...(p.certs || [])].sort((a, b) => CR_RANK[crState(a)] - CR_RANK[crState(b)] || (a[1] || "").localeCompare(b[1] || ""));
  const ins = crInspFor(p);
  return (
    <div className="wo-dr-sec cr-dr">
      <div className="cr-dr-hd"><span className="caps">Certifications · {certs.length}</span><button className="btn btn-secondary btn-xs" onClick={() => crOpen({ mode: "add", pid: p.id })}><Icon name="plus" size={10}/>Add</button></div>
      {(c.groups.length > 0) && <div className={`cr-clear ${c.ok ? (c.warn.length ? "is-warn" : "is-ok") : "is-bad"}`}>
        <Icon name={c.ok ? "check_circle" : "alert"} size={14}/>
        <div><b>{c.ok ? (c.pilot ? "Cleared to fly" : "Compliant for assigned skills") : (c.pilot ? "Not cleared to fly" : "Not compliant")}</b>
          <span>{c.ok ? (c.warn.length ? `${c.warn.map(w => w.label).join(", ")} expires within 30 days` : `${c.groups.length} required certification${c.groups.length > 1 ? "s" : ""} valid`) : c.blockers.map(b => `${b.label} ${CR_ST[b.st].toLowerCase()}`).join(" · ")}</span>
          {!c.ok && ins.length > 0 && <span className="cr-clear-imp">Lead on {ins.map(i => i.id).join(", ")} — reassign or renew before flight.</span>}</div>
      </div>}
      {certs.map(cert => { const m = crMeta(cert), st = crState(cert); return (
        <div key={crKey(cert)} className={`cr-item cr-item-${st}`}>
          <div className="cr-item-m"><b>{cert[0]}</b><span className="sec">{m.cat || crType(cert[0])?.cat || "Other"}{m.num ? <> · <span className="mono">{m.num}</span></> : null}{m.issuer ? ` · ${m.issuer}` : ""}</span></div>
          <div className="cr-item-r"><CrPill st={st}/><span className="mono sec">{cert[1]}</span></div>
          <div className="cr-item-a">
            {m.doc && <span className="cr-doc" title={m.doc.name}><Icon name="file" size={10}/>{m.doc.name.length > 22 ? m.doc.name.slice(0, 20) + "…" : m.doc.name}</span>}
            <span className="grow"/>
            {["crit", "expired", "soon"].includes(st) && <button className="btn btn-ghost btn-xs" disabled={m.reminded === CR_T()} onClick={() => crRemind(p, cert)}><Icon name="bell" size={10}/>{m.reminded === CR_T() ? "Reminded" : "Remind"}</button>}
            <button className="btn btn-ghost btn-xs" onClick={() => crOpen({ mode: "renew", pid: p.id, cid: crKey(cert) })}><Icon name="refresh" size={10}/>Renew</button>
            <button className="btn btn-ghost btn-xs" onClick={() => crOpen({ mode: "edit", pid: p.id, cid: crKey(cert) })}><Icon name="settings" size={10}/>Edit</button>
          </div>
        </div>); })}
      {c.groups.filter(g => g.st === "missing").map(g => <div key={g.g.join()} className="cr-item cr-item-missing">
        <div className="cr-item-m"><b>{g.label}</b><span className="sec">Required for {(p.skills || []).filter(s => (CR_REQ[s] || []).some(x => x.join() === g.g.join())).join(", ")}</span></div>
        <div className="cr-item-r"><CrPill st="missing"/></div>
        <div className="cr-item-a"><span className="grow"/><button className="btn btn-secondary btn-xs" onClick={() => crOpen({ mode: "add", pid: p.id, typeId: g.g[0] })}><Icon name="plus" size={10}/>Add certificate</button></div>
      </div>)}
      {certs.length === 0 && c.groups.length === 0 && <div className="sec" style={{ fontSize: 11 }}>No certifications on file.</div>}
    </div>
  );
}

/* Certifications tab: register + matrix */
function CrewCertsTab({ people, onPerson }) {
  const [view, setView] = uSCR("register");
  const [st, setSt] = uSCR("attention");
  const [cat, setCat] = uSCR("all");
  const [q, setQ] = uSCR("");
  const all = uMCR(() => people.flatMap(p => [
    ...(p.certs || []).map(c => ({ p, c, m: crMeta(c), st: crState(c), days: crDays(c[1]), cat: crMeta(c).cat || crType(c[0])?.cat || "Other" })),
    ...crCompliance(p).groups.filter(g => g.st === "missing").map(g => ({ p, missing: g, st: "missing", days: -Infinity, cat: crTypeById(g.g[0]).cat })),
  ]).sort((a, b) => CR_RANK[a.st] - CR_RANK[b.st] || a.days - b.days), [people]);
  const cnt = (s) => all.filter(x => x.st === s).length;
  const compliant = people.filter(p => crCompliance(p).ok).length;
  const rows = all.filter(x => (st === "all" || (st === "attention" ? x.st !== "ok" : x.st === st)) && (cat === "all" || x.cat === cat)
    && (!q || `${x.p.name} ${x.c ? x.c[0] : x.missing.label} ${x.m?.num || ""}`.toLowerCase().includes(q.toLowerCase())));
  const remindable = all.filter(x => x.c && (x.st === "crit" || x.st === "expired") && x.m.reminded !== CR_T());
  const pgRows = usePaged(rows, "certs");
  const cols = CR_TYPES.filter(t => people.some(p => (p.certs || []).some(c => crType(c[0])?.id === t.id) || crRequired(p).some(g => g.includes(t.id))));
  const custom = [...new Set(people.flatMap(p => (p.certs || []).filter(c => !crType(c[0])).map(c => c[0])))];

  return (<>
    <div className="cr-kpis">
      {[["ok", "Valid", cnt("ok")], ["soon", "Expire ≤ 90 d", cnt("soon")], ["crit", "Expire ≤ 30 d", cnt("crit")], ["expired", "Expired", cnt("expired") + cnt("suspended")], ["missing", "Missing required", cnt("missing")]].map(([k, l, n]) =>
        <button key={k} className={`cr-kpi cr-kpi-${k} ${st === k ? "on" : ""}`} onClick={() => setSt(st === k ? "all" : k)}><span className="caps">{l}</span><b className="mono">{n}</b></button>)}
      <div className="cr-kpi cr-kpi-comp"><span className="caps">Crew compliant</span><b className="mono">{compliant}/{people.length}</b><span className="cr-kpi-bar"><i style={{ width: (compliant / Math.max(1, people.length) * 100) + "%" }}/></span></div>
    </div>
    <div className="wo-tb cr-tb">
      <div className="ps-seg">{[["register", "Register"], ["matrix", "Compliance matrix"]].map(([v, l]) => <button key={v} className={view === v ? "on" : ""} onClick={() => setView(v)}>{l}</button>)}</div>
      {view === "register" && <>
        <div className="ps-seg">{[["attention", "Needs attention"], ["all", "All"]].map(([v, l]) => <button key={v} className={st === v ? "on" : ""} onClick={() => setSt(v)}>{l}</button>)}</div>
        <select className="sel cr-sel" value={cat} onChange={e => setCat(e.target.value)}><option value="all">All categories</option>{CR_CATS.map(c => <option key={c}>{c}</option>)}</select>
        <label className="cr-search"><Icon name="search" size={12}/><input value={q} onChange={e => setQ(e.target.value)} placeholder="Search holder, certificate, number"/></label>
      </>}
      <span className="grow"/>
      <button className="btn btn-ghost btn-xs" disabled={!remindable.length} onClick={() => { remindable.forEach(x => crRemind(x.p, x.c)); }}><Icon name="bell" size={11}/>Remind {remindable.length || ""}</button>
      <button className="btn btn-ghost btn-xs" onClick={() => crExportCSV(people)}><Icon name="download" size={11}/>Export CSV</button>
      <button className="btn btn-primary btn-xs" onClick={() => crOpen({ mode: "add" })}><Icon name="plus" size={11}/>Add certificate</button>
    </div>

    {view === "register" ? <div className="card"><table className="tbl cr-tbl">
      <thead><tr><th>Holder</th><th>Certificate</th><th>Number · issuer</th><th>Issued</th><th>Expires</th><th>Remaining</th><th>Status</th><th className="r"></th></tr></thead>
      <tbody>{pgRows.rows.map((x, i) => x.missing ? (
        <tr key={"m" + i} className="cr-row-missing">
          <td><button className="cr-link" onClick={() => onPerson(x.p.id)}><b>{x.p.name}</b></button><div className="sec cr-sub">{x.p.role}</div></td>
          <td><b>{x.missing.label}</b><div className="sec cr-sub">{x.cat} · required</div></td>
          <td className="sec">—</td><td className="sec">—</td><td className="sec">—</td><td className="sec">—</td>
          <td><CrPill st="missing"/></td>
          <td className="r"><button className="btn btn-secondary btn-xs" onClick={() => crOpen({ mode: "add", pid: x.p.id, typeId: x.missing.g[0] })}><Icon name="plus" size={10}/>Add</button></td>
        </tr>) : (
        <tr key={crKey(x.c)} onClick={() => crOpen({ mode: "edit", pid: x.p.id, cid: crKey(x.c) })}>
          <td><button className="cr-link" onClick={e => { e.stopPropagation(); onPerson(x.p.id); }}><b>{x.p.name}</b></button><div className="sec cr-sub">{x.p.role}</div></td>
          <td><b>{x.c[0]}</b><div className="sec cr-sub">{x.cat}{x.m.doc ? <> · <Icon name="file" size={9}/> PDF</> : <span className="cr-nodoc"> · no document</span>}</div></td>
          <td><span className="mono">{x.m.num || "—"}</span><div className="sec cr-sub">{x.m.issuer || "—"}</div></td>
          <td className="mono sec">{x.m.issued || "—"}</td>
          <td className="mono">{x.c[1]}</td>
          <td><CrBar c={x.c}/></td>
          <td><CrPill st={x.st}/>{x.m.reminded && <div className="sec cr-sub">reminded {x.m.reminded}</div>}</td>
          <td className="r" onClick={e => e.stopPropagation()}><div className="cr-acts">
            {["crit", "expired", "soon"].includes(x.st) && <button className="btn btn-ghost btn-xs" title="Send renewal reminder" disabled={x.m.reminded === CR_T()} onClick={() => crRemind(x.p, x.c)}><Icon name="bell" size={11}/></button>}
            <button className="btn btn-ghost btn-xs" title="Renew" onClick={() => crOpen({ mode: "renew", pid: x.p.id, cid: crKey(x.c) })}><Icon name="refresh" size={11}/></button>
            <button className="btn btn-ghost btn-xs" title="Edit" onClick={() => crOpen({ mode: "edit", pid: x.p.id, cid: crKey(x.c) })}><Icon name="settings" size={11}/></button>
          </div></td>
        </tr>))}
        {rows.length === 0 && <tr><td colSpan={8} className="sec" style={{ textAlign: "center", padding: 24 }}>No certificates match these filters.</td></tr>}
      </tbody></table><Pager pg={pgRows} label="certificates"/></div>
    : <div className="card cr-mx-wrap"><table className="cr-mx">
      <thead><tr><th>Crew</th>{cols.map(t => <th key={t.id}><span>{t.n.split(" (")[0]}</span><em>{t.cat}</em></th>)}{custom.map(n => <th key={n}><span>{n}</span><em>Other</em></th>)}<th>Status</th></tr></thead>
      <tbody>{people.map(p => { const comp = crCompliance(p); return (
        <tr key={p.id}>
          <td><button className="cr-link" onClick={() => onPerson(p.id)}><b>{p.name}</b></button><div className="sec cr-sub">{(p.skills || []).join(" · ")}</div></td>
          {[...cols.map(t => ({ t, c: (p.certs || []).find(c => crType(c[0])?.id === t.id), req: crRequired(p).some(g => g.includes(t.id)), group: crRequired(p).find(g => g.includes(t.id)) })),
            ...custom.map(n => ({ n, c: (p.certs || []).find(c => c[0] === n) }))].map((x, j) => {
            const s = x.c ? crState(x.c) : x.req && !(x.group.length > 1 && crGroupBest(p, x.group)) ? "missing" : null;
            return <td key={j}>{s ? <button className={`cr-mx-c cr-mx-${s}`} title={x.c ? `${x.c[0]} · ${x.c[1]}` : "Required · missing"} onClick={() => x.c ? crOpen({ mode: "edit", pid: p.id, cid: crKey(x.c) }) : crOpen({ mode: "add", pid: p.id, typeId: x.t.id })}>
              {x.c ? <><i/><span className="mono">{crDays(x.c[1]) < 0 ? "exp" : crDays(x.c[1]) > 365 ? `${Math.floor(crDays(x.c[1]) / 30)}m` : `${crDays(x.c[1])}d`}</span></> : <><Icon name="plus" size={10}/><span>req.</span></>}
            </button> : <span className="cr-mx-na">{x.req ? "·" : ""}</span>}</td>; })}
          <td>{comp.groups.length ? <span className={`cr-cell cr-cell-${comp.ok ? (comp.warn.length ? "crit" : "ok") : "expired"}`}><i/>{comp.ok ? (comp.pilot ? "Cleared" : "Compliant") : `${comp.blockers.length} gap${comp.blockers.length > 1 ? "s" : ""}`}</span> : <span className="sec">—</span>}</td>
        </tr>); })}</tbody></table>
      <div className="cr-mx-legend">{["ok", "soon", "crit", "expired", "missing"].map(s => <span key={s} className={`cr-mx-${s}`}><i/>{CR_ST[s]}</span>)}<span className="sec">Cells show time to expiry · empty = not required</span></div>
    </div>}
  </>);
}

/* Add / edit / renew modal — mounted once, opened via crOpen() */
function CrewCertModalHost() {
  const [req, setReq] = uSCR(null);
  uECR(() => { const h = (e) => setReq(e.detail); window.addEventListener("skyops:cert-modal", h); return () => window.removeEventListener("skyops:cert-modal", h); }, []);
  if (!req) return null;
  return <CrewCertModal key={JSON.stringify(req)} req={req} onClose={() => setReq(null)}/>;
}
function CrewCertModal({ req, onClose }) {
  const people = window.useRes().people;
  const p0 = people.find(p => p.id === req.pid);
  const cert = p0 && req.cid ? (p0.certs || []).find(c => crKey(c) === req.cid) : null;
  const m0 = cert ? crMeta(cert) : {};
  const renew = req.mode === "renew";
  const t0 = cert ? crType(cert[0]) : crTypeById(req.typeId);
  const issued0 = renew ? CR_T() : m0.issued || CR_T();
  const [f, setF] = uSCR({ pid: req.pid || people[0]?.id, typeId: t0 ? t0.id : cert ? "other" : "easa-a2", custom: cert && !t0 ? cert[0] : "", cat: m0.cat || t0?.cat || "Other",
    num: renew ? "" : m0.num || "", issuer: m0.issuer || (p0 && t0 ? crIssuer(p0, t0) : ""), issued: issued0, exp: renew ? crAddMonths(issued0, t0?.mo || 24) : cert ? cert[1] : crAddMonths(CR_T(), t0?.mo || 60),
    doc: renew ? null : m0.doc || null, notes: renew ? "" : m0.notes || "", suspended: renew ? false : !!m0.suspended });
  const [expTouched, setExpTouched] = uSCR(!!cert && !renew);
  const [confirmDel, setConfirmDel] = uSCR(false);
  const p = people.find(x => x.id === f.pid);
  const t = crTypeById(f.typeId);
  const set = (k, v) => setF(x => ({ ...x, [k]: v }));
  const pickType = (id) => { const nt = crTypeById(id); setF(x => ({ ...x, typeId: id, cat: nt ? nt.cat : x.cat, issuer: nt && p ? crIssuer(p, nt) : x.issuer, exp: expTouched || !nt ? x.exp : crAddMonths(x.issued, nt.mo) })); };
  const pickIssued = (v) => setF(x => ({ ...x, issued: v, exp: expTouched || !t ? x.exp : crAddMonths(v, t.mo) }));
  const name = f.typeId === "other" ? f.custom.trim() : t?.n;
  const dup = p && !cert && (p.certs || []).some(c => c[0] === name);
  const err = !p ? "Choose a holder" : !name ? "Certificate type is required" : !f.exp ? "Expiry date is required" : f.issued && f.exp <= f.issued ? "Expiry must be after issue date" : dup ? `${p.name} already holds ${name} — use Renew` : null;
  const onFile = (e) => { const file = e.target.files[0]; if (!file) return;
    if (file.size < 400000) { const r = new FileReader(); r.onload = () => set("doc", { name: file.name, size: file.size, data: r.result }); r.readAsDataURL(file); } else set("doc", { name: file.name, size: file.size }); };
  const save = () => {
    if (err) return;
    const meta = { ...m0, id: m0.id || crId(), cat: f.cat, num: f.num.trim(), issuer: f.issuer.trim(), issued: f.issued, doc: f.doc, notes: f.notes.trim(), suspended: f.suspended, reminded: renew ? null : m0.reminded };
    if (renew) { meta.prev = [{ num: m0.num, issued: m0.issued, exp: cert[1], doc: m0.doc?.name }, ...(m0.prev || [])]; meta.history = crHist(m0, `Renewed · was valid until ${cert[1]}`); }
    else meta.history = cert ? crHist(m0, f.suspended && !m0.suspended ? "Suspended" : !f.suspended && m0.suspended ? "Reinstated" : "Edited") : [{ d: CR_T(), a: "Added", by: "You" }];
    const tuple = [name, f.exp, meta];
    if (cert && f.pid === req.pid) crUpdate(f.pid, cs => cs.map(c => crKey(c) === req.cid ? tuple : c));
    else { if (cert) crUpdate(req.pid, cs => cs.filter(c => crKey(c) !== req.cid)); crUpdate(f.pid, cs => [...cs, tuple]); }
    crToast(renew ? `${name} renewed for ${p.name} · valid until ${f.exp}` : cert ? `${name} updated` : `${name} added to ${p.name}`);
    onClose();
  };
  const del = () => { crUpdate(req.pid, cs => cs.filter(c => crKey(c) !== req.cid)); crToast(`${cert[0]} removed from ${p0.name}`); onClose(); };
  const comp = p ? crCompliance(p) : null;

  return (
    <div className="ni-scrim" onClick={onClose}><div className="ni-modal cr-modal" onClick={e => e.stopPropagation()} role="dialog">
      <div className="ni-hd"><div><div className="kicker">Certifications{p0 ? ` · ${p0.name}` : ""}</div><h3>{renew ? `Renew ${cert[0]}` : cert ? "Edit certificate" : "Add certificate"}</h3></div><button className="ask-hd-btn" onClick={onClose}><Icon name="x" size={13}/></button></div>
      <div className="ni-body">
        {renew && <div className="cr-renew-prev"><Icon name="history" size={12}/><span>Current: <b className="mono">{m0.num || "—"}</b> · issued <span className="mono">{m0.issued || "—"}</span> · expires <span className="mono">{cert[1]}</span></span><CrPill st={crState(cert)}/></div>}
        <div className="ni-grid">
          <label className="field"><span>Holder *</span><select className="sel" value={f.pid} disabled={renew} onChange={e => { const np = people.find(x => x.id === e.target.value); setF(x => ({ ...x, pid: e.target.value, issuer: t && np ? crIssuer(np, t) : x.issuer })); }}>{people.map(x => <option key={x.id} value={x.id}>{x.name} · {x.role}</option>)}</select></label>
          <label className="field"><span>Certificate type *</span><select className="sel" value={f.typeId} disabled={renew} onChange={e => pickType(e.target.value)}>
            {CR_CATS.filter(c => c !== "Other").map(c => <optgroup key={c} label={c}>{CR_TYPES.filter(x => x.cat === c).map(x => <option key={x.id} value={x.id}>{x.n}</option>)}</optgroup>)}
            <option value="other">Other…</option></select></label>
          {f.typeId === "other" && <><label className="field"><span>Name *</span><input className="inp" value={f.custom} disabled={renew} onChange={e => set("custom", e.target.value)} placeholder="e.g. Confined spaces"/></label>
            <label className="field"><span>Category</span><select className="sel" value={f.cat} onChange={e => set("cat", e.target.value)}>{CR_CATS.map(c => <option key={c}>{c}</option>)}</select></label></>}
          <label className="field"><span>Certificate number</span><input className="inp mono" value={f.num} onChange={e => set("num", e.target.value)} placeholder={renew ? "New certificate number" : "e.g. PRT-RP-00412"}/></label>
          <label className="field"><span>Issuing authority</span><input className="inp" value={f.issuer} onChange={e => set("issuer", e.target.value)}/></label>
          <label className="field"><span>Issued</span><input className="inp" type="date" value={f.issued} onChange={e => pickIssued(e.target.value)}/></label>
          <label className="field"><span>Expires *{t && !expTouched ? <em className="cr-hint"> · auto {t.mo / 12} yr validity</em> : null}</span><input className="inp" type="date" value={f.exp} onChange={e => { setExpTouched(true); set("exp", e.target.value); }}/></label>
          <div className="field ni-span"><span>Document</span>
            {f.doc ? <div className="cr-file"><Icon name="file" size={13}/><b>{f.doc.name}</b><span className="sec mono">{Math.round(f.doc.size / 1024)} KB</span><span className="grow"/>
              {f.doc.data && <a className="btn btn-ghost btn-xs" href={f.doc.data} download={f.doc.name}><Icon name="download" size={10}/>Download</a>}
              <button className="btn btn-ghost btn-xs" onClick={() => set("doc", null)}><Icon name="x" size={10}/>Remove</button></div>
            : <label className="cr-drop"><Icon name="upload" size={14}/><span>Upload certificate scan <em>PDF, JPG or PNG</em></span><input type="file" accept=".pdf,image/*" onChange={onFile}/></label>}
          </div>
          <label className="field ni-span"><span>Notes</span><textarea className="inp" rows={2} style={{ height: "auto", resize: "vertical" }} value={f.notes} onChange={e => set("notes", e.target.value)} placeholder="Restrictions, endorsements, training provider…"/></label>
          {cert && !renew && <label className="cr-susp ni-span"><input type="checkbox" checked={f.suspended} onChange={e => set("suspended", e.target.checked)}/><span><b>Suspended</b> — treat as invalid regardless of expiry (e.g. under investigation, medical hold)</span></label>}
        </div>
        {comp && comp.groups.length > 0 && !cert && <div className="cr-req-hint"><span className="caps">Required for {p.name}</span>{comp.groups.map(g => <span key={g.g.join()} className={`cr-req cr-req-${g.st}`}>{g.label}<em>{CR_ST[g.st]}</em></span>)}</div>}
        {cert && !renew && (m0.history || []).length > 0 && <div className="cr-hist"><span className="caps">History</span>
          {(m0.history || []).slice(0, 6).map((h, i) => <div key={i}><span className="mono">{h.d}</span><span>{h.a}</span><span className="sec">{h.by}</span></div>)}
          {(m0.prev || []).map((v, i) => <div key={"p" + i} className="cr-hist-prev"><span className="mono">{v.issued || "—"}</span><span>Previous · {v.num || "no number"} · valid until {v.exp}</span><span className="sec">{v.doc || ""}</span></div>)}
        </div>}
      </div>
      <div className="ni-ft">
        {cert && !renew && (confirmDel ? <div className="cr-del"><span>Delete this certificate?</span><button className="btn btn-ghost btn-xs" onClick={() => setConfirmDel(false)}>Keep</button><button className="btn btn-xs cr-btn-danger" onClick={del}><Icon name="trash" size={10}/>Delete</button></div>
          : <button className="btn btn-ghost cr-del-btn" onClick={() => setConfirmDel(true)}><Icon name="trash" size={12}/>Delete</button>)}
        <span className="grow"/>
        {err && <span className="cr-err">{err}</span>}
        <button className="btn btn-ghost" onClick={onClose}>Cancel</button>
        <button className="btn btn-primary" disabled={!!err} onClick={save}><Icon name="check" size={12}/>{renew ? "Renew" : cert ? "Save" : "Add certificate"}</button>
      </div>
    </div></div>
  );
}

Object.assign(window, { CR_TYPES, CR_REQ, crState, crDays, crCompliance, crIssues, crOpen, CrewCertAlerts, CrewCertCell, CrewPersonCerts, CrewCertsTab, CrewCertModalHost });
