// ============================================================
// Nami Order Portal — main app shell, routing, modals, tweaks
// ============================================================

const { useState, useEffect, useMemo, useRef } = React;

// ---- tweaks defaults (persisted) ----
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "coverTreatment": "gradient",
  "variantLayout": "two-column",
  "statusPalette": "default",
  "showSparklines": true
}/*EDITMODE-END*/;

// ---- validation ----
// The real rule engine lives in validators.js (loaded as a separate script
// before app.jsx). This wrapper keeps the existing call-sites working
// while we migrate them to the structured shape.
const computeValidation = (deal) => {
  if (!window.NAMI_VALIDATE) {
    return { items: [], blocks: 0, warnings: 0, passes: 0, pendingExceptions: 0 };
  }
  return window.NAMI_VALIDATE.validate(deal);
};

// ============================================================
// Send Modal — choose Review vs. Sign, then execute
// ============================================================
// Single-path send: AE only sends a DRAFT for customer review. The
// customer triggers e-signature from their one-time link. Sign-from-AE
// is no longer a separate option in the modal — it lives behind an
// override (kept for cases where the AE has explicit pre-approval), not
// in the primary flow.
const SendModal = ({ deal, validation, onClose, onSendForReview, onOpenPreview }) => {
  const [step, setStep] = useState("choose"); // choose | sending | sent
  const numVariants = Object.values(deal.variants || {}).filter((v) => v != null && v !== false && !(Array.isArray(v) && !v.length)).length;

  const handleSend = () => {
    setStep("sending");
    setTimeout(() => {
      setStep("sent");
      setTimeout(() => onSendForReview(), 1100);
    }, 1400);
  };

  return (
    <div className="modal-scrim" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()} style={{ maxWidth: 560 }}>
        {step === "choose" && (
          <>
            <div style={{ display: "inline-flex", alignItems: "center", gap: 8, padding: "4px 12px", borderRadius: 9999, background: "rgba(255,193,7,0.16)", color: "#8a5d00", fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 12 }}>
              <Icon name="warn" size={11} /> Draft — not yet binding
            </div>
            <h2>Send draft to {deal.customer}</h2>
            <p>
              Your customer receives a one-time link with the assembled Service Order package. From that link they can review, forward to a colleague, request changes (loops back to you), or kick off DocuSign for signature.
            </p>

            <div className="summary-box" style={{ marginTop: 16 }}>
              <div className="row"><span className="l">Recipient</span><span className="v">{deal.contractAdmin?.name || "—"} · {deal.contractAdmin?.email || "—"}</span></div>
              <div className="row"><span className="l">Package</span><span className="v">Cover · Service Order · ToS · DPA · SLA{numVariants ? " · Amendment Addendum" : ""}</span></div>
              <div className="row"><span className="l">Variants applied</span><span className="v">{numVariants}</span></div>
              <div className="row"><span className="l">Validation</span><span className="v">{validation.blocks} blocks · {validation.warnings} warnings · {validation.passes} cleared</span></div>
            </div>

            <div className="modal__actions">
              <button className="btn btn--ghost btn--sm" onClick={onClose}>Cancel</button>
              <button
                className="btn btn--outline btn--sm"
                onClick={() => { onClose(); if (onOpenPreview) onOpenPreview(); }}
              >
                <Icon name="eye" size={13} /> Open full preview
              </button>
              <button className="btn btn--alloy btn--sm" disabled={validation.blocks > 0} onClick={handleSend}>
                <Icon name="send" size={13} /> Send draft for review
              </button>
            </div>
          </>
        )}

        {step === "sending" && (
          <div style={{ padding: 24, textAlign: "center" }}>
            <div className="spinner" style={{ margin: "0 auto 18px" }} />
            <h2 style={{ fontSize: 18 }}>Building review link…</h2>
            <p>Compiling document, generating a shareable review link, and emailing the customer.</p>
            <div style={{ fontSize: 11, fontFamily: "ui-monospace, Menlo, monospace", color: "var(--dark-grey)", textAlign: "left", background: "var(--alice-grey)", padding: 12, borderRadius: 10, lineHeight: 1.7 }}>
              ✓ Generating PDF (5 pages)<br />
              ✓ Indexing clauses for inline comments<br />
              ⏳ Provisioning review link order.nami.ml/r/…<br />
            </div>
          </div>
        )}

        {step === "sent" && (
          <div style={{ padding: 24, textAlign: "center" }}>
            <div style={{ width: 64, height: 64, borderRadius: "50%", background: "rgba(102,204,153,0.18)", color: "#1e7849", margin: "0 auto 18px", display: "flex", alignItems: "center", justifyContent: "center" }}>
              <Icon name="check" size={32} />
            </div>
            <h2>Draft sent for review.</h2>
            <p>{deal.customer} can now open the package, request changes, or trigger DocuSign for signature from their link.</p>
            <div style={{ background: "var(--alice-grey)", borderRadius: 10, padding: 12, fontSize: 12.5, color: "var(--dark-grey)", textAlign: "left" }}>
              Review link: <strong style={{ color: "var(--navy)", fontFamily: "ui-monospace, Menlo, monospace" }}>order.nami.ml/r/…</strong>
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

// ============================================================
// Sidebar
// ============================================================
// Role hierarchy — most-privileged first. The top-nav chip surfaces the
// "primary" role from this list so a user with multiple roles still sees a
// single, predictable label.
const ROLE_HIERARCHY = ["admin", "manager", "legal", "ae"];
const ROLE_LABEL = {
  admin: "Admin",
  manager: "Manager",
  legal: "Legal",
  ae: "AE",
};
const primaryRole = (user) => {
  if (!user || !Array.isArray(user.roles)) return "ae";
  return ROLE_HIERARCHY.find((r) => user.roles.includes(r)) || "ae";
};
const hasRole = (user, role) =>
  !!user && Array.isArray(user.roles) && user.roles.includes(role);

const Sidebar = ({ deals, statusFilter, onStatusFilter, onNewDeal, user }) => {
  const counts = useMemo(() => {
    const c = { all: deals.length };
    deals.forEach((d) => { c[d.status] = (c[d.status] || 0) + 1; });
    return c;
  }, [deals]);

  return (
    <aside className="sidebar">
      <button className="sidebar__cta" onClick={onNewDeal}>
        <Icon name="plus" size={16} />
        New deal
      </button>

      <div className="sidebar__group">
        <div className="sidebar__group-label">Pipeline</div>
        <button className={`sidebar__item ${statusFilter === "all" ? "is-active" : ""}`} onClick={() => onStatusFilter("all")}>
          <Icon name="layers" size={16} /> All deals <span className="count">{counts.all}</span>
        </button>
        {window.NAMI_DATA.STATUSES.map((s) => (
          <button key={s.key} className={`sidebar__item ${statusFilter === s.key ? "is-active" : ""}`} onClick={() => onStatusFilter(s.key)}>
            <Icon name={SIDEBAR_ICONS[s.key] || "dot"} size={16} />
            {s.label}
            <span className="count">{counts[s.key] || 0}</span>
          </button>
        ))}
      </div>

      {(hasRole(user, "manager") || hasRole(user, "admin")) && (() => {
        const pendingExc = deals.filter((d) => (d.pendingExceptionCount || 0) > 0).length;
        return (
          <div className="sidebar__group">
            <div className="sidebar__group-label">Deal Desk</div>
            <button
              className={`sidebar__item ${statusFilter === "exceptions" ? "is-active" : ""}`}
              onClick={() => onStatusFilter("exceptions")}
            >
              <Icon name="warn" size={16} />
              Pending exceptions
              <span className="count">{pendingExc}</span>
            </button>
          </div>
        );
      })()}

      {(hasRole(user, "legal") || hasRole(user, "admin")) && (() => {
        const staleCount = deals.filter((d) => d.status === "legal" && d.isStale).length;
        return (
          <div className="sidebar__group">
            <div className="sidebar__group-label">Legal review</div>
            <button
              className={`sidebar__item ${statusFilter === "legal" ? "is-active" : ""}`}
              onClick={() => onStatusFilter("legal")}
            >
              <Icon name="warn" size={16} />
              Pending Legal
              <span className="count">{counts.legal || 0}</span>
              {staleCount > 0 && (
                <span
                  className="stale-pill"
                  title={`${staleCount} deal${staleCount === 1 ? "" : "s"} stale in legal`}
                >
                  {staleCount} stale
                </span>
              )}
            </button>
          </div>
        );
      })()}

      <div className="sidebar__group">
        <div className="sidebar__group-label">Library</div>
        <button className="sidebar__item">
          <Icon name="files" size={16} /> Variant library
        </button>
        <button className="sidebar__item">
          <Icon name="file" size={16} /> Template documents
        </button>
        <button className="sidebar__item">
          <Icon name="package" size={16} /> Pricing tiers
        </button>
      </div>

      <div className="sidebar__footer">
        Order Portal v0.1 — beta<br />
        Powered by Nami Pricing Engine
      </div>
    </aside>
  );
};

const SIDEBAR_ICONS = {
  draft: "edit",
  legal: "warn",
  approved: "checkCircle",
  review: "users",
  sent: "send",
  signed: "file",
  active: "activity",
  renewal: "refresh",
};

// ============================================================
// Top nav
// ============================================================
const initialsFor = (user) => {
  if (!user?.name) return "SK";
  return user.name
    .split(/\s+/)
    .filter(Boolean)
    .slice(0, 2)
    .map((p) => p[0].toUpperCase())
    .join("") || "SK";
};

const TopNav = ({ user }) => {
  const role = primaryRole(user);
  const roleList = (user?.roles || []).map((r) => ROLE_LABEL[r] || r).join(", ");
  return (
    <header className="top-nav">
      <div className="top-nav__logo">
        <img src="assets/nami-wordmark-navy.svg" alt="Nami" />
        <span className="divider" />
        <span className="product">Order Portal</span>
      </div>
      <div className="top-nav__search">
        <Icon name="search" size={14} />
        <input placeholder="Search deals, customers, variants… (⌘K)" />
      </div>
      <div className="top-nav__spacer" />
      <div className="top-nav__actions">
        {user && (
          <span
            className={`role-chip role-chip--${role}`}
            title={`Signed in as ${user.email}\nRoles: ${roleList}`}
          >
            {ROLE_LABEL[role]}
          </span>
        )}
        <button className="top-nav__iconbtn"><Icon name="inbox" size={16} /></button>
        <button className="top-nav__iconbtn"><Icon name="bell" size={16} /><span className="dot" /></button>
        <button className="top-nav__iconbtn"><Icon name="settings" size={16} /></button>
        <div className="top-nav__avatar" title={user?.email || ""}>
          {initialsFor(user)}
        </div>
      </div>
    </header>
  );
};

// ============================================================
// Tweaks panel
// ============================================================
const TweaksUI = ({ tweaks, setTweak }) => (
  <TweaksPanel title="Tweaks">
    <TweakSection title="PDF cover treatment">
      <TweakRadio
        value={tweaks.coverTreatment}
        onChange={(v) => setTweak("coverTreatment", v)}
        options={[
          { value: "gradient", label: "Hero gradient" },
          { value: "smoke", label: "Smoke / formal" },
        ]}
      />
    </TweakSection>

    <TweakSection title="Variant builder layout">
      <TweakRadio
        value={tweaks.variantLayout}
        onChange={(v) => setTweak("variantLayout", v)}
        options={[
          { value: "two-column", label: "Two column" },
          { value: "single-column", label: "Single + sticky" },
        ]}
      />
    </TweakSection>

    <TweakSection title="Status badge palette">
      <TweakRadio
        value={tweaks.statusPalette}
        onChange={(v) => setTweak("statusPalette", v)}
        options={[
          { value: "default", label: "Soft" },
          { value: "bold", label: "Bold" },
          { value: "minimal", label: "Outline" },
        ]}
      />
    </TweakSection>

    <TweakSection title="Dashboard sparklines">
      <TweakToggle
        value={tweaks.showSparklines}
        onChange={(v) => setTweak("showSparklines", v)}
        label="Show on stat cards"
      />
    </TweakSection>
  </TweaksPanel>
);

// ============================================================
// Hash router + main App
// ============================================================
const useHashRoute = () => {
  const [hash, setHash] = useState(() => window.location.hash || "#/list");
  useEffect(() => {
    const onChange = () => setHash(window.location.hash || "#/list");
    window.addEventListener("hashchange", onChange);
    return () => window.removeEventListener("hashchange", onChange);
  }, []);
  const navigate = (h) => {
    window.location.hash = h;
  };
  return [hash, navigate];
};

const parseRoute = (hash) => {
  const path = hash.replace(/^#/, "") || "/list";
  const parts = path.split("/").filter(Boolean);
  if (parts[0] === "list") return { view: "list", status: parts[1] || "all" };
  if (parts[0] === "deal") return { view: "deal", dealId: parts[1] };
  if (parts[0] === "wizard") return { view: "wizard", dealId: parts[1], step: parseInt(parts[2] || "1", 10) };
  if (parts[0] === "pdf") return { view: "pdf", dealId: parts[1] };
  if (parts[0] === "review") return { view: "review", dealId: parts[1] };
  return { view: "list", status: "all" };
};

// Email-verification gate for the customer review flow. Two phases:
// gate-email (collect address) and gate-code (collect 6-digit code).
const VerificationGate = ({ token, gate, setGate, onVerified }) => {
  const [email, setEmail] = React.useState("");
  const [code, setCode] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [err, setErr] = React.useState(null);

  const codeInputRef = React.useRef(null);
  React.useEffect(() => {
    if (gate.phase === "gate-code" && codeInputRef.current) {
      codeInputRef.current.focus();
    }
  }, [gate.phase]);

  const sendCode = async (e) => {
    if (e) e.preventDefault();
    if (!email.trim()) return;
    setBusy(true); setErr(null);
    try {
      const res = await fetch(`/api/review/${encodeURIComponent(token)}/verify-email`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ email: email.trim() }),
      });
      if (res.ok) {
        setGate({ ...gate, phase: "gate-code", email: email.trim() });
        setCode("");
      } else {
        const body = await res.json().catch(() => ({}));
        setErr(body.error || "Something went wrong. Try again.");
      }
    } catch (e) {
      setErr("Couldn't reach the server. Check your connection.");
    } finally {
      setBusy(false);
    }
  };

  const verify = async (e) => {
    if (e) e.preventDefault();
    if (code.length !== 6) return;
    setBusy(true); setErr(null);
    try {
      const res = await fetch(`/api/review/${encodeURIComponent(token)}/verify-code`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ code }),
      });
      if (res.ok) {
        onVerified();
        return;
      }
      const body = await res.json().catch(() => ({}));
      if (body.error === "bad_code") {
        setErr(`That code didn't match. ${body.attemptsRemaining} attempt${body.attemptsRemaining === 1 ? "" : "s"} remaining.`);
      } else if (body.error === "code_expired" || body.error === "no_code_issued") {
        setErr("That code expired. Send a new one.");
      } else if (body.error === "revoked") {
        setErr("Too many wrong codes — this link is now revoked. Ask your Nami AE for a new one.");
      } else {
        setErr("Something went wrong. Try again.");
      }
    } catch (e) {
      setErr("Couldn't reach the server. Check your connection.");
    } finally {
      setBusy(false);
    }
  };

  const isEmail = gate.phase === "gate-email";

  return (
    <div className="review-gate">
      <div className="review-gate__card">
        <img src="assets/nami-wordmark-navy.svg" alt="Nami" className="review-gate__logo" />

        {isEmail ? (
          <>
            <h1>Contract review {gate.customer ? `for ${gate.customer}` : ""}</h1>
            <p className="review-gate__lede">
              For your security, enter the email your Nami AE recorded on
              this deal. We'll send you a code to open the package.
            </p>
            {gate.emailHint && (
              <div className="review-gate__hint">
                On file: <strong>{gate.emailHint}</strong>
              </div>
            )}
            <form onSubmit={sendCode} className="review-gate__form">
              <label className="review-gate__label" htmlFor="rv-email">Your work email</label>
              <input
                id="rv-email"
                type="email"
                required
                autoFocus
                autoComplete="email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="you@company.com"
              />
              <button
                type="submit"
                className="btn btn--alloy"
                disabled={busy || !email.trim()}
              >
                {busy ? "Sending…" : "Send code"}
              </button>
            </form>
          </>
        ) : (
          <>
            <h1>Check your email</h1>
            <p className="review-gate__lede">
              We sent a 6-digit code to <strong>{gate.email}</strong>. It
              expires in 10 minutes.
            </p>
            <form onSubmit={verify} className="review-gate__form">
              <label className="review-gate__label" htmlFor="rv-code">Verification code</label>
              <input
                id="rv-code"
                ref={codeInputRef}
                inputMode="numeric"
                autoComplete="one-time-code"
                pattern="\d{6}"
                maxLength={6}
                required
                value={code}
                onChange={(e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6))}
                placeholder="000000"
                className="review-gate__code-input"
              />
              <button
                type="submit"
                className="btn btn--alloy"
                disabled={busy || code.length !== 6}
              >
                {busy ? "Verifying…" : "Open review"}
              </button>
            </form>
            <button
              type="button"
              className="btn btn--ghost btn--sm review-gate__back"
              onClick={() => { setErr(null); setGate({ ...gate, phase: "gate-email" }); }}
            >
              Use a different email
            </button>
          </>
        )}

        {err && <div className="review-gate__error">{err}</div>}

        <div className="review-gate__foot">
          Trouble signing in? Reply to your Nami AE's email.
        </div>
      </div>
    </div>
  );
};

