/* global React, ReactDOM, Icon, Sidebar, AIDock, HomeScreen, DealsScreen, DealDetail, AnalyticsScreen, AlertsScreen, SetupScreen, PlaybooksScreen, SettingsModule, ReceiptsPanel, ParallaxDots, BuyerForecast, shouldShowForecastToday, markForecastShown, SEED_DEALS, useTweaks, TweaksPanel, TweakSection, TweakRadio, TweakToggle, TweakSelect, TweakSlider, DemoTour */

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "accent": "ember",
  "density": "comfortable",
  "dock": "center",
  "dealView": "cards",
  "addyTone": "proactive"
}/*EDITMODE-END*/;

// Operational instrument: "ember" is the brand WATCH tone (amber-600,
// #D97706) — the middle-severity signal, used sparingly. Not the
// primary action color; that's a warm-white neutral. Other named
// accents stay bright for the Tweaks panel preview.
const ACCENTS = {
  ember:    { hue: 50,  chroma: 0.155, lightness: 0.68, ink: "#1A0F02" },
  electric: { hue: 280, chroma: 0.16,  lightness: 0.74, ink: "#0e0a1a" },
  citrus:   { hue: 95,  chroma: 0.18,  lightness: 0.74, ink: "#0d1505" },
  rose:     { hue: 10,  chroma: 0.18,  lightness: 0.74, ink: "#1a0507" },
  graphite: { hue: 240, chroma: 0.04,  lightness: 0.74, ink: "#0a0f1a" },
};

const PROJECT_SETTINGS_KEY = "nudge.project.settings";
function loadProjectSettings() {
  const merge = window.mergeNudgeSettings || ((settings) => settings || {});
  try {
    const raw = window.localStorage?.getItem(PROJECT_SETTINGS_KEY);
    return merge(raw ? JSON.parse(raw) : undefined);
  } catch (_) {
    return merge();
  }
}
function saveProjectSettings(settings) {
  try {
    window.localStorage?.setItem(PROJECT_SETTINGS_KEY, JSON.stringify(settings));
  } catch (_) {}
}

// Pipeline strip — signature visual moment pinned to the top of the
// viewport. Tone reflects the same weighted buyer↔seller alignment that
// PipelineWeather surfaces, so the strip is honest at-a-glance health:
// soft green breathing when stable, ember traversing when drifting, and a
// muted red pulse when heavy drift. Pulled into App so every route gets it
// without each screen having to mount its own.
function isClosedPipelineDealForStrip(deal) {
  return /^closed/i.test(deal.stage || "") || /^closed/i.test(deal.statusLabel || "");
}
function computePipelineStripState(deals) {
  const open = deals.filter((d) => !isClosedPipelineDealForStrip(d));
  if (open.length === 0) return { tone: "good" };
  const totalValue = open.reduce((s, d) => s + (d.value || 0), 0);
  if (totalValue <= 0) return { tone: "good" };
  const weighted = (key) =>
    Math.round(open.reduce((s, d) => s + (d[key] || 0) * d.value, 0) / totalValue);
  const buyer = weighted("buyerScore");
  const seller = weighted("sellerScore");
  const gap = Math.abs(buyer - seller);
  const tone = gap >= 25 ? "warn" : gap >= 10 ? "amber" : "good";
  return { tone };
}
function PipelineStrip({ deals }) {
  const { tone } = useMemo(() => computePipelineStripState(deals), [deals]);
  return (
    <div
      className={`pipeline-strip tone-${tone}`}
      role="presentation"
      aria-hidden="true"
      title={
        tone === "warn"
          ? "Heavy drift across the pipeline"
          : tone === "amber"
          ? "Drift forming"
          : "Stable pipeline"
      }
    >
      <span className="pipeline-strip-fill" />
    </div>
  );
}

function formatHeaderMoney(value = 0) {
  const n = Math.abs(value || 0);
  if (n >= 1000000) return `$${(n / 1000000).toFixed(n >= 10000000 ? 0 : 1)}M`;
  return `$${Math.round(n / 1000)}K`;
}

