/* Marque Talent Desk — the product's own routing and its commit machinery.
   Every action that changes anything goes through this file, so the
   reversibility tiers are decided in one place: harmless controls live on their
   screens, consequential ones apply here and raise an undo toast, irreversible
   ones open a confirm dialog, and anything needing two or three fields opens a
   staged dialog.

   The chain that matters: recording a brand's payment moves the invoice to
   paid AND releases the creator's payout from `awaiting_funds` to `due`, in one
   commit, because that is what actually happens. Marking a post published
   writes its metrics into the fixture, so the promise wall moves. */
function App(props) {
  const options = props || {};
  const [path, setPath] = React.useState(options.startPath || "/");
  const [dark, setDark] = React.useState(options.theme === "dark");
  // Control density is one product-wide decision (AGENTS.md §9), so it rides on
  // the shell root rather than being passed per screen.
  const controls = options.controlSize === "medium" ? "medium" : "compact";
  /* The address the reset flow was started with, so the confirmation screen can
     name it back without the URL carrying it. */
  const [resetEmail, setResetEmail] = React.useState("");
  /* Which notification lines have been read. A set of ids rather than one flag,
     because the inbox marks them one at a time and the bar's badge counts what
     is left. */
  const [readIds, setReadIds] = React.useState([]);
  /* The app-scope search's query. It is state rather than a path segment because
     /search is one registered route; the palette cannot hand its own query over
     (lib/command-palette.js owns it and stays byte-identical between products),
     so the footer opens the screen and the screen's field takes over. */
  const [search, setSearch] = React.useState("");
  const [toasts, setToasts] = React.useState([]);
  const [confirm, setConfirm] = React.useState(null);
  const [form, setForm] = React.useState(null);
  const [data, setData] = React.useState({ creators: CREATORS, brands: BRANDS, managers: MANAGERS, briefs: BRIEFS, deals: DEALS, now: MARQUE_NOW });
  const [user, setUser] = React.useState(MARQUE_USER);
  const [agency, setAgency] = React.useState(MARQUE_AGENCY);
  const palette = useCommandPalette();
  const now = data.now;

  React.useEffect(() => {
    document.documentElement.classList.toggle("dark", dark);
    document.body.classList.toggle("dark", dark);
  }, [dark]);

  const dismiss = (id) => setToasts((list) => list.filter((t) => t.id !== id));
  const pushToast = (toast) => {
    const id = "t" + Date.now() + Math.random().toString(36).slice(2, 6);
    setToasts((list) => list.concat([Object.assign({ id: id }, toast)]));
    window.setTimeout(() => dismiss(id), 8000);
  };

  /* A committing action writes into the fixture and re-renders, so every figure
     that claims to derive from it actually moves. */
  const patchBrief = (id, fields) => setData((d) => Object.assign({}, d, { briefs: d.briefs.map((b) => (b.id === id ? Object.assign({}, b, fields) : b)) }));
  const restoreBrief = (snapshot) => setData((d) => Object.assign({}, d, { briefs: d.briefs.map((b) => (b.id === snapshot.id ? snapshot : b)) }));
  const patchDeal = (id, fields) => setData((d) => Object.assign({}, d, { deals: d.deals.map((x) => (x.id === id ? Object.assign({}, x, fields) : x)) }));
  const restoreDeal = (snapshot) => setData((d) => Object.assign({}, d, { deals: d.deals.map((x) => (x.id === snapshot.id ? snapshot : x)) }));
  const patchDeliverable = (dealId, itemId, fields) => setData((d) => Object.assign({}, d, {
    deals: d.deals.map((deal) => (deal.id === dealId
      ? Object.assign({}, deal, { deliverables: deal.deliverables.map((i) => (i.id === itemId ? Object.assign({}, i, fields) : i)) })
      : deal)),
  }));

  /* Creates. Each writes into the fixture and re-renders, so every count it
     feeds moves — and each raises an undo toast, because signing somebody or
     logging a brief is consequential and entirely reversible. Ids continue the
     product's own series rather than being invented per record. */
  const nextId = (values, prefix, width) => {
    const numbers = values.map((v) => Number(String(v).split("-").pop())).filter((n) => !isNaN(n));
    return prefix + String(Math.max.apply(null, numbers.concat([0])) + 1).padStart(width, "0");
  };
  const addBrief = (draft) => {
    const brandId = draft.newBrand ? draft.newBrand.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") : draft.brandId;
    const brief = Object.assign({}, draft, {
      id: nextId(data.briefs.map((b) => b.id), "BR-26-", 3),
      brandId: brandId,
      brand: draft.newBrand ? draft.newBrand.name : brandNameOf(brandId),
      receivedAt: now, status: "to_triage",
    });
    delete brief.newBrand;
    setData((d) => Object.assign({}, d, {
      brands: draft.newBrand
        ? d.brands.concat([{ id: brandId, name: draft.newBrand.name, sector: draft.newBrand.sector, hq: draft.newBrand.hq, note: "First brief on the desk. Nothing else on file yet." }])
        : d.brands,
      briefs: [brief].concat(d.briefs),
    }));
    setPath("/briefs/" + brief.id);
    pushToast({
      variant: "success",
      title: brief.brand + " logged",
      description: brief.id + " · " + formatMoney(brief.budgetCents) + " · they need an answer by " + formatAbsolute(brief.respondByAt).split(",")[0],
      onUndo: () => { setData((d) => Object.assign({}, d, { briefs: d.briefs.filter((b) => b.id !== brief.id), brands: draft.newBrand ? d.brands.filter((b) => b.id !== brandId) : d.brands })); setPath("/briefs"); },
    });
  };
  const addCreator = (draft) => {
    const id = draft.handle.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 12);
    /* Portraits come from the design system's avatar assets; a new signing takes
       the next one in the set rather than an empty frame. */
    const portraits = ["avatar-80-01", "avatar-80-02", "avatar-80-03", "avatar-80-04", "avatar-80-05", "avatar-80-06"];
    const creator = Object.assign({}, draft, {
      id: id, status: "available", signedAt: now, exclusivities: [],
      portrait: "../../assets/img/" + portraits[data.creators.length % portraits.length] + ".jpg",
    });
    setData((d) => Object.assign({}, d, { creators: d.creators.concat([creator]) }));
    setPath("/talent/" + id);
    pushToast({
      variant: "success",
      title: creator.name + " signed",
      description: formatFollowers(reachOf(creator)) + " of audience · " + formatMoney(rateFloorOf(creator)) + "–" + formatMoney(rateCeilOf(creator)) + " a piece · free to pitch today",
      onUndo: () => { setData((d) => Object.assign({}, d, { creators: d.creators.filter((c) => c.id !== id) })); setPath("/"); },
    });
  };

  const nextPayoutReference = () => {    const numbers = data.deals
      .map((d) => Number(String(d.payout.reference || "").split("-")[1]))
      .filter((n) => !isNaN(n));
    return "SEPA-" + String(Math.max.apply(null, numbers.concat([70])) + 1).padStart(4, "0");
  };

  /* ── Briefs ────────────────────────────────────────────────────────── */
  const onBriefTransition = (brief, transition) => {
    if (transition.id === "pitch_creators") { setPath("/briefs/" + brief.id); return; }
    if (transition.id === "chase_brand") {
      patchBrief(brief.id, { chasedAt: now });
      pushToast({ title: "Chased " + brief.brand, description: brief.contact.name + " · " + brief.id, onUndo: () => restoreBrief(brief) });
      return;
    }
    if (transition.id === "reopen_brief") {
      patchBrief(brief.id, { status: "to_triage", passReason: undefined, passNote: undefined, passedAt: undefined, lostNote: undefined, lostAt: undefined });
      pushToast({ title: "Brief reopened", description: brief.brand + " is back on your desk", onUndo: () => restoreBrief(brief) });
      return;
    }
    if (transition.id === "book_deal") {
      const pitched = (brief.pitched || []).map((id) => creatorById(data.creators, id)).filter(Boolean);
      const first = pitched[0];
      setForm({
        title: "Book " + brief.brand + "'s campaign",
        description: brief.id + " · " + formatMoney(brief.budgetCents) + " budget · " + briefUnits(brief) + " pieces, live " + formatAbsolute(brief.liveFromAt).split(",")[0] + " to " + formatAbsolute(brief.liveToAt).split(",")[0] + ". The deal starts with its paper undrafted and its invoice held until the contract comes back.",
        submitLabel: "Book the deal",
        initial: { creatorId: first ? first.id : "", fee: first ? moneyInputValue(quoteFor(first, brief).totalCents) : "", commission: String(AGENCY_POLICY.standardCommissionRate), reach: "" },
        fields: [
          { id: "creatorId", kind: "radio", label: "Which creator did they take?", options: pitched.map((c) => ({
            value: c.id,
            label: c.name + " — quoted " + (quoteFor(c, brief).complete ? formatMoney(quoteFor(c, brief).totalCents) : "nothing, they do not take all of this") + " · " + formatFollowers(reachOf(c)) + " audience",
          })) },
          { id: "fee", kind: "money", label: "Fee agreed", hint: "What the brand pays. The invoice and the payout both read this one number." },
          { id: "commission", kind: "select", label: "Our commission", placeholder: "Pick the rate", options: [
            { value: "0.2", label: "20% — the standard rate" },
            { value: "0.18", label: "18% — negotiated down" },
            { value: "0.15", label: "15% — negotiated down" },
          ] },
          { id: "reach", kind: "count", label: "Reach we are promising them", hint: "Performance is measured against this and nothing else, so promise what you would defend at the wrap." },
        ],
        validate: (v) => {
          const errors = {};
          const fee = parseMoney(v.fee || "");
          const reach = parseCount(v.reach || "");
          if (!v.creatorId) errors.creatorId = "Say who the brand took.";
          if (isNaN(fee) || fee <= 0) errors.fee = "Enter the fee they agreed.";
          if (!v.commission) errors.commission = "Pick the commission this deal was signed at.";
          if (isNaN(reach) || reach <= 0) errors.reach = "Enter the reach we promised.";
          return errors;
        },
        run: (v) => {
          const creator = creatorById(data.creators, v.creatorId);
          const number = nextId(data.deals.map((d) => d.id), "", 3);
          const dealId = "DEAL-26-" + number;
          const span = Math.max(1, brief.deliverables.length - 1);
          const deal = {
            id: dealId, briefId: brief.id, creatorId: creator.id, brandId: brief.brandId, brand: brief.brand,
            title: brief.title, agreedAt: now, feeCents: parseMoney(v.fee), commissionRate: parseFloat(v.commission),
            contract: { status: "drafted", draftedAt: now, sentAt: null, signedAt: null },
            usage: brief.usage, usageStartAt: brief.liveFromAt,
            /* The engagement half of the promise is the creator's own current
               rate, computed from their platform rows — not a number typed to
               look plausible. The reach half is the one the manager commits to. */
            promise: { reach: parseCount(v.reach), engagementRate: engagementOf(creator) },
            /* One deliverable per line the brand asked for, keeping the line's
               quantity, due evenly across their window. The titles are working
               names until somebody writes the real ones on the piece. */
            deliverables: brief.deliverables.map((line, i) => ({
              id: "DLV-" + number + "-" + (i + 1),
              kind: line.kind, quantity: line.quantity,
              title: brief.title + " — " + labelOf(DELIVERABLE_KINDS, line.kind),
              dueAt: Math.round(brief.liveFromAt + (i / span) * (brief.liveToAt - brief.liveFromAt)),
              status: "scheduled",
            })),
            invoice: { id: nextId(data.deals.map((d) => d.invoice.id), "INV-26-", 4), status: "draft", issuedAt: null, paidAt: null, holdReason: "Held until the contract is countersigned." },
            payout: { id: nextId(data.deals.map((d) => d.payout.id), "PAY-26-", 4), status: "awaiting_funds", reference: null, paidAt: null },
          };
          setData((d) => Object.assign({}, d, {
            deals: d.deals.concat([deal]),
            briefs: d.briefs.map((b) => (b.id === brief.id ? Object.assign({}, b, { status: "won", dealId: dealId }) : b)),
          }));
          setPath("/deals/" + dealId);
          pushToast({
            variant: "success",
            title: "Booked " + brief.brand + " × " + creator.name,
            description: formatMoney(parseMoney(v.fee)) + " · " + formatMoney(Math.round(parseMoney(v.fee) * parseFloat(v.commission))) + " commission · the contract needs drafting and sending",
            onUndo: () => {
              setData((d) => Object.assign({}, d, { deals: d.deals.filter((x) => x.id !== dealId), briefs: d.briefs.map((b) => (b.id === brief.id ? brief : b)) }));
              setPath("/briefs/" + brief.id);
            },
          });
        },
      });
      return;
    }
    if (transition.id === "pass_brief") {
      const groups = matchGroups(data.creators, brief, now, data.deals);
      const floor = rosterFloor(data.creators, brief);
      setForm({
        title: "Pass on " + brief.brand + "?",
        description: brief.id + " · " + formatMoney(brief.budgetCents) + " · " + labelOf(CATEGORIES, brief.category) + ". " + groups.fits.length + " creators fit every axis" + (floor === null ? " and nobody on the book takes all of it." : "; the cheapest complete quote is " + formatMoney(floor) + "."),
        submitLabel: "Pass on brief",
        initial: {},
        fields: [
          { id: "reason", kind: "radio", label: "Why are we saying no?", options: PASS_REASONS },
          { id: "note", kind: "text", label: "What we tell them", placeholder: "In your own words — this is what goes back to the brand and what the file remembers.", hint: "At least a sentence. A brand that gets a real reason comes back with a better brief." },
        ],
        validate: (v) => {
          const errors = {};
          if (!v.reason) errors.reason = "Pick the reason this brief is not for us.";
          if (!v.note || v.note.trim().length < 20) errors.note = "Write the reason out — at least a sentence.";
          return errors;
        },
        run: (v) => {
          patchBrief(brief.id, { status: "passed", passReason: v.reason, passNote: v.note.trim(), passedAt: now });
          pushToast({ title: "Passed on " + brief.brand, description: labelOf(PASS_REASONS, v.reason), onUndo: () => restoreBrief(brief) });
        },
      });
    }
  };

  const onSendPitch = (brief, creatorIds, quotedCents) => {
    const names = creatorIds.map((id) => creatorById(data.creators, id).name.split(" ")[0]);
    patchBrief(brief.id, { status: "pitching", pitched: creatorIds, pitchedAt: now });
    pushToast({
      variant: "success",
      title: "Pitched to " + brief.brand,
      description: names.join(", ") + " · " + formatMoney(quotedCents) + " against a budget of " + formatMoney(brief.budgetCents),
      onUndo: () => restoreBrief(brief),
    });
  };

  /* ── Contracts and deals ───────────────────────────────────────────── */
  const onContractTransition = (deal, transition) => {
    if (transition.id === "send_contract") {
      patchDeal(deal.id, { contract: Object.assign({}, deal.contract, { status: "out_for_signature", sentAt: now }) });
      pushToast({ title: "Contract sent for signature", description: deal.brand + " · " + deal.id, onUndo: () => restoreDeal(deal) });
      return;
    }
    if (transition.id === "chase_signature") {
      patchDeal(deal.id, { contract: Object.assign({}, deal.contract, { chasedAt: now }) });
      pushToast({ title: "Chased " + deal.brand, description: CONTRACT_FLAGS.daysOut(deal, now) + " days out on " + deal.id, onUndo: () => restoreDeal(deal) });
      return;
    }
    if (transition.id === "mark_signed") {
      patchDeal(deal.id, { contract: Object.assign({}, deal.contract, { status: "signed", signedAt: now }) });
      pushToast({
        variant: "success",
        title: "Contract signed",
        description: deal.brand + " × " + creatorById(data.creators, deal.creatorId).name + " · " + formatMoney(deal.feeCents) + " — the invoice can be issued",
        onUndo: () => restoreDeal(deal),
      });
    }
  };

  const onDealTransition = (deal, transition) => {
    if (transition.id !== "void_deal") return;
    const creator = creatorById(data.creators, deal.creatorId);
    const brief = briefForDeal(data.briefs, deal);
    setConfirm({
      title: "Void " + deal.id + " with " + deal.brand + "?",
      description: "The paper dies and " + creator.name + " loses " + formatMoney(creatorNetOf(deal)) + " of booked work. " + (brief ? brief.id + " goes back to pitched, so you can put somebody else in front of them." : "") + " Nothing has gone live, so nothing has to be taken down — but this cannot be undone.",
      confirmLabel: "Void the deal",
      destructive: true,
      run: () => {
        patchDeal(deal.id, {
          contract: Object.assign({}, deal.contract, { status: "void", voidedAt: now }),
          invoice: Object.assign({}, deal.invoice, { holdReason: "The deal was voided on " + formatAbsolute(now).split(",")[0] + "." }),
        });
        if (brief) patchBrief(brief.id, { status: "pitching", dealId: undefined });
        pushToast({ title: "Deal voided", description: deal.brand + " × " + creator.name + " · " + deal.id });
      },
    });
  };

  /* ── Deliverables ──────────────────────────────────────────────────── */
  const onDeliverableTransition = (row, transition) => {
    const { item, deal, creator } = row;
    if (transition.id === "chase_creator") {
      patchDeliverable(deal.id, item.id, { chasedAt: now });
      pushToast({ title: "Chased " + creator.name.split(" ")[0], description: item.title + " · " + formatCountdown(item.dueAt, now), onUndo: () => restoreDeal(deal) });
      return;
    }
    if (transition.id === "submit_to_brand") {
      patchDeliverable(deal.id, item.id, { status: "submitted", submittedAt: now });
      pushToast({ title: "Sent to " + deal.brand, description: item.title + " · with them for approval", onUndo: () => restoreDeal(deal) });
      return;
    }
    if (transition.id === "mark_approved") {
      patchDeliverable(deal.id, item.id, { status: "approved", approvedAt: now });
      pushToast({ variant: "success", title: "Approved by " + deal.brand, description: item.title + " · " + creator.name.split(" ")[0] + " can post it", onUndo: () => restoreDeal(deal) });
      return;
    }
    if (transition.id === "record_revisions") {
      const brief = briefForDeal(data.briefs, deal);
      setForm({
        title: "What has " + deal.brand + " asked for?",
        description: item.title + " · " + creator.name + " · submitted " + formatAbsolute(item.submittedAt).split(",")[0] + ".",
        submitLabel: "Record revisions",
        initial: { days: "3" },
        fields: [
          { id: "note", kind: "text", label: "In their words", placeholder: "Quote the note, so the creator reads the brand rather than your summary of it.", hint: "At least a sentence." },
          { id: "days", kind: "select", label: "New due date", placeholder: "How long do they get?", options: [
            { value: "2", label: "In 2 days — " + formatAbsolute(now + 2 * 864e5).split(",")[0] },
            { value: "3", label: "In 3 days — " + formatAbsolute(now + 3 * 864e5).split(",")[0] },
            { value: "7", label: "In a week — " + formatAbsolute(now + 7 * 864e5).split(",")[0] },
          ] },
        ],
        validate: (v) => {
          const errors = {};
          if (!v.note || v.note.trim().length < 20) errors.note = "Record what they actually said — at least a sentence.";
          if (!v.days) errors.days = "Give the creator a date to work to.";
          return errors;
        },
        run: (v) => {
          patchDeliverable(deal.id, item.id, {
            status: "revisions_requested", revisionNote: v.note.trim(), revisionAt: now,
            revisionBy: brief ? brief.contact.name : deal.brand,
            dueAt: now + parseInt(v.days, 10) * 864e5,
          });
          pushToast({ title: "Revisions recorded", description: item.title + " · back to " + creator.name.split(" ")[0] + ", due in " + v.days + " days", onUndo: () => restoreDeal(deal) });
        },
      });
      return;
    }
    if (transition.id === "mark_published") {
      setForm({
        title: "Mark " + item.title + " live",
        description: creator.name + " · " + deal.brand + " · these figures feed the campaign's promise, which stands at " + formatFollowers(deal.promise.reach) + " reach.",
        submitLabel: "Mark published",
        initial: {},
        fields: [
          { id: "url", kind: "count", label: "Where it lives", placeholder: "tiktok.com/@handle/1234", hint: "The public link, so the brand can be sent it." },
          { id: "reach", kind: "count", label: "Reach", placeholder: "412000", hint: "Accounts reached, from the platform's own numbers." },
          { id: "engagements", kind: "count", label: "Engagements", placeholder: "31900" },
          { id: "saves", kind: "count", label: "Saves", placeholder: "4100" },
        ],
        validate: (v) => {
          const errors = {};
          if (!v.url || String(v.url).trim().length < 6) errors.url = "Paste the link to the post.";
          const reach = parseCount(v.reach || "");
          const engagements = parseCount(v.engagements || "");
          const saves = parseCount(v.saves || "");
          if (isNaN(reach) || reach <= 0) errors.reach = "Enter the reach the post actually got.";
          if (isNaN(engagements) || engagements <= 0) errors.engagements = "Enter the engagements.";
          else if (!isNaN(reach) && engagements > reach) errors.engagements = "Engagements cannot be higher than reach.";
          if (isNaN(saves) || saves < 0) errors.saves = "Enter the saves, even if it is nought.";
          else if (!isNaN(engagements) && saves > engagements) errors.saves = "Saves are part of engagements, so they cannot exceed them.";
          return errors;
        },
        run: (v) => {
          patchDeliverable(deal.id, item.id, {
            status: "published", publishedAt: now, url: String(v.url).trim(),
            metrics: { reach: parseCount(v.reach), engagements: parseCount(v.engagements), saves: parseCount(v.saves) },
          });
          pushToast({ variant: "success", title: "Live", description: item.title + " · " + formatFollowers(parseCount(v.reach)) + " reached — the promise wall has moved" });
        },
      });
    }
  };

  /* ── Money ─────────────────────────────────────────────────────────── */
  const onInvoiceTransition = (row, transition) => {
    const deal = row.deal;
    if (transition.id === "issue_invoice") {
      patchDeal(deal.id, { invoice: Object.assign({}, deal.invoice, { status: "issued", issuedAt: now, holdReason: undefined }) });
      pushToast({
        title: "Invoice issued",
        description: formatMoney(invoiceAmountOf(deal)) + " to " + deal.brand + " · net " + AGENCY_POLICY.paymentTermsDays + ", due " + formatAbsolute(now + AGENCY_POLICY.paymentTermsDays * 864e5).split(",")[0],
        onUndo: () => restoreDeal(deal),
      });
      return;
    }
    if (transition.id === "chase_payment") {
      patchDeal(deal.id, { invoice: Object.assign({}, deal.invoice, { chasedAt: now }) });
      pushToast({ title: "Chased " + deal.brand, description: deal.invoice.id + " · " + formatCountdown(INVOICE_FLAGS.dueAt(deal.invoice), now), onUndo: () => restoreDeal(deal) });
      return;
    }
    if (transition.id === "record_payment") {
      /* One commit: the brand's money lands and the creator's payout stops
         being blocked. Two records describing the same money, moved together. */
      const creator = creatorById(data.creators, deal.creatorId);
      patchDeal(deal.id, {
        invoice: Object.assign({}, deal.invoice, { status: "paid", paidAt: now }),
        payout: deal.payout.status === "awaiting_funds" ? Object.assign({}, deal.payout, { status: "due" }) : deal.payout,
      });
      pushToast({
        variant: "success",
        title: deal.brand + " paid " + formatMoney(invoiceAmountOf(deal)),
        description: formatMoney(commissionOf(deal)) + " commission banked · " + creator.name.split(" ")[0] + "'s " + formatMoney(payoutAmountOf(deal)) + " is now ready to release",
        onUndo: () => restoreDeal(deal),
      });
    }
  };

  const onPayoutTransition = (row, transition) => {
    if (transition.id !== "release_payout") return;
    const deal = row.deal;
    setConfirm({
      title: "Release " + formatMoney(row.amount) + " to " + row.creator.name + "?",
      description: "The money leaves the agency account on the run of " + formatAbsolute(nextPayoutRun(now)).split(",")[0] + " and cannot be recalled. It is " + deal.brand + "'s " + formatMoney(invoiceAmountOf(deal)) + " less our " + Math.round(deal.commissionRate * 100) + "% commission of " + formatMoney(commissionOf(deal)) + ".",
      confirmLabel: "Release payout",
      run: () => {
        const reference = nextPayoutReference();
        patchDeal(deal.id, { payout: Object.assign({}, deal.payout, { status: "paid", paidAt: now, reference: reference }) });
        pushToast({ variant: "success", title: "Payout released", description: formatMoney(row.amount) + " to " + row.creator.name + " · " + reference });
      },
    });
  };

  /* ── Account, staged ───────────────────────────────────────────────── */  const onSaveProfile = (draft) => {
    const snapshot = user;
    setUser(Object.assign({}, user, { name: draft.name.trim(), role: draft.role.trim(), email: draft.email.trim(), avatar: draft.avatar }));
    pushToast({ variant: "success", title: "Profile saved", description: draft.name.trim() + " · " + draft.role.trim(), onUndo: () => setUser(snapshot) });
  };
  const onSaveAgency = (draft) => {
    const snapshot = agency;
    setAgency(Object.assign({}, agency, { name: draft.name.trim(), book: draft.book.trim(), billingEmail: draft.billingEmail.trim() }));
    pushToast({ variant: "success", title: "Agency saved", description: draft.name.trim() + " · " + draft.book.trim(), onUndo: () => setAgency(snapshot) });
  };

  const goSearch = (query) => { setSearch(query); setPath("/search"); };
  const markRead = (id) => setReadIds((list) => (list.indexOf(id) > -1 ? list : list.concat([id])));
  const markAllRead = () => setReadIds(NOTIFICATIONS.map((n) => n.id));
  const unreadCount = NOTIFICATIONS.filter((n) => readIds.indexOf(n.id) === -1).length;

  /* ── Routing ───────────────────────────────────────────────────────── */
  /* Auth has no navigation owner, so it is returned before the shell. */
  /* Harness chrome, not product chrome: the floating page menu is how a reader
     reaches every registered screen, including the auth ones the shell cannot
     render. It sits outside the shell so it is on those screens too. */
  const preview = (
    <TemplateMenu product="Marque" path={path} onNavigate={setPath} groups={MARQUE_PAGES(data)}
      dark={dark} onToggleTheme={() => setDark(!dark)} />
  );

  if (MARQUE_AUTH_PATHS.indexOf(path) > -1) {
    const auth = () => {
      if (path === "/signin") return <SignInScreen agency={agency} user={user} onSignIn={() => setPath("/")} onNavigate={setPath} />;
      if (path === "/signout") return <SignedOutScreen agency={agency} user={user} onSignIn={() => setPath("/signin")} />;
      if (path === "/forgot-password") return <ForgotPasswordScreen agency={agency} onNavigate={setPath} onRequest={(email) => { setResetEmail(email); setPath("/forgot-password/sent"); }} />;
      if (path === "/forgot-password/sent") return <ResetSentScreen agency={agency} email={resetEmail} onNavigate={setPath} onResend={() => undefined} />;
      if (path === "/reset-password") return <SetPasswordScreen agency={agency} email={resetEmail} onNavigate={setPath} onSet={() => setPath("/reset-password/done")} />;
      if (path === "/reset-password/done") return <ResetDoneScreen agency={agency} onNavigate={setPath} />;
      if (path === "/reset-password/expired") return <ResetExpiredScreen agency={agency} onNavigate={setPath} />;
      return <AccountLockedScreen agency={agency} minutes={15} onNavigate={setPath} />;
    };
    return (
      <>
        {auth()}
        {preview}
      </>
    );
  }

  const talentMatch = path.match(/^\/talent\/([^/]+)$/);
  const statementMatch = path.match(/^\/talent\/([^/]+)\/statement$/);
  const creator = talentMatch ? creatorById(data.creators, talentMatch[1]) : null;
  const statementCreator = statementMatch ? creatorById(data.creators, statementMatch[1]) : null;
  const briefMatch = path.match(/^\/briefs\/([^/]+)$/);
  const dealMatch = path.match(/^\/deals\/([^/]+)$/);
  const brandMatch = path.match(/^\/brands\/([^/]+)$/);
  const itemMatch = path.match(/^\/deliverables\/([^/]+)$/);
  const wrapMatch = path.match(/^\/performance\/([^/]+)$/);
  const brief = briefMatch ? briefById(data.briefs, briefMatch[1]) : null;
  const deal = dealMatch ? dealById(data.deals, dealMatch[1]) : null;
  const brand = brandMatch ? brandById(data.brands, brandMatch[1]) : null;
  const item = itemMatch ? deliverableRowById(data.deals, data.creators, itemMatch[1]) : null;
  const wrapDeal = wrapMatch ? dealById(data.deals, wrapMatch[1]) : null;

  const screen = () => {
    if (path === "/") return <RosterScreen data={data} now={now} onNavigate={setPath} pushToast={pushToast} />;
    if (path === "/talent/new") return <NewCreatorScreen data={data} now={now} onNavigate={setPath} onCreate={addCreator} />;
    if (path === "/briefs/new") return <NewBriefScreen data={data} now={now} onNavigate={setPath} onCreate={addBrief} />;
    if (creator) return <TalentScreen creator={creator} data={data} now={now} onNavigate={setPath} pushToast={pushToast} />;
    if (statementCreator) return <StatementScreen creator={statementCreator} data={data} now={now} onNavigate={setPath} pushToast={pushToast} />;
    if (brand) return <BrandScreen brand={brand} data={data} now={now} onNavigate={setPath} pushToast={pushToast} />;
    if (item) return <DeliverableScreen row={item} data={data} now={now} onNavigate={setPath} onDeliverableTransition={onDeliverableTransition} pushToast={pushToast} />;
    if (wrapDeal) return <WrapScreen deal={wrapDeal} data={data} now={now} onNavigate={setPath} pushToast={pushToast} />;
    if (path === "/inbox") return <InboxScreen notifications={NOTIFICATIONS} readIds={readIds} data={data} now={now} onNavigate={setPath} onRead={markRead} onReadAll={markAllRead} />;
    if (path === "/search") return <SearchScreen query={search} data={data} now={now} onNavigate={setPath} onQuery={setSearch} palette={palette} />;
    if (path === "/account/team") return <TeamScreen data={data} now={now} onNavigate={setPath} />;
    if (path === "/briefs") return <BriefsScreen data={data} now={now} onNavigate={setPath} onBriefTransition={onBriefTransition} pushToast={pushToast} />;
    if (path === "/deliverables") return <DeliverablesScreen data={data} now={now} onNavigate={setPath} onDeliverableTransition={onDeliverableTransition} pushToast={pushToast} />;
    if (path === "/performance") return <PerformanceScreen data={data} now={now} onNavigate={setPath} pushToast={pushToast} />;
    if (path === "/money") return <MoneyScreen data={data} now={now} onNavigate={setPath} onInvoiceTransition={onInvoiceTransition} onPayoutTransition={onPayoutTransition} pushToast={pushToast} />;
    if (path === "/account/profile") return <ProfileScreen user={user} data={data} now={now} onNavigate={setPath} onSave={onSaveProfile} />;
    if (path === "/account/agency") return <AgencyScreen agency={agency} data={data} now={now} onNavigate={setPath} onSave={onSaveAgency} />;
    if (brief) return <MatchScreen brief={brief} data={data} now={now} onNavigate={setPath} onBriefTransition={onBriefTransition} onSendPitch={onSendPitch} />;
    if (deal) return <DealScreen deal={deal} data={data} now={now} onNavigate={setPath} onContractTransition={onContractTransition} onDealTransition={onDealTransition} />;
    return <RouteNotBuilt path={path} onNavigate={setPath} />;
  };

  const recordGroups = [
    { label: "Briefs waiting on you", items: briefsToTriage(data.briefs).map((b) => ({ value: b.id, label: b.brand + " · " + formatMoney(b.budgetCents) + " · " + labelOf(CATEGORIES, b.category), route: "/briefs/" + b.id })) },
    { label: "Deals", items: data.deals.map((d) => ({ value: d.id, label: d.brand + " × " + creatorById(data.creators, d.creatorId).name + " · " + formatMoney(d.feeCents), route: "/deals/" + d.id })) },
    { label: "Brands", items: data.brands.map((b) => ({ value: b.id, label: b.name + " · " + labelOf(CATEGORIES, b.sector) + " · " + briefsForBrand(data.briefs, b).length + " briefs", route: "/brands/" + b.id })) },
    { label: "Past due", items: invoicesOverdue(invoiceRows(data.deals), now).map((r) => ({ value: r.invoice.id, label: r.deal.brand + " · " + formatMoney(r.amount) + " · " + INVOICE_FLAGS.daysOverdue(r.invoice, now) + " days over", route: "/money" })) },
  ];

  return (
    <MarqueShell
      user={user}
      path={path}
      onNavigate={setPath}
      dark={dark}
      onToggleTheme={() => setDark(!dark)}
      data={data}
      notifications={NOTIFICATIONS}
      unread={unreadCount}
      onRead={markAllRead}
      palette={palette}
      toasts={toasts}
      onDismissToast={dismiss}
      controls={controls}
    >
      {screen()}
      <CommandPalette
        open={palette.open}
        onClose={() => palette.setOpen(false)}
        routes={MARQUE_ROUTES}
        recordGroups={recordGroups}
        onNavigate={setPath}
        placeholder="Search briefs, deals and brands…"
        footer={(close) => (
          <button type="button" className="mq-link" onClick={() => { close(); goSearch(search); }}>Open the full results screen</button>
        )}
      />
      <ConfirmDialog request={confirm} onCancel={() => setConfirm(null)} onConfirm={() => { confirm.run(); setConfirm(null); }} />
      <FormDialog request={form} onCancel={() => setForm(null)} onSubmit={(values) => { form.run(values); setForm(null); }} />
      {preview}
    </MarqueShell>
  );
}
/* boot.js publishes a stub on window.TalentDeskApp before anything is fetched,
   so <x-import> always resolves; the real component lands here and the stub
   swaps it in. */
window.__talentDeskReal = App;
if (!window.TalentDeskApp) window.TalentDeskApp = App;
