// ---------------------------------------------------------------------------
// pages / PageSurface.tsx — EXIT wave 2 (W2-7).
//
// The stateful half: fetch a Y1 envelope, keep the controls honest, hold the
// drill panel. `PageView` stays pure so it can be reasoned about (and gated)
// without a network; everything that can fail lives here.
//
// ⚠ CONTROL STATE COMES FROM THE RESPONSE, NOT FROM THE CLICK (Y1 rule 9). The
// select is rendered from `controls[].value` in the payload the server just
// sent, so a `bu` the server rejected or coerced cannot look accepted — the
// picker visibly snaps back. Optimistically holding the clicked value would
// show a Royal-only user the word "All" over Royal-only numbers, which is the
// single most expensive lie this page could tell.
//
// ⚠ An in-flight refetch keeps the OLD page on screen, dimmed, rather than
// blanking to a spinner. The first `/pages/sales` per scope is genuinely slow
// (it runs `sales.validate()`), and a page that empties itself on every BU
// change reads as broken long before it reads as loading.
// ---------------------------------------------------------------------------

import { useCallback, useEffect, useState } from "react";
import { UNAUTHORIZED_EVENT, signal } from "../apiContract";
import type { DrillDescriptor, PageEnvelope } from "../ui/types";
import { DrillPanel } from "./DrillPanel";
import { PageView } from "./PageView";
import { fetchPage } from "./pageApi";

type State =
  | { phase: "loading" }
  | { phase: "ready"; page: PageEnvelope }
  | { phase: "error"; message: string };

export function PageSurface({ pageKey, label }: { pageKey: string; label: string }) {
  const [state, setState] = useState<State>({ phase: "loading" });
  const [params, setParams] = useState<Record<string, string>>({});
  const [busy, setBusy] = useState(false);
  const [drill, setDrill] = useState<DrillDescriptor | null>(null);

  // `params` is serialised into the dependency so a NEW object with the same
  // contents does not refetch — the same reason the shell keys its nav effect
  // on the username rather than the session object.
  const paramKey = JSON.stringify(params);

  useEffect(() => {
    let dead = false;
    setBusy(true);
    void fetchPage(pageKey, JSON.parse(paramKey) as Record<string, string>).then((r) => {
      if (dead) return;
      setBusy(false);
      if (r.ok) {
        setState({ phase: "ready", page: r.page });
        return;
      }
      // 401 is not "this page is broken", it is "the session died under us".
      // The shell owns that transition; conflating the two would leave a
      // signed-out user staring at an error panel about Sales.
      if (r.status === 401) {
        signal(UNAUTHORIZED_EVENT);
        return;
      }
      setState({ phase: "error", message: r.message });
    });
    return () => {
      dead = true;
    };
  }, [pageKey, paramKey]);

  // Changing a control resets the drill: a panel describing last month's
  // Fisch revenue, still open over Royal numbers, is a wrong answer that looks
  // like a right one.
  const onControl = useCallback((key: string, value: string) => {
    setDrill(null);
    setParams((p) => ({ ...p, [key]: value }));
  }, []);

  if (state.phase === "loading") {
    return (
      <div className="pg-state">
        {/* ⚠ THE TITLE STAYS, THE SENTENCE GOES (wave 17 item 3/R6). R6 deletes
            loading COPY — "Loading live figures from Odoo. The first read of a
            new scope takes a moment." was an apology for a wait the mark states
            without excusing. The heading is not loading copy: it is this page's
            name, already chosen in the rail, and dropping it would make arrival
            a title POP-IN on a surface that had been anonymous. */}
        <h1>{label}</h1>
        <span className="lp-spin lp-spin--lg" role="status" aria-label="Loading" />
      </div>
    );
  }

  if (state.phase === "error") {
    return (
      <div className="pg-state pg-error">
        <h1>{label}</h1>
        <p>{state.message}</p>
        <button
          className="login-submit shell-retry"
          type="button"
          onClick={() => setParams((p) => ({ ...p }))}
        >
          Try again
        </button>
      </div>
    );
  }

  return (
    <div className={busy ? "pg-host is-busy" : "pg-host"}>
      <PageView page={state.page} onControl={onControl} onOpen={setDrill} busy={busy} />
      {drill ? <DrillPanel drill={drill} onClose={() => setDrill(null)} /> : null}
    </div>
  );
}