// One-time customer-review links live at /r/<token>. The token alone isn't
// strong enough — forwarded links would hand access to anyone who sees the
// URL — so we layer an email-verification gate on top. The flow is:
//
//   1. GET /api/review/<token>          → 401 { needs_verification, email_hint }
//   2. POST /verify-email { email }     → server emails a 6-digit code
//   3. POST /verify-code  { code }      → server sets nami_rv signed cookie
//   4. GET /api/review/<token> again    → 200 + deal payload
//
// All in-app mutations (comment post, terminal decision) require the cookie,
// which is bound to this specific token.
const RemoteReview = ({ token }) => {
  const [state, setState] = React.useState({ phase: "loading" });

  const loadDeal = React.useCallback(() => {
    setState({ phase: "loading" });
    fetch(`/api/review/${encodeURIComponent(token)}`, {
      headers: { accept: "application/json" },
    })
      .then(async (res) => {
        const body = await res.json().catch(() => ({}));
        if (res.status === 401 && body.needs_verification) {
          setState({
            phase: "gate-email",
            emailHint: body.email_hint,
            customer: body.customer,
          });
          return;
        }
        if (!res.ok) {
          setState({
            phase: "error",
            status: res.status,
            error: body.error || "unknown_error",
          });
          return;
        }
        setState({ phase: "ready", envelope: body });
      })
      .catch((err) => {
        setState({ phase: "error", status: 0, error: String(err) });
      });
  }, [token]);

  React.useEffect(() => { loadDeal(); }, [loadDeal]);

  if (state.phase === "loading") {
    return <div className="review-loading"><div className="spinner" /><div>Loading review package…</div></div>;
  }
  if (state.phase === "gate-email" || state.phase === "gate-code") {
    return (
      <VerificationGate
        token={token}
        gate={state}
        setGate={setState}
        onVerified={loadDeal}
      />
    );
  }
  if (state.phase === "error") {
    const msg = {
      not_found: "This review link is not valid. Ask your Nami AE to issue a new one.",
      expired: "This review link has expired. Ask your Nami AE to issue a new one.",
      revoked: "This review link was revoked. Ask your Nami AE for a new one.",
      already_completed: "This review has already been completed.",
    }[state.error] || `Something went wrong (${state.error}).`;
    return (
      <div className="review-loading">
        <h2 style={{ fontSize: 20, fontWeight: 700, color: "var(--navy)" }}>Review unavailable</h2>
        <div style={{ color: "var(--dark-grey)", maxWidth: 420, textAlign: "center" }}>{msg}</div>
      </div>
    );
  }

  const { envelope } = state;
  // Customer-side edits live on the envelope alongside the original
  // snapshot. Merge them in shallow + one level deep for the nested
  // contact objects so the document the customer sees reflects their
  // own edits live (and so does the DocuSign envelope when they
  // trigger signature).
  const edits = envelope.customerEdits || {};
  const deal = {
    ...envelope.deal,
    ...edits,
    contractAdmin: { ...(envelope.deal.contractAdmin || {}), ...(edits.contractAdmin || {}) },
    billingContact: { ...(envelope.deal.billingContact || {}), ...(edits.billingContact || {}) },
    comments: envelope.comments || [],
  };

  const postComment = (comment) => {
    fetch(`/api/review/${encodeURIComponent(token)}`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        body: comment.body,
        anchor: comment.anchor,
        anchorLabel: comment.anchorLabel,
        author: comment.author,
      }),
    }).then(async (res) => {
      if (!res.ok) return;
      const body = await res.json().catch(() => null);
      if (body?.comments) {
        setState({ phase: "ready", envelope: { ...envelope, comments: body.comments } });
      }
    });
  };

  const finalize = (decision) => {
    fetch(`/api/review/${encodeURIComponent(token)}`, {
      method: "PATCH",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ decision }),
    }).then(async (res) => {
      if (!res.ok) return;
      const body = await res.json().catch(() => null);
      setState({
        phase: "completed",
        decision,
        envelope: body || envelope,
      });
    });
  };

  // Persist a partial customer-edits payload. The server merges into
  // envelope.customerEdits; we update local state with the server's
  // authoritative response so concurrent edits from multiple recipients
  // (e.g. forwarded link) don't clobber each other.
  const saveCustomerEdits = (patch) => {
    return fetch(`/api/review/${encodeURIComponent(token)}/customer-edits`, {
      method: "PATCH",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(patch),
    }).then(async (res) => {
      if (!res.ok) {
        const body = await res.json().catch(() => ({}));
        throw new Error(body.error || `save_failed:${res.status}`);
      }
      const body = await res.json();
      setState({
        phase: "ready",
        envelope: { ...envelope, customerEdits: body.customerEdits },
      });
    });
  };

  // Customer-initiated send-for-signature. Pre-renders the amendment
  // text client-side (variant langTemplate functions live in the browser)
  // and posts to the new public endpoint which fires DocuSign +
  // transitions the deal to `sent`.
  const sendForSignature = () => {
    const variants = window.NAMI_DATA?.VARIANTS || [];
    const sel = deal.variants || {};
    const amendments = variants
      .map((v) => {
        const val = sel[v.id];
        if (val == null || val === false || (Array.isArray(val) && !val.length)) return null;
        try {
          const text = v.langTemplate(val);
          if (!text) return null;
          return { id: v.id, category: v.category, section: v.section, text };
        } catch { return null; }
      })
      .filter(Boolean);

    fetch(`/api/review/${encodeURIComponent(token)}/send-for-signature`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ amendments }),
    }).then(async (res) => {
      const body = await res.json().catch(() => null);
      if (!res.ok) {
        setState({
          phase: "ready",
          envelope,
          sendError: body?.error || `send_failed:${res.status}`,
        });
        return;
      }
      setState({
        phase: "completed",
        decision: "send_for_signature",
        envelope: { ...envelope, status: "completed", envelopeId: body?.envelopeId },
      });
    }).catch((err) => {
      setState({
        phase: "ready",
        envelope,
        sendError: String(err?.message || err),
      });
    });
  };

  if (state.phase === "completed") {
    const sent = state.decision === "send_for_signature";
    return (
      <div className="review-loading">
        <h2 style={{ fontSize: 22, fontWeight: 700, color: "var(--navy)" }}>
          {sent ? "Sent for signature." :
           state.decision === "approve" ? "Approved." : "Sent back with comments."}
        </h2>
        <div style={{ color: "var(--dark-grey)", maxWidth: 460, textAlign: "center" }}>
          {sent
            ? "Check your inbox for the DocuSign envelope. Once both parties sign, this contract becomes active."
            : state.decision === "approve"
              ? "Your Nami AE will now prepare the DocuSign envelope for signature."
              : "Your Nami AE has been notified and will respond to your comments."}
        </div>
        {state.envelope?.envelopeId && (
          <div style={{ marginTop: 12, fontSize: 12, color: "var(--dark-grey)", fontFamily: "ui-monospace, Menlo, monospace" }}>
            Envelope: {state.envelope.envelopeId}
          </div>
        )}
      </div>
    );
  }

  return (
    <CustomerReview
      deal={deal}
      remoteMode
      onExit={() => { window.location.href = "/"; }}
      onSubmitComment={postComment}
      onApprove={() => finalize("approve")}
      onSendBack={() => finalize("send_back")}
      onSendForSignature={sendForSignature}
      onSaveCustomerEdits={saveCustomerEdits}
    />
  );
};

