/* Argus App root.
   Top-level routing decides between AuthShell mode and Workspace mode
   based on session state + URL parameters. Inside Workspace mode the
   LPQ left rail is always present; the main area shows: home (empty
   welcome), processing, failure, workspace (per-essay deconstruction),
   ops (admin), admin invites, or settings.

   Legacy marker Dashboard / Cohort / Library / Graph / Upload-page
   were retired in this build — all functionality now flows through
   LPQ + workspace + (admin-only) Command Centre.
*/

const { useState: useS, useEffect: useE, useCallback } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "dark",
  "density": "comfortable",
  "accent": "gold"
}/*EDITMODE-END*/;

/* ── URL → mode detection ─────────────────────────────────────── */
function readUrlIntent() {
  const params = new URLSearchParams(window.location.search);
  if (params.get("share"))   return { mode: "signup",      token: params.get("share") };
  if (params.get("welcome")) return { mode: "setpassword", token: params.get("welcome") };
  if (params.get("reset"))   return { mode: "reset",       token: params.get("reset") };
  if (params.get("forgot"))  return { mode: "forgot" };
  return null;
}

/* ── Local dev bypass — skip auth on Talos/localhost/LAN ─────────
   The static dev server (python3 -m http.server) has no Pages
   Functions, so /argus/api/me 404s. Auth becomes impossible. When
   we see a private/tailscale/localhost hostname, inject a mock
   authenticated user so the workspace renders against the mock
   data in data.jsx. Production (akademeia.ai) takes the real path. */
function isLocalDev() {
  const h = (typeof window !== "undefined" && window.location && window.location.hostname) || "";
  return h === "localhost"
      || h === "127.0.0.1"
      || h.startsWith("100.")     // tailscale CGNAT
      || h.startsWith("192.168.") // common LAN
      || h.startsWith("10.");     // private
}

