// App shell: grouped sidebar nav, sessions, topbar, router, theme + prefs, toasts.
// Adapted from the Claude Design prototype for production: preferences and the
// last route persist to localStorage (see tweaks.js), the Settings page is the
// single source of truth for theme/accent/density/motion (the prototype's
// floating Tweaks dock is removed), and the sidebar uses the real Sylar avatar.
const { useState, useEffect, useRef, useCallback } = React;

const NAV = [
  { id: "main", section: "Main", items: [
    { id: "dashboard", label: "Dashboard", icon: "overview" },
    { id: "news", label: "News", icon: "activity" },
    { id: "chat", label: "Chat", icon: "chat" },
    { id: "files", label: "Files", icon: "files" },
    { id: "terminal", label: "Terminal", icon: "terminal" },
    { id: "kanban", label: "Kanban", icon: "kanban" },
    { id: "projects", label: "Projects", icon: "folder" },
    { id: "intelligence", label: "Project Intelligence", icon: "memory" },
    { id: "operations", label: "Operations", icon: "operations" },
    { id: "advanced", label: "Advanced", icon: "sliders" },
  ]},
];

// Full page inventory, grouped the way the sidebar used to be, now reachable
// via the Advanced hub page instead of directly in the sidebar. Nothing here
// was removed — every route below is still in ROUTES/PAGE_COMP/pageProps.
const ADVANCED_GROUPS = [
  { id: "workspace", section: "Workspace", items: [
    { id: "jobs", label: "Jobs", icon: "jobs", desc: "One-off and recurring background task runs." },
    { id: "conductor", label: "Conductor", icon: "conductor", desc: "Orchestrate multi-agent workflows end to end." },
    { id: "swarm", label: "Swarm", icon: "swarm", desc: "Coordinate parallel agent swarms on a task." },
    { id: "playground", label: "Playground", icon: "play", desc: "Sandbox for testing prompts and model behavior." },
  ]},
  { id: "knowledge", section: "Knowledge", items: [
    { id: "memory", label: "Memory", icon: "memory", desc: "Browse and curate the workspace's long-term memory." },
    { id: "skills", label: "Skills", icon: "skills", desc: "Manage installed skills available to agents." },
    { id: "mcp", label: "MCP", icon: "mcp", desc: "Configure MCP server connectors." },
    { id: "profiles", label: "Profiles", icon: "profiles", desc: "Manage agent profiles and their defaults." },
  ]},
  { id: "system", section: "System", items: [
    { id: "gateway", label: "Gateway", icon: "providers", desc: "Provider and gateway connection settings." },
    { id: "models", label: "Models", icon: "cpu", desc: "Model routing, defaults, and availability." },
    { id: "config", label: "Configuration", icon: "sliders", desc: "Low-level workspace configuration." },
  ]},
  { id: "admin", section: "Admin", items: [
    { id: "status", label: "System", icon: "gauge", desc: "Gateway status, host stats, and maintenance ops." },
    { id: "logs", label: "Logs", icon: "file", desc: "Search and tail workspace and agent logs." },
    { id: "channels", label: "Channels", icon: "discord", desc: "Connected messaging channels and bots." },
    { id: "webhooks", label: "Webhooks", icon: "external", desc: "Inbound and outbound webhook configuration." },
    { id: "keys", label: "Keys & Env", icon: "lock", desc: "API keys and environment variables." },
    { id: "pairing", label: "Pairing", icon: "check", desc: "Pair and authorize new devices or clients." },
  ]},
];

// route -> window component name. Looked up lazily so a page script that is
// still compiling (or being built) renders a placeholder instead of crashing.
const PAGE_COMP = {
  dashboard: "PageDashboard", chat: "PageChat", files: "PageFiles", terminal: "PageTerminal",
  jobs: "PageJobs", kanban: "PageKanban", conductor: "PageConductor", operations: "PageOperations",
  swarm: "PageSwarm", gateway: "PageGateway", models: "PageModels", config: "PageConfig", playground: "PagePlayground", projects: "PageProjects",
  memory: "PageMemory", skills: "PageSkills", mcp: "PageMCP", profiles: "PageProfiles", settings: "PageSettings",
  intelligence: "PageProjectIntelligence",
  status: "PageSystem", logs: "PageLogs", channels: "PageMessaging", webhooks: "PageWebhooks", keys: "PageKeys", pairing: "PagePairing",
  advanced: "PageAdvanced", news: "PageNews",
};
const HIDDEN_ROUTES = new Set(ADVANCED_GROUPS.flatMap((g) => g.items.map((i) => i.id)));
const ROUTES = new Set(NAV.flatMap((g) => g.items.map((i) => i.id)).concat("settings", [...HIDDEN_ROUTES]));

