/* The two-row shell every caddie app wears.
 *
 * Row 1, GLOBAL — owned by the platform, identical across apps: the brand
 * (click = home), the LOCATOR ("where am I": app · project · screen…), and the
 * account chrome on the right (the bell and the account menu). When an app is
 * launched from caddie, the host bar's "Part of {project} on Caddie · Back"
 * is what the locator shows — so the row exists whether or not caddie did
 * the launching, and the drop-in has a slot to land in.
 *
 * Row 2, APP — the app's own functions for the thing on screen (modes,
 * navigation between siblings, the primary action). Three slots; centre is
 * for a mode/segment control and stays centred.
 *
 * The locator is a small context so any view can set it without threading
 * props through the tree; the shell reads it. */

import {
  createContext, useContext, useEffect, useMemo, useRef, useState,
  type ReactNode,
} from "react";

// ------------------------------------------------------------- locator

type LocatorCtx = {
  segments: string[];
  setSegments: (s: string[]) => void;
};

const Locator = createContext<LocatorCtx | null>(null);

export function LocatorProvider({ children }: { children: ReactNode }) {
  const [segments, setSegments] = useState<string[]>([]);
  const value = useMemo(() => ({ segments, setSegments }), [segments]);
  return <Locator.Provider value={value}>{children}</Locator.Provider>;
}

/** Declare where the current view is. Cleared when the view unmounts, so a
 * stale path never outlives the screen it described. */
export function useLocator(segments: string[] | null) {
  const ctx = useContext(Locator);
  const key = segments ? segments.join(" ") : "";
  useEffect(() => {
    if (!ctx) return;
    ctx.setSegments(segments ?? []);
    return () => ctx.setSegments([]);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [key]);
}

function LocatorText() {
  const ctx = useContext(Locator);
  const segs = ctx?.segments ?? [];
  if (segs.length === 0) return null;
  return (
    <span className="cu-locator" aria-label="You are in">
      {segs.map((s, i) => (
        <span key={i} className="cu-locator-seg-wrap">
          {i > 0 && <span className="cu-locator-sep" aria-hidden="true" />}
          <span>{s}</span>
        </span>
      ))}
    </span>
  );
}

// ----------------------------------------------------------- global bar

export function GlobalBar({
  brand, onHome, side, right, locator,
}: {
  brand: ReactNode;
  onHome: () => void;
  /** "Team" / "Reviewing" style orientation chip; pass null to omit. */
  side?: { label: string; team: boolean } | null;
  /** Bell + account menu. */
  right?: ReactNode;
  /** Override the locator (e.g. a caddie host bar); default reads context. */
  locator?: ReactNode;
}) {
  const ref = useRef<HTMLElement | null>(null);
  // Publish where the global row actually ENDS as `--cu-shell-bottom`. The
  // side panel pins itself to it. The row's nominal height is not enough: an
  // app strip above the row (a dev-mode banner, a host bar) pushes it down,
  // and a panel pinned to the nominal height then starts underneath the row —
  // with its close and back controls hidden. Re-measured on resize and scroll
  // (a strip above a sticky row scrolls away; the row does not).
  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const root = document.documentElement;
    let pending = 0;
    const set = () => {
      pending = 0;
      root.style.setProperty(
        "--cu-shell-bottom", `${Math.round(el.getBoundingClientRect().bottom)}px`);
    };
    // One measurement per tick however many signals arrive — the signals are
    // broad on purpose (anything that can move the row: a strip mounting
    // above it, a resize, a scroll in any scroller), so they must be cheap. A
    // timer, not requestAnimationFrame: rAF is paused in a background tab,
    // and a tab opened in the background must not show a stale offset.
    const schedule = () => { if (!pending) pending = window.setTimeout(set, 0); };
    set();
    const ro = new ResizeObserver(schedule);
    ro.observe(el);
    ro.observe(document.body);
    const mo = new MutationObserver(schedule);
    mo.observe(document.body, { childList: true, subtree: true });
    document.addEventListener("scroll", schedule, { passive: true, capture: true });
    window.addEventListener("resize", schedule);
    return () => {
      if (pending) window.clearTimeout(pending);
      ro.disconnect();
      mo.disconnect();
      document.removeEventListener("scroll", schedule, { capture: true });
      window.removeEventListener("resize", schedule);
      root.style.removeProperty("--cu-shell-bottom");
    };
  }, []);
  return (
    <header ref={ref} className="cu-global">
      <button type="button" className="cu-brand" onClick={onHome} title="Home">
        <svg className="cu-brand-mark" width="18" height="18" viewBox="0 0 24 24"
             fill="none" stroke="currentColor" strokeWidth="1.7"
             strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M4 4h16v11H4z" /><path d="M12 15v5" /><path d="M8 21l4-3 4 3" />
        </svg>
        {brand}
      </button>
      {side && (
        <span className={`cu-side ${side.team ? "is-team" : ""}`}>{side.label}</span>
      )}
      {locator ?? <LocatorText />}
      <div className="cu-global-right">{right}</div>
    </header>
  );
}

// -------------------------------------------------------------- app bar

export function AppBar({
  left, center, right, className = "",
}: {
  left?: ReactNode; center?: ReactNode; right?: ReactNode; className?: string;
}) {
  return (
    <div className={`cu-appbar ${className}`}>
      {left && <div className="cu-appbar-left">{left}</div>}
      {center && <div className="cu-appbar-center">{center}</div>}
      {right && <div className="cu-appbar-right">{right}</div>}
    </div>
  );
}