function WorkspaceHeader({ dashboardView, route, managerSection, deals = [], currentTourMode = null }) {
  const isManager = dashboardView === "manager";
  const showHeaderSlot = (!isManager && route === "deals") || (isManager && managerSection === "deals");
  const managerSecondAePipeline = 275000 + 390000 + 118000;
  const managerPipeline = deals
    .filter((deal) => !isClosedPipelineDealForStrip(deal))
    .reduce((sum, deal) => sum + (deal.value || 0), managerSecondAePipeline);
  const managerMeta = [
    { label: "AEs", value: "2" },
    { label: "deals", value: String((deals?.length || 0) + 3) },
    { label: "pipeline", value: formatHeaderMoney(managerPipeline) },
  ];
  const aeMeta = route === "home"
    ? [
        { label: "Pipeline check", value: "10:02 AM" },
      ]
    : [];
  const config = (() => {
    if (route === "settings") {
      return {
        eyebrow: "",
        dot: "good",
        title: "Settings",
        subtitle: "Manage your profile, team, integrations, and preferences.",
        meta: [],
      };
    }
    if (isManager) {
      if (managerSection === "deals") {
        return {
          eyebrow: "",
          dot: "amber",
          title: "Deal forecast review",
          subtitle: "Every team deal shown through the same buyer-proof lens as AE Deal Truth.",
          meta: managerMeta,
        };
      }
      if (managerSection === "config") {
        return {
          eyebrow: "",
          dot: "good",
          title: "Analytics",
          subtitle: "Buyer drop-off, stage concentration, and won/lost patterns across the team's pipeline.",
          meta: managerMeta,
        };
      }
      return {
        eyebrow: "",
        dot: "amber",
        title: "Team forecast cockpit",
        subtitle: "A manager lens on buyer proof, forecast risk, and coaching focus across the team.",
        meta: managerMeta,
      };
    }
    if (route === "deals") {
      return {
        eyebrow: "",
        dot: "good",
        title: "Deal Truth",
        subtitle: "Seller activity tells one side. Buyer progression tells the other. Deal Truth shows the full story.",
        meta: [],
      };
    }
    if (route === "analytics") {
      return {
        eyebrow: "",
        dot: "good",
        title: "Analytics",
        subtitle: "Buyer drop-off, stage concentration, and won/lost patterns across your pipeline.",
        meta: [],
      };
    }
    if (route === "alerts") {
      return {
        eyebrow: "",
        dot: "amber",
        title: "Nudges",
        subtitle: "Scout, Drafter, and Forecaster surface actions ranked by deal risk and decay.",
        meta: [{ label: "active nudges", value: "5" }],
      };
    }
    if (route === "setup") {
      return {
        eyebrow: "",
        dot: "good",
        title: "Set up Nudge",
        subtitle: "Verify your account, connect the sales stack, and generate the first buyer-proof readout.",
        meta: [],
      };
    }
    if (route === "playbook") {
      return {
        eyebrow: "",
        dot: "good",
        title: "Playbooks",
        subtitle: "Repeatable plays Addy runs on your behalf.",
        meta: [{ label: "active playbooks", value: "4" }],
      };
    }
    return {
      eyebrow: "",
      dot: "good",
      title: "Today's deal command",
      subtitle: "Buyer proof, next actions, and forecast risk in one ranked workspace.",
      meta: aeMeta,
    };
  })();

  return (
    <header className={`workspace-header ${showHeaderSlot ? "has-control-slot" : ""}`}>
      <div className="workspace-header-copy">
        {config.eyebrow && (
          <span className="platform-eyebrow">
            <span className={`cp-eyebrow-dot ${config.dot}`} aria-hidden />
            {config.eyebrow}
          </span>
        )}
        <h1 className="workspace-header-title">{config.title}</h1>
        <p className="workspace-header-subtitle">{config.subtitle}</p>
      </div>
      <div className={`workspace-header-actions ${showHeaderSlot ? "has-control-slot" : ""}`}>
        {showHeaderSlot && <div id="workspace-header-slot" className="workspace-header-slot" />}
        {currentTourMode && typeof window.TourTrigger === "function" && (
          <window.TourTrigger defaultMode={currentTourMode} />
        )}
        {config.meta.length > 0 && (
          <div className="workspace-header-meta">
            {config.meta.map((item) => (
              <div key={item.label} className="workspace-header-stat">
                <span className="workspace-header-stat-label">{item.label}</span>
                <span className="workspace-header-stat-value">{item.value}</span>
              </div>
            ))}
          </div>
        )}
      </div>
    </header>
  );
}

// Login stage — the universe matches the platform: dark canvas, ambient
// parallax dots, the same Nudge brand mark. Centered card with email +
// password. Prototype behaviour: any non-empty submit signs you in.
function LoginScreen({ onSubmit }) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [touched, setTouched] = useState(false);
  const emailRef = useRef(null);

  useEffect(() => {
    if (emailRef.current) emailRef.current.focus();
  }, []);

  const submit = (e) => {
    if (e) e.preventDefault();
    setTouched(true);
    // Prototype gate — any click signs you in, whether fields are filled
    // or not. Real auth lives behind a real backend; this is just a stage.
    onSubmit();
  };

  return (
    <div className="login-screen">
      <div className="login-card" role="dialog" aria-label="Sign in to Nudge">
        <header className="login-card-head">
          <span className="login-brand-mark" aria-hidden="true">
            <span className="brand-mark-dot brand-mark-dot--1" />
            <span className="brand-mark-dot brand-mark-dot--2" />
            <span className="brand-mark-dot brand-mark-dot--3" />
          </span>
          <span className="login-brand-wordmark">
            Nudge<sup>®</sup>
          </span>
        </header>

        <div className="login-card-intro">
          <h1 className="login-card-title">Sign in</h1>
          <p className="login-card-sub">
            Welcome back. Pick up your deals where you left them.
          </p>
        </div>

        <form className="login-form" onSubmit={submit} noValidate>
          <label className="login-field">
            <span className="login-field-label">Work email</span>
            <input
              ref={emailRef}
              type="email"
              autoComplete="email"
              className="login-input"
              placeholder="you@company.com"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              aria-invalid={touched && !email.trim()}
            />
          </label>

          <label className="login-field">
            <span className="login-field-label">
              Password
              <button type="button" className="login-field-aux" tabIndex={-1}>
                Forgot?
              </button>
            </span>
            <input
              type="password"
              autoComplete="current-password"
              className="login-input"
              placeholder="••••••••"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              aria-invalid={touched && !password.trim()}
            />
          </label>

          <button type="submit" className="btn accent login-submit">
            Sign in <Icon name="arrow" size={14} />
          </button>
        </form>

        <footer className="login-card-foot">
          <span>New here?</span>
          <button type="button" className="login-link" onClick={submit}>
            Request access
          </button>
        </footer>
      </div>
    </div>
  );
}

