/* Freelancers — Content Operations desk: find, fix and book freelancers.
   Assigns window.LedgerFreelancers. Spec: specs/content-ops-freelancers-desk-v1.md.

   Four tabs: Roster (by state or list), Review (cleanup + tag proposals), Lists (the
   tag list, edited by Content Ops) and Sync (health). A row opens the person's
   profile, which is also where they are edited, starred, tagged and booked.

   PHASE 2 (0139, 2026-10-06): Preferred flag (seeded from the Airtable "*"), tags,
   bookings with the quote, typical rates = median quote per job type.

   ONE-WAY FROM AIRTABLE (operator, 2026-09-25; 0129). The nightly sync brings
   Airtable in; nothing goes back. A field edited here becomes a LOCAL OVERRIDE that
   wins over Airtable permanently. The profile lists each override beside what
   Airtable currently says, with Revert to Airtable. */
(function () {
  const DS = window.LawCrimeDesignSystem_e18d68;
  const T = window.titleize;
  const icons = () => setTimeout(() => window.lucide && window.lucide.createIcons(), 30);
  const API = () => window.LedgerAPI.fl;

  const AIRTABLE_RECORD = (id) => `https://airtable.com/appxT6texBVhbJBoh/tblAG5dMTG2VSUOp8/${id}`;

  // What each validation rule means, in the desk's words (freelancer_issues, 0125).
  const ISSUE = {
    name_missing: "First or last name is missing",
    name_has_extra: "Name field holds more than a name (company, notes, a marker)",
    contact_missing: "No email or phone",
    email_invalid: "Email isn't a valid address",
    phone_invalid: "Phone isn't a 10-digit US number (or +international)",
    location_missing: "Home state or city is missing",
    city_has_extra: "City field holds more than a city",
    state_unrecognized: "Airtable's State isn't a state Ledger recognizes",
    also_covers_invalid: "An “also covers” state isn't valid",
    role_missing: "No role",
    role_invalid: "Has a role that can't be used (e.g. Unknown)",
    duplicate_email: "Another freelancer has this email",
    duplicate_phone: "Another freelancer has this phone",
    rates_unstructured: "Rate notes haven't been turned into rates yet",
    unavailable_reason_missing: "Marked unavailable without a reason",
  };
  const KIND = {
    name_fix: "Name", record_split: "Two people", location_fix: "Location",
    state_recovery: "State", rate_parse: "Rates", merge: "Duplicate", equipment_tags: "Tags",
  };
  const FIELD_LABEL = {
    first_name: "First name", last_name: "Last name", preferred_name: "Goes by", company_name: "Company",
    email: "Email", phone: "Phone", home_state: "Home state", city: "City", also_covers_states: "Also covers",
    will_travel: "Will travel", role_keys: "Roles", equipment_details: "Equipment", website: "Website",
    gdrive_folder_url: "GDrive folder", address: "Address", workers_comp: "Workers comp",
    contact_source: "Contact source", rate_notes_raw: "Rate notes", prev_work_notes: "Previous work",
    general_notes: "General notes",
  };

  const fullName = (p) => `${p.first_name || ""} ${p.last_name || ""}`.trim() || "(no name)";
  const money = (n) => "$" + Number(n).toLocaleString(undefined, { maximumFractionDigits: 2 });
  const rateLine = (rates, types) => (rates || []).map((r) => {
    const t = (types || []).find((x) => x.key === r.rate_type);
    return money(r.amount) + (t ? t.suffix : "") + (r.note ? ` (${r.note})` : "");
  }).join(" · ");
  const dayRate = (p) => {
    const d = (p.rates || []).find((r) => r.rate_type === "day") || (p.rates || [])[0];
    return d ? money(d.amount) + (d.rate_type === "day" ? "/day" : "") : null;
  };
  const hasLiveU = (p) => (p.tag_keys || []).some((k) => k === "liveu" || k === "tvu") ||
    /live\s*-?\s*u\b|liveu|tvu/i.test(`${p.equipment_details || ""} ${p.general_notes || ""}`);
  const parseFlags = (reason) => { const m = /\[flags: ([^\]]+)\]/.exec(reason || ""); return m ? m[1].split(", ") : []; };
  const cleanReason = (reason) => (reason || "").replace(/\s*\[flags: [^\]]+\]/, "");

  /**
   * Has the field this suggestion was built from changed since?
   *
   * The cleanup tool read the name / city / rate notes as they were on import and
   * quotes that source as the start of `summary`. If someone has since edited the
   * field by hand, the suggestion describes text that no longer exists, and approving
   * it would overwrite the person's fix (found in the harness: a hand-fixed name got
   * its "*" marker put back). Same rule as Title Review. Stale → reject only.
   */
  function isStale(pr, person) {
    if (!person) return false;
    const m = /^"((?:[^"]|"(?! →))*)" →/.exec(pr.summary || "");
    if (!m) return false;
    const src = m[1];
    const cur = pr.kind === "location_fix" || pr.kind === "state_recovery" ? (person.city || "")
      : pr.kind === "rate_parse" ? (person.rate_notes_raw || "")
      : pr.kind === "equipment_tags" ? (person.equipment_details || "")
      : pr.kind === "name_fix" || pr.kind === "record_split" ? `${person.first_name || ""} ${person.last_name || ""}`.trim()
      : null;
    if (cur === null) return false;
    // The tool shortens long rate notes to 77 chars + "…" in the summary.
    return src.endsWith("…") ? !cur.startsWith(src.slice(0, -1)) : src !== cur;
  }

  function Conf({ value }) {
    const v = Number(value);
    const tone = v >= 0.9 ? "high" : v >= 0.7 ? "mid" : "low";
    return <span className={"tr-conf tr-conf--" + tone}>{v.toFixed(2)}</span>;
  }

  // =====================================================================
  // Roster
  // =====================================================================
  // Rate shown for the job type picked in the roster: the stated rate, plus the
  // median of what we were actually quoted when booking (0139).
  function RateCell({ p, job, lookups }) {
    const stated = (p.rates || []).find((r) => r.rate_type === job);
    const typ = (p.typical || []).find((t) => t.rate_type === job);
    const suffix = ((lookups.rateTypes || []).find((t) => t.key === job) || {}).suffix || "";
    if (!stated && !typ) {
      return p.rate_notes_raw && !(p.rates || []).length
        ? <span className="fl-faint" title={p.rate_notes_raw}>notes only</span>
        : <span className="fl-faint">—</span>;
    }
    return (
      <div>
        {stated ? <span>{money(stated.amount)}{suffix}</span> : <span className="fl-faint">no stated rate</span>}
        {typ && <div className="fl-sub" title={`Median of ${typ.bookings} booking${typ.bookings === 1 ? "" : "s"}`}>
          booked at {money(typ.median_amount)} ({typ.bookings})</div>}
      </div>
    );
  }

  function PersonRow({ p, st, job, lookups, onOpen }) {
    const roleLabel = (k) => (lookups.roles.find((r) => r.key === k) || {}).label || T(k);
    const tagLabel = (k) => ((lookups.tags || []).find((t) => t.key === k) || {}).label || T(k);
    return (
      <tr className="opsq-row--click" onClick={() => onOpen(p.freelancer_id)}>
        <td>
          <div className="fl-name">
            {p.preferred && <span className="fl-star" title="Preferred">★ </span>}
            {fullName(p)}{p.preferred_name && <span className="fl-pref"> “{p.preferred_name}”</span>}
          </div>
          {p.company_name && <div className="fl-sub">{p.company_name}</div>}
          {(p.tag_keys || []).length > 0 && (
            <div className="fl-tagrow">{p.tag_keys.map((k) => <span key={k} className="fl-tag">{tagLabel(k)}</span>)}</div>
          )}
        </td>
        <td><div className="opsq-kinds">{(p.role_keys || []).map((k) => <span key={k} className="chip">{roleLabel(k)}</span>)}</div></td>
        <td>
          {p.city || <span className="fl-faint">—</span>}{p.home_state ? `, ${p.home_state}` : p.country !== "US" ? `, ${p.country}` : ""}
          {st && p.home_state !== st && <div className="fl-sub">{(p.also_covers_states || []).includes(st) ? "covers " + st : "will travel"}</div>}
        </td>
        <td>{(p.also_covers_states || []).join(" ") || <span className="fl-faint">—</span>}{p.will_travel && <span className="chip fl-travel">travels</span>}</td>
        <td><RateCell p={p} job={job} lookups={lookups} /></td>
        <td className="fl-num">
          {p.trials || ""}
          {p.last_booked && <div className="fl-sub" title="Last booking logged in Ledger">booked {p.last_booked}</div>}
        </td>
        <td className="fl-badges">
          {p.unavailable && <span className="fl-badge fl-badge--off" title={p.unavailable_reason || ""}>Unavailable</span>}
          {p.needs_cleanup && <span className="fl-badge fl-badge--warn" title={(p.cleanup_issues || []).map((i) => ISSUE[i] || i).join("\n")}>Cleanup {p.cleanup_issues.length}</span>}
          {!p.airtable_record_id && <span className="fl-badge" title="Created in Ledger; not in Airtable">Ledger only</span>}
          {(p.ledger_dirty_fields || []).length > 0 && p.airtable_record_id && <span className="fl-badge" title={"Edited in Ledger, so Airtable no longer updates: " + p.ledger_dirty_fields.join(", ")}>Overrides {p.ledger_dirty_fields.length}</span>}
        </td>
      </tr>
    );
  }

  function RosterTable({ children, job, lookups }) {
    const jobLabel = ((lookups.rateTypes || []).find((t) => t.key === job) || {}).label || "Rate";
    return (
      <DS.Card><DS.Card.Body style={{ padding: 0 }}>
        <div className="opsq-table-wrap">
          <table className="opsq-table fl-table">
            <thead><tr>
              <th>Name</th><th>Roles</th><th>Based</th><th>Also covers</th><th>{jobLabel}</th><th>Trials</th><th></th>
            </tr></thead>
            <tbody>{children}</tbody>
          </table>
        </div>
      </DS.Card.Body></DS.Card>
    );
  }

  // One state: people based there, then those who also cover it, then (folded) the
  // ones who only said they travel. A person can appear under several states.
  function StateGroup({ code, name, based, also, travel, open, onToggle, job, lookups, onOpen }) {
    const [showTravel, setShowTravel] = React.useState(false);
    const sub = (label, n) => <tr className="fl-subhead"><td colSpan={7}>{label} <b>{n}</b></td></tr>;
    return (
      <div className="fl-group">
        <button type="button" className={"fl-group-head" + (open ? " fl-group-head--open" : "")} onClick={onToggle}>
          <span className="fl-chev">▸</span>
          <span className="fl-group-name">{name}{code ? ` (${code})` : ""}</span>
          <span className="fl-group-n">{based.length} based{also.length ? ` · ${also.length} also cover` : ""}</span>
        </button>
        {open && (
          <RosterTable job={job} lookups={lookups}>
            {based.length > 0 && sub(code ? `Based in ${code}` : "Based", based.length)}
            {based.map((p) => <PersonRow key={"b" + p.freelancer_id} p={p} st={code} job={job} lookups={lookups} onOpen={onOpen} />)}
            {also.length > 0 && sub(`Also cover ${code}`, also.length)}
            {also.map((p) => <PersonRow key={"a" + p.freelancer_id} p={p} st={code} job={job} lookups={lookups} onOpen={onOpen} />)}
            {travel.length > 0 && (
              <tr className="fl-subhead"><td colSpan={7}>
                <button type="button" className="link-btn" onClick={() => setShowTravel(!showTravel)}>
                  {showTravel ? "Hide" : "Show"} {travel.length} who will travel</button>
              </td></tr>
            )}
            {showTravel && travel.map((p) => <PersonRow key={"t" + p.freelancer_id} p={p} st={code} job={job} lookups={lookups} onOpen={onOpen} />)}
          </RosterTable>
        )}
      </div>
    );
  }

  const byName = (a, b) => (a.last_name || "").localeCompare(b.last_name || "") || (a.first_name || "").localeCompare(b.first_name || "");

  function Roster({ people, lookups, onOpen, onAdd }) {
    const [q, setQ] = React.useState("");
    const [st, setSt] = React.useState("");
    const [travel, setTravel] = React.useState(true);
    const [role, setRole] = React.useState("");
    const [avail, setAvail] = React.useState(true);
    const [cleanup, setCleanup] = React.useState(false);
    const [liveu, setLiveu] = React.useState(false);
    const [pref, setPref] = React.useState(false);
    const [tagSel, setTagSel] = React.useState([]);
    const [job, setJob] = React.useState("day");
    const [view, setView] = React.useState(() => { try { return localStorage.getItem("fl.view") || "state"; } catch (e) { return "state"; } });
    const [openSet, setOpenSet] = React.useState({});
    React.useEffect(() => { icons(); });
    const pickView = (v) => { setView(v); try { localStorage.setItem("fl.view", v); } catch (e) { /* private window */ } };

    const needle = q.trim().toLowerCase();
    const match = (p) => {
      if (avail && p.unavailable) return false;
      if (cleanup && !p.needs_cleanup) return false;
      if (pref && !p.preferred) return false;
      if (role && !(p.role_keys || []).includes(role)) return false;
      if (liveu && !hasLiveU(p)) return false;
      if (tagSel.length && !tagSel.every((k) => (p.tag_keys || []).includes(k))) return false;
      if (needle) {
        const hay = [p.first_name, p.last_name, p.preferred_name, p.company_name, p.email, p.phone, p.city]
          .filter(Boolean).join(" ").toLowerCase();
        if (!hay.includes(needle)) return false;
      }
      return true;
    };
    const inState = (p) => {
      if (!st) return true;
      const home = p.home_state === st, also = (p.also_covers_states || []).includes(st);
      return home || also || (travel && p.will_travel);
    };
    const matched = people.filter(match);
    // Flat list: with a state picked, people based there first, then those who also
    // cover it, then the ones who only said they travel.
    const rank = (p) => !st ? 0 : p.home_state === st ? 0 : (p.also_covers_states || []).includes(st) ? 1 : 2;
    const rows = matched.filter(inState).sort((a, b) => rank(a) - rank(b) || byName(a, b));

    const groups = React.useMemo(() => {
      const list = lookups.states.filter((s) => !st || s.code === st).map((s) => ({
        code: s.code, name: s.name,
        based: matched.filter((p) => p.home_state === s.code).sort(byName),
        also: matched.filter((p) => p.home_state !== s.code && (p.also_covers_states || []).includes(s.code)).sort(byName),
        travel: matched.filter((p) => p.will_travel && p.home_state !== s.code && !(p.also_covers_states || []).includes(s.code)).sort(byName),
      })).filter((g) => g.based.length || g.also.length || (st && g.travel.length));
      if (!st) {
        const none = matched.filter((p) => !p.home_state).sort(byName);
        if (none.length) list.push({ code: "", name: "No home state (or outside the US)", based: none, also: [], travel: [] });
      }
      return list;
    }, [matched, st, lookups.states]);
    // Narrowed views open every group; the full roster starts folded to a state index.
    const narrowed = !!(needle || st || pref || tagSel.length || role || liveu || cleanup) || groups.length <= 3;
    const isOpen = (g) => (g.code in openSet ? openSet[g.code] : narrowed);
    const allOpen = groups.length > 0 && groups.every(isOpen);
    const setAll = (v) => setOpenSet(Object.fromEntries(groups.map((g) => [g.code, v])));

    const { Select } = DS;
    const stateOpts = [{ value: "", label: "Any state" }].concat(lookups.states.map((s) => ({ value: s.code, label: `${s.name} (${s.code})` })));
    const roleOpts = [{ value: "", label: "Any role" }].concat(lookups.roles.filter((r) => r.is_active).map((r) => ({ value: r.key, label: r.label })));
    const jobOpts = (lookups.rateTypes || []).map((t) => ({ value: t.key, label: `Rate: ${t.label}` }));
    const activeTags = (lookups.tags || []).filter((t) => t.is_active);
    const toggleTag = (k) => setTagSel(tagSel.includes(k) ? tagSel.filter((x) => x !== k) : tagSel.concat(k));
    const Toggle = ({ on, set, children, title }) => (
      <button type="button" title={title} className={"tagopt" + (on ? " tagopt--on" : "")} onClick={() => set(!on)}>{children}</button>
    );
    const shown = view === "state" ? new Set(groups.flatMap((g) => g.based.concat(g.also).map((p) => p.freelancer_id))).size : rows.length;

    return (
      <div>
        <div className="fl-filters">
          <input className="lc-input fl-search" placeholder="Search name, company, email, phone, city…"
            value={q} onChange={(e) => setQ(e.target.value)} />
          <Select value={st} onChange={(e) => setSt(e.target.value)} options={stateOpts} />
          <Select value={role} onChange={(e) => setRole(e.target.value)} options={roleOpts} />
          <Select value={job} onChange={(e) => setJob(e.target.value)} options={jobOpts} />
        </div>
        <div className="fl-toggles">
          <Toggle on={avail} set={setAvail} title="Hide people marked unavailable">Available only</Toggle>
          <Toggle on={pref} set={setPref} title="Only people marked Preferred">★ Preferred</Toggle>
          {st && view === "list" && <Toggle on={travel} set={setTravel} title="Include people who say they will travel">Include will-travel</Toggle>}
          <Toggle on={liveu} set={setLiveu} title="Tagged LiveU / TVU, or the equipment or notes mention one">Has LiveU</Toggle>
          <Toggle on={cleanup} set={setCleanup} title="Records that fail a validation rule">Needs cleanup</Toggle>
          <span className="fl-count">{shown} of {people.length}</span>
          <span style={{ flex: 1 }} />
          <span className="fl-viewsw">
            <button type="button" className={"tagopt" + (view === "state" ? " tagopt--on" : "")} onClick={() => pickView("state")}>By state</button>
            <button type="button" className={"tagopt" + (view === "list" ? " tagopt--on" : "")} onClick={() => pickView("list")}>List</button>
          </span>
          <DS.Button size="sm" variant="secondary" icon="user-plus" onClick={onAdd}
            title="Add a freelancer">Add freelancer</DS.Button>
        </div>
        {activeTags.length > 0 && (
          <div className="fl-toggles fl-tagfilter">
            <span className="fl-sub">Tags:</span>
            {activeTags.map((t) => (
              <button key={t.key} type="button" title={t.hint || ""}
                className={"tagopt" + (tagSel.includes(t.key) ? " tagopt--on" : "")} onClick={() => toggleTag(t.key)}>{t.label}</button>
            ))}
            {tagSel.length > 0 && <button type="button" className="link-btn" onClick={() => setTagSel([])}>Clear</button>}
          </div>
        )}

        {(view === "state" ? groups.length === 0 : rows.length === 0) ? (
          <div className="ledger-empty"><span><i data-lucide="users" /></span><b>No one matches</b>
            <span>Loosen a filter{st ? ", or include people who travel" : ""}.</span></div>
        ) : view === "state" ? (
          <div>
            <div className="fl-group-tools">
              <button type="button" className="link-btn" onClick={() => setAll(!allOpen)}>{allOpen ? "Collapse all" : "Expand all"}</button>
              <span className="fl-sub">{groups.length} state{groups.length === 1 ? "" : "s"}</span>
            </div>
            {groups.map((g) => (
              <StateGroup key={g.code || "none"} {...g} open={isOpen(g)} job={job} lookups={lookups} onOpen={onOpen}
                onToggle={() => setOpenSet({ ...openSet, [g.code]: !isOpen(g) })} />
            ))}
          </div>
        ) : (
          <RosterTable job={job} lookups={lookups}>
            {rows.map((p) => <PersonRow key={p.freelancer_id} p={p} st={st} job={job} lookups={lookups} onOpen={onOpen} />)}
          </RosterTable>
        )}
      </div>
    );
  }

  // =====================================================================
  // Edit form (create or edit)
  // =====================================================================
  function EditForm({ person, lookups, onSaved, onCancel }) {
    const blank = { first_name: "", last_name: "", preferred_name: "", company_name: "", email: "", phone: "",
      home_state: "", country: "US", city: "", also_covers_states: [], will_travel: false, role_keys: [],
      equipment_details: "", website: "", gdrive_folder_url: "", address: "", workers_comp: "",
      contact_source: "", prev_work_notes: "", general_notes: "" };
    const init = person ? Object.fromEntries(Object.keys(blank).map((k) => [k, person[k] ?? blank[k]])) : blank;
    const [f, setF] = React.useState(init);
    const [issues, setIssues] = React.useState([]);
    const [dup, setDup] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    React.useEffect(() => { icons(); }, [issues, dup]);
    const set = (k) => (e) => setF({ ...f, [k]: e.target.type === "checkbox" ? e.target.checked : e.target.value });
    const toggleIn = (k, v) => setF({ ...f, [k]: f[k].includes(v) ? f[k].filter((x) => x !== v) : f[k].concat(v) });

    // Send only what changed on an edit; everything on a create.
    const patch = () => {
      const out = {};
      Object.keys(blank).forEach((k) => {
        const a = JSON.stringify(f[k] ?? null), b = JSON.stringify(init[k] ?? null);
        if (!person || a !== b) out[k] = f[k] === "" ? null : f[k];
      });
      return out;
    };

    async function submit(confirmDup = false) {
      setBusy(true); setErr(null);
      try {
        const r = await API().save(person ? person.freelancer_id : null, patch(), confirmDup);
        if (r.ok) { onSaved(r.freelancer_id); return; }
        setIssues(r.issues || []);
        setDup(r.warnings && r.warnings.includes("duplicate_name") ? r.matches : null);
      } catch (e) { setErr(e.message || String(e)); }
      setBusy(false);
    }

    const bad = (k) => issues.some((i) => ({
      first_name: ["name_missing", "name_has_extra"], last_name: ["name_missing", "name_has_extra"],
      email: ["email_invalid", "contact_missing", "duplicate_email"], phone: ["phone_invalid", "contact_missing", "duplicate_phone"],
      city: ["location_missing", "city_has_extra"], home_state: ["location_missing"], role_keys: ["role_missing", "role_invalid"],
    }[k] || []).includes(i));
    // A plain function, not a component: a component defined inside render is a new
    // type every render, so React would remount the input and drop focus per keystroke.
    const field = (k, { wide, area, hint } = {}) => (
      <label key={k} className={"fl-field" + (wide ? " fl-field--wide" : "") + (bad(k) ? " fl-field--bad" : "")}>
        <span className="field-label">{FIELD_LABEL[k]}{hint && <span className="fnote"> {hint}</span>}</span>
        {area ? <textarea className="lc-input ledger-textarea" value={f[k] || ""} onChange={set(k)} />
              : <input className="lc-input" value={f[k] || ""} onChange={set(k)} />}
      </label>
    );

    return (
      <DS.Card><DS.Card.Body>
        {issues.length > 0 && (
          <div className="fl-issues">
            <b>Can't save yet. Fix these:</b>
            <ul>{issues.map((i) => <li key={i}>{ISSUE[i] || i}</li>)}</ul>
            {person && <div className="fnote">A saved record must pass every rule, including problems it was imported with.</div>}
          </div>
        )}
        {dup && (
          <div className="fl-dup">
            <i data-lucide="users" /> Someone with this name is already on the roster: {dup.map((d) => d.name).join(", ")}.
            <DS.Button size="sm" variant="secondary" onClick={() => submit(true)} disabled={busy}>It's a different person, save</DS.Button>
          </div>
        )}
        {err && <div className="login__err">{err}</div>}

        <div className="fl-form">
          {field("first_name")}{field("last_name")}{field("preferred_name")}{field("company_name")}
          {field("email")}{field("phone", { hint: "10 digits" })}
          {field("city", { hint: "city only" })}
          <label className={"fl-field" + (bad("home_state") ? " fl-field--bad" : "")}>
            <span className="field-label">Home state</span>
            <select className="lc-input" value={f.home_state || ""} onChange={set("home_state")}>
              <option value="">—</option>
              {lookups.states.map((s) => <option key={s.code} value={s.code}>{s.name}</option>)}
            </select>
          </label>
          <div className="fl-field fl-field--wide">
            <span className="field-label">Also covers</span>
            <div className="fl-states">
              {lookups.states.map((s) => (
                <button type="button" key={s.code} title={s.name}
                  className={"fl-st" + (f.also_covers_states.includes(s.code) ? " fl-st--on" : "")}
                  disabled={s.code === f.home_state}
                  onClick={() => toggleIn("also_covers_states", s.code)}>{s.code}</button>
              ))}
            </div>
            <label className="fl-check"><input type="checkbox" checked={!!f.will_travel} onChange={set("will_travel")} /> Will travel</label>
          </div>
          <div className={"fl-field fl-field--wide" + (bad("role_keys") ? " fl-field--bad" : "")}>
            <span className="field-label">Roles</span>
            <div className="fl-roles">
              {lookups.roles.filter((r) => r.is_active || f.role_keys.includes(r.key)).map((r) => (
                <button type="button" key={r.key}
                  className={"tagopt" + (f.role_keys.includes(r.key) ? " tagopt--on" : "") + (!r.is_active ? " fl-role--dead" : "")}
                  onClick={() => toggleIn("role_keys", r.key)}>{r.label}</button>
              ))}
            </div>
          </div>
          {field("equipment_details", { wide: true, area: true })}
          {field("website")}{field("gdrive_folder_url")}
          <label className="fl-field">
            <span className="field-label">Workers comp</span>
            <select className="lc-input" value={f.workers_comp || ""} onChange={set("workers_comp")}>
              <option value="">Unknown</option><option value="yes">Yes</option><option value="no">No</option>
            </select>
          </label>
          {field("contact_source")}
          {field("address", { wide: true })}
          {field("prev_work_notes", { wide: true, area: true })}{field("general_notes", { wide: true, area: true })}
        </div>

        <div className="tr-actions">
          <button type="button" className="link-btn" onClick={onCancel} disabled={busy}>Cancel</button>
          <DS.Button variant="primary" icon="check" onClick={() => submit(false)} disabled={busy}>
            {busy ? "Saving…" : person ? "Save" : "Add freelancer"}
          </DS.Button>
        </div>
      </DS.Card.Body></DS.Card>
    );
  }

  // =====================================================================
  // Rates editor
  // =====================================================================
  function RatesEditor({ person, lookups, onDone, onCancel }) {
    const [rows, setRows] = React.useState((person.rates || []).map((r) => ({ ...r })));
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const upd = (i, k, v) => setRows(rows.map((r, j) => j === i ? { ...r, [k]: v } : r));
    async function save() {
      const clean = rows.filter((r) => r.amount !== "" && r.amount != null);
      if (clean.some((r) => !(Number(r.amount) > 0))) { setErr("Every amount must be a number above 0."); return; }
      setBusy(true); setErr(null);
      try {
        await API().setRates(person.freelancer_id, clean.map((r) => ({ rate_type: r.rate_type, amount: Number(r.amount), note: r.note || null })));
        onDone();
      } catch (e) { setErr(e.message || String(e)); setBusy(false); }
    }
    return (
      <div className="fl-rates-edit">
        {person.rate_notes_raw && <div className="fl-sub">Airtable rate notes: “{person.rate_notes_raw}”</div>}
        {rows.map((r, i) => (
          <div key={i} className="fl-rate-row">
            <select className="lc-input" value={r.rate_type} onChange={(e) => upd(i, "rate_type", e.target.value)}>
              {lookups.rateTypes.map((t) => <option key={t.key} value={t.key}>{t.label}</option>)}
            </select>
            <input className="lc-input fl-amt" inputMode="decimal" value={r.amount ?? ""} placeholder="$"
              onChange={(e) => upd(i, "amount", e.target.value.replace(/[^0-9.]/g, ""))} />
            <input className="lc-input" value={r.note || ""} placeholder="note (optional)" onChange={(e) => upd(i, "note", e.target.value)} />
            <button type="button" className="link-btn" onClick={() => setRows(rows.filter((_, j) => j !== i))}><i data-lucide="x" /></button>
          </div>
        ))}
        <button type="button" className="link-btn" onClick={() => setRows(rows.concat({ rate_type: "day", amount: "", note: "" }))}>
          <i data-lucide="plus" /> Add a rate</button>
        {err && <div className="login__err">{err}</div>}
        <div className="tr-actions">
          <button type="button" className="link-btn" onClick={onCancel} disabled={busy}>Cancel</button>
          <DS.Button size="sm" variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save rates"}</DS.Button>
        </div>
      </div>
    );
  }

  // =====================================================================
  // Tags editor (profile) — Ledger-only, so saving never makes an override
  // =====================================================================
  function TagEditor({ person, lookups, onDone, onCancel }) {
    const [sel, setSel] = React.useState(person.tag_keys || []);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const tags = (lookups.tags || []).filter((t) => t.is_active || sel.includes(t.key));
    const toggle = (k) => setSel(sel.includes(k) ? sel.filter((x) => x !== k) : sel.concat(k));
    async function save() {
      setBusy(true); setErr(null);
      try {
        const r = await API().setTags(person.freelancer_id, sel);
        if (r.ok) onDone(); else { setErr("Unknown tag: " + (r.unknown_tags || []).join(", ")); setBusy(false); }
      } catch (e) { setErr(e.message || String(e)); setBusy(false); }
    }
    return (
      <div>
        {[["equipment", "Equipment"], ["shoot_type", "Shoot type"]].map(([kind, label]) => (
          <div key={kind} className="fl-tagedit">
            <span className="fl-sub">{label}</span>
            <div className="fl-roles">
              {tags.filter((t) => t.kind === kind).map((t) => (
                <button type="button" key={t.key} title={t.hint || ""}
                  className={"tagopt" + (sel.includes(t.key) ? " tagopt--on" : "") + (!t.is_active ? " fl-role--dead" : "")}
                  onClick={() => toggle(t.key)}>{t.label}</button>
              ))}
            </div>
          </div>
        ))}
        {err && <div className="login__err">{err}</div>}
        <div className="tr-actions">
          <button type="button" className="link-btn" onClick={onCancel} disabled={busy}>Cancel</button>
          <DS.Button size="sm" variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? "Saving…" : "Save tags"}</DS.Button>
        </div>
      </div>
    );
  }

  // =====================================================================
  // Bookings: log the quote every time someone is booked (0139)
  // =====================================================================
  const BOOKING_ISSUE = {
    date_missing: "Pick the date of the job", job_type_invalid: "Pick a job type",
    amount_invalid: "The quote must be a number above 0", job_missing: "Pick a matter or describe the job",
    matter_not_found: "That matter no longer exists",
  };
  const today = () => new Date().toLocaleDateString("en-CA");

  function BookingForm({ person, lookups, onDone, onCancel }) {
    const [f, setF] = React.useState({ work_date: today(), rate_type: "trial_day", amount: "", description: "", note: "" });
    const [matter, setMatter] = React.useState(null);
    const [mq, setMq] = React.useState("");
    const [hits, setHits] = React.useState(null);
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    React.useEffect(() => {
      const t = mq.trim();
      if (matter || t.length < 2) { setHits(null); return; }
      let live = true;
      const h = setTimeout(() => API().findMatters(t).then((r) => live && setHits(r)).catch((e) => live && setErr(e.message || String(e))), 250);
      return () => { live = false; clearTimeout(h); };
    }, [mq, matter]);
    const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
    const stated = (person.rates || []).find((r) => r.rate_type === f.rate_type);
    async function save() {
      setBusy(true); setErr(null);
      try {
        const r = await API().logBooking(person.freelancer_id, {
          work_date: f.work_date || null, rate_type: f.rate_type, amount: f.amount === "" ? null : Number(f.amount),
          matter_id: matter ? matter.matter_id : null, description: f.description || null, note: f.note || null,
        });
        if (r.ok) { onDone(); return; }
        setErr((r.issues || []).map((i) => BOOKING_ISSUE[i] || i).join(". ") + ".");
      } catch (e) { setErr(e.message || String(e)); }
      setBusy(false);
    }
    return (
      <div className="fl-bookform">
        <div className="fl-form">
          <div className="fl-field fl-field--wide">
            <span className="field-label">Matter <span className="fnote">or describe the job below</span></span>
            {matter ? (
              <div className="fl-picked"><b>{matter.display_alias || matter.working_title}</b>
                <span className="fl-sub"> {matter.display_id}</span>
                <button type="button" className="link-btn" onClick={() => { setMatter(null); setMq(""); }}>Change</button></div>
            ) : (
              <div>
                <input className="lc-input" value={mq} onChange={(e) => setMq(e.target.value)} placeholder="Find a matter: name, title or LNC-…" />
                {hits && hits.length === 0 && <div className="fl-faint">No matters match.</div>}
                {hits && hits.map((m) => (
                  <button key={m.matter_id} type="button" className="fl-pick" onClick={() => setMatter(m)}>
                    <b>{m.display_alias || m.working_title}</b>
                    <span className="fl-sub"> {[m.display_id, m.crime_location_state].filter(Boolean).join(" · ")}</span>
                  </button>
                ))}
              </div>
            )}
          </div>
          {!matter && (
            <label className="fl-field fl-field--wide">
              <span className="field-label">Job <span className="fnote">when there is no matter</span></span>
              <input className="lc-input" value={f.description} onChange={set("description")} placeholder="e.g. Doc interview, Tampa" />
            </label>
          )}
          <label className="fl-field">
            <span className="field-label">Date</span>
            <input className="lc-input" type="date" value={f.work_date} onChange={set("work_date")} />
          </label>
          <label className="fl-field">
            <span className="field-label">Job type</span>
            <select className="lc-input" value={f.rate_type} onChange={set("rate_type")}>
              {lookups.rateTypes.map((t) => <option key={t.key} value={t.key}>{t.label}</option>)}
            </select>
          </label>
          <label className="fl-field">
            <span className="field-label">Quote ($){stated && <span className="fnote"> stated {money(stated.amount)}</span>}</span>
            <input className="lc-input fl-amt" inputMode="decimal" value={f.amount} placeholder="$"
              onChange={(e) => setF({ ...f, amount: e.target.value.replace(/[^0-9.]/g, "") })} />
          </label>
          <label className="fl-field">
            <span className="field-label">Note</span>
            <input className="lc-input" value={f.note} onChange={set("note")} placeholder="optional: plus travel, 3 days…" />
          </label>
        </div>
        {err && <div className="login__err">{err}</div>}
        <div className="tr-actions">
          <button type="button" className="link-btn" onClick={onCancel} disabled={busy}>Cancel</button>
          <DS.Button size="sm" variant="primary" icon="check" onClick={save} disabled={busy}>{busy ? "Saving…" : "Log booking"}</DS.Button>
        </div>
      </div>
    );
  }

  function Bookings({ person, detail, lookups, onChanged, onOpenMatter }) {
    const [adding, setAdding] = React.useState(false);
    const [cancelId, setCancelId] = React.useState(null);
    const [docsId, setDocsId] = React.useState(null);   // booking whose quote/invoice panel is open
    const [why, setWhy] = React.useState("");
    const [err, setErr] = React.useState(null);
    const typeLabel = (k) => ((lookups.rateTypes || []).find((t) => t.key === k) || {}).label || T(k);
    async function cancel(id) {
      setErr(null);
      try {
        const r = await API().cancelBooking(id, why);
        if (!r.ok) setErr(r.reason || "Couldn't cancel."); else { setCancelId(null); setWhy(""); onChanged(); }
      } catch (e) { setErr(e.message || String(e)); }
    }
    const list = detail ? detail.bookings : null;
    return (
      <DS.Card style={{ marginTop: 14 }}><DS.Card.Body>
        <div className="fl-sec-head">
          <div className="tr-label">Bookings {list ? `(${list.filter((b) => !b.cancelled_at).length})` : ""}</div>
          {!adding && <button type="button" className="link-btn" onClick={() => setAdding(true)}><span><i data-lucide="calendar-plus" /></span> Log booking</button>}
        </div>
        {adding && <BookingForm person={person} lookups={lookups} onCancel={() => setAdding(false)}
          onDone={() => { setAdding(false); onChanged(); }} />}
        {err && <div className="login__err">{err}</div>}
        {!list ? <div className="fl-faint">Loading…</div>
          : list.length === 0 ? <div className="fl-faint">No bookings logged yet. Log one each time they're booked, with the quote, and their typical rates build up here.</div>
          : (
            <ul className="fl-history">
              {list.map((b) => (
                <li key={b.id} className={b.cancelled_at ? "fl-cancelled" : ""}>
                  <span className="fl-bk-date">{b.work_date}</span>{" "}
                  {b.matter
                    ? <button type="button" className="link-btn" onClick={() => onOpenMatter && onOpenMatter(b.matter_id)}>{b.matter.display_alias || b.matter.working_title}</button>
                    : <span>{b.description}</span>}
                  <span className="fl-sub"> · {typeLabel(b.rate_type)} · <b>{money(b.amount)}</b>{b.note ? ` · ${b.note}` : ""} · {b.booked_by}</span>
                  {b.cancelled_at
                    ? <span className="fl-sub"> · cancelled{b.cancel_reason ? `: ${b.cancel_reason}` : ""}</span>
                    : cancelId === b.id ? (
                      <span className="fl-cancel-form">
                        <input className="lc-input" value={why} onChange={(e) => setWhy(e.target.value)} placeholder="Why? (optional)" />
                        <button type="button" className="link-btn" onClick={() => setCancelId(null)}>Keep</button>
                        <button type="button" className="link-btn" onClick={() => cancel(b.id)}>Cancel booking</button>
                      </span>
                    ) : <button type="button" className="link-btn fl-bk-x" onClick={() => setCancelId(b.id)}>Cancel</button>}
                  {window.LedgerAttachments && (
                    <button type="button" className="link-btn fl-bk-x" onClick={() => setDocsId(docsId === b.id ? null : b.id)}>
                      {docsId === b.id ? "Hide documents" : "Quote / invoice"}</button>
                  )}
                  {docsId === b.id && (
                    <div className="fl-bk-docs"><window.LedgerAttachments entityType="booking" entityId={b.id} /></div>
                  )}
                </li>
              ))}
            </ul>
          )}
      </DS.Card.Body></DS.Card>
    );
  }

  // =====================================================================
  // Profile
  // =====================================================================
  function Profile({ person, lookups, onBack, onChanged, onOpenMatter, onReview }) {
    const [detail, setDetail] = React.useState(null);
    const [mode, setMode] = React.useState(null);          // null | "edit" | "rates" | "unavail" | "tags"
    const [reason, setReason] = React.useState("");
    const [busy, setBusy] = React.useState(false);
    const [err, setErr] = React.useState(null);
    const load = React.useCallback(() => {
      API().profile(person.freelancer_id).then(setDetail).catch((e) => setErr(e.message || String(e)));
    }, [person.freelancer_id]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); });

    const roleLabel = (k) => (lookups.roles.find((r) => r.key === k) || {}).label || T(k);
    const refresh = () => { setMode(null); onChanged(); load(); };

    async function setAvail(on) {
      setBusy(true); setErr(null);
      try {
        const r = await API().setUnavailable(person.freelancer_id, on, reason);
        if (!r.ok) setErr((r.issues || []).map((i) => ISSUE[i] || i).join("; "));
        else { setReason(""); refresh(); }
      } catch (e) { setErr(e.message || String(e)); }
      setBusy(false);
    }

    async function togglePreferred() {
      setBusy(true); setErr(null);
      try { await API().setPreferred(person.freelancer_id, !person.preferred); refresh(); }
      catch (e) { setErr(e.message || String(e)); }
      setBusy(false);
    }

    async function revert(field) {
      setBusy(true); setErr(null);
      try {
        const r = await API().clearOverride(person.freelancer_id, field);
        if (!r.ok) setErr(r.reason || "Couldn't revert.");
        else refresh();
      } catch (e) { setErr(e.message || String(e)); }
      setBusy(false);
    }

    const p = person;
    const Row = ({ label, children }) => children ? (<div className="fl-kv"><span>{label}</span><div>{children}</div></div>) : null;
    const W = ({ label, value }) => <Row label={label}>{value}</Row>;

    if (mode === "edit") {
      return (
        <div>
          <button type="button" className="link-btn" onClick={() => setMode(null)}><i data-lucide="arrow-left" /> Back to {fullName(p)}</button>
          <h1 className="page-title" style={{ margin: "8px 0 14px" }}>Edit {fullName(p)}</h1>
          <EditForm person={p} lookups={lookups} onSaved={refresh} onCancel={() => setMode(null)} />
        </div>
      );
    }

    return (
      <div className="fl-profile">
        <button type="button" className="link-btn" onClick={onBack}><i data-lucide="arrow-left" /> Roster</button>
        <div className="page-head">
          <div>
            <div className="eyebrow">Freelancer</div>
            <h1 className="page-title">{p.preferred && <span className="fl-star" title="Preferred">★ </span>}{fullName(p)}{p.preferred_name && <span className="fl-pref"> “{p.preferred_name}”</span>}<window.LedgerComment section="Freelancer profile" /></h1>
            <div className="page-lede">
              {(p.role_keys || []).map(roleLabel).join(" · ") || "No role"}
              {p.city ? ` — ${p.city}${p.home_state ? ", " + p.home_state : ""}` : ""}
              {p.company_name ? ` · ${p.company_name}` : ""}
            </div>
            {p.preferred && (
              <div className="fl-sub">Preferred · {p.preferred_source === "ledger"
                ? `set by ${p.preferred_set_by || "someone"}${p.preferred_set_at ? " on " + String(p.preferred_set_at).slice(0, 10) : ""}`
                : "starred in Airtable"}</div>
            )}
          </div>
          <div className="fl-head-actions">
            <DS.Button variant="secondary" icon="star" onClick={togglePreferred} disabled={busy}
              title={p.preferred ? "Remove the Preferred flag" : "Mark as a preferred freelancer (shared with the team)"}>
              {p.preferred ? "Unmark preferred" : "Mark preferred"}</DS.Button>
            <DS.Button variant="secondary" icon="pencil" onClick={() => setMode("edit")}>Edit</DS.Button>
          </div>
        </div>
        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}

        {p.unavailable && (
          <div className="fl-unavail"><i data-lucide="ban" /> <b>Unavailable</b>{p.unavailable_reason ? `: ${p.unavailable_reason}` : ""}
            {p.unavailable_since && <span className="fl-sub"> · since {p.unavailable_since}{p.unavailable_set_by ? ` by ${p.unavailable_set_by}` : ""}</span>}
          </div>
        )}
        {p.needs_cleanup && (
          <div className="fl-issues fl-issues--soft">
            <b>Needs cleanup</b>
            <ul>{p.cleanup_issues.map((i) => <li key={i}>{ISSUE[i] || i}</li>)}</ul>
            {detail && detail.proposals.length > 0 && (
              <button type="button" className="link-btn" onClick={onReview}>
                <i data-lucide="sparkles" /> {detail.proposals.length} suggested fix{detail.proposals.length === 1 ? "" : "es"} waiting in Review</button>
            )}
          </div>
        )}

        <div className="fl-cols">
          <DS.Card><DS.Card.Body>
            <div className="tr-label">Contact</div>
            <W label="Email" value={p.email && <a href={`mailto:${p.email}`}>{p.email}</a>} />
            <W label="Phone" value={p.phone && <a href={`tel:${p.phone}`}>{p.phone}</a>} />
            <W label="Website" value={p.website && <a href={p.website.startsWith("http") ? p.website : `https://${p.website}`} target="_blank" rel="noopener">{p.website}</a>} />
            <W label="Company" value={p.company_name} />
            <W label="Address" value={p.address} />
            <div className="tr-label" style={{ marginTop: 14 }}>Location</div>
            <W label="Based" value={[p.city, p.home_state || (p.country !== "US" ? p.country : null)].filter(Boolean).join(", ") || "—"} />
            <W label="Also covers" value={(p.also_covers_states || []).join(" ")} />
            <W label="Travel" value={p.will_travel ? "Will travel" : null} />
            <div className="tr-label" style={{ marginTop: 14 }}>Links</div>
            <W label="Airtable" value={p.airtable_record_id
              ? <a href={AIRTABLE_RECORD(p.airtable_record_id)} target="_blank" rel="noopener">Open record{p.resume_count ? ` · ${p.resume_count} resume file${p.resume_count === 1 ? "" : "s"}` : ""}</a>
              : "Created in Ledger (not in Airtable)"} />
            <W label="GDrive" value={p.gdrive_folder_url && <a href={p.gdrive_folder_url} target="_blank" rel="noopener">Folder</a>} />
            <W label="Workers comp" value={p.workers_comp ? T(p.workers_comp) : null} />
            <W label="Source" value={p.contact_source} />
          </DS.Card.Body></DS.Card>

          <DS.Card><DS.Card.Body>
            <div className="fl-sec-head">
              <div className="tr-label">Rates</div>
              {mode !== "rates" && <button type="button" className="link-btn" onClick={() => setMode("rates")}><i data-lucide="pencil" /> Edit</button>}
            </div>
            {mode === "rates"
              ? <RatesEditor person={p} lookups={lookups} onDone={refresh} onCancel={() => setMode(null)} />
              : (p.rates || []).length
                ? <div className="fl-rates">{rateLine(p.rates, lookups.rateTypes)}</div>
                : <div className="fl-faint">{p.rate_notes_raw ? `Notes only: “${p.rate_notes_raw}”` : "No rates recorded"}</div>}
            {(p.typical || []).length > 0 && (
              <div className="fl-typical">
                <span className="fl-sub">Typical, from bookings:</span>{" "}
                {p.typical.map((t) => {
                  const lt = (lookups.rateTypes || []).find((x) => x.key === t.rate_type) || {};
                  return <span key={t.rate_type} className="fl-typ" title={`${t.bookings} booking${t.bookings === 1 ? "" : "s"}, last ${t.last_booked}`}>
                    {money(t.median_amount)}{lt.suffix || ""} <span className="fl-sub">({t.bookings})</span></span>;
                })}
              </div>
            )}

            <div className="fl-sec-head" style={{ marginTop: 14 }}>
              <div className="tr-label">Tags</div>
              {mode !== "tags" && <button type="button" className="link-btn" onClick={() => setMode("tags")}><span><i data-lucide="pencil" /></span> Edit</button>}
            </div>
            {mode === "tags"
              ? <TagEditor person={p} lookups={lookups} onDone={refresh} onCancel={() => setMode(null)} />
              : (p.tag_keys || []).length
                ? <div className="fl-tagrow">{p.tag_keys.map((k) => <span key={k} className="fl-tag">{((lookups.tags || []).find((t) => t.key === k) || {}).label || T(k)}</span>)}</div>
                : <div className="fl-faint">No tags yet</div>}

            <div className="tr-label" style={{ marginTop: 14 }}>Equipment notes</div>
            <div className="fl-pre">{p.equipment_details || <span className="fl-faint">—</span>}</div>

            <div className="tr-label" style={{ marginTop: 14 }}>Availability</div>
            {mode === "unavail" ? (
              <div className="fl-unavail-form">
                <input className="lc-input" value={reason} onChange={(e) => setReason(e.target.value)} placeholder="Why? (required)" />
                <button type="button" className="link-btn" onClick={() => setMode(null)} disabled={busy}>Cancel</button>
                <DS.Button size="sm" variant="secondary" icon="ban" onClick={() => setAvail(true)} disabled={busy || !reason.trim()}>Mark unavailable</DS.Button>
              </div>
            ) : p.unavailable ? (
              <DS.Button size="sm" variant="secondary" icon="check" disabled={busy} onClick={() => setAvail(false)}>Mark available again</DS.Button>
            ) : (
              <button type="button" className="link-btn" onClick={() => setMode("unavail")}><i data-lucide="ban" /> Mark unavailable</button>
            )}

            <div className="tr-label" style={{ marginTop: 14 }}>Notes</div>
            {p.prev_work_notes && <div className="fl-pre"><b>Previous work.</b> {p.prev_work_notes}</div>}
            {p.general_notes && <div className="fl-pre">{p.general_notes}</div>}
            {!p.prev_work_notes && !p.general_notes && <span className="fl-faint">—</span>}
          </DS.Card.Body></DS.Card>
        </div>

        {p.airtable_record_id && (p.ledger_dirty_fields || []).length > 0 && (
          <DS.Card style={{ marginTop: 14 }}><DS.Card.Body>
            <div className="tr-label">Edited in Ledger ({p.ledger_dirty_fields.length})</div>
            <div className="fl-sub" style={{ marginBottom: 6 }}>These fields keep Ledger's value. Airtable changes to them are ignored until you revert.</div>
            {p.ledger_dirty_fields.map((k) => (
              <div key={k} className="fl-ovr">
                <span className="fl-diff-k">{k}</span>
                <span className="fl-sub">Airtable says: {fmt((p.airtable_snapshot || {})[k])}</span>
                <button type="button" className="link-btn" disabled={busy} onClick={() => revert(k)}>
                  <i data-lucide="undo-2" /> Revert to Airtable</button>
              </div>
            ))}
          </DS.Card.Body></DS.Card>
        )}

        <Bookings person={p} detail={detail} lookups={lookups} onChanged={() => { onChanged(); load(); }} onOpenMatter={onOpenMatter} />

        {/* Documents: resume, W-9, insurance, contract. Restricted drive (attachments A2) */}
        {window.LedgerAttachments && (
          <DS.Card style={{ marginTop: 14 }}><DS.Card.Body>
            <window.LedgerAttachments entityType="freelancer" entityId={p.freelancer_id} />
          </DS.Card.Body></DS.Card>
        )}

        <DS.Card style={{ marginTop: 14 }}><DS.Card.Body>
          <div className="tr-label">Trials and coverage {detail ? `(${detail.history.length})` : ""}</div>
          {!detail ? <div className="fl-faint">Loading…</div>
            : detail.history.length === 0 ? <div className="fl-faint">No trials on record.</div>
            : (
              <ul className="fl-history">
                {detail.history.map((h, i) => (
                  <li key={i}>
                    <button type="button" className="link-btn" onClick={() => onOpenMatter && onOpenMatter(h.matter_id)}>
                      {h.matter ? (h.matter.display_alias || h.matter.working_title) : "Matter"}
                    </button>
                    <span className="fl-sub"> {h.matter && h.matter.display_id}{h.event_date ? ` · ${h.event_date}` : ""} · {h.kind === "trial_link" ? "Airtable trial link" : h.kind === "booking" ? "Booked in Ledger" : T(h.kind)}</span>
                  </li>
                ))}
              </ul>
            )}
          {detail && detail.conflicts.length > 0 && (
            <div style={{ marginTop: 14 }}>
              <div className="tr-label">Airtable changes ignored (Ledger override)</div>
              <ul className="fl-history">
                {detail.conflicts.map((c, i) => (
                  <li key={i}><b>{c.field}</b>: Airtable had “{JSON.stringify(c.airtable_value)}”, Ledger kept “{JSON.stringify(c.ledger_value)}” <span className="fl-sub">{String(c.detected_at).slice(0, 10)}</span></li>
                ))}
              </ul>
            </div>
          )}
        </DS.Card.Body></DS.Card>
      </div>
    );
  }

  // =====================================================================
  // Review queue
  // =====================================================================
  function Before({ person, patch }) {
    if (!person) return null;
    return (
      <div className="fl-diff">
        {Object.keys(patch).filter((k) => k !== "general_notes").map((k) => (
          <div key={k} className="fl-diff-row">
            <span className="fl-diff-k">{FIELD_LABEL[k] || k}</span>
            <span className="fl-diff-a">{fmt(person[k])}</span>
            <i data-lucide="arrow-right" />
            <span className="fl-diff-b">{fmt(patch[k])}</span>
          </div>
        ))}
        {"general_notes" in patch && (
          <div className="fl-diff-row fl-diff-row--note"><span className="fl-diff-k">Notes</span>
            <span className="fl-sub">the removed text is kept in General Notes</span></div>
        )}
      </div>
    );
  }
  function fmt(v) {
    if (v == null || v === "" || (Array.isArray(v) && !v.length)) return <span className="fl-faint">—</span>;
    if (Array.isArray(v)) return v.join(" ");
    if (typeof v === "boolean") return v ? "Yes" : "No";
    return String(v);
  }

  function ProposalCard({ pr, person, into, lookups, onDone, onError }) {
    const [busy, setBusy] = React.useState(false);
    const [editing, setEditing] = React.useState(false);
    const [patch, setPatch] = React.useState((pr.proposed && pr.proposed.patch) || null);
    const [rates, setRates] = React.useState((pr.proposed && pr.proposed.rates) || null);
    const [tagKeys, setTagKeys] = React.useState((pr.proposed && pr.proposed.tag_keys) || null);
    const flags = parseFlags(pr.reason);
    const stale = isStale(pr, person);

    async function decide(decision) {
      setBusy(true);
      try {
        const override = editing ? { ...pr.proposed, ...(patch ? { patch } : {}), ...(rates ? { rates } : {}), ...(tagKeys ? { tag_keys: tagKeys } : {}) } : null;
        const r = await API().decide(pr.id, decision, override);
        if (r.ok) onDone(pr.id);
        else if (r.unknown_tags) { onError(`Can't approve: ${r.unknown_tags.join(", ")} is not on the tag list any more. Edit it or reject.`); setBusy(false); }
        else { onError(`Can't approve: it would add “${(r.issues || []).map((i) => ISSUE[i] || i).join("; ")}”. Edit it or reject.`); setBusy(false); }
      } catch (e) { onError(e.message || String(e)); setBusy(false); }
    }

    return (
      <DS.Card style={{ marginBottom: 12 }}><DS.Card.Body>
        <div className="tr-head">
          <span className="tr-id">{KIND[pr.kind] || pr.kind} · {person ? fullName(person) : "…"}</span>
          <span className="tr-meta"><Conf value={pr.confidence} />
            {flags.map((f) => <span key={f} className="tr-flag"><i data-lucide="alert-triangle" /> {T(f)}</span>)}</span>
        </div>
        <div className="fl-summary">{pr.summary}</div>
        {stale && (
          <div className="tr-warn"><i data-lucide="alert-circle" /> This field has been edited since the suggestion was made, so
            it no longer applies. Reject it; approving would undo the edit.</div>
        )}
        <div className="fl-sub" style={{ marginBottom: 8 }}>{cleanReason(pr.reason)}</div>

        {patch && !editing && !stale && <Before person={person} patch={patch} />}
        {patch && editing && (
          <div className="fl-form">
            {Object.keys(patch).filter((k) => k !== "general_notes").map((k) => (
              <label key={k} className="fl-field">
                <span className="field-label">{FIELD_LABEL[k] || k}</span>
                <input className="lc-input" value={Array.isArray(patch[k]) ? patch[k].join(" ") : patch[k] ?? ""}
                  onChange={(e) => setPatch({ ...patch, [k]: Array.isArray(patch[k]) ? e.target.value.toUpperCase().split(/[\s,]+/).filter(Boolean) : typeof patch[k] === "boolean" ? e.target.value === "true" : e.target.value })} />
              </label>
            ))}
          </div>
        )}
        {rates && (
          <div className="fl-diff">
            <div className="fl-diff-row"><span className="fl-diff-k">Rate notes</span><span className="fl-diff-a">{person && person.rate_notes_raw}</span></div>
            {rates.map((r, i) => (
              <div key={i} className="fl-rate-row">
                {editing ? (
                  <React.Fragment>
                    <select className="lc-input" value={r.rate_type} onChange={(e) => setRates(rates.map((x, j) => j === i ? { ...x, rate_type: e.target.value } : x))}>
                      {lookups.rateTypes.map((t) => <option key={t.key} value={t.key}>{t.label}</option>)}
                    </select>
                    <input className="lc-input fl-amt" value={r.amount} onChange={(e) => setRates(rates.map((x, j) => j === i ? { ...x, amount: Number(e.target.value.replace(/[^0-9.]/g, "")) } : x))} />
                    <input className="lc-input" value={r.note || ""} placeholder="note" onChange={(e) => setRates(rates.map((x, j) => j === i ? { ...x, note: e.target.value } : x))} />
                  </React.Fragment>
                ) : <span>{rateLine([r], lookups.rateTypes)}</span>}
              </div>
            ))}
          </div>
        )}
        {tagKeys && pr.proposed.tag_keys && (
          <div className="fl-diff fl-tagprop">
            <span className="fl-diff-k">Add tags</span>
            {pr.proposed.tag_keys.map((k) => {
              const t = (lookups.tags || []).find((x) => x.key === k);
              const on = tagKeys.includes(k);
              const ev = (pr.proposed.evidence || {})[k];
              return editing
                ? <button key={k} type="button" className={"tagopt" + (on ? " tagopt--on" : "")}
                    onClick={() => setTagKeys(on ? tagKeys.filter((x) => x !== k) : tagKeys.concat(k))}>{t ? t.label : k}</button>
                : <span key={k} className="fl-tag" title={ev ? `“${ev}”` : ""}>{t ? t.label : k}{ev && <span className="fl-ev"> “{ev}”</span>}</span>;
            })}
          </div>
        )}
        {pr.proposed && pr.proposed.new_people && (
          <div className="fl-diff"><span className="fl-diff-k">New record{pr.proposed.new_people.length > 1 ? "s" : ""}</span>
            {pr.proposed.new_people.map((n, i) => <span key={i} className="chip">{`${n.first_name || ""} ${n.last_name || ""}`.trim()}</span>)}</div>
        )}
        {pr.proposed && pr.proposed.merge_into && (
          <div className="fl-diff"><span className="fl-diff-k">Keep</span><b>{into ? fullName(into) : "the other record"}</b>
            <span className="fl-sub"> — this record is retired (never deleted) and its trials move over.</span></div>
        )}

        <div className="tr-actions">
          {(patch || rates || tagKeys) && !stale && (
            <button type="button" className="link-btn" onClick={() => setEditing(!editing)} disabled={busy}>
              <i data-lucide={editing ? "eye" : "pencil"} /> {editing ? "Preview" : "Edit first"}</button>
          )}
          <button type="button" className="link-btn" onClick={() => decide("rejected")} disabled={busy}><i data-lucide="x" /> Reject</button>
          <DS.Button variant="primary" icon="check" onClick={() => decide("approved")} disabled={busy || stale || (tagKeys && !tagKeys.length)}>{busy ? "…" : "Approve"}</DS.Button>
        </div>
      </DS.Card.Body></DS.Card>
    );
  }

  function Review({ people, lookups, onChanged }) {
    const [kind, setKind] = React.useState(null);
    const [rows, setRows] = React.useState(null);
    const [counts, setCounts] = React.useState(null);
    const [err, setErr] = React.useState(null);
    const [msg, setMsg] = React.useState(null);
    const [armed, setArmed] = React.useState(false);
    const [bulkBusy, setBulkBusy] = React.useState(false);
    const byId = React.useMemo(() => Object.fromEntries(people.map((p) => [p.freelancer_id, p])), [people]);

    const load = React.useCallback(() => {
      setErr(null);
      Promise.all([API().proposals({ kind }), API().proposalCounts()])
        .then(([r, c]) => { setRows(r); setCounts(c); }).catch((e) => setErr(e.message || String(e)));
    }, [kind]);
    React.useEffect(() => { load(); }, [load]);
    React.useEffect(() => { icons(); });

    const done = (id) => { setRows((rs) => rs.filter((r) => r.id !== id)); API().proposalCounts().then(setCounts); onChanged(); };

    // Bulk: rates, locations and tags only, ≥ 0.9, unflagged. Names, splits and merges
    // are always one at a time (spec).
    const bulkable = (rows || []).filter((r) => ["rate_parse", "location_fix", "state_recovery", "equipment_tags"].includes(r.kind) &&
      Number(r.confidence) >= 0.9 && !parseFlags(r.reason).length && !isStale(r, byId[r.freelancer_id]));
    async function approveAll() {
      setBulkBusy(true); setErr(null);
      let ok = 0, held = 0;
      for (const r of bulkable) {
        try { const x = await API().decide(r.id, "approved"); if (x.ok) ok++; else held++; }
        catch (e) { setErr(`Stopped after ${ok}: ${e.message || e}`); break; }
      }
      setBulkBusy(false); setArmed(false);
      setMsg(`${ok} approved${held ? `, ${held} held back (approving would have added a problem)` : ""}.`);
      load(); onChanged();
    }

    const chips = [{ id: null, label: "All", n: counts && counts.pending },
      { id: "location_fix", label: "Location" }, { id: "rate_parse", label: "Rates" },
      { id: "name", label: "Names", n: counts && ((counts.byKind.name_fix || 0) + (counts.byKind.record_split || 0)) },
      { id: "merge", label: "Duplicates" }, { id: "equipment_tags", label: "Tags" }];

    return (
      <div>
        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
        {msg && <div className="ledger-ok" style={{ marginBottom: 12 }}>{msg}</div>}
        <div className="tr-bar">
          <div className="tr-bands">
            {chips.map((c) => (
              <button key={String(c.id)} type="button" className={"tagopt" + (kind === c.id ? " tagopt--on" : "")} onClick={() => setKind(c.id)}>
                {c.label}<b className="tr-n">{c.n != null ? c.n : counts ? (counts.byKind[c.id] || 0) : ""}</b></button>
            ))}
          </div>
          {counts && <div className="tr-done">{counts.approved || 0} approved · {counts.rejected || 0} rejected</div>}
        </div>

        {bulkable.length > 0 && (
          <div className="tr-bulk">
            <div><b>{bulkable.length}</b> rate, location or tag suggestion{bulkable.length === 1 ? "" : "s"} in view {bulkable.length === 1 ? "is" : "are"} 0.9+ with no flag.</div>
            {armed ? (
              <span className="tr-bulk-arm">
                <button type="button" className="link-btn" onClick={() => setArmed(false)} disabled={bulkBusy}>Cancel</button>
                <DS.Button variant="primary" icon="check-check" onClick={approveAll} disabled={bulkBusy}>{bulkBusy ? "Applying…" : `Yes, approve ${bulkable.length}`}</DS.Button>
              </span>
            ) : <DS.Button size="sm" variant="secondary" icon="check-check" onClick={() => setArmed(true)}>Approve all {bulkable.length}</DS.Button>}
          </div>
        )}

        {rows === null && <div className="fl-faint">Loading…</div>}
        {rows && rows.length === 0 && (
          <div className="ledger-empty"><i data-lucide="check-check" /><b>Nothing waiting here</b><span>Every suggestion in this filter has been reviewed.</span></div>
        )}
        {(rows || []).map((pr) => (
          <ProposalCard key={pr.id} pr={pr} person={byId[pr.freelancer_id]}
            into={pr.proposed && pr.proposed.merge_into ? byId[pr.proposed.merge_into] : null}
            lookups={lookups} onDone={done} onError={setErr} />
        ))}
      </div>
    );
  }

  // =====================================================================
  // Lists: the tag list, edited by Content Ops (Admin is admin-only)
  // =====================================================================
  function TagListRow({ t, onSaved, onError }) {
    const [f, setF] = React.useState({ label: t.label, hint: t.hint || "" });
    const [busy, setBusy] = React.useState(false);
    const dirty = f.label !== t.label || f.hint !== (t.hint || "");
    async function save(patch) {
      setBusy(true);
      try {
        const r = await API().saveTag({ key: t.key, label: f.label, kind: t.kind, hint: f.hint || null, is_active: t.is_active, sort_order: t.sort_order, ...patch });
        if (r.ok) onSaved(); else onError(r.reason || "Couldn't save.");
      } catch (e) { onError(e.message || String(e)); }
      setBusy(false);
    }
    return (
      <tr className={t.is_active ? "" : "fl-cancelled"}>
        <td><input className="lc-input" value={f.label} onChange={(e) => setF({ ...f, label: e.target.value })} /></td>
        <td><input className="lc-input" value={f.hint} placeholder="What counts (helps people and the AI)" onChange={(e) => setF({ ...f, hint: e.target.value })} /></td>
        <td className="fl-badges">
          {dirty && <button type="button" className="link-btn" disabled={busy} onClick={() => save({})}>Save</button>}
          <button type="button" className="link-btn" disabled={busy} onClick={() => save({ is_active: !t.is_active })}>
            {t.is_active ? "Retire" : "Restore"}</button>
        </td>
      </tr>
    );
  }

  function Lists({ lookups, onChanged }) {
    const [err, setErr] = React.useState(null);
    const [nf, setNf] = React.useState({ label: "", kind: "equipment", hint: "" });
    const [busy, setBusy] = React.useState(false);
    async function add() {
      setBusy(true); setErr(null);
      try {
        const r = await API().saveTag({ label: nf.label, kind: nf.kind, hint: nf.hint || null });
        if (r.ok) { setNf({ label: "", kind: nf.kind, hint: "" }); onChanged(); } else setErr(r.reason || "Couldn't add.");
      } catch (e) { setErr(e.message || String(e)); }
      setBusy(false);
    }
    return (
      <div>
        <p className="fl-sub" style={{ marginTop: 0 }}>Tags describe kit and the kind of work someone does. Retiring a tag hides it from pickers;
          people who already have it keep it. The AI tagger reads this list, including the "what counts" note.</p>
        {err && <div className="login__err" style={{ marginBottom: 12 }}>{err}</div>}
        {[["equipment", "Equipment"], ["shoot_type", "Shoot type"]].map(([kind, label]) => (
          <DS.Card key={kind} style={{ marginBottom: 14 }}><DS.Card.Body style={{ padding: 0 }}>
            <div className="tr-label" style={{ padding: "12px 14px 0" }}>{label}</div>
            <div className="opsq-table-wrap">
              <table className="opsq-table fl-table">
                <thead><tr><th>Tag</th><th>What counts</th><th></th></tr></thead>
                <tbody>
                  {(lookups.tags || []).filter((t) => t.kind === kind).map((t) => (
                    <TagListRow key={t.key + String(t.is_active) + t.label + (t.hint || "")} t={t} onSaved={onChanged} onError={setErr} />
                  ))}
                </tbody>
              </table>
            </div>
          </DS.Card.Body></DS.Card>
        ))}
        <DS.Card><DS.Card.Body>
          <div className="tr-label">Add a tag</div>
          <div className="fl-addtag">
            <input className="lc-input" value={nf.label} placeholder="Label, e.g. Teleprompter" onChange={(e) => setNf({ ...nf, label: e.target.value })} />
            <select className="lc-input" value={nf.kind} onChange={(e) => setNf({ ...nf, kind: e.target.value })}>
              <option value="equipment">Equipment</option><option value="shoot_type">Shoot type</option>
            </select>
            <input className="lc-input" value={nf.hint} placeholder="What counts (optional)" onChange={(e) => setNf({ ...nf, hint: e.target.value })} />
            <DS.Button size="sm" variant="primary" icon="plus" onClick={add} disabled={busy || !nf.label.trim()}>Add</DS.Button>
          </div>
        </DS.Card.Body></DS.Card>
      </div>
    );
  }

  // =====================================================================
  // Sync
  // =====================================================================
  function Sync({ status, onRefresh }) {
    React.useEffect(() => { icons(); });
    if (!status) return <div className="fl-faint">Loading…</div>;
    const Tile = ({ label, value, tone }) => (
      <div className={"opsq-kpi" + (tone ? " opsq-kpi--" + tone : "")}><div className="opsq-kpi-val">{value}</div><div className="opsq-kpi-lbl">{label}</div></div>
    );
    return (
      <div>
        <div className="opsq-kpis">
          <Tile label="People with Ledger overrides" value={status.overridden_people} tone={status.overridden_people ? "active" : null} />
          <Tile label="Airtable changes ignored (7d)" value={status.conflicts_7d} tone={status.conflicts_7d ? "warn" : null} />
          <Tile label="Added in Ledger only" value={status.ledger_only} />
          <Tile label="Nightly sync" value={status.last_status === "ok" ? "OK" : (status.last_status || "—")} tone={status.last_status === "ok" ? "done" : "warn"} />
        </div>
        <DS.Card><DS.Card.Body>
          <div className="fl-kv"><span>Direction</span><div>One way: Airtable → Ledger, nightly. Nothing is written to Airtable.</div></div>
          <div className="fl-kv"><span>Overrides</span><div>A field edited in Ledger keeps Ledger's value from then on. Airtable changes to it are ignored and counted above. Revert a field from the person's profile.</div></div>
          <div className="fl-kv"><span>Last run</span><div>{status.track_enabled ? "" : "Sync is OFF. "}{status.last_run_at ? new Date(status.last_run_at).toLocaleString() : "—"}</div></div>
          {status.last_message && <div className="fl-kv"><span>Result</span><div className="fl-sub">{status.last_message}</div></div>}
          <div className="tr-actions"><DS.Button size="sm" variant="ghost" icon="refresh-cw" onClick={onRefresh}>Refresh</DS.Button></div>
        </DS.Card.Body></DS.Card>
      </div>
    );
  }

  // =====================================================================
  // Desk
  // =====================================================================
  function Freelancers({ onOpenMatter }) {
    const [tab, setTab] = React.useState("roster");
    const [people, setPeople] = React.useState(null);
    const [lookups, setLookups] = React.useState(null);
    const [status, setStatus] = React.useState(null);
    const [openId, setOpenId] = React.useState(null);
    const [adding, setAdding] = React.useState(false);
    const [err, setErr] = React.useState(null);

    const loadPeople = React.useCallback(() => API().roster().then(setPeople).catch((e) => setErr(e.message || String(e))), []);
    const loadStatus = React.useCallback(() => API().syncStatus().then(setStatus).catch(() => {}), []);
    const loadLookups = React.useCallback(() => API().lookups().then(setLookups).catch((e) => setErr(e.message || String(e))), []);
    React.useEffect(() => { loadPeople(); loadStatus(); loadLookups(); }, [loadPeople, loadStatus, loadLookups]);
    React.useEffect(() => { icons(); });

    const changed = () => { loadPeople(); loadStatus(); };

    if (err) return <div className="login__err">{err}</div>;
    if (!people || !lookups) return <div className="fl-faint">Loading the roster…</div>;

    const open = openId && people.find((p) => p.freelancer_id === openId);
    if (open) {
      return <Profile person={open} lookups={lookups}
        onBack={() => setOpenId(null)} onChanged={changed} onOpenMatter={onOpenMatter}
        onReview={() => { setOpenId(null); setTab("review"); }} />;
    }
    if (adding) {
      return (
        <div>
          <button type="button" className="link-btn" onClick={() => setAdding(false)}><i data-lucide="arrow-left" /> Roster</button>
          <h1 className="page-title" style={{ margin: "8px 0 14px" }}>Add a freelancer</h1>
          <EditForm person={null} lookups={lookups} onCancel={() => setAdding(false)}
            onSaved={(id) => { setAdding(false); loadPeople().then(() => setOpenId(id)); loadStatus(); }} />
        </div>
      );
    }

    const tabs = [["roster", "Roster", "users"], ["review", "Review", "sparkles"], ["lists", "Lists", "tags"], ["sync", "Sync", "refresh-cw"]];
    return (
      <div className="fl">
        <div className="page-head">
          <div>
            <div className="eyebrow">Content Operations</div>
            <h1 className="page-title">Freelancers<window.LedgerComment section="Freelancers" /></h1>
            <p className="page-sub">Find, fix and book freelance crew. Synced nightly from Airtable. An edit here stays in Ledger and wins over Airtable for that field. Preferred, tags and bookings live only in Ledger.</p>
          </div>
        </div>
        <div className="fl-tabs">
          {tabs.map(([id, label, icon]) => (
            <button key={id} type="button" className={"fl-tab" + (tab === id ? " fl-tab--on" : "")} onClick={() => setTab(id)}>
              <i data-lucide={icon} /> {label}
              {id === "sync" && status && (status.failed > 0) && <span className="fl-badge fl-badge--warn">{status.failed}</span>}
            </button>
          ))}
        </div>
        {tab === "roster" && <Roster people={people} lookups={lookups} onOpen={setOpenId} onAdd={() => setAdding(true)} />}
        {tab === "review" && <Review people={people} lookups={lookups} onChanged={changed} />}
        {tab === "lists" && <Lists lookups={lookups} onChanged={loadLookups} />}
        {tab === "sync" && <Sync status={status} onRefresh={loadStatus} />}
      </div>
    );
  }

  window.LedgerFreelancers = Freelancers;
})();