const App = () => {
  // /r/<token> is the public, one-time customer review path. It must work
  // without any of the hash-based AE shell, so check the pathname before any
  // hash routing runs.
  const reviewToken = React.useMemo(() => {
    const m = window.location.pathname.match(/^\/r\/([A-Za-z0-9_\-]+)\/?$/);
    return m ? m[1] : null;
  }, []);
  if (reviewToken) {
    return <RemoteReview token={reviewToken} />;
  }

  const [hash, navigate] = useHashRoute();
  const route = parseRoute(hash);
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [deals, setDeals] = useState(window.NAMI_DATA.DEALS);
  const [sendModalOpen, setSendModalOpen] = useState(false);
  const [user, setUser] = useState(null);
  // Tracks whether the D1-backed API is reachable. When false, every
  // mutation stays purely client-side so the static prototype keeps
  // working without complaint.
  const [apiAvailable, setApiAvailable] = useState(false);

  // Load deals from D1 once the API confirms availability. Falls back to
  // the data.js seed if the request fails.
  React.useEffect(() => {
    let aborted = false;
    fetch("/api/deals", { headers: { accept: "application/json" } })
      .then(async (res) => {
        if (!res.ok) return null;
        return res.json();
      })
      .then((body) => {
        if (aborted || !body?.deals) return;
        setApiAvailable(true);
        setDeals(body.deals);
      })
      .catch(() => {});
    return () => { aborted = true; };
  }, []);

  // Fire-and-forget persistence. Each mutation runs locally first (snappy
  // UX) and is mirrored to the server. Failures get logged but don't
  // roll back — D1 will reconcile on the next page load.
  const persistDeal = (id, patch) => {
    if (!apiAvailable) return;
    fetch(`/api/deals/${encodeURIComponent(id)}`, {
      method: "PUT",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ patch }),
    }).catch((err) => console.warn("[persistDeal]", id, err));
  };

  const persistActivity = (dealId, entry) => {
    if (!apiAvailable) return;
    fetch(`/api/deals/${encodeURIComponent(dealId)}/activity`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ event: entry.event, note: entry.note }),
    }).catch((err) => console.warn("[persistActivity]", dealId, err));
  };

  // Replaces a deal in local state with the server's authoritative copy.
  // Used by every transition handler after a successful POST so the UI
  // reflects status, activity log, and (where applicable) internal
  // comments without an extra round-trip.
  const replaceDeal = (deal) => {
    if (!deal?.id) return;
    setDeals((prev) => {
      const i = prev.findIndex((d) => d.id === deal.id);
      if (i < 0) return [deal, ...prev];
      const next = prev.slice();
      next[i] = deal;
      return next;
    });
  };

  // Calls a transition endpoint and replaces local state with the returned
  // deal. Returns the deal on success, null on offline / failure so the
  // caller can fall back to in-memory mutation for the static prototype.
  const callTransition = async (dealId, action, body) => {
    if (!apiAvailable) return null;
    try {
      const res = await fetch(`/api/deals/${encodeURIComponent(dealId)}/${action}`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(body || {}),
      });
      if (!res.ok) {
        const err = await res.json().catch(() => ({}));
        throw new Error(err.error || `${action}_failed:${res.status}`);
      }
      const j = await res.json();
      if (j.deal) replaceDeal(j.deal);
      return j;
    } catch (e) {
      console.warn(`[transition:${action}]`, dealId, e);
      return null;
    }
  };

  // Load the current Nami user + roles on boot. /api/whoami sits behind
  // Cloudflare Access, so this 401s in the static-prototype context — fall
  // back to a mock AE so the design demo still works.
  React.useEffect(() => {
    let aborted = false;
    fetch("/api/whoami", { headers: { accept: "application/json" } })
      .then((res) => (res.ok ? res.json() : null))
      .then((u) => {
        if (aborted) return;
        if (u) setUser(u);
        else setUser({
          email: "sarah.kim@namiml.com",
          name: "Sarah Kim",
          roles: ["ae"],
          team: "east",
          mock: true,
        });
      })
      .catch(() => {
        if (!aborted) setUser({
          email: "sarah.kim@namiml.com",
          name: "Sarah Kim",
          roles: ["ae"],
          team: "east",
          mock: true,
        });
      });
    return () => { aborted = true; };
  }, []);

  const findDeal = (id) => deals.find((d) => d.id === id);

  const updateDeal = (id, patch, opts = {}) => {
    setDeals((prev) => prev.map((d) => {
      if (d.id !== id) return d;
      // simple patch — top-level keys override
      const merged = { ...d };
      Object.entries(patch).forEach(([k, v]) => {
        merged[k] = v;
      });
      return merged;
    }));
    // Some fields are kept purely on the client (mostly arrays we mutate
    // through dedicated endpoints — activity, internalComments). Skip
    // persistence for those.
    if (!opts.skipPersist) {
      const { activity, internalComments, ...persisted } = patch;
      if (Object.keys(persisted).length > 0) {
        persistDeal(id, persisted);
      }
    }
  };

  const newDeal = () => {
    const id = `new-${Date.now()}`;
    const fresh = {
      id,
      name: "New deal",
      customer: "",
      customerShort: "",
      logoSeed: "??",
      logoColor: "#0B4A8E",
      ae: "Sarah Kim",
      status: "draft",
      tcv: 0,
      trim: "premier",
      // 1-year default — multi-year is an opt-in upsell, not the default.
      // The wizard's term picker swaps in the escalator-based commits
      // when the AE flips to 3 years.
      termYears: 1,
      startDate: "",
      endDate: "",
      updated: "just now",
      variants: {},
      // Volume defaults to the trim minimum. The wizard's selectTrim
      // handler keeps this in sync when the AE changes trim.
      commits: [window.NAMI_PRICING.TRIMS.premier.minCommit * 1_000_000],
      cpm: window.NAMI_PRICING.effectiveCpm(
        window.NAMI_PRICING.TRIMS.premier.minCommit,
        "premier",
      ),
      cadence: "annual",
      netTerms: "Net 30",
      currency: "USD",
      primaryContact: { name: "", email: "" },
      contractAdmin: { name: "", title: "", email: "" },
      billingContact: { name: "", email: "" },
      // Premier defaults to standard support; Elite is the only trim
      // that bundles Concierge automatically.
      support: "standard",
      applications: [],
      addons: [],
    };
    setDeals((prev) => [fresh, ...prev]);
    navigate(`#/wizard/${id}/1`);
  };

  const handleStatusFilter = (key) => navigate(`#/list/${key}`);
  const openDeal = (id) => {
    const d = findDeal(id);
    if (d && d.status === "draft") {
      navigate(`#/wizard/${id}/1`);
    } else {
      navigate(`#/deal/${id}`);
    }
  };
  const openWizard = (id, step = 1) => navigate(`#/wizard/${id}/${step}`);

  // Compute active deal & validation
  const currentDeal = route.dealId ? findDeal(route.dealId) : null;
  const validation = useMemo(() => currentDeal ? computeValidation(currentDeal) : null, [currentDeal]);

  const handleSendForReview = async () => {
    if (!currentDeal) return;
    const result = await callTransition(currentDeal.id, "send-review", {});
    if (!result) {
      // Offline fallback for the static prototype — placeholder URL, no
      // real KV envelope.
      const sentAt = new Date().toLocaleString("en-US", {
        month: "short", day: "numeric", year: "numeric",
        hour: "numeric", minute: "2-digit",
      });
      const sentTo = currentDeal.contractAdmin?.email || "customer@example.com";
      updateDeal(currentDeal.id, {
        status: "review",
        reviewSentAt: sentAt,
        reviewSentTo: sentTo,
        reviewLink: `${window.location.origin}/r/${currentDeal.id}`,
        reviewToken: null,
      }, { skipPersist: true });
      appendActivity(currentDeal.id, {
        event: "sent_for_review",
        note: `Review link sent to ${sentTo}.`,
      });
    }
    setSendModalOpen(false);
    navigate(`#/deal/${currentDeal.id}`);
  };

  // Build the amendment array the server needs to render the envelope's
  // HTML. The variant catalog (with langTemplate functions) lives on the
  // client; the server only sees the resolved text.
  const renderAmendmentsForSend = (deal) => {
    const variants = window.NAMI_DATA?.VARIANTS || [];
    const sel = deal.variants || {};
    return variants
      .map((v) => {
        const val = sel[v.id];
        if (val == null || val === false || (Array.isArray(val) && !val.length)) return null;
        try {
          const text = v.langTemplate(val);
          if (!text) return null;
          return { id: v.id, category: v.category, section: v.section, text };
        } catch {
          return null;
        }
      })
      .filter(Boolean);
  };

  const handleSendToSign = async () => {
    if (!currentDeal) return;
    const amendments = renderAmendmentsForSend(currentDeal);
    const result = await callTransition(currentDeal.id, "send-sign", { amendments });
    if (!result) {
      updateDeal(currentDeal.id, {
        status: "sent",
        sentTo: currentDeal.contractAdmin?.email || "customer@example.com",
        sentAt: new Date().toLocaleString("en-US", {
          month: "short", day: "numeric", year: "numeric",
          hour: "numeric", minute: "2-digit",
        }),
      }, { skipPersist: true });
      appendActivity(currentDeal.id, {
        event: "sent_to_docusign",
        note: `Envelope sent to ${currentDeal.contractAdmin?.email}.`,
      });
    }
    setSendModalOpen(false);
    navigate(`#/deal/${currentDeal.id}`);
  };

  // ----- Internal legal-review workflow -----
  // Activity-log entries that carry actor + role so the audit trail can
  // answer "who approved this deal and in what capacity." Today these live
  // on the deal record alongside the state field; future D1 migration will
  // split them into their own table.
  const appendActivity = (dealId, entry) => {
    const d = findDeal(dealId);
    if (!d) return;
    const now = new Date();
    const next = [
      ...(d.activity || []),
      {
        id: `act-${Date.now().toString(36)}`,
        time: now.toISOString(),
        date: now.toLocaleString("en-US", {
          month: "short", day: "numeric", year: "numeric",
          hour: "numeric", minute: "2-digit",
        }),
        actor: user?.name || user?.email || "system",
        actorEmail: user?.email || null,
        role: primaryRole(user),
        ...entry,
      },
    ];
    updateDeal(dealId, { activity: next, updated: "just now" }, { skipPersist: true });
    persistActivity(dealId, entry);
  };

  const handleSubmitForLegal = async () => {
    if (!currentDeal) return;
    const flags = window.NAMI_DATA.dealLegalFlags(currentDeal);
    const reason = flags.length
      ? `${flags.length} variant${flags.length === 1 ? "" : "s"} outside the pre-approved menu: ${flags.map((f) => f.category).join(", ")}.`
      : "Submitted for counsel review.";

    const result = await callTransition(currentDeal.id, "submit-legal", { reason });
    if (!result) {
      // Offline fallback: mutate locally so the static prototype still demos.
      updateDeal(currentDeal.id, {
        status: "legal",
        legalReason: reason,
        legalSubmittedAt: new Date().toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit" }),
      }, { skipPersist: true });
      appendActivity(currentDeal.id, { event: "submitted_for_legal", note: reason });
    }
    navigate(`#/deal/${currentDeal.id}`);
  };

  const handleLegalApprove = async (dealId, note) => {
    const result = await callTransition(dealId, "legal-decision", { decision: "approve", note });
    if (result) return;
    // Offline fallback
    updateDeal(dealId, {
      status: "approved",
      legalApprovedAt: new Date().toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit" }),
      legalApprovedBy: user?.email,
    }, { skipPersist: true });
    appendActivity(dealId, {
      event: "legal_approved",
      note: note || "Approved. Variants outside the menu have been signed off.",
    });
  };

  const handleLegalReject = async (dealId, note) => {
    if (!note?.trim()) return;
    const result = await callTransition(dealId, "legal-decision", { decision: "reject", note });
    if (result) return;
    // Offline fallback
    updateDeal(dealId, {
      status: "draft",
      legalRejectedAt: new Date().toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "numeric", minute: "2-digit" }),
      legalRejectedBy: user?.email,
      legalRejectionNote: note,
    }, { skipPersist: true });
    appendActivity(dealId, { event: "legal_rejected", note });
  };

  const handleRevokeReviewLink = async (dealId) => {
    const result = await callTransition(dealId, "revoke-review", { reason: "ae_revoke" });
    if (result) return;
    // Offline fallback for the static prototype — local-only revoke, no
    // KV envelope to mark.
    updateDeal(dealId, {
      status: "draft",
      reviewLink: null,
      reviewToken: null,
      reviewSentAt: null,
      reviewSentTo: null,
    }, { skipPersist: true });
    appendActivity(dealId, {
      event: "review_revoked",
      note: "Review link revoked by AE. Deal returned to draft.",
    });
  };

  const handleRequestException = async (dealId, ruleId, justification) => {
    const d = findDeal(dealId);
    if (!d) return;
    // Optimistic local insert so the UI flips to "Pending approval" without
    // round-trip lag.
    const optimistic = {
      id: `pending-${Date.now()}`,
      ruleId,
      status: "requested",
      requestedBy: user?.email,
      requestedAt: new Date().toISOString(),
      justification,
    };
    const next = [...(d.exceptions || []).filter((e) => e.ruleId !== ruleId), optimistic];
    updateDeal(dealId, { exceptions: next }, { skipPersist: true });
    appendActivity(dealId, {
      event: "exception_requested",
      note: `Requested an exception on \`${ruleId}\`: ${justification.slice(0, 240)}${justification.length > 240 ? "…" : ""}`,
    });

    if (!apiAvailable) return;
    try {
      const res = await fetch(`/api/deals/${encodeURIComponent(dealId)}/request-exception`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ ruleId, justification }),
      });
      if (!res.ok) throw new Error(`exception_failed:${res.status}`);
      const j = await res.json();
      if (j.deal) replaceDeal(j.deal);
    } catch (err) {
      console.warn("[requestException]", dealId, err);
    }
  };

  const handleResolveException = async (dealId, exceptionId, decision, note) => {
    const d = findDeal(dealId);
    if (!d) return;
    // Optimistic local flip.
    const stamp = new Date().toISOString();
    const next = (d.exceptions || []).map((e) =>
      e.id === exceptionId
        ? { ...e, status: decision, resolvedBy: user?.email, resolvedAt: stamp, resolutionNote: note }
        : e,
    );
    updateDeal(dealId, { exceptions: next }, { skipPersist: true });
    appendActivity(dealId, {
      event: decision === "approved" ? "exception_approved" : "exception_rejected",
      note: note ? `${decision === "approved" ? "Approved" : "Rejected"}: ${note}` : null,
    });

    if (!apiAvailable) return;
    try {
      const res = await fetch(`/api/deals/${encodeURIComponent(dealId)}/resolve-exception`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ exceptionId, decision, note }),
      });
      if (!res.ok) throw new Error(`resolve_exception_failed:${res.status}`);
      const j = await res.json();
      if (j.deal) replaceDeal(j.deal);
    } catch (err) {
      console.warn("[resolveException]", dealId, err);
    }
  };

  // Customer-proposed edits live on the KV review envelope. The
  // deal-detail surface fetches them via the AE-side endpoint and
  // stashes them in a per-deal map; apply/discard mutate the same.
  const [customerEditsByDeal, setCustomerEditsByDeal] = useState({});

  const loadCustomerEdits = async (dealId) => {
    if (!apiAvailable) return;
    try {
      const res = await fetch(`/api/deals/${encodeURIComponent(dealId)}/customer-edits`);
      if (!res.ok) return;
      const body = await res.json();
      setCustomerEditsByDeal((m) => ({ ...m, [dealId]: body }));
    } catch (err) {
      console.warn("[loadCustomerEdits]", dealId, err);
    }
  };

  const handleApplyCustomerEdits = async (dealId) => {
    if (!apiAvailable) return;
    try {
      const res = await fetch(`/api/deals/${encodeURIComponent(dealId)}/customer-edits`, {
        method: "POST",
      });
      if (!res.ok) throw new Error(`apply_failed:${res.status}`);
      const body = await res.json();
      if (body.deal) replaceDeal(body.deal);
      setCustomerEditsByDeal((m) => ({ ...m, [dealId]: { edits: {}, editedAt: null } }));
    } catch (err) {
      console.warn("[applyCustomerEdits]", dealId, err);
    }
  };

  const handleDiscardCustomerEdits = async (dealId) => {
    if (!apiAvailable) return;
    try {
      const res = await fetch(`/api/deals/${encodeURIComponent(dealId)}/customer-edits`, {
        method: "DELETE",
      });
      if (!res.ok) throw new Error(`discard_failed:${res.status}`);
      setCustomerEditsByDeal((m) => ({ ...m, [dealId]: { edits: {}, editedAt: null } }));
      appendActivity(dealId, {
        event: "customer_edits_discarded",
        note: "Discarded customer-proposed changes without applying.",
      });
    } catch (err) {
      console.warn("[discardCustomerEdits]", dealId, err);
    }
  };

  // When the user lands on a deal-detail page for a deal in review,
  // pull the pending edits. Triggered any time the route changes.
  React.useEffect(() => {
    if (route.view !== "deal" || !route.dealId) return;
    const d = findDeal(route.dealId);
    if (d?.status !== "review") return;
    loadCustomerEdits(route.dealId);
  }, [route.view, route.dealId, deals]);

  const handleResolveComment = async (dealId, commentId, resolved) => {
    const d = findDeal(dealId);
    if (!d) return;
    const targetStatus = resolved === false ? "open" : "resolved";

    // Optimistic local flip so the UI moves immediately.
    const optimistic = (d.comments || []).map((c) =>
      c.id === commentId ? { ...c, status: targetStatus } : c,
    );
    updateDeal(dealId, { comments: optimistic }, { skipPersist: true });

    if (!apiAvailable) return;
    try {
      const res = await fetch(`/api/deals/${encodeURIComponent(dealId)}/resolve-comment`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ commentId, resolved: resolved !== false }),
      });
      if (!res.ok) throw new Error(`resolve_failed:${res.status}`);
      const j = await res.json();
      if (j.comments) {
        updateDeal(dealId, { comments: j.comments }, { skipPersist: true });
      }
    } catch (err) {
      console.warn("[resolveComment]", dealId, err);
    }
  };

  const handlePostInternalComment = (dealId, body) => {
    const d = findDeal(dealId);
    if (!d || !body?.trim()) return;
    const optimistic = {
      id: `ic-${Date.now().toString(36)}`,
      author: user?.name || "Nami staff",
      authorEmail: user?.email,
      role: primaryRole(user),
      time: new Date().toISOString(),
      body: body.trim(),
      status: "open",
    };
    updateDeal(dealId, {
      internalComments: [...(d.internalComments || []), optimistic],
      updated: "just now",
    }, { skipPersist: true });

    if (!apiAvailable) return;
    fetch(`/api/deals/${encodeURIComponent(dealId)}/comments`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ body: body.trim() }),
    })
      .then(async (res) => {
        if (!res.ok) return;
        const j = await res.json().catch(() => null);
        if (j?.comments) {
          // Replace the optimistic list with the server's authoritative one.
          updateDeal(dealId, { internalComments: j.comments }, { skipPersist: true });
        }
      })
      .catch((err) => console.warn("[postComment]", dealId, err));
  };

  // ----- Customer review fullscreen route (no shell) -----
  if (route.view === "review") {
    if (!currentDeal) {
      return <div style={{ padding: 80, textAlign: "center" }}>Review link not found.</div>;
    }
    return (
      <CustomerReview
        deal={currentDeal}
        onExit={() => navigate(`#/deal/${currentDeal.id}`)}
        onApprove={() => {
          updateDeal(currentDeal.id, { status: "approved", updated: "just now" });
          navigate(`#/deal/${currentDeal.id}`);
        }}
        onSendBack={() => {
          updateDeal(currentDeal.id, { status: "review", updated: "just now" });
          navigate(`#/deal/${currentDeal.id}`);
        }}
      />
    );
  }

  return (
    <div
      className={`shell palette-${tweaks.statusPalette} ${tweaks.showSparklines ? "" : "no-sparklines"}`}
      data-screen-label={
        route.view === "list" ? "01 Deal list" :
        route.view === "wizard" ? `02 Wizard step ${route.step}` :
        route.view === "deal" ? "03 Deal detail" :
        route.view === "pdf" ? "04 Order Form PDF" : ""
      }
    >
      <TopNav user={user} />
      <Sidebar
        deals={deals}
        statusFilter={route.view === "list" ? route.status : null}
        onStatusFilter={handleStatusFilter}
        onNewDeal={newDeal}
        user={user}
      />
      <main className="main">
        {route.view === "list" && (
          <Dashboard
            deals={deals}
            statusFilter={route.status}
            onStatusFilter={handleStatusFilter}
            onOpenDeal={openDeal}
            onNewDeal={newDeal}
          />
        )}
        {route.view === "wizard" && currentDeal && (
          <Wizard
            deal={currentDeal}
            step={route.step}
            onStepChange={(s) => openWizard(currentDeal.id, s)}
            onUpdate={(patch) => updateDeal(currentDeal.id, patch)}
            onCancel={() => navigate("#/list")}
            onSendModal={() => setSendModalOpen(true)}
            onSubmitForLegal={handleSubmitForLegal}
            onRequestException={(ruleId, justification) =>
              handleRequestException(currentDeal.id, ruleId, justification)
            }
            onOpenPreview={() => navigate(`#/pdf/${currentDeal.id}`)}
            getValidation={() => computeValidation(currentDeal)}
            variantLayout={tweaks.variantLayout}
          />
        )}
        {route.view === "deal" && currentDeal && (
          <DealDetail
            deal={currentDeal}
            user={user}
            onBack={() => navigate("#/list")}
            onEdit={() => openWizard(currentDeal.id, 1)}
            onSendModal={() => setSendModalOpen(true)}
            onViewPdf={(id) => navigate(`#/pdf/${id}`)}
            onViewAsCustomer={() => navigate(`#/review/${currentDeal.id}`)}
            onSubmitForLegal={handleSubmitForLegal}
            onLegalApprove={(note) => handleLegalApprove(currentDeal.id, note)}
            onLegalReject={(note) => handleLegalReject(currentDeal.id, note)}
            onPostInternalComment={(body) => handlePostInternalComment(currentDeal.id, body)}
            onRevokeReviewLink={() => handleRevokeReviewLink(currentDeal.id)}
            onResolveComment={(commentId, resolved) => handleResolveComment(currentDeal.id, commentId, resolved)}
            onResolveException={(exceptionId, decision, note) =>
              handleResolveException(currentDeal.id, exceptionId, decision, note)
            }
            onRequestException={(ruleId, justification) =>
              handleRequestException(currentDeal.id, ruleId, justification)
            }
            customerEdits={customerEditsByDeal[currentDeal.id]?.edits || null}
            customerEditedAt={customerEditsByDeal[currentDeal.id]?.editedAt || null}
            onApplyCustomerEdits={() => handleApplyCustomerEdits(currentDeal.id)}
            onDiscardCustomerEdits={() => handleDiscardCustomerEdits(currentDeal.id)}
          />
        )}
        {route.view === "pdf" && currentDeal && (
          <OrderFormPDF
            deal={currentDeal}
            onBack={() => navigate(`#/deal/${currentDeal.id}`)}
            coverTreatment={tweaks.coverTreatment}
          />
        )}
        {(route.view === "wizard" || route.view === "deal" || route.view === "pdf") && !currentDeal && (
          <div className="main__inner" style={{ textAlign: "center", padding: 80 }}>
            <div style={{ fontSize: 18, fontWeight: 600, color: "var(--navy)" }}>Deal not found</div>
            <button className="btn btn--outline btn--sm mt-4" onClick={() => navigate("#/list")}>Back to deals</button>
          </div>
        )}
      </main>

      {sendModalOpen && currentDeal && (
        <SendModal
          deal={currentDeal}
          validation={validation}
          onClose={() => setSendModalOpen(false)}
          onSendForReview={handleSendForReview}
          onOpenPreview={() => navigate(`#/pdf/${currentDeal.id}`)}
        />
      )}

      <TweaksUI tweaks={tweaks} setTweak={setTweak} />
    </div>
  );
};

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