function App() {
  const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [projectSettings, setProjectSettings] = useState(loadProjectSettings);
  // Navigation starts at the login screen. Anything typed and submitted
  // counts as a successful sign-in — this is a prototype gate, not real
  // auth — so the rest of the app loads after the first click.
  const [loggedIn, setLoggedIn] = useState(false);
  const [route, setRouteRaw] = useState("deals");
  const [prevRoute, setPrevRoute] = useState("deals");
  const [settingsSection, setSettingsSection] = useState("profile");
  const setRoute = useCallback((next) => {
    setRouteRaw((current) => {
      if (next === "settings" && current !== "settings") setPrevRoute(current);
      return next;
    });
  }, []);
  const exitSettings = useCallback(() => {
    setRouteRaw(prevRoute && prevRoute !== "settings" ? prevRoute : "deals");
  }, [prevRoute]);
  const [dashboardView, setDashboardView] = useState(() => projectSettings.workspace?.defaultView || "ae");
  const [managerSection, setManagerSection] = useState("home");
  const [deals, setDeals] = useState(() => {
    const cloned = SEED_DEALS.map((deal) => ({
      ...deal,
      activity: [...(deal.activity || [])],
      meetings: deal.meetings ? deal.meetings.map((meeting) => ({ ...meeting })) : undefined,
    }));
    // Demo freshener — inject "right now" activity + a near-term meeting
    // per priority deal so every demo opens in a deal-in-motion state.
    return typeof window.freshenDeals === "function"
      ? window.freshenDeals(cloned)
      : cloned;
  });
  const [openId, setOpenId] = useState(null);
  const [detailTab, setDetailTab] = useState("overview");
  const [view, setView] = useState(tweaks.dealView || "cards");
  const [forecastOpen, setForecastOpen] = useState(false);
  const [sidebarExpanded, setSidebarExpanded] = useState(true);
  const handleProjectSettingsChange = useCallback((edits) => {
    setProjectSettings((current) => {
      const merge = window.mergeNudgeSettings || ((settings) => settings || {});
      return merge({ ...current, ...edits });
    });
    if (edits.workspace?.defaultView && edits.workspace.defaultView !== dashboardView) {
      setDashboardView(edits.workspace.defaultView);
      setManagerSection("home");
    }
  }, [dashboardView]);
  const resetProjectSettings = useCallback((nextSettings) => {
    const merge = window.mergeNudgeSettings || ((settings) => settings || {});
    const next = merge(nextSettings);
    setProjectSettings(next);
    setDashboardView(next.workspace?.defaultView || "ae");
    setRoute("home");
    setManagerSection("home");
  }, []);
  useEffect(() => {
    saveProjectSettings(projectSettings);
  }, [projectSettings]);
  // Global handle so the homepage weather banner (or any other surface)
  // can open the forecast — the sidebar no longer carries a dedicated
  // forecast button.
  useEffect(() => {
    window.openBuyerForecast = () => setForecastOpen(true);
    return () => { delete window.openBuyerForecast; };
  }, []);
  // Global receipts target — any AgentChip can call window.openReceipts({ ... })
  // to open the slide-over. Lives at App level so a single panel serves every
  // screen and modal.
  const [receiptPayload, setReceiptPayload] = useState(null);
  useEffect(() => {
    window.openReceipts = (payload) => setReceiptPayload(payload || null);
    return () => { delete window.openReceipts; };
  }, []);

  // Global toast — any CTA can call window.fireToast(msg) to confirm an
  // action. Single slot, auto-dismisses; new toasts replace the previous.
  const [toast, setToast] = useState(null);
  useEffect(() => {
    window.fireToast = (msg, opts = {}) => {
      if (!msg) return;
      setToast({ id: Date.now() + Math.random(), msg, ...opts });
    };
    return () => { delete window.fireToast; };
  }, []);

  // Action preview — modal that shows WHAT will happen before it fires.
  // Carousel CTAs call window.previewAction({ ..., onConfirm }) instead of
  // executing directly. Confirm runs the payload.onConfirm callback.
  const [previewPayload, setPreviewPayload] = useState(null);
  useEffect(() => {
    window.previewAction = (payload) => setPreviewPayload(payload || null);
    return () => { delete window.previewAction; };
  }, []);

  // Addy state — the sidebar logo dots are Addy's presence. State drives
  // their animation vocabulary (idle / scanning / has-something / speaking
  // / needs-you / listening). Demo hooks: window.setAddyState(state) and
  // window.setAddyUnread(n).
  const [addyState, setAddyState] = useState("idle");
  const [addyUnread, setAddyUnread] = useState(0);
  useEffect(() => {
    window.setAddyState = (state) => setAddyState(state || "idle");
    window.setAddyUnread = (n) => setAddyUnread(Math.max(0, Number(n) || 0));
    return () => {
      delete window.setAddyState;
      delete window.setAddyUnread;
    };
  }, []);

  // Addy whisper — floating bubble where she speaks. Single slot; a new
  // whisper replaces the previous one. While visible, dots run "speaking";
  // on dismiss they return to "idle". Every fired whisper is also pushed
  // onto whisperLog so the spotlight can show what she recently noticed.
  const [whisper, setWhisper] = useState(null);
  const [whisperLog, setWhisperLog] = useState([]);
  const dismissWhisper = useCallback((opts = {}) => {
    setWhisper(null);
    setAddyState((s) => (s === "speaking" ? "idle" : s));
    if (opts.unread) {
      setAddyUnread((n) => n + 1);
      setAddyState("has-something");
    }
  }, []);
  useEffect(() => {
    window.fireWhisper = (payload) => {
      if (!payload || !payload.text) return;
      const item = { id: Date.now() + Math.random(), at: new Date(), ...payload };
      setWhisper(item);
      setWhisperLog((log) => [item, ...log].slice(0, 20));
      setAddyState("speaking");
    };
    window.dismissWhisper = () => dismissWhisper();
    return () => {
      delete window.fireWhisper;
      delete window.dismissWhisper;
    };
  }, [dismissWhisper]);

  // Addy spotlight — summoned by clicking the sidebar dots or pressing
  // ⌘K / Ctrl+K. Anchored top-left so it reads as "growing from her eyes."
  // Opening absorbs any visible whisper, clears the unread badge, and
  // sets dots to "listening".
  const [spotlightOpen, setSpotlightOpen] = useState(false);
  const openSpotlight = useCallback(() => {
    setSpotlightOpen(true);
    setWhisper(null);
    setAddyUnread(0);
    setAddyState("listening");
  }, []);
  const closeSpotlight = useCallback(() => {
    setSpotlightOpen(false);
    setAddyState("idle");
  }, []);
  useEffect(() => {
    window.openAddy = openSpotlight;
    window.closeAddy = closeSpotlight;
    return () => {
      delete window.openAddy;
      delete window.closeAddy;
    };
  }, [openSpotlight, closeSpotlight]);
  // ⌘K / Ctrl+K — global summon hotkey.
  useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
        e.preventDefault();
        if (spotlightOpen) closeSpotlight();
        else openSpotlight();
      }
    };
    document.addEventListener("keydown", onKey);
    return () => document.removeEventListener("keydown", onKey);
  }, [spotlightOpen, openSpotlight, closeSpotlight]);

  // Keep the first screen on Deal Truth; the forecast remains available from
  // the sidebar instead of opening over the default experience.
  useEffect(() => {
    if (typeof window !== "undefined" && window !== window.parent) return;
    if (route !== "deals" && shouldShowForecastToday()) setForecastOpen(true);
  }, [route]);
  const closeForecast = useCallback(() => {
    markForecastShown();
    setForecastOpen(false);
  }, []);

  // Apply theme + density + accent CSS vars to document
  useEffect(() => {
    const themeTarget = document.documentElement;
    themeTarget.dataset.theme = tweaks.theme;
    themeTarget.dataset.density = tweaks.density;
    document.body.dataset.theme = tweaks.theme;
    document.body.dataset.density = tweaks.density;
    const a = ACCENTS[tweaks.accent] || ACCENTS.ember;
    const L = a.lightness ?? 0.74;
    const root = themeTarget.style;
    root.setProperty("--accent", `oklch(${L} ${a.chroma} ${a.hue})`);
    root.setProperty("--accent-soft", `oklch(${L} ${a.chroma} ${a.hue} / 0.18)`);
    root.setProperty("--accent-strong", `oklch(${Math.min(L + 0.05, 0.92)} ${a.chroma + 0.02} ${a.hue - 5})`);
    root.setProperty("--accent-ink", a.ink);
  }, [tweaks.theme, tweaks.density, tweaks.accent]);

  useEffect(() => { setView(tweaks.dealView); }, [tweaks.dealView]);

  // Hash-based deep link to the /brand preview. Doesn't replace the
  // sidebar router — just lets `#brand` (or `#/brand`) land directly on
  // the brand identity page so it can be linked from outside the app.
  useEffect(() => {
    const checkHash = () => {
      const h = (window.location.hash || "").replace(/^#\/?/, "");
      if (h === "brand" || h === "brand-preview") setRoute("brand");
    };
    checkHash();
    window.addEventListener("hashchange", checkHash);
    return () => window.removeEventListener("hashchange", checkHash);
  }, []);

  const openDeal = (id, initialTab = "overview") => {
    setDetailTab(initialTab);
    setOpenId(id);
  };
  const closeDeal = () => setOpenId(null);
  const updateDeal = useCallback((id, updater) => {
    setDeals((current) => current.map((deal) => {
      if (deal.id !== id) return deal;
      return typeof updater === "function" ? updater(deal) : { ...deal, ...updater };
    }));
  }, []);
  // Resolve the open deal. SEED_DEALS first; manager-synthetic deals
  // (mgr-meridian / mgr-evergreen / mgr-kestrel) aren't in SEED_DEALS so
  // fall back to the manager-shape lookup exposed by ManagerView when in
  // manager mode.
  const deal = (() => {
    if (!openId) return null;
    const fromSeed = deals.find((d) => d.id === openId);
    if (fromSeed) return fromSeed;
    if (dashboardView === "manager" && typeof window.findManagerDeal === "function") {
      return window.findManagerDeal(openId);
    }
    return null;
  })();

  const homeSuggestion = {
    title: "Would you like me to draft a follow-up email for Sarah?",
    sub: "I can prepare a personalised email based on the deal context.",
    deal: "atlas",
  };
  const dealsSuggestion = {
    title: "3 deals have seller activity ahead of buyer progression.",
    sub: "Switch lenses to see the seller story, buyer story, and full deal truth.",
  };
  const routeLabels = {
    home: "Home",
    deals: "Deal Truth",
    analytics: "Analytics",
    alerts: "Alerts",
    setup: "Setup",
    settings: "Settings",
    playbook: "Playbooks",
  };
  const routeLabel = routeLabels[route] || "Nudge";
  const currentTourMode = (() => {
    if (deal) return dashboardView === "manager" ? "mgr-deal-room" : "ae-deal-room";
    if (dashboardView === "manager" && managerSection === "deals") return "mgr-deals";
    if (dashboardView === "manager" && managerSection === "config") return "mgr-analytics";
    if (route === "home") return dashboardView === "manager" ? "manager" : "ae";
    if (route === "deals") return "deals";
    if (route === "analytics") return "analytics";
    if (route === "alerts") return "alerts";
    if (route === "setup") return "setup";
    if (route === "playbook") return "playbook";
    return null;
  })();
  const currentTourAutoKey = currentTourMode
    ? `nudge.demoTour.pageSeen.v3.${currentTourMode}`
    : "";
  const contextualSuggestion = deal
    ? {
        title: `Ask Addy about ${deal.company}`,
        sub: `${deal.stage} · ${(deal.value / 1000).toFixed(0)}K · ${deal.statusLabel}`,
        deal: deal.id,
      }
    : {
        title: `Ask Addy about ${routeLabel}`,
        sub: route === "deals" ? "Seller activity, buyer progression, and the full deal truth." : "Context-aware help for this section.",
      };
  const suggestion = route === "home" ? homeSuggestion : deal ? contextualSuggestion : route === "deals" ? dealsSuggestion : contextualSuggestion;
  const activeSuggestion = suggestion;
  const navigateTour = useCallback((mode = "ae") => {
    setPreviewPayload(null);
    setForecastOpen(false);
    if (mode === "ae-deal-room" || mode === "mgr-deal-room") {
      const isMgr = mode === "mgr-deal-room";
      setDashboardView(isMgr ? "manager" : "ae");
      setRoute("deals");
      setManagerSection("deals");
      setDetailTab("overview");
      setOpenId("atlas");
      return;
    }
    if (mode === "mgr-deals" || mode === "mgr-analytics") {
      setOpenId(null);
      setDashboardView("manager");
      setRoute("home");
      setManagerSection(mode === "mgr-deals" ? "deals" : "config");
      if (mode === "mgr-deals") {
        setView("cards");
        setTweak("dealView", "cards");
      }
      return;
    }
    if (mode === "deals" || mode === "analytics" || mode === "alerts" || mode === "setup" || mode === "playbook") {
      setOpenId(null);
      setRoute(mode);
      if (mode === "deals") setManagerSection("deals");
      if (mode === "deals") {
        setView("cards");
        setTweak("dealView", "cards");
      }
      return;
    }
    setOpenId(null);
    setDashboardView(mode === "manager" ? "manager" : "ae");
    setRoute("home");
    setManagerSection("home");
  }, []);

  // Login gate — the rest of the app is hidden until the user signs in.
  // Any non-empty submit counts as success (prototype, not real auth).
  if (!loggedIn) {
    return (
      <div className="login-stage">
        <PipelineStrip deals={deals} />
        <ParallaxDots />
        <LoginScreen onSubmit={() => setLoggedIn(true)} />
      </div>
    );
  }

  // Embedded mode (mobile preview iframe): show only the deals list view.
  // No sidebar, no AI dock, no forecast popup, no tweaks panel, no tour.
  const isEmbedded = typeof window !== "undefined" && window !== window.parent;
  if (isEmbedded) {
    return (
      <div className="app is-embedded">
        <PipelineStrip deals={deals} />
        <ParallaxDots />
        <div className="workspace">
          <DealsScreen
            deals={deals}
            openDeal={openDeal}
            view="table"
            setView={() => {}}
            embedded
          />
          {deal && <DealDetail deal={deal} onClose={closeDeal} onUpdateDeal={updateDeal} initialTab={detailTab} managerMode={dashboardView === "manager"} />}
        </div>
      </div>
    );
  }

  return (
    <div className={`app ${sidebarExpanded ? "sidebar-expanded" : ""}`}>
      <PipelineStrip deals={deals} />
      <ParallaxDots />
      <Sidebar
        route={route}
        setRoute={setRoute}
        theme={tweaks.theme}
        setTheme={(t) => setTweak("theme", t)}
        nudgeCount={5}
        dashboardView={dashboardView}
        setDashboardView={setDashboardView}
        managerSection={managerSection}
        setManagerSection={setManagerSection}
        addyState={addyState}
        addyUnread={addyUnread}
        expanded={sidebarExpanded}
        onToggleExpanded={() => setSidebarExpanded((v) => !v)}
        profile={projectSettings.profile}
        workspace={projectSettings.workspace}
      />
      <div className="workspace">
        <WorkspaceHeader
          dashboardView={dashboardView}
          route={route}
          managerSection={managerSection}
          deals={deals}
          currentTourMode={currentTourMode}
        />
        <div className="workspace-main">
          {route === "home" && (
            <HomeScreen
              deals={deals}
              openDeal={openDeal}
              dashboardView={dashboardView}
              setDashboardView={setDashboardView}
              managerSection={managerSection}
              setManagerSection={setManagerSection}
              showPageHeader={false}
            />
          )}
          {route === "deals" && <DealsScreen deals={deals} openDeal={openDeal} view={view} setView={(v) => { setView(v); setTweak("dealView", v); }} showPageHeader={false} />}
          {route === "analytics" && <AnalyticsScreen deals={deals} openDeal={openDeal} showPageHeader={false} />}
          {route === "alerts" && <AlertsScreen deals={deals} openDeal={openDeal} showPageHeader={false} />}
          {route === "setup" && <SetupScreen goToDeals={() => setRoute("deals")} showPageHeader={false} />}
          {route === "settings" && typeof SettingsModule === "function" && (
            <SettingsModule
              settings={projectSettings}
              onSettingsChange={handleProjectSettingsChange}
              onReset={resetProjectSettings}
              active={settingsSection}
              onActiveChange={setSettingsSection}
              onSave={() => {
                if (typeof window.fireToast === "function") window.fireToast("Settings saved");
              }}
            />
          )}
          {route === "playbook" && <PlaybooksScreen showPageHeader={false} />}
          {route === "brand" && typeof window.BrandPreview === "function" && <window.BrandPreview />}

          {deal && <DealDetail deal={deal} onClose={closeDeal} onUpdateDeal={updateDeal} initialTab={detailTab} managerMode={dashboardView === "manager"} />}
        </div>
      </div>

      <BuyerForecast
        open={forecastOpen}
        onClose={closeForecast}
        deals={deals}
        onOpenDeal={openDeal}
      />

      <ReceiptsPanel payload={receiptPayload} onClose={() => setReceiptPayload(null)} />

      <NudgeToast toast={toast} onDismiss={() => setToast(null)} />

      <ActionPreview payload={previewPayload} onClose={() => setPreviewPayload(null)} />

      <AddyWhisper whisper={whisper} onDismiss={dismissWhisper} />

      <AddySpotlight
        open={spotlightOpen}
        onClose={closeSpotlight}
        whisperLog={whisperLog}
      />

      <TweaksPanel title="Tweaks">
        <TweakSection label="Appearance">
          <TweakRadio label="Theme" value={tweaks.theme} onChange={(v) => setTweak("theme", v)}
            options={[{ value: "dark", label: "Dark" }, { value: "light", label: "Light" }]} />
          <TweakSelect label="Accent" value={tweaks.accent} onChange={(v) => setTweak("accent", v)}
            options={[
              { value: "ember", label: "Ember (default)" },
              { value: "electric", label: "Electric violet" },
              { value: "citrus", label: "Citrus" },
              { value: "rose", label: "Rose" },
              { value: "graphite", label: "Graphite" },
            ]} />
          <TweakRadio label="Density" value={tweaks.density} onChange={(v) => setTweak("density", v)}
            options={[{ value: "comfortable", label: "Comfortable" }, { value: "compact", label: "Compact" }]} />
        </TweakSection>
        <TweakSection label="Layout">
          <TweakRadio label="AI dock" value={tweaks.dock} onChange={(v) => setTweak("dock", v)}
            options={[{ value: "left", label: "Left" }, { value: "center", label: "Center" }, { value: "right", label: "Right" }]} />
          <TweakRadio label="Deal Truth layout" value={tweaks.dealView} onChange={(v) => setTweak("dealView", v)}
            options={[{ value: "cards", label: "Kanban" }, { value: "table", label: "List" }]} />
        </TweakSection>
        <TweakSection label="Quick jump">
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 6 }}>
            {["home", "deals", "analytics", "alerts", "setup", "playbook", "brand"].map((r) => (
              <button key={r} className="btn sm" onClick={() => setRoute(r)} style={{ justifyContent: "center" }}>
                {r}
              </button>
            ))}
          </div>
        </TweakSection>
      </TweaksPanel>

      <DemoTour
        onNavigate={navigateTour}
        autoStartOnceKey={currentTourAutoKey}
        autoStartMode={currentTourMode}
      />
    </div>
  );
}