function Toast({ msg }) {
  if (!msg) return null;
  return (
    <div role="status" aria-live="polite" style={{ position: "fixed", bottom: 24, left: "50%", transform: "translateX(-50%)", zIndex: 90, background: "var(--surface-3)", border: "1px solid var(--line-strong)", color: "var(--text)", padding: "12px 18px", borderRadius: 12, boxShadow: "var(--shadow)", fontSize: 13.5, fontWeight: 550, display: "flex", alignItems: "center", gap: 10, maxWidth: "90vw" }}>
      <span className="status-dot running live" style={{ color: "var(--success)" }} />{msg}
    </div>
  );
}

const TWEAK_DEFAULTS = {
  theme: "obsidian",
  accent: "default",
  density: "comfortable",
  motion: "on",
  collapsed: false,
  route: "dashboard",
};

// Catches render/lifecycle errors thrown by a page component so one broken
// page can't blank out the whole app shell (sidebar/topbar stay usable).
// Keyed by route+param from the caller so switching pages resets the boundary.
class PageErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
  static getDerivedStateFromError(error) {
    return { error };
  }
  componentDidCatch(error, info) {
    console.error("Page crashed:", error, info);
  }
  render() {
    if (this.state.error) {
      return (
        <div className="page">
          <div className="card" style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            <div className="card-head">
              <div>
                <h2>Something went wrong</h2>
                <p>This page crashed while rendering. The rest of the app is unaffected.</p>
              </div>
            </div>
            <pre className="mono" style={{ margin: 0, padding: "12px 14px", border: "1px solid var(--line)", borderRadius: 10, background: "var(--surface-2)", fontSize: 12, lineHeight: 1.55, color: "var(--muted)", maxHeight: 320, overflow: "auto", whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
              {String((this.state.error && this.state.error.message) || this.state.error)}
            </pre>
            <button className="btn btn-primary" style={{ alignSelf: "flex-start" }} onClick={() => window.location.reload()}>
              <Icon name="refresh" size={15} />Reload page
            </button>
          </div>
        </div>
      );
    }
    return this.props.children;
  }
}

function NavGroup({ grp, route, go, collapsed }) {
  const [open, setOpen] = useState(true);
  return (
    <React.Fragment>
      <button type="button" className={cls("nav-group-head", !open && "closed")} aria-expanded={open} onClick={() => setOpen((o) => !o)}>
        <span className="nav-section">{grp.section}</span>
        {!collapsed && <Icon name="chevronDown" size={14} className="chev" />}
      </button>
      {open && grp.items.map((it) => {
        const active = it.id === "advanced" ? (route === "advanced" || HIDDEN_ROUTES.has(route)) : route === it.id;
        return (
          <button key={it.id} className={cls("nav-item", active && "active")} onClick={() => go(it.id)} title={it.label}>
            <Icon name={it.icon} size={20} /><span className="nav-label">{it.label}</span>
          </button>
        );
      })}
    </React.Fragment>
  );
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [route, setRoute] = useState(() => {
    const linked = new URLSearchParams(window.location.search).get("view");
    return ROUTES.has(linked) ? linked : (ROUTES.has(t.route) ? t.route : "dashboard");
  });
  const [param, setParam] = useState(null);
  const [collapsed, setCollapsed] = useState(t.collapsed);
  const [mobileOpen, setMobileOpen] = useState(false);
  const [toastMsg, setToastMsg] = useState(null);
  const searchRef = useRef(null);
  const toastTimer = useRef(null);

  useEffect(() => { document.documentElement.setAttribute("data-theme", t.theme); }, [t.theme]);
  useEffect(() => {
    const root = document.documentElement;
    const a = (window.ACCENTS || {})[t.accent];
    const vars = ["--primary", "--primary-strong", "--primary-fg", "--primary-soft", "--primary-ring"];
    if (a) {
      root.style.setProperty("--primary", a.primary);
      root.style.setProperty("--primary-strong", a.strong);
      root.style.setProperty("--primary-fg", a.fg);
      root.style.setProperty("--primary-soft", `color-mix(in oklab, ${a.primary} 14%, transparent)`);
      root.style.setProperty("--primary-ring", `color-mix(in oklab, ${a.primary} 42%, transparent)`);
    } else {
      vars.forEach((v) => root.style.removeProperty(v));
    }
  }, [t.accent, t.theme]);
  useEffect(() => { document.documentElement.setAttribute("data-density", t.density); }, [t.density]);
  useEffect(() => { document.documentElement.setAttribute("data-motion", t.motion); }, [t.motion]);
  useEffect(() => { setCollapsed(t.collapsed); }, [t.collapsed]);

  const toast = useCallback((msg) => { setToastMsg(msg); clearTimeout(toastTimer.current); toastTimer.current = setTimeout(() => setToastMsg(null), 2600); }, []);
  const go = useCallback((r, p = null) => {
    setRoute(r); setParam(p); setMobileOpen(false); window.scrollTo({ top: 0 });
    if (ROUTES.has(r)) {
      setTweak("route", r);
      const linked = new URL(window.location.href);
      linked.searchParams.set("view", r);
      if (r !== "intelligence") linked.searchParams.delete("tab");
      window.history.replaceState({}, "", linked);
    }
  }, [setTweak]);

  useEffect(() => {
    const onKey = (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") { e.preventDefault(); searchRef.current?.focus(); } };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const pageProps = {
    dashboard: { go }, chat: { toast, sessionId: param }, files: { toast }, terminal: { toast },
    jobs: { toast }, kanban: { toast }, conductor: { go, toast }, operations: { go, toast },
    swarm: { go, toast }, gateway: { toast }, models: { toast }, config: { toast }, playground: { toast }, projects: { go, toast },
    memory: { toast }, skills: { toast }, mcp: { toast }, profiles: { toast },
    intelligence: { toast },
    status: { toast }, logs: { toast }, channels: { toast }, webhooks: { toast }, keys: { toast }, pairing: { toast },
    advanced: { go }, news: { toast },
    settings: { toast, theme: t.theme, setTheme: (v) => setTweak("theme", v), accent: t.accent, setAccent: (v) => setTweak("accent", v), density: t.density, setDensity: (v) => setTweak("density", v), motion: t.motion, setMotion: (v) => setTweak("motion", v) },
  };
  const renderPage = (r) => {
    const C = window[PAGE_COMP[r]] || window[PAGE_COMP.dashboard];
    if (!C) return <div className="page"><div className="empty">Loading {r}…</div></div>;
    return <C {...(pageProps[r] || pageProps.dashboard)} />;
  };

  return (
    <div className={cls("app", collapsed && "collapsed", mobileOpen && "mobile-open")}>
      {mobileOpen && <div className="drawer-scrim" style={{ zIndex: 65 }} onClick={() => setMobileOpen(false)} />}
      <aside className="sidebar">
        <div className="brand">
          <div className="brand-mark"><Icon name="loop" size={19} /></div>
          <div className="brand-text"><b>Sylar's WorkSpace</b><small>hermes · forge</small></div>
        </div>
        <button className="nav-item" onClick={() => searchRef.current?.focus()} title="Search"><Icon name="search" size={20} /><span className="nav-label">Search</span></button>
        <button className="nav-item" onClick={() => go("chat")} title="New Session"><Icon name="edit" size={20} /><span className="nav-label">New Session</span></button>

        <div className="scroll" style={{ flex: 1, maxHeight: "none", overflowY: "auto", overflowX: "hidden", margin: "0 -4px", paddingRight: 0, paddingLeft: 0 }}>
          {NAV.map((grp) => <NavGroup key={grp.id} grp={grp} route={route} go={go} collapsed={collapsed} />)}
        </div>

        <button className="nav-item" onClick={() => setTweak("collapsed", !collapsed)} title="Collapse">
          <Icon name="arrowLeft" size={20} style={{ transform: collapsed ? "rotate(180deg)" : "none", transition: "transform .3s" }} />
          <span className="nav-label">Collapse</span>
        </button>
        <div className="side-foot">
          <img className="avatar" src="assets/sylar-avatar-v2.png" alt="Varun" style={{ objectFit: "cover" }} />
          <div className="side-foot-text"><b>Varun C. <span style={{ color: "var(--primary)", fontSize: 11 }}>●</span></b><small>varun@varunc.com</small></div>
          <button className="foot-btn" title="Settings" aria-label="Settings" onClick={() => go("settings")}><Icon name="settings" size={17} /></button>
          <button className="foot-btn" title="Toggle light / dark" aria-label="Toggle light or dark theme" onClick={() => setTweak("theme", t.theme === "daylight" ? "eclipse" : "daylight")}><Icon name={t.theme === "daylight" ? "moon" : "sun"} size={17} /></button>
        </div>
      </aside>

      <div className="main">
        <header className="topbar">
          <button className="icon-btn" aria-label="Toggle navigation" onClick={() => { window.innerWidth <= 860 ? setMobileOpen((o) => !o) : setTweak("collapsed", !collapsed); }}><Icon name="menu" size={18} /></button>
          <div className="search">
            <Icon name="search" size={16} />
            <input ref={searchRef} aria-label="Search workspace" placeholder="Search sessions, files, agents, skills…" />
            <kbd>⌘K</kbd>
          </div>
          <div className="top-right">
            <button className="icon-btn top-dot" aria-label="Notifications" onClick={() => toast("No new alerts")}><Icon name="bell" size={18} /></button>
            <button className="btn btn-primary btn-sm" onClick={() => go("chat")}><Icon name="chat" size={15} />New chat</button>
          </div>
        </header>
        <main className="content">
          <PageErrorBoundary key={route + (param || "")}>{renderPage(route)}</PageErrorBoundary>
        </main>
      </div>

      <Toast msg={toastMsg} />
    </div>
  );
}

// Password-only sign-in (no username). Posts to the BFF /api/auth which sets the
// claude-auth cookie; gating is driven by /api/auth-check.
function SignIn({ onAuthed }) {
  const [pw, setPw] = useState("");
  const [err, setErr] = useState("");
  const [busy, setBusy] = useState(false);
  const inputRef = useRef(null);
  useEffect(() => { inputRef.current && inputRef.current.focus(); }, []);
  const submit = async (e) => {
    e.preventDefault();
    if (!pw || busy) return;
    setBusy(true); setErr("");
    try {
      const r = await fetch("/api/auth", { method: "POST", credentials: "same-origin", headers: { "content-type": "application/json" }, body: JSON.stringify({ password: pw }) });
      if (r.ok) { onAuthed(); return; }
      const b = await r.json().catch(() => ({}));
      setErr(b.error || "Incorrect password");
    } catch { setErr("Could not reach the server"); }
    setBusy(false);
  };
  return (
    <div className="signin">
      <form className="card signin-card" onSubmit={submit} style={{ display: "flex", flexDirection: "column", gap: 16 }}>
        <div className="flex" style={{ gap: 11 }}>
          <div className="brand-mark"><Icon name="loop" size={19} /></div>
          <div className="brand-text"><b style={{ fontSize: 16 }}>Sylar's WorkSpace</b><small>hermes · forge</small></div>
        </div>
        <div>
          <h1 style={{ fontSize: 22, fontWeight: 700, letterSpacing: "-.01em", margin: "4px 0 4px" }}>Sign in</h1>
          <p className="page-sub" style={{ fontSize: 13.5 }}>Enter the workspace password to access the control plane.</p>
        </div>
        <div className="field">
          <label>Password</label>
          <div className="flex" style={{ gap: 8 }}>
            <input ref={inputRef} className="input mono" type="password" autoComplete="current-password" value={pw} onChange={(e) => { setPw(e.target.value); setErr(""); }} placeholder="••••••••••" />
            <button className="btn btn-primary" type="submit" disabled={busy || !pw} style={{ flexShrink: 0 }}>
              <Icon name="lock" size={15} />{busy ? "Checking…" : "Unlock"}
            </button>
          </div>
        </div>
        {err && <div className="badge bad" style={{ alignSelf: "flex-start" }}><Icon name="x" size={12} />{err}</div>}
        <p className="faint" style={{ fontSize: 11.5, marginTop: 2 }}>Protected by the Hermes workspace gateway.</p>
      </form>
    </div>
  );
}

function Root() {
  const [gate, setGate] = useState("checking"); // checking | login | app
  const check = useCallback(() => {
    fetch("/api/auth-check", { credentials: "same-origin" })
      .then((r) => r.json())
      .then((d) => setGate(d && d.authRequired && !d.authenticated ? "login" : "app"))
      .catch(() => setGate("app")); // backend unreachable (e.g. offline preview) -> show app
  }, []);
  useEffect(() => { check(); }, [check]);
  if (gate === "checking") return <div className="signin"><span className="dotpulse" /></div>;
  if (gate === "login") return <SignIn onAuthed={() => setGate("app")} />;
  return <App />;
}

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