// ─── Discovery wizard (4a–c) ──────────────────────────────────────────────────
// The first page after Clerk login. Walks the YAML-driven question sections, then
// shows the fork screen (Email vs Manual). Pure presentational component: App
// passes the questionnaire + saved answers in, and the submit/navigation handlers.
//
// Props:
//   data        { sections, profile, contacts, completed }  (from GET /wizard)
//   onSubmit    async (answers) => void                      (POST /wizard/answers)
//   onPickPath  (path: "email" | "manual") => void           (first-run only)
//   editMode    bool — Wizard tab: pre-filled, ends on Save (no fork)
function DiscoveryWizard({ data, onSubmit, onPickPath, editMode = false }) {
  const { useState } = React;
  // Personal details (name/DOB/address) are edited only on the "Edit personal
  // details" card, never in this linear flow — drop that section from the steps.
  const sections = (data.sections || []).filter(s => s.id !== "personal_details");

  // Repeatable people: children, parents and siblings are fixed slots in the YAML
  // (child_1.., parent_1.., sibling_1..) but we reveal them one at a time via an
  // "Add a X / Add another" button instead of showing every empty slot at once.
  // Defined before `answers` because the seed below needs it to know which
  // synthesized slot ids (executor_2, executor_3, ...) to look for.
  const REPEAT_GROUPS = [
    { prefix: "child_",   addFirst: "+ Add a child" },
    { prefix: "parent_",  addFirst: "+ Add a parent" },
    { prefix: "sibling_", addFirst: "+ Add a sibling" },
    ...sections
      .flatMap(s => s.questions)
      .filter(q => q.target.repeatable)
      .map(q => ({
        prefix: q.target.role.replace(/_1$/, "_"),
        max: q.target.max_repeat,
        addFirst: `+ Add ${q.prompt.toLowerCase()}`,
        base: q,
      })),
  ];

  // Seed answers: scalar profile facts + any saved contact matched to its question
  // by (contact_type, role) so the Wizard tab pre-fills people too.
  const [answers, setAnswers] = useState(() => {
    const seed = { ...(data.profile || {}) };
    const contacts = data.contacts || [];
    for (const s of sections) {
      for (const q of s.questions) {
        // bool answers come back from the DB as strings/numbers — coerce to a real
        // boolean so the Yes/No buttons (compared with ===) preselect correctly.
        if (q.type === "bool" && q.id in seed) {
          const v = seed[q.id];
          seed[q.id] = v === true || v === "true" || v === "True" || v === 1 || v === "1";
          continue;
        }
        if (q.target.sink !== "contact") continue;
        const match = contacts.find(c => c.contact_type === q.target.contact_type && c.role === q.target.role);
        if (match) seed[q.id] = { name: match.name, email: match.email || "" };
      }
    }
    // The loop above only matches literal YAML questions (e.g. executor_1). Slots
    // beyond n=1 (executor_2..8) are synthesized client-side and never appear in
    // s.questions, so they'd otherwise be left out of the seed even when a saved
    // contact exists for that role — pre-fill those here from REPEAT_GROUPS.
    for (const g of REPEAT_GROUPS) {
      if (!g.max) continue; // legacy fixed-slot groups (child_/parent_/sibling_) are all literal YAML questions, already seeded above
      for (let n = 2; n <= g.max; n++) {
        const role = `${g.prefix}${n}`;
        const match = contacts.find(c => c.contact_type === g.base.target.contact_type && c.role === role);
        if (match) seed[role] = { name: match.name, email: match.email || "" };
      }
    }
    return seed;
  });
  const [sectionIdx, setSectionIdx] = useState(0);
  const [phase, setPhase] = useState("questions");   // "questions" | "fork"
  const [busy, setBusy] = useState(false);
  const [saved, setSaved] = useState(false);
  const [error, setError] = useState(null);

  const [repeatCounts, setRepeatCounts] = useState(() => {
    const contacts = data.contacts || [];
    const counts = {};
    for (const g of REPEAT_GROUPS) {
      counts[g.prefix] = contacts.filter(c => c.role && c.role.startsWith(g.prefix) && c.name).length;
    }
    return counts;
  });

  const setAnswer = (qid, value) => { setSaved(false); setAnswers(a => ({ ...a, [qid]: value })); };

  // A question is shown only if every show_if entry matches the answers so far.
  const isVisible = (q) =>
    Object.entries(q.show_if || {}).every(([k, v]) => String(answers[k]) === String(v));

  const section = sections[sectionIdx];
  const visible = section ? section.questions.filter(isVisible) : [];
  const isLast = sectionIdx >= sections.length - 1;

  // Split the repeatable people (child/parent/sibling) out of the visible list so
  // we can reveal each group progressively; everything else renders normally.
  const groupOf = (q) => REPEAT_GROUPS.find(g => q.target.role && q.target.role.startsWith(g.prefix));
  const otherQuestions = visible.filter(q => !groupOf(q));

  // First-run: every visible question must be answered before advancing. People
  // (contact questions) stay optional — you may genuinely have no spouse/solicitor.
  const isAnswered = (q) => {
    const v = answers[q.id];
    if (q.type === "contact") {
      // Next of kin is mandatory; other contacts (spouse, advisors) stay optional.
      if (q.target.role === "next_of_kin") return !!(v && v.name);
      return true;
    }
    return v !== undefined && v !== null && v !== "";
  };
  const unanswered = visible.filter(q => !isAnswered(q));
  const canAdvance = editMode || unanswered.length === 0;

  async function save() {
    setBusy(true);
    setError(null);
    try {
      await onSubmit(answers);
      if (editMode) setSaved(true);
      else setPhase("fork");
    } catch (e) {
      setError(e.message || "Could not save your answers");
    } finally {
      setBusy(false);
    }
  }

  function primaryAction() {
    if (editMode || isLast) return save();
    setSectionIdx(i => i + 1);
  }

  if (phase === "fork") {
    return (
      <div className="wizard">
        <div className="wizard__card">
          <h1 className="wizard__title">How do you want to add your accounts?</h1>
          <p className="wizard__hint">Pick one — you can do the other later.</p>
          <div className="wizard__fork">
            <button className="wizard__forkbtn" onClick={() => onPickPath("email")}>
              <span className="wizard__forkbtn-title">Email</span>
              <span className="wizard__forkbtn-sub">Connect your mailbox and let AI discover your accounts.</span>
            </button>
            <button className="wizard__forkbtn" onClick={() => onPickPath("manual")}>
              <span className="wizard__forkbtn-title">Manual</span>
              <span className="wizard__forkbtn-sub">Add your accounts and people by hand.</span>
            </button>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="wizard">
      <div className="wizard__card">
        <div className="wizard__progress">Step {sectionIdx + 1} of {sections.length}</div>
        <h1 className="wizard__title">{section ? section.title : "About you"}</h1>

        <div className="wizard__questions">
          {otherQuestions.map(q => (
            <QuestionField
              key={q.id}
              q={q}
              value={answers[q.id]}
              onChange={(v) => setAnswer(q.id, v)}
              onRemove={q.type === "contact" ? () => setAnswer(q.id, { name: "", email: "" }) : null} />
          ))}
          {REPEAT_GROUPS.map(g => {
            const groupQuestions = visible.filter(q => groupOf(q) === g);
            if (groupQuestions.length === 0) return null;
            const count = repeatCounts[g.prefix];
            // Legacy groups (child_/parent_/sibling_) cap at however many literal
            // YAML slots exist. New repeatable groups (executor_ etc.) cap at
            // target.max_repeat and synthesize slots beyond the single declared
            // YAML question — same id/role naming convention (`${prefix}${n}`).
            const max = g.max || groupQuestions.length;
            const slots = [];
            for (let n = 1; n <= Math.min(count, max); n++) {
              const slotId = `${g.prefix}${n}`;
              const literal = groupQuestions.find(q => q.target.role === slotId);
              slots.push(literal || { ...g.base, id: slotId, target: { ...g.base.target, role: slotId } });
            }
            return (
              <React.Fragment key={g.prefix}>
                {slots.map(q => (
                  <QuestionField
                    key={q.id}
                    q={q}
                    value={answers[q.id]}
                    onChange={(v) => setAnswer(q.id, v)}
                    onRemove={() => setAnswer(q.id, { name: "", email: "" })} />
                ))}
                {count < max && (
                  <button
                    type="button"
                    className="wizard__addchild"
                    onClick={() => setRepeatCounts(c => ({ ...c, [g.prefix]: (c[g.prefix] || 0) + 1 }))}>
                    {count === 0 ? g.addFirst : "+ Add another"}
                  </button>
                )}
              </React.Fragment>
            );
          })}
        </div>

        {error && <p className="wizard__error">{error}</p>}
        {saved && <p className="wizard__saved">Saved ✓</p>}
        {!canAdvance && <p className="wizard__hint">Please answer every question to continue.</p>}

        <div className="wizard__nav">
          {sectionIdx > 0 && (
            <button className="btn-secondary" onClick={() => setSectionIdx(i => i - 1)} disabled={busy}>Back</button>
          )}
          {editMode && !isLast && (
            <button className="btn-secondary" onClick={() => setSectionIdx(i => i + 1)} disabled={busy}>Next</button>
          )}
          <button className="btn-primary" onClick={primaryAction} disabled={busy || !canAdvance}>
            {busy ? "Saving…" : (editMode ? "Save" : isLast ? "Finish" : "Next")}
          </button>
        </div>
      </div>
    </div>
  );
}

// One question, rendered by type.
function QuestionField({ q, value, onChange, onRemove }) {
  const hasContact = q.type === "contact" && value && value.name;
  return (
    <div className="wizard__field">
      <label className="wizard__label">{q.prompt}</label>
      {q.type === "select" && (
        <div className="wizard__options">
          {(q.options || []).map(opt => (
            <button
              key={opt}
              type="button"
              className={`wizard__opt ${value === opt ? "is-selected" : ""}`}
              onClick={() => onChange(opt)}>
              {opt.replace(/_/g, " ")}
            </button>
          ))}
        </div>
      )}
      {q.type === "bool" && (
        <div className="wizard__options">
          {[["Yes", true], ["No", false]].map(([label, val]) => (
            <button
              key={label}
              type="button"
              className={`wizard__opt ${value === val ? "is-selected" : ""}`}
              onClick={() => onChange(val)}>
              {label}
            </button>
          ))}
        </div>
      )}
      {(q.type === "text" || q.type === "number") && (
        <input
          className="wizard__input"
          type={q.type === "number" ? "number" : "text"}
          value={value || ""}
          onChange={(e) => onChange(e.target.value)} />
      )}
      {q.type === "contact" && (
        <div className="wizard__contact">
          <input
            className="wizard__input"
            placeholder="Name"
            value={(value && value.name) || ""}
            onChange={(e) => onChange({ ...(value || {}), name: e.target.value })} />
          <input
            className="wizard__input"
            placeholder="Email (optional)"
            value={(value && value.email) || ""}
            onChange={(e) => onChange({ ...(value || {}), email: e.target.value })} />
          {onRemove && hasContact && (
            <button type="button" className="wizard__remove" onClick={onRemove}>Remove</button>
          )}
        </div>
      )}
    </div>
  );
}

if (typeof window !== "undefined") window.DiscoveryWizard = DiscoveryWizard;


// ─── Wizard tab hub ───────────────────────────────────────────────────────────
// The Wizard tab landing: section cards. Click one to open its form.
//   About you  → the profile/people wizard (edit mode, pre-filled)
//   Your estate → manual preset-category builder (resource sink)   [next build]
//   Fill AI gaps → complete what email ingestion missed             [next build]
// ─── Life graph ───────────────────────────────────────────────────────────────
// A radial relationship map: "You" in the centre, spokes out to the family and
// advisers the user added in "About you". Pure SVG (no chart lib — the frontend
// has no build step). Family vs adviser are coloured differently.
const _ROLE_LABEL = {
  spouse: "Spouse", next_of_kin: "Next of kin",
  child_1: "Child", child_2: "Child", child_3: "Child", child_4: "Child",
  parent_1: "Parent", parent_2: "Parent",
  sibling_1: "Sibling", sibling_2: "Sibling", sibling_3: "Sibling", sibling_4: "Sibling",
  accountant: "Accountant", solicitor: "Solicitor", ifa: "Financial adviser",
  trustee: "Trustee", guardian: "Guardian",
};
// Repeatable estate roles (executor_2, poa_holder_3, ...) aren't individually
// listed above — strip the trailing "_N" and label by the base role instead.
const _REPEATABLE_ROLE_LABEL = {
  executor: "Executor", beneficiary: "Beneficiary",
  poa_holder: "POA holder", business_partner: "Business partner",
};
function _roleLabel(role) {
  if (_ROLE_LABEL[role]) return _ROLE_LABEL[role];
  const base = String(role || "").replace(/_\d+$/, "");
  return _REPEATABLE_ROLE_LABEL[base] || role;
}
// Estimate the half-width (px) a single text line needs, so a circle can grow to
// fit its label instead of clipping it. ~0.6 of font size per glyph is a safe avg.
const _halfTextW = (s, fontPx) => (String(s || "").length * fontPx * 0.6) / 2;
// Three relationship arcs, each a clustered third of the circle instead of one
// flat ring — family, estate roles (new), and professional advisors read as
// visually distinct groups.
function _kindOf(c) {
  if (c.contact_type === "legal_role") return "legal";
  if (c.contact_type === "advisor") return "advisor";
  return c.role === "next_of_kin" ? "nok" : "family";
}
function _arcOf(kind) { return kind === "legal" ? "legal" : kind === "advisor" ? "advisor" : "family"; }
const _ARC_ORDER = ["family", "legal", "advisor"];
function LifeGraph({ contacts, profileName }) {
  const people = (contacts || []).filter(c => c && c.name);
  const W = 620, H = 480, cx = W / 2, cy = H / 2, R = 150;
  const groups = _ARC_ORDER.map(arc => people.filter(c => _arcOf(_kindOf(c)) === arc));
  const arcSpan = (2 * Math.PI) / 3;
  return (
    <section className="lifegraph">
      <h2 className="lifegraph__title">Your relationships</h2>
      {people.length === 0 ? (
        <p className="lifegraph__empty">Add your family, estate roles and advisers in the People tab to build your relationship map.</p>
      ) : (
        <svg className="lifegraph__svg" viewBox={`0 0 ${W} ${H}`} role="img" aria-label="Relationship map">
          {groups.flatMap((arcPeople, arcIdx) => arcPeople.map((c, i) => {
            const arcStart = -Math.PI / 2 + arcIdx * arcSpan;
            const ang = arcStart + (arcSpan * (i + 1)) / (arcPeople.length + 1);
            const x = cx + R * Math.cos(ang), y = cy + R * Math.sin(ang);
            const kind = _kindOf(c);
            const role = _roleLabel(c.role);
            const needed = Math.max(_halfTextW(c.name, 11), _halfTextW(role, 9)) + 8;
            const r = Math.min(Math.max(kind === "nok" ? 42 : 36, needed), 60);
            return (
              <g key={c.id || `${arcIdx}-${i}`}>
                <line className={`lifegraph__edge${kind === "nok" ? " lifegraph__edge--nok" : ""}`} x1={cx} y1={cy} x2={x} y2={y} />
                <circle className={`lifegraph__node lifegraph__node--${kind}`} cx={x} cy={y} r={r} />
                <text className="lifegraph__name" x={x} y={y - 1} textAnchor="middle">{c.name}</text>
                <text className="lifegraph__role" x={x} y={y + 14} textAnchor="middle">{role}</text>
              </g>
            );
          }))}
          <circle className="lifegraph__center" cx={cx} cy={cy} r="46" />
          <text className="lifegraph__center-label" x={cx} y={cy + 5} textAnchor="middle">{profileName || "You"}</text>
        </svg>
      )}
    </section>
  );
}

// ─── People tab ────────────────────────────────────────────────────────────
// Top-level "People" nav tab: the relationship graph plus the same
// people-editing sections DiscoveryWizard renders in edit mode (about_you,
// connected_people, estate_roles, advisors). Replaces the old "People &
// relationships" card under Manual Entry (see WizardHub, Task 8).
function PeopleTab({ data, onSubmit }) {
  return (
    <div className="hub">
      <p className="hub__eyebrow">People</p>
      <h1 className="hub__title">Your people</h1>
      <p className="hub__sub">Family, estate roles and professional advisers.</p>
      <LifeGraph contacts={data.contacts || []} profileName={data.profile?.first_name} />
      <DiscoveryWizard data={data} onSubmit={onSubmit} editMode={true} />
    </div>
  );
}
if (typeof window !== "undefined") window.PeopleTab = PeopleTab;

// ─── Personal details (edit) ──────────────────────────────────────────────────
// Focused editor for name / date of birth / address. Lives ONLY here — these fields
// are not asked in the linear discovery wizard. Saves via the same POST /wizard/answers
// as the wizard (a partial answers object is fine; keys match the hidden
// personal_details YAML section). Validation is client-side:
//   - first name + surname required (middle name optional)
//   - date of birth must give an age between 18 and 120
//   - postcode validated via postcodes.io ("Look up" button), proxied through
//     GET /wizard/address-lookup; a valid postcode auto-fills town/county, the user
//     types their own street line. Lookup failure falls back to manual entry.
function _ageFromDob(dob) {
  if (!dob) return null;
  const d = new Date(dob);
  if (isNaN(d.getTime())) return null;
  const now = new Date();
  let a = now.getFullYear() - d.getFullYear();
  const m = now.getMonth() - d.getMonth();
  if (m < 0 || (m === 0 && now.getDate() < d.getDate())) a -= 1;
  return a;
}

// onboarding=true is the first-run capture step shown before the discovery wizard
// (blocking, "Continue"); false is the editable card in the wizard hub ("Save").
function PersonalDetailsSection({ data, onSubmit, addressApi, onboarding = false }) {
  const { useState } = React;
  const profile = data.profile || {};
  const [f, setF] = useState({
    first_name:    profile.first_name || "",
    middle_name:   profile.middle_name || "",
    surname:       profile.surname || "",
    dob:           profile.dob || "",
    address_line1: profile.address_line1 || "",
    town:          profile.town || "",
    county:        profile.county || "",
    postcode:      profile.postcode || "",
  });
  const [looking, setLooking] = useState(false);
  const [lookupMsg, setLookupMsg] = useState(null);
  const [busy, setBusy] = useState(false);
  const [saved, setSaved] = useState(false);
  const [error, setError] = useState(null);

  const set = (k, v) => { setSaved(false); setF(s => ({ ...s, [k]: v })); };

  const age = _ageFromDob(f.dob);
  const dobOk = age !== null && age >= 18 && age <= 120;
  const today = new Date().toISOString().slice(0, 10);

  async function lookup() {
    const pc = f.postcode.trim();
    if (!pc) return;
    setLooking(true); setLookupMsg(null);
    try {
      const res = await addressApi.lookup(pc);
      if (!res.valid) {
        setLookupMsg("That postcode wasn't recognised — please check it or type your address below.");
      } else {
        // Auto-fill town/county; the user types their own street line.
        setSaved(false);
        setF(s => ({
          ...s,
          town: res.town || s.town,
          county: res.county || s.county,
          postcode: res.postcode || s.postcode,
        }));
        setLookupMsg("Postcode found — town and county filled in below.");
      }
    } catch (e) {
      // Service down. Let the user type the address manually rather than block them.
      setLookupMsg("Postcode lookup is unavailable — please type your address below.");
    } finally { setLooking(false); }
  }

  const canSave = !!f.first_name.trim() && !!f.surname.trim() && dobOk
    && !!f.address_line1.trim() && !!f.postcode.trim() && !busy;

  async function save() {
    setBusy(true); setError(null);
    try { await onSubmit(f); setSaved(true); }
    catch (e) { setError(e.message || "Could not save"); }
    finally { setBusy(false); }
  }

  const invalidStyle = { borderColor: "#c0532f" };

  return (
    <div className="wizard">
      <div className="wizard__card">
        <h1 className="wizard__title">{onboarding ? "Tell us about you" : "Edit personal details"}</h1>
        <p className="wizard__hint">{onboarding
          ? "First, a few details about you — your name, date of birth and address."
          : "Your name, date of birth and address."}</p>
        <div className="wizard__questions">
          <div className="wizard__field">
            <label className="wizard__label">First name</label>
            <input className="wizard__input" type="text"
              value={f.first_name} onChange={e => set("first_name", e.target.value)} />
          </div>
          <div className="wizard__field">
            <label className="wizard__label">Middle name (optional)</label>
            <input className="wizard__input" type="text"
              value={f.middle_name} onChange={e => set("middle_name", e.target.value)} />
          </div>
          <div className="wizard__field">
            <label className="wizard__label">Surname</label>
            <input className="wizard__input" type="text"
              value={f.surname} onChange={e => set("surname", e.target.value)} />
          </div>
          <div className="wizard__field">
            <label className="wizard__label">Date of birth</label>
            <input className="wizard__input" type="date" max={today}
              style={f.dob && !dobOk ? invalidStyle : undefined}
              value={f.dob} onChange={e => set("dob", e.target.value)} />
            {f.dob && !dobOk && <p className="wizard__error">Age must be between 18 and 120.</p>}
          </div>

          <div className="wizard__field">
            <label className="wizard__label">Postcode</label>
            <div className="wizard__contact">
              <input className="wizard__input" type="text"
                value={f.postcode}
                onChange={e => { set("postcode", e.target.value); setLookupMsg(null); }} />
              <button type="button" className="btn-secondary"
                onClick={lookup} disabled={looking || !f.postcode.trim()}>
                {looking ? "Looking up…" : "Look up"}
              </button>
            </div>
            {lookupMsg && <p className="wizard__hint">{lookupMsg}</p>}
          </div>

          <div className="wizard__field">
            <label className="wizard__label">Street address</label>
            <input className="wizard__input" type="text"
              value={f.address_line1} onChange={e => set("address_line1", e.target.value)} />
          </div>
          <div className="wizard__field">
            <label className="wizard__label">Town / city</label>
            <input className="wizard__input" type="text"
              value={f.town} onChange={e => set("town", e.target.value)} />
          </div>
          <div className="wizard__field">
            <label className="wizard__label">County (optional)</label>
            <input className="wizard__input" type="text"
              value={f.county} onChange={e => set("county", e.target.value)} />
          </div>
        </div>
        {error && <p className="wizard__error">{error}</p>}
        <div className="wizard__nav">
          <button className="btn-primary" onClick={save} disabled={!canSave}>
            {busy ? "Saving…" : (onboarding ? "Continue" : "Save")}
          </button>
          {saved && !onboarding && <span className="wizard__saved">Saved ✓</span>}
        </div>
      </div>
    </div>
  );
}

function WizardHub({ data, onSubmit, resourcesApi, gapsApi, addressApi, onResourcesChanged }) {
  const { useState } = React;
  const [view, setView] = useState(null);

  const back = <button className="hub__back" onClick={() => setView(null)}>← All sections</button>;

  if (view === "details") {
    return (
      <div className="hub">
        {back}
        <PersonalDetailsSection data={data} onSubmit={onSubmit} addressApi={addressApi} />
      </div>
    );
  }
  if (view === "add")  { return (<div className="hub">{back}<ResourcesSection api={resourcesApi} onChanged={onResourcesChanged} mode="add" /></div>); }
  if (view === "edit") { return (<div className="hub">{back}<ResourcesSection api={resourcesApi} onChanged={onResourcesChanged} mode="edit" /></div>); }

  // Note: "Fill AI gaps" lives ONLY in the post-ingest popup (app.jsx renders
  // window.GapsSection there). The GapsSection component is still defined below.
  const cards = [
    { id: "details", title: "Personal details",       sub: "Your name, age and address." },
    { id: "add",   title: "Add resources",           sub: "Add accounts and assets from preset categories." },
    { id: "edit",  title: "Manage resources",        sub: "Review and update resources you've added or confirmed." },
  ];
  return (
    <div className="hub">
      <p className="hub__eyebrow">Discovery wizard</p>
      <h1 className="hub__title">Your handover</h1>
      <p className="hub__sub">Tell us about you, add your accounts, and fill anything our AI missed.</p>
      <div className="hub__cards">
        {cards.map(c => (
          <button key={c.id} className="hub__card" onClick={() => setView(c.id)}>
            <span className="hub__card-title">{c.title}</span>
            <span className="hub__card-sub">{c.sub}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

if (typeof window !== "undefined") window.WizardHub = WizardHub;
if (typeof window !== "undefined") window.PersonalDetailsSection = PersonalDetailsSection;


// ─── Manual resources section ─────────────────────────────────────────────────
// Predefined categories (from seeded resource_types) → fields (from resources.yaml).
// Saves to the SAME user_resources tables AI ingestion writes to — source=MANUAL is
// the only difference. Props: api = { load, add, remove }.
// Per-family example brand for the add-resource placeholders (so it's not always
// "Barclays"). Falls back to a neutral hint for unmapped families.
const _FAMILY_EG = {
  banking: "Barclays", payments: "Revolut", insurance: "Aviva",
  investment: "Vanguard", trading: "Trading 212", streaming: "Netflix",
  ecommerce: "Amazon", retail: "Amazon", telecoms: "Vodafone",
  energy: "British Gas", utilities: "British Gas", water: "Thames Water",
  travel: "British Airways", transport: "Trainline", fitness: "PureGym",
  food_delivery: "Deliveroo", government: "HMRC", social: "Facebook",
  gaming: "Steam", property: "Rightmove", automotive: "DVLA",
  healthcare: "Bupa", education: "Coursera", charity: "Oxfam",
  membership: "National Trust", professional: "LinkedIn",
  digital: "iCloud", employment_benefit: "your employer",
};

// Best-effort favicon for a manual resource: guess a domain from the provider
// name (first word + .com) and hit Google's favicon service. Falls back to the
// monogram if the image errors.
function resFaviconUrl(brand) {
  const s = (brand || "").trim().toLowerCase();
  if (!s) return "";
  const token = s.split(/\s+/)[0].replace(/[^a-z0-9]/g, "");
  if (!token) return "";
  return `https://www.google.com/s2/favicons?domain=${token}.com&sz=128`;
}

// ─── ResEditCard — one card in the "Edit existing resources" grid ────────────
function ResEditCard({ it, family, typeLabel, pending, onEdit, onDelete, busy }) {
  const [favOk, setFavOk] = React.useState(true);
  const brand = it.organisation || it.name || "";
  const mono = (brand || "?").trim().charAt(0).toUpperCase();
  const fav = resFaviconUrl(brand);
  return (
    <div className="rescard" data-family={family}>
      <div className="rescard__pillrow">
        <span className={`rescard__pill rescard__pill--${pending ? "pending" : "confirmed"}`}>
          <span className="rescard__pill-dot" aria-hidden="true" />{pending ? "Pending" : "Confirmed"}
        </span>
      </div>
      <div className="rescard__head">
        <span className="rescard__logo">
          <span className="rescard__mono" aria-hidden="true">{mono}</span>
          {fav && favOk && (
            <img className="rescard__fav" src={fav} alt="" loading="lazy" onError={() => setFavOk(false)} />
          )}
        </span>
        <div className="rescard__name-block">
          <div className="rescard__type">{typeLabel}</div>
          <div className="rescard__name">{it.name || "Untitled"}</div>
        </div>
      </div>
      <div className="rescard__divider" />
      <div className="rescard__actions">
        <button className="btn-secondary rescard__edit" onClick={onEdit} disabled={busy}>Edit</button>
        <button className="rescard__del" onClick={onDelete} disabled={busy} aria-label="Delete resource" title="Delete">🗑</button>
      </div>
    </div>
  );
}

function ResourcesSection({ api, onChanged, mode = "add" }) {
  const { useState, useEffect } = React;
  const isEdit = mode === "edit";
  const [state, setState] = useState(null);   // { categories, items } | null
  const [family, setFamily] = useState(null);
  const [typeId, setTypeId] = useState("");
  const [name, setName] = useState("");
  const [organisation, setOrganisation] = useState("");
  const [fields, setFields] = useState({});
  const [editId, setEditId] = useState(null);   // resource_id being edited (null = add mode)
  const [filter, setFilter] = useState("all");   // edit view: "all" | family — which pill is active
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    let on = true;
    api.load().then(d => on && setState(d)).catch(e => on && setError(e.message));
    return () => { on = false; };
  }, []);

  if (error && !state) return <div className="wizard"><div className="wizard__card"><p className="wizard__error">{error}</p></div></div>;
  if (!state) return <div className="wizard"><div className="wizard__card"><p className="wizard__hint">Loading…</p></div></div>;

  const category = state.categories.find(c => c.family === family);
  const eg = _FAMILY_EG[family] || "your provider";
  // ONE org field, relabelled per family. Families not listed have no provider concept
  // (cars, devices, valuables) and show no org field at all.
  const ORG_LABELS = { financial: "Provider", investment: "Provider", insurance: "Insurer",
    subscription: "Provider", digital: "Service", membership: "Organisation", document: "Issuer",
    employment_benefit: "Employer" };
  const orgLabel = ORG_LABELS[family];
  const showOrg = !!orgLabel;
  // Options filtered by the exact SELECTED TYPE: a Video Streaming Subscription shows
  // streaming orgs only; a Credit Card Account shows banking/payments — never a SaaS org.
  const selType = category?.types.find(t => String(t.resource_type_id) === String(typeId));
  const orgOptions = (selType && state.organisations?.[selType.type_name]) || [];
  // Keep an already-set value selectable on edit even if it's outside the filtered list
  // (e.g. a user-added org not in the intelligence layer).
  const orgChoices = organisation && !orgOptions.includes(organisation) ? [organisation, ...orgOptions] : orgOptions;
  const typeName = (id) => {
    for (const c of state.categories) {
      const t = c.types.find(t => t.resource_type_id === id);
      if (t) return t.type_name;
    }
    return "";
  };
  const familyOf = (rtId) => {
    for (const c of state.categories) {
      if (c.types.some(t => t.resource_type_id === rtId)) return c.family;
    }
    return null;
  };
  const resetForm = () => { setFamily(null); setTypeId(""); setName(""); setOrganisation(""); setFields({}); setEditId(null); setError(null); };

  // Open the add form pre-filled with an existing resource → save patches it (and
  // flips its source to MANUAL). Works for confirmed (MANUAL/DISCOVERY_WIZARD) rows.
  const startEdit = (it) => {
    setEditId(it.id);
    setFamily(familyOf(it.resource_type_id));
    setTypeId(String(it.resource_type_id));
    setName(it.name || "");
    const d = it.details || {};
    setOrganisation(it.organisation || "");   // prefill the org/provider/issuer so it can be edited
    setFields(d);
    setError(null);
  };

  async function save() {
    if (!typeId || !name.trim() || (showOrg && !organisation.trim())) { setError(showOrg ? `Pick a type and enter a name and ${orgLabel.toLowerCase()}.` : "Pick a type and enter a name."); return; }
    setBusy(true); setError(null);
    try {
      if (editId) {
        setState(await api.edit(editId, { resource_type_id: Number(typeId), name: name.trim(), organisation: organisation.trim(), fields }));
      } else {
        setState(await api.add({ resource_type_id: Number(typeId), name: name.trim(), organisation: organisation.trim(), fields }));
      }
      resetForm();
      onChanged?.();   // tell the dashboard/report to reload — no page refresh needed
    } catch (e) { setError(e.message); } finally { setBusy(false); }
  }

  async function remove(id) {
    setBusy(true); setError(null);
    try { setState(await api.remove(id)); onChanged?.(); }
    catch (e) { setError(e.message); } finally { setBusy(false); }
  }

  return (
    <div className="wizard">
      <div className={`wizard__card ${isEdit && !category ? "wizard__card--wide" : ""}`}>
        <h1 className="wizard__title">{isEdit ? "Edit existing resources" : "Add your resources"}</h1>
        <p className="wizard__hint">{isEdit ? "Review and update resources you've added or confirmed." : "Add accounts, policies and assets by category."}</p>

        {!isEdit && !category && (
          <div className="hub__cards">
            {state.categories.map(c => (
              <button key={c.family} className="hub__card" onClick={() => setFamily(c.family)}>
                <span className="hub__card-title">{c.title}</span>
                <span className="hub__card-sub">{c.types.length} types</span>
              </button>
            ))}
          </div>
        )}

        {category && (
          <div className="wizard__questions">
            <button className="hub__back" onClick={resetForm}>{editId ? "← All resources" : "← Categories"}</button>
            <div className="wizard__field">
              <label className="wizard__label">Type</label>
              <select className="wizard__input" value={typeId} onChange={e => setTypeId(e.target.value)}>
                <option value="">Select…</option>
                {category.types.map(t => (
                  <option key={t.resource_type_id} value={t.resource_type_id}>{t.type_name}</option>
                ))}
              </select>
            </div>
            <div className="wizard__field">
              <label className="wizard__label">Name</label>
              <input className="wizard__input" value={name}
                onChange={e => setName(e.target.value)} placeholder={`e.g. ${eg}${category && category.types.length ? " " + (typeName(Number(typeId)) || category.title).toLowerCase() : ""}`} />
            </div>
            {showOrg && (
            <div className="wizard__field">
              <label className="wizard__label">{orgLabel} *</label>
              <select className="wizard__input" value={organisation} required
                onChange={e => setOrganisation(e.target.value)}>
                <option value="">Select…</option>
                {orgChoices.map(o => <option key={o} value={o}>{o}</option>)}
              </select>
            </div>
            )}
            {category.fields.map(f => (
              <div className="wizard__field" key={f.id}>
                <label className="wizard__label">{f.label}</label>
                <input className="wizard__input" type={f.type === "number" ? "number" : "text"}
                  value={fields[f.id] || ""}
                  onChange={e => setFields(s => ({ ...s, [f.id]: e.target.value }))} />
              </div>
            ))}
            {error && <p className="wizard__error">{error}</p>}
            <div className="wizard__nav">
              <button className="btn-primary" onClick={save} disabled={busy || !typeId || !name.trim() || (showOrg && !organisation.trim())}>{busy ? "Saving…" : (editId ? "Save changes" : "Add resource")}</button>
            </div>
          </div>
        )}

        {/* Edit view: grouped cards. Hidden while the edit form is open (category set). */}
        {isEdit && !category && state.items.length === 0 && <p className="wizard__hint">No resources yet — add some in “Add your resources”.</p>}

        {isEdit && !category && state.items.length > 0 && (() => {
          // Bucket items by family using familyOf(), then order the groups by the
          // categories list so the layout is stable. Unknown families fall to the end.
          const byFamily = {};
          for (const it of state.items) {
            const fam = familyOf(it.resource_type_id) || "unknown";
            (byFamily[fam] ||= []).push(it);
          }
          const groups = state.categories
            .filter(c => byFamily[c.family])
            .map(c => ({ family: c.family, title: c.title, items: byFamily[c.family] }));
          const known = new Set(groups.map(g => g.family));
          for (const fam of Object.keys(byFamily)) {
            if (!known.has(fam)) groups.push({ family: fam, title: fam, items: byFamily[fam] });
          }
          const total = state.items.length;
          const shown = groups.filter(g => filter === "all" || g.family === filter);

          return (
            <div className="reslib">
              <div className="reslib__pills">
                <button className={`respill ${filter === "all" ? "is-active" : ""}`} onClick={() => setFilter("all")}>
                  All <span className="respill__n">{total}</span>
                </button>
                {groups.map(g => (
                  <button key={g.family} className={`respill ${filter === g.family ? "is-active" : ""}`} onClick={() => setFilter(g.family)}>
                    {g.title} <span className="respill__n">{g.items.length}</span>
                  </button>
                ))}
              </div>

              {shown.map(g => (
                <section className="resgrp" data-family={g.family} key={g.family}>
                  <div className="resgrp__head">
                    <h3 className="resgrp__title">{g.title}</h3>
                    <span className="resgrp__count">{g.items.length} {g.items.length === 1 ? "item" : "items"}</span>
                  </div>
                  <div className="resgrid">
                    {g.items.map(it => {
                      const pending = it.source === "AI_INGESTION";
                      return (
                        <ResEditCard
                          key={it.id}
                          it={it}
                          family={g.family}
                          typeLabel={typeName(it.resource_type_id)}
                          pending={pending}
                          busy={busy}
                          onEdit={() => startEdit(it)}
                          onDelete={() => remove(it.id)}
                        />
                      );
                    })}
                  </div>
                </section>
              ))}
            </div>
          );
        })()}
      </div>
    </div>
  );
}

if (typeof window !== "undefined") window.ResourcesSection = ResourcesSection;


// ─── Fill AI gaps section ─────────────────────────────────────────────────────
// Three kinds of gap, all surfaced in the post-ingest popup (app.jsx):
//   1. Missing details — complete fields AI left blank (writes user_resources).
//   2. "You might also have" — inferred likely resources (YAML rules engine).
//   3. Open-ended — a free-text "anything else we missed?" box (saved to profile).
// Props: api = { load, fill, saveOpen }; resourcesApi = manual-add API (load/add/
// remove/edit); onChanged = () => reload the dashboard/report after a change.
function GapsSection({ api, resourcesApi, onChanged }) {
  const { useState, useEffect } = React;
  const [gaps, setGaps] = useState(null);
  const [adding, setAdding] = useState(false);   // true = show the manual add form in place
  const [active, setActive] = useState(null);   // resource_id whose form is open
  const [values, setValues] = useState({});
  const [openNotes, setOpenNotes] = useState("");
  const [openSaved, setOpenSaved] = useState(false);
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    let on = true;
    api.load().then(d => { if (on) { setGaps(d); setOpenNotes(d.open_notes || ""); } })
      .catch(e => on && setError(e.message));
    return () => { on = false; };
  }, []);

  if (error && !gaps) return <div className="wizard"><div className="wizard__card"><p className="wizard__error">{error}</p></div></div>;
  if (!gaps) return <div className="wizard"><div className="wizard__card"><p className="wizard__hint">Loading…</p></div></div>;

  // "Add" on a gap opens the manual add form IN PLACE — the popup stays open, and
  // "← Back to gaps" returns to the list. onChanged keeps the dashboard live.
  if (adding) {
    return (
      <div className="hub">
        <button className="hub__back" onClick={() => setAdding(false)}>← Back to gaps</button>
        <ResourcesSection api={resourcesApi} onChanged={onChanged} />
      </div>
    );
  }

  const inferred = gaps.inferred || [];
  const nothing = gaps.missing_details.length === 0 && gaps.coverage.length === 0 && inferred.length === 0;

  async function fill(resourceId) {
    setBusy(true); setError(null);
    try { setGaps(await api.fill({ resource_id: resourceId, fields: values })); setActive(null); setValues({}); }
    catch (e) { setError(e.message); } finally { setBusy(false); }
  }

  async function saveOpen() {
    if (!api.saveOpen) return;
    setBusy(true); setError(null); setOpenSaved(false);
    try { setGaps(await api.saveOpen({ notes: openNotes })); setOpenSaved(true); }
    catch (e) { setError(e.message); } finally { setBusy(false); }
  }

  return (
    <div className="wizard">
      <div className="wizard__card">
        <h1 className="wizard__title">Fill AI gaps</h1>
        <p className="wizard__hint">Things ingestion couldn't complete, what you might also have, and anything else we missed.</p>
        {nothing && <p className="wizard__saved">No gaps — you're all caught up ✓</p>}

        {gaps.missing_details.length > 0 && (
          <div className="estate__list">
            <h3 className="estate__list-title">Missing details</h3>
            {gaps.missing_details.map(m => (
              <div className="gap" key={m.resource_id}>
                <div className="gap__head">
                  <div>
                    <div className="estate__item-name">{m.name}</div>
                    <div className="estate__item-type">Missing: {m.fields.map(f => f.label).join(", ")}</div>
                  </div>
                  {active !== m.resource_id && (
                    <button className="btn-secondary" onClick={() => { setActive(m.resource_id); setValues({}); }}>Complete</button>
                  )}
                </div>
                {active === m.resource_id && (
                  <div className="wizard__questions">
                    {m.fields.map(f => (
                      <div className="wizard__field" key={f.id}>
                        <label className="wizard__label">{f.label}</label>
                        <input className="wizard__input" type={f.type === "number" ? "number" : "text"}
                          value={values[f.id] || ""} onChange={e => setValues(s => ({ ...s, [f.id]: e.target.value }))} />
                      </div>
                    ))}
                    <div className="wizard__nav">
                      <button className="btn-primary" onClick={() => fill(m.resource_id)} disabled={busy}>{busy ? "Saving…" : "Save"}</button>
                    </div>
                  </div>
                )}
              </div>
            ))}
          </div>
        )}

        {inferred.length > 0 && (
          <div className="estate__list">
            <h3 className="estate__list-title">You might also have</h3>
            {inferred.map(i => (
              <div className="gap__head" key={i.id || i.family}>
                <div>
                  <div className="estate__item-name">{i.title}</div>
                  {i.reason && <div className="estate__item-type">{i.reason}</div>}
                </div>
                <button className="btn-secondary" onClick={() => setAdding(true)}>Add</button>
              </div>
            ))}
          </div>
        )}

        {gaps.coverage.length > 0 && (
          <div className="estate__list">
            <h3 className="estate__list-title">Anything we missed?</h3>
            {gaps.coverage.map(c => (
              <div className="gap__head" key={c.family}>
                <div className="estate__item-name">No {c.title.toLowerCase()} found</div>
                <button className="btn-secondary" onClick={() => setAdding(true)}>Add</button>
              </div>
            ))}
          </div>
        )}

        <div className="estate__list">
          <h3 className="estate__list-title">Anything else we missed?</h3>
          <div className="wizard__field">
            <textarea
              className="wizard__input"
              rows={3}
              placeholder="Tell us about anything else — accounts, assets, people…"
              value={openNotes}
              onChange={e => { setOpenNotes(e.target.value); setOpenSaved(false); }} />
          </div>
          <div className="wizard__nav">
            <button className="btn-primary" onClick={saveOpen} disabled={busy}>{busy ? "Saving…" : "Save"}</button>
            {openSaved && <span className="wizard__saved">Saved ✓</span>}
          </div>
        </div>

        {error && <p className="wizard__error">{error}</p>}
      </div>
    </div>
  );
}

if (typeof window !== "undefined") window.GapsSection = GapsSection;