// Format big money — used by AnimatedMoney as the dollars tick up.
// Matches the existing fmtK shape used by carousels ($X.XM or $XXX K).
function fmtBigMoney(n) {
  const abs = Math.abs(n);
  if (abs >= 1000000) return `$${(abs / 1000000).toFixed(abs >= 10000000 ? 0 : 1)}M`;
  return `$${Math.round(abs / 1000)}K`;
}

// Tick a dollar value from $0 up to the target on mount. Quartic ease-out
// so it sprints out, then lingers on the final value — felt as the system
// "landing" on a number, not just animating one in. ~1.2s feels noticed
// without dragging on the modal reveal.
function AnimatedMoney({ value, duration = 1200, delay = 220 }) {
  const [n, setN] = useState(0);
  useEffect(() => {
    if (!value || value <= 0) { setN(0); return; }
    let raf;
    let startDelay;
    const begin = (startTime) => {
      const step = (t) => {
        const p = Math.min(1, (t - startTime) / duration);
        const eased = 1 - Math.pow(1 - p, 4);
        setN(value * eased);
        if (p < 1) raf = requestAnimationFrame(step);
      };
      raf = requestAnimationFrame(step);
    };
    // small delay so the modal slide-in plays first, then the number ticks.
    startDelay = setTimeout(() => begin(performance.now()), delay);
    return () => {
      if (raf) cancelAnimationFrame(raf);
      if (startDelay) clearTimeout(startDelay);
    };
  }, [value, duration, delay]);
  return fmtBigMoney(n);
}