function clearAuthParamsFromUrl() {
  const u = new URL(window.location.href);
  ["share", "welcome", "reset", "forgot", "invite"].forEach(k => u.searchParams.delete(k));
  window.history.replaceState({}, "", u.toString());
}

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [boot, setBoot]       = useS(true);
  const [me, setMe]           = useS(null);
  const [authMode, setAuthMode] = useS(readUrlIntent());
  const [section, setSection] = useS("home");
  // Marker-facing Dashboard / Upload / Cohort / Library / Graph all
  // retired. "Home" is the empty welcome state when no essay is open;
  // everything else opens dynamically from the LPQ queue.
  const [tabs, setTabs] = useS([
    { id: "home", kind: "home", label: "Home", closable: false },
  ]);
  const [activeTab, setActiveTab] = useS("home");
  const [processingJob, setProcessingJob] = useS(null);
  const [failure, setFailure] = useS(null);
  const [navOpen, setNavOpen] = useS(false);

  useE(() => { document.documentElement.setAttribute("data-theme", t.theme || "dark"); }, [t.theme]);
  useE(() => { document.getElementById("boot")?.remove(); }, []);

  useE(() => {
    if (authMode) { setBoot(false); return; }
    if (isLocalDev()) {
      // Synthetic admin user so the workspace renders against mock data.
      // No backend on localhost dev server; views fall back to data.jsx.
      setMe({ user_id: "00000000", email: "dev@local", name: "Local Dev", is_admin: true });
      setBoot(false);
      return;
    }
    (async () => {
      const r = await Api.me();
      if (r.ok && r.data && r.data.user_id) setMe(r.data);
      else setAuthMode({ mode: "login" });
      setBoot(false);
    })();
  }, []);

  // WorkspaceReport dispatches this once it has loaded the report and
  // knows the assignment title. Update the matching tab's label so the
  // top-bar stops saying "New report".
  useE(() => {
    const h = (e) => {
      const { jobId, label } = e.detail || {};
      if (!jobId || !label) return;
      setTabs(prev => prev.map(tt =>
        tt.id === `workspace:${jobId}` ? { ...tt, label } : tt
      ));
    };
    window.addEventListener("argus-tab-label", h);
    return () => window.removeEventListener("argus-tab-label", h);
  }, []);

  const onSignedIn = useCallback(async () => {
    clearAuthParamsFromUrl();
    setAuthMode(null);
    const r = await Api.me();
    if (r.ok && r.data) setMe(r.data);
  }, []);

  const onSignedOut = useCallback(() => {
    setMe(null);
    setAuthMode({ mode: "login" });
  }, []);

  const tabKindToSection = (kind) => {
    if (["home","workspace","processing","failure","ops","admin","settings"].includes(kind)) return kind;
    return "home";
  };

  const openTab = useCallback((kind, refId, labelOverride, tone) => {
    const id = refId ? `${kind}:${refId}` : kind;
    setTabs(prev => prev.find(tt => tt.id === id)
      ? prev
      : [...prev, { id, kind, label: labelOverride || kind, tone, closable: true }]);
    setActiveTab(id);
    setSection(tabKindToSection(kind));
  }, []);

  const closeTab = useCallback((id) => {
    setTabs(prev => {
      const idx = prev.findIndex(tt => tt.id === id);
      const next = prev.filter(tt => tt.id !== id);
      if (id === activeTab && next.length) {
        const newActive = next[Math.max(0, idx - 1)];
        setActiveTab(newActive.id);
        setSection(tabKindToSection(newActive.kind));
      }
      return next;
    });
  }, [activeTab]);

  const changeTab = useCallback((id) => {
    setActiveTab(id);
    const tab = tabs.find(tt => tt.id === id);
    if (tab) setSection(tabKindToSection(tab.kind));
  }, [tabs]);

  const gotoSection = useCallback((sec) => {
    setSection(sec);
    if (sec === "home")     { setActiveTab("home"); return; }
    if (sec === "ops")      { openTab("ops",      null, "Command Centre"); return; }
    if (sec === "admin")    { openTab("admin",    null, "Invitations"); return; }
    if (sec === "settings") { openTab("settings", null, "Settings"); return; }
  }, [openTab]);

  const openEssay = useCallback((eid) => { openTab("workspace", eid, "Essay", null); }, [openTab]);

  const onSubmitted = useCallback(({ kind, id }) => {
    setProcessingJob(id);
    setSection("processing");
    setActiveTab("home");
  }, []);

  const onProcessingDone = useCallback((jobId) => {
    setProcessingJob(null);
    openTab("workspace", jobId, "New report", null);
  }, [openTab]);

  const onProcessingFail = useCallback((jobId, message) => {
    setFailure({ jobId, message });
    setProcessingJob(null);
    setSection("failure");
  }, []);

  const onThemeToggle = () => setTweak("theme", (t.theme || "dark") === "dark" ? "light" : "dark");

  const onProfileUpdated = useCallback((profile) => {
    setMe(prev => prev ? { ...prev, ...profile } : prev);
  }, []);

  // Direct-from-LPQ submission: New Essay opens a file picker, picks
  // one file, and posts it straight to /analyse. No upload view — title
  // + student auto-detect or stay blank. Multi-file batches still go
  // through the upload view.
  const analyseFile = useCallback(async (file) => {
    if (!file) return;
    const fd = new FormData();
    fd.append("file", file);
    const r = await Api.analyse(fd);
    if (!r.ok) {
      const serverMsg = r.data && typeof r.data.message === "string" ? r.data.message : null;
      alert(r.status === 401 ? "Session expired — sign in again."
          : r.status === 413 ? "Essay over 20 MB. Try a smaller file."
          : r.status === 429 ? (serverMsg || "Quota reached. Contact the site owner to raise the limit.")
          : (serverMsg || "Submission failed. Try again."));
      return;
    }
    // Proxy returns { report_id, state, queue_position, title }. Older
    // callsites read .job_id; accept either to stay tolerant.
    const id = r.data && (r.data.report_id || r.data.job_id);
    if (id) {
      onSubmitted({ kind: "single", id });
    } else {
      console.error("[Argus] analyse returned no id", r);
      alert("Submission accepted but no job id came back. Refresh and check your queue.");
    }
  }, []);

  const onRerun = useCallback(async (jobId) => {
    if (!jobId) return;
    if (!confirm("Re-run this job? The original report will be replaced when it completes.")) return;
    const r = await Api.rerun(jobId);
    if (!r.ok) {
      alert(r.status === 404 ? "Job not found." : r.status === 403 ? "Re-run requires admin." : "Couldn't queue re-run.");
      return;
    }
    setProcessingJob((r.data && (r.data.job_id || r.data.new_job_id)) || jobId);
    setSection("processing");
  }, []);

  if (boot) return null;

  if (authMode) {
    const m = authMode.mode;
    if (m === "signup")      return <ViewSignup       shareToken={authMode.token} onClaimed={onSignedIn}/>;
    if (m === "setpassword") return <ViewSetPassword  token={authMode.token}      onDone={onSignedIn}/>;
    if (m === "reset")       return <ViewReset        token={authMode.token}      onDone={() => setAuthMode({ mode: "login" })}/>;
    if (m === "forgot")      return <ViewForgot       onBack={() => setAuthMode({ mode: "login" })}/>;
    if (m === "login")       return <ViewLogin        onSignedIn={onSignedIn} onForgot={() => setAuthMode({ mode: "forgot" })}/>;
    return <ViewGate onSignIn={() => setAuthMode({ mode: "login" })}/>;
  }

  if (!me) return null;

  const activeTabObj = tabs.find(tt => tt.id === activeTab);
  const activeEssayId = (activeTabObj?.id || "").replace(/^(?:workspace|essay):/, "");

  return (
    <>
      <div id="app"
           data-nav-open={navOpen ? "true" : "false"}
           data-layout="workspace">
        <div className="nav-backdrop" onClick={() => setNavOpen(false)}/>

        <LeftPaneQueue
          section={section}
          isAdmin={!!me.is_admin}
          me={me}
          activeEssayId={activeEssayId}
          theme={t.theme}
          onThemeToggle={onThemeToggle}
          onAnalyseFile={analyseFile}
          openEssay={(eid) => { setNavOpen(false); openEssay(eid); }}
          gotoSection={(s) => { setNavOpen(false); gotoSection(s); }}/>

        <main className="work">
          {section === "home" && <HomeWelcome me={me} onAnalyseFile={analyseFile}/>}
          {section === "processing" && processingJob && (
            <ViewProcessing jobId={processingJob}
                            onDone={onProcessingDone}
                            onFail={onProcessingFail}/>
          )}
          {section === "failure"    && failure && (
            <ViewFailure jobId={failure.jobId} message={failure.message}
                         onRetry={() => { setFailure(null); setSection("home"); }}
                         onHome={() => { setFailure(null); setSection("home"); }}/>
          )}
          {section === "workspace"  && <ViewWorkspace essayId={activeEssayId}/>}
          {section === "ops"        && <ViewOps onRerun={onRerun} openEssay={openEssay}/>}
          {section === "admin"      && me.is_admin && <ViewAdminInvites/>}
          {section === "settings"   && (
            <ViewSettings theme={t.theme}
                          onThemeToggle={onThemeToggle}
                          onSignedOut={onSignedOut}
                          onProfileUpdated={onProfileUpdated}/>
          )}
        </main>
      </div>

      <TweaksPanel title="Workspace tweaks">
        <TweakSection label="Theme">
          <TweakRadio
            label="Mode"
            value={t.theme}
            onChange={(v) => setTweak("theme", v)}
            options={[{value: "dark", label: "Dark"}, {value: "light", label: "Light"}]}/>
        </TweakSection>
      </TweaksPanel>
    </>
  );
}