// Global action toast. Pops bottom-center when window.fireToast(msg) is
// called. Auto-dismisses after ~3.5s; consecutive toasts swap the slot.
function NudgeToast({ toast, onDismiss }) {
  useEffect(() => {
    if (!toast) return;
    const t = setTimeout(onDismiss, 3500);
    return () => clearTimeout(t);
  }, [toast && toast.id, onDismiss]);
  if (!toast) return null;
  return (
    <div key={toast.id} className="nudge-toast" role="status" aria-live="polite">
      <span className="nudge-toast-dot" aria-hidden="true" />
      <span className="nudge-toast-msg">{toast.msg}</span>
    </div>
  );
}

// Action preview modal — shown before any CTA fires its action. Renders a
// drafted message / challenge / review item, with evidence backing the
// move. Cancel closes; Confirm calls payload.onConfirm and closes.
function ActionPreview({ payload, onClose }) {
  useEffect(() => {
    if (!payload) return;
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    const prev = document.body.style.overflow;
    document.body.style.overflow = "hidden";
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.style.overflow = prev;
    };
  }, [payload, onClose]);

  if (!payload) return null;

  const confirm = () => {
    if (typeof payload.onConfirm === "function") payload.onConfirm();
    onClose();
  };

  return (
    <div className="action-preview-back" onClick={onClose} role="presentation">
      <div
        className="action-preview"
        data-tour="action-preview"
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-label={payload.title || "Action preview"}
      >
        <header className="action-preview-head">
          <div className="action-preview-head-text">
            <span className="action-preview-kicker">
              {payload.kicker || "Preview"}
            </span>
            <h2 className="action-preview-title">{payload.title}</h2>
          </div>
          <button
            type="button"
            className="action-preview-close"
            onClick={onClose}
            aria-label="Close preview"
          >
            <Icon name="close" size={14} />
          </button>
        </header>

        {Array.isArray(payload.meta) && payload.meta.length > 0 && (
          <dl className="action-preview-meta">
            {payload.meta.map((m, i) => (
              <React.Fragment key={i}>
                <dt>{m.label}</dt>
                <dd>{m.value}</dd>
              </React.Fragment>
            ))}
          </dl>
        )}

        {payload.body && (
          <div className="action-preview-body">
            {payload.body.subject && (
              <div className="action-preview-subject">
                <span className="action-preview-mini-label">Subject</span>
                <p>{payload.body.subject}</p>
              </div>
            )}
            {payload.body.text && (
              <div className="action-preview-text">
                <span className="action-preview-mini-label">
                  {payload.body.label || "Message"}
                </span>
                <p>{payload.body.text}</p>
              </div>
            )}
          </div>
        )}

        {payload.impact && (
          <div className="action-preview-impact" data-tour="action-impact">
            <span className="action-preview-mini-label">Expected impact</span>
            <div className="action-preview-impact-panel">
              {(payload.impact.firmness || payload.impact.firmnessRaw) && (
                <div className="action-preview-impact-hero">
                  <b>+{payload.impact.firmnessRaw != null
                       ? <AnimatedMoney value={payload.impact.firmnessRaw} />
                       : payload.impact.firmness}</b>
                  <em>forecast firmness</em>
                </div>
              )}
              <div className="action-preview-impact-rows">
                {payload.impact.likelihood != null && (
                  <div className="action-preview-impact-row">
                    <div className="action-preview-impact-row-head">
                      <span className="action-preview-impact-row-label">Likelihood</span>
                      <b>{payload.impact.likelihood}%</b>
                      <em>within {payload.impact.timeHorizon || "48h"}</em>
                    </div>
                    <div className="action-preview-prob" aria-hidden="true">
                      <span style={{ width: `${Math.max(2, Math.min(100, payload.impact.likelihood))}%` }} />
                    </div>
                  </div>
                )}
                {payload.impact.gapBefore != null && payload.impact.gapBefore > 0 && (
                  <div className="action-preview-impact-row">
                    <div className="action-preview-impact-row-head">
                      <span className="action-preview-impact-row-label">{payload.impact.gapLabel || "Gap close"}</span>
                      <span className="action-preview-gap-flow">
                        <b className="from">{payload.impact.gapBefore}pt</b>
                        <Icon name="arrow" size={11} />
                        <b className="to">~{payload.impact.gapAfter != null ? payload.impact.gapAfter : Math.round(payload.impact.gapBefore * 0.3)}pt</b>
                      </span>
                    </div>
                  </div>
                )}
              </div>
            </div>
          </div>
        )}

        {Array.isArray(payload.evidence) && payload.evidence.length > 0 && (
          <div className="action-preview-evidence">
            <span className="action-preview-mini-label">Evidence</span>
            <ul>
              {payload.evidence.map((e, i) => (
                <li key={i}>
                  {typeof e === "string"
                    ? e
                    : (<><b>{e.label}</b>{e.label ? " · " : ""}{e.value}</>)}
                </li>
              ))}
            </ul>
          </div>
        )}

        <footer className="action-preview-foot">
          <button type="button" className="coach-btn ghost" onClick={onClose}>
            Cancel
          </button>
          <button
            type="button"
            className="coach-cta action-preview-confirm"
            onClick={confirm}
          >
            <span className="coach-cta-text">{payload.primaryLabel || "Confirm"}</span>
            <span className="coach-cta-track" aria-hidden="true">
              <span className="coach-cta-dot" />
              <span className="coach-cta-dot" />
              <span className="coach-cta-dot" />
              <Icon name="arrow" size={14} className="coach-cta-arrow" />
            </span>
          </button>
        </footer>
      </div>
    </div>
  );
}

// Addy whisper — Addy's voice surfacing in context. Pops bottom-right of
// the viewport. Auto-dismisses unless dismissed manually. Identity tied to
// the sidebar dots via the mini ·· mark inside the bubble. Up to 3 actions
// + always a close × (dismisses without acting). Esc closes too.
function AddyWhisper({ whisper, onDismiss }) {
  useEffect(() => {
    if (!whisper) return;
    const duration = whisper.durationMs || 12000;
    const t = setTimeout(() => onDismiss({ unread: true }), duration);
    const onKey = (e) => { if (e.key === "Escape") onDismiss({ unread: true }); };
    document.addEventListener("keydown", onKey);
    return () => {
      clearTimeout(t);
      document.removeEventListener("keydown", onKey);
    };
  }, [whisper && whisper.id, onDismiss]);

  if (!whisper) return null;

  const variant = whisper.variant || "default";
  const actions = (whisper.actions || []).slice(0, 3);

  return (
    <div
      key={whisper.id}
      className={`addy-whisper variant-${variant}`}
      role="status"
      aria-live="polite"
    >
      <header className="addy-whisper-head">
        <span className="addy-whisper-mark" aria-hidden="true">
          <span className="addy-whisper-mark-dot" />
          <span className="addy-whisper-mark-dot" />
          <span className="addy-whisper-mark-dot" />
        </span>
        <span className="addy-whisper-name">Addy</span>
        <button
          type="button"
          className="addy-whisper-close"
          onClick={() => onDismiss({ unread: true })}
          aria-label="Dismiss"
        >
          <Icon name="close" size={11} />
        </button>
      </header>
      <p className="addy-whisper-text">{whisper.text}</p>
      {actions.length > 0 && (
        <div className="addy-whisper-actions">
          {actions.map((a, i) => (
            <button
              key={i}
              type="button"
              className={`addy-whisper-btn ${i === 0 ? "is-primary" : "is-ghost"}`}
              onClick={() => {
                if (typeof a.onClick === "function") a.onClick();
                // Acting on a whisper consumes it — no unread accrual.
                onDismiss({ unread: false });
              }}
            >
              {a.label}
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

// AddyTrace — small ◆ Addy badge on any card she originated, with a
// click-to-trace popover. Visually ties to the dots (same ·· mark, accent
// color). Trace content is per-card: signal → variable → drafted action →
// time. Exposed via window.AddyTrace so screen files can render it without
// prop-drilling. Popover dismisses on outside click or Esc.
function AddyTrace({ signal, variable, action, drafted, time }) {
  const [open, setOpen] = useState(false);
  const wrapRef = useRef(null);

  useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false);
    };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  return (
    <span className="addy-trace-wrap" ref={wrapRef}>
      <button
        type="button"
        className={`addy-trace-chip ${open ? "is-open" : ""}`}
        onClick={(e) => { e.stopPropagation(); setOpen((v) => !v); }}
        aria-expanded={open}
        aria-label="View Addy trace"
      >
        <span className="addy-trace-chip-mark" aria-hidden="true">
          <span className="addy-trace-chip-dot" />
          <span className="addy-trace-chip-dot" />
          <span className="addy-trace-chip-dot" />
        </span>
        <span className="addy-trace-chip-text">
          {drafted ? "Drafted by Addy" : "Surfaced by Addy"}
          {time && <em> · {time}</em>}
        </span>
      </button>
      {open && (
        <div className="addy-trace-pop" role="dialog" aria-label="Addy trace">
          <header className="addy-trace-pop-head">
            <span className="addy-trace-pop-mark" aria-hidden="true">
              <span /><span />
            </span>
            <b>How Addy got here</b>
          </header>
          <ol className="addy-trace-steps">
            {signal && (
              <li>
                <span className="addy-trace-step-label">Signal</span>
                <p>{signal}</p>
              </li>
            )}
            {variable && (
              <li>
                <span className="addy-trace-step-label">Variable</span>
                <p>{variable}</p>
              </li>
            )}
            {action && (
              <li>
                <span className="addy-trace-step-label">{drafted ? "Drafted" : "Surfaced"}</span>
                <p>{action}</p>
              </li>
            )}
            {time && (
              <li>
                <span className="addy-trace-step-label">When</span>
                <p>{time}</p>
              </li>
            )}
          </ol>
        </div>
      )}
    </span>
  );
}

// Tiny relative-time formatter — "just now", "12m ago", "2h ago".
function addyTimeAgo(date) {
  if (!date) return "";
  const ms = Date.now() - new Date(date).getTime();
  if (ms < 60000)    return "just now";
  if (ms < 3600000)  return `${Math.round(ms / 60000)}m ago`;
  if (ms < 86400000) return `${Math.round(ms / 3600000)}h ago`;
  return `${Math.round(ms / 86400000)}d ago`;
}

// Addy spotlight — summoned by clicking the sidebar dots or ⌘K. Anchored
// top-left so it reads as growing from her eyes. Shows what she recently
// noticed (the whisper log), plus a free-text ask input.
function AddySpotlight({ open, onClose, whisperLog }) {
  const inputRef = useRef(null);
  const threadRef = useRef(null);
  const replyTimerRef = useRef(null);
  const [messages, setMessages] = useState([]);
  const [thinking, setThinking] = useState(false);

  useEffect(() => {
    if (!open) {
      // Reset thread when the spotlight closes so each summon starts fresh.
      setMessages([]);
      setThinking(false);
      if (replyTimerRef.current) {
        clearTimeout(replyTimerRef.current);
        replyTimerRef.current = null;
      }
      return;
    }
    const t = setTimeout(() => { if (inputRef.current) inputRef.current.focus(); }, 220);
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("keydown", onKey);
    return () => {
      clearTimeout(t);
      document.removeEventListener("keydown", onKey);
    };
  }, [open, onClose]);

  // Auto-scroll the thread to the latest message.
  useEffect(() => {
    const el = threadRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages.length, thinking]);

  if (!open) return null;

  const recent = (whisperLog || []).slice(0, 6);
  const noticed = recent.length;
  const hasThread = messages.length > 0 || thinking;

  // Cheap improv replies so Addy responds in-character without an LLM call.
  // Matched by keywords; falls through to a neutral acknowledgment.
  const improviseReply = (q) => {
    const t = q.toLowerCase();
    if (/(next|move|do now|priorit)/.test(t)) return "Pull the Atlas pricing thread first — it's been quiet 9 days and the redline-clarification draft is the lowest-friction restart. I have it ready.";
    if (/(summar|status|where)/.test(t))     return "Three deals carry risk: Atlas (procurement blocked), Nexus (single-threaded), Quantum (no decision criteria). Atlas is the most leveraged of the three.";
    if (/(draft|write|email)/.test(t))       return "I can draft a champion check-in for Atlas or a finance intro for Nexus. Which one helps more right now?";
    if (/(forecast|number|commit)/.test(t))  return "Weighted forecast holds at $2.4M, but $640K of that depends on Atlas closing — worth treating that one as the swing.";
    if (/(why|reason)/.test(t))              return "Buyer activity dropped 15 pts on Atlas after the procurement handoff, and the champion went dark for 9 days. That's the signal.";
    if (/(thank|thanks|ok|got it)/.test(t))  return "On it. I'll surface anything new as it lands.";
    return "Got it. I'll pull the latest on that and surface what I'm seeing.";
  };

  const onAskKey = (e) => {
    if (e.key === "Enter" && e.target.value.trim()) {
      e.preventDefault();
      const q = e.target.value.trim();
      e.target.value = "";
      setMessages((m) => [...m, { role: "user", text: q, ts: Date.now() }]);
      setThinking(true);
      if (replyTimerRef.current) clearTimeout(replyTimerRef.current);
      replyTimerRef.current = setTimeout(() => {
        setThinking(false);
        setMessages((m) => [...m, { role: "addy", text: improviseReply(q), ts: Date.now() }]);
        replyTimerRef.current = null;
      }, 900);
    }
  };

  return (
    <div className="addy-spotlight-back" onClick={onClose} role="presentation">
      <div
        className="addy-spotlight"
        onClick={(e) => e.stopPropagation()}
        role="dialog"
        aria-label="Addy"
      >
        <header className="addy-spotlight-head">
          <span className="addy-spotlight-mark" aria-hidden="true">
            <span className="addy-spotlight-mark-dot" />
            <span className="addy-spotlight-mark-dot" />
            <span className="addy-spotlight-mark-dot" />
          </span>
          <div className="addy-spotlight-head-text">
            <b>Addy</b>
            <span>
              Watching 12 deals
              {noticed > 0 && <> · noticed <b>{noticed}</b> thing{noticed === 1 ? "" : "s"}</>}
            </span>
          </div>
          <button
            type="button"
            className="addy-spotlight-close"
            onClick={onClose}
            aria-label="Close"
          >
            <Icon name="close" size={12} />
          </button>
        </header>

        {hasThread ? (
          <ul className="addy-spotlight-thread" ref={threadRef}>
            {messages.map((m, i) => (
              <li key={i} className={`addy-spot-msg role-${m.role}`}>
                {m.role === "addy" && <span className="addy-spot-msg-avatar" aria-hidden="true">A</span>}
                <p>{m.text}</p>
              </li>
            ))}
            {thinking && (
              <li className="addy-spot-msg role-addy is-thinking" aria-label="Addy is typing">
                <span className="addy-spot-msg-avatar" aria-hidden="true">A</span>
                <p className="addy-spot-typing">
                  <span /><span /><span />
                </p>
              </li>
            )}
          </ul>
        ) : recent.length > 0 ? (
          <ul className="addy-spotlight-notices">
            {recent.map((w) => {
              const variant = w.variant || "default";
              const actions = (w.actions || []).slice(0, 2);
              return (
                <li key={w.id} className={`addy-spotlight-notice variant-${variant}`}>
                  <span className="addy-spotlight-notice-dot" aria-hidden="true" />
                  <div className="addy-spotlight-notice-body">
                    <p>{w.text}</p>
                    {actions.length > 0 && (
                      <div className="addy-spotlight-notice-actions">
                        {actions.map((a, ai) => (
                          <button
                            key={ai}
                            type="button"
                            className={`addy-whisper-btn ${ai === 0 ? "is-primary" : "is-ghost"}`}
                            onClick={() => {
                              if (typeof a.onClick === "function") a.onClick();
                              onClose();
                            }}
                          >
                            {a.label}
                          </button>
                        ))}
                      </div>
                    )}
                  </div>
                  <span className="addy-spotlight-notice-time">{addyTimeAgo(w.at)}</span>
                </li>
              );
            })}
          </ul>
        ) : (
          <p className="addy-spotlight-empty">
            Quiet so far. I&rsquo;ll surface things as they happen.
          </p>
        )}

        <div className="addy-spotlight-ask">
          <input
            ref={inputRef}
            type="text"
            className="addy-spotlight-input"
            placeholder="Ask me anything…"
            onKeyDown={onAskKey}
          />
          <span className="addy-spotlight-hint" aria-hidden="true">
            <kbd>⌘ K</kbd> · <kbd>Esc</kbd>
          </span>
        </div>
      </div>
    </div>
  );
}

// Expose AddyTrace so screen files (screens-home / screens-forecast-model)
// can render the provenance chip without prop drilling.
window.AddyTrace = AddyTrace;

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