/* Empty-state landing — shown right after sign-in when no essay is
   open. Restates the New Essay affordance in the main work area so it
   isn't only living in the left rail. The LPQ continues to show the
   queue underneath. */
function HomeWelcome({ me, onAnalyseFile }) {
  const fileRef = React.useRef(null);
  const firstName = (me && (me.name || me.email || "").split(" ")[0]) || "there";
  return (
    <div style={{height: "100%", display: "grid", placeItems: "center", padding: "32px"}}>
      <div style={{
        maxWidth: 520, textAlign: "center",
        background: "var(--surface)", border: "1px solid var(--hair)",
        borderRadius: 12, padding: "36px 40px",
      }}>
        <h1 style={{margin: 0, fontSize: 22, fontWeight: 600, letterSpacing: "-0.01em", color: "var(--ink)"}}>
          Welcome back, {firstName}.
        </h1>
        <p style={{margin: "10px 0 22px", color: "var(--ink-3)", fontSize: 14, lineHeight: 1.55}}>
          Pick an essay from the queue on the left, or drop a fresh one in to begin a new analysis.
        </p>
        <button className="btn primary"
                onClick={() => fileRef.current && fileRef.current.click()}
                style={{margin: "0 auto"}}>
          <Icon.Plus size={13} stroke={1.8}/> New Essay
        </button>
        <input ref={fileRef}
               type="file"
               hidden
               accept=".pdf,.docx,.rtf,.odt,.txt,.html"
               onChange={async (e) => {
                 const f = e.target.files && e.target.files[0];
                 e.target.value = "";
                 if (f && onAnalyseFile) await onAnalyseFile(f);
               }}/>
        <p style={{margin: "18px 0 0", color: "var(--ink-4)", fontSize: 12}}>
          Accepted: PDF · DOCX · RTF · ODT · TXT · HTML — up to 20 MB
        </p>
        <p style={{margin: "8px 0 0", color: "var(--ink-5)", fontSize: 11.5, lineHeight: 1.45}}>
          Legacy .doc files won't parse — re-save as .docx first.
        </p>
      </div>
    </div>
  );
}

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