/* lib-viewas.jsx — DEVELOPER-ONLY "View As" bar.
 *
 * WHY THIS EXISTS
 * BUILD_36 phase A moved module scoping into the client. A client scoping
 * change can only honestly be tested by rendering it as the other person, and
 * on a weekend there is nobody to ask. Real test accounts are the wrong tool:
 * each needs a Cloudflare Access identity backed by a real mailbox, and a fake
 * or shared login is precisely what R35-5 refused.
 *
 * WHAT IT DOES NOT DO — READ THIS BEFORE TRUSTING IT
 * It changes the viewer the UI RENDERS FOR. It does NOT change the database
 * identity: x-user-email is still stamped by the Worker from YOUR Access JWT,
 * so every row that comes back is still fetched as you. So this proves the UI
 * boundary — which pickers offer what, which nav items appear — and proves
 * NOTHING about whether the database would refuse another module. That is RLS
 * and it needs a real second login.
 *
 * The bar is deliberately loud. A quiet impersonation banner is how you spend
 * twenty minutes debugging "why can't I see Data Uploads".
 *
 * IT IS position:fixed, AND THAT IS NOT COSMETIC.
 * v1 rendered it as a child of <div class="app">, which is
 *   display:grid; grid-template-columns: var(--sidebar-w) 1fr
 * -- a TWO-COLUMN grid expecting exactly two laid-out children. A third child
 * took the first cell, pushed the sidebar into column two and the content onto
 * the next row, and the whole app looked destroyed. Fixed positioning takes no
 * grid cell at all, so this cannot break whatever it is dropped into.
 *
 * z-index 900: BELOW the settings overlay (1000) and toasts (1100), which is
 * the ordering shell.jsx already establishes. A dev tool must never cover a
 * real one.
 *
 * Gating: app.jsx renders this only when the REAL tier is developer, and it
 * passes the real user in so the bar can name who you actually are.
 */
(function () {
  const { useState, useMemo } = React;

  function VaultViewAsBar({ value, onChange, realUser }) {
    const [open, setOpen] = useState(false);

    /* The list is VAULT_ACCOUNTS — real people with real logins — plus a few
       synthetic personas for tiers nobody in the array holds yet. Dave and
       Bradley are the ones worth having: they are `administration` with NO
       module, which is the case most likely to break, because every
       module-scoped rule has to fall back to firm scope for them rather than
       failing closed. */
    const people = useMemo(() => {
      const accts = (window.VAULT_ACCOUNTS || []).map(a => ({
        // DERIVED, like the bar. `a.tier` is the raw login field and login.jsx
        // carries one only on the developer rows, so this printed "user" for
        // everybody -- Kaneko included, who derives module_lead. Second place
        // the same mistake lived; the bar was fixed and this was not.
        key: a.email, label: a.name, acct: { ...a },
        sub: (window.VaultOrg && window.VaultOrg.viewerTier)
          ? window.VaultOrg.viewerTier(a) : (a.tier || "user"),
      }));
      const synthetic = [
        { key: "__dharvey", label: "Dave Harvey", sub: "administration · no module",
          // personId WAS "p_dharveyinc" and the real people row is "dharvey".
          // "dharveyinc" is the scope-chain FIXTURE's handle; it got copied into
          // the live picker. personIdOf then failed all three ways -- the handle
          // is not in `people`, "Dave Harvey" does not equal the row's "David
          // Harvey", and the email is a placeholder -- so personOf returned null,
          // deriveTier returned null, and viewerTier fell to "user".
          //
          // Every screen was empty for Dave, and the picker still LABELLED him
          // "administration" because that label reads this hardcoded tier while
          // the app derives from the org chart. Two sources for one fact.
          //
          // name matched to the row as well, so the name fallback in personIdOf
          // would also resolve him if the handle ever drifts again.
          acct: { email: "dharvey@example.invalid", name: "David Harvey", role: "CEO",
                  access: "admin", tier: "administration", personId: "p_dharvey" } },
        { key: "__briegler", label: "Bradley Riegler", sub: "administration · no module",
          acct: { email: "briegler@example.invalid", name: "Bradley Riegler", role: "CFO",
                  access: "admin", tier: "administration", personId: "p_briegler" } },
        { key: "__nobody", label: "Unresolvable Person", sub: "user · NO team at all",
          acct: { email: "nobody@example.invalid", name: "Unresolvable Person",
                  role: "Analyst I", access: "user", tier: "user", personId: "p_notaperson" } },
      ];
      return accts.concat(synthetic);
    }, []);

    const current = value ? (people.find(p => p.acct.email === value.email) || null) : null;
    const scope = window.VaultOrg;
    const mods = scope && scope.selectableModules
      ? scope.selectableModules(value || realUser) : [];

    if (!value) {
      return (
        <div style={{ ...BAR_IDLE, left: "auto", right: 14, bottom: 14,
                      borderRadius: "var(--r-pill)", border: "1px solid var(--accent)",
                      background: "var(--surface)", color: "var(--accent)",
                      boxShadow: "var(--shadow-pop)", padding: "6px 12px" }}>
          <span aria-hidden="true">&#128065;</span>
          <span style={{ fontWeight: "var(--w-medium)" }}>View As</span>
          <select
            value=""
            onChange={e => {
              const p = people.find(x => x.key === e.target.value);
              onChange(p ? p.acct : null);
            }}
            style={SELECT}>
            <option value="">View as…</option>
            {people.map(p => (
              <option key={p.key} value={p.key}>{p.label} — {p.sub}</option>
            ))}
          </select>
          <span style={{ color: "var(--muted)" }} title="x-user-email still comes from YOUR Access JWT, so every row is fetched as you. This proves the UI boundary and nothing about RLS.">
            UI only
          </span>
        </div>
      );
    }

    return (
      <div style={BAR_ON}>
        <span style={{ fontWeight: "var(--w-bold)", letterSpacing: ".04em" }}>VIEWING AS</span>
        <span style={{ fontWeight: "var(--w-medium)" }}>{value.name}</span>
        <span style={{ opacity: .85 }}>
          {/* DERIVED, not value.tier. The raw field is only present on the
              developer rows since login.jsx stopped hand-maintaining tiers, so
              printing it showed "user" for everyone -- Brian Kaneko included,
              who derives module_lead. The bar has to say what the app
              CONCLUDED, or it is a second opinion about access. */}
          {(scope && scope.viewerTier ? scope.viewerTier(value) : (value.tier || "user"))}
          {" · "}
          {mods.length ? (mods.length > 3 ? mods.length + " modules" : mods.join(", "))
                       : "NO MODULE — screens will be empty"}
        </span>
        <button onClick={() => setOpen(o => !o)} style={LINK}>switch</button>
        {open && (
          <select
            value={current ? current.key : ""}
            onChange={e => {
              const p = people.find(x => x.key === e.target.value);
              onChange(p ? p.acct : null); setOpen(false);
            }}
            style={SELECT}>
            {people.map(p => <option key={p.key} value={p.key}>{p.label} — {p.sub}</option>)}
          </select>
        )}
        <button onClick={() => onChange(null)} style={EXIT}>
          Exit — back to {(realUser && realUser.name) || "yourself"}
        </button>
      </div>
    );
  }

  const BASE = {
  /* left: var(--sidebar-w), NOT 0.
     Full-width, this covered the bottom of the SIDEBAR and hid its last nav
     item -- Data Uploads, which is the thing being tested. A hidden item reads
     as "not available to this user", which is exactly the wrong conclusion
     from a tool built to show what a user can see.
     .app is grid-template-columns: var(--sidebar-w) 1fr, so anchoring to that
     same variable tracks the sidebar collapsing with no second source of
     truth for its width. */
    position: "fixed", left: "var(--sidebar-w, 0px)", right: 0, bottom: 0, zIndex: 900,
    display: "flex", alignItems: "center", gap: 10, flexWrap: "wrap",
    padding: "6px 14px", fontSize: "var(--t-small)",
    borderTop: "1px solid var(--line)",
  };
  // Idle is quiet chrome. Active is GOLD -- the token for "waiting, in an
  // unusual state", not --warn, which reads as an error, and not --risk.
  const BAR_IDLE = { ...BASE, background: "var(--surface-2)", color: "var(--ink-3)" };
  const BAR_ON = { ...BASE, background: "var(--gold)", color: "var(--accent-ink)", borderTop: "none" };
  const SELECT = {
    font: "inherit", fontSize: "var(--t-small)", padding: "3px 6px",
    borderRadius: "var(--r-ctl)", border: "1px solid var(--line-strong)",
    background: "var(--surface)", color: "var(--ink)",
  };
  const LINK = {
    font: "inherit", fontSize: "var(--t-small)", background: "none", border: "none",
    color: "inherit", textDecoration: "underline", cursor: "pointer", padding: 0,
  };
  const EXIT = {
    marginLeft: "auto", font: "inherit", fontSize: "var(--t-small)",
    fontWeight: "var(--w-medium)", padding: "3px 10px", borderRadius: "var(--r-ctl)",
    border: "1px solid var(--accent-ink)", background: "transparent",
    color: "var(--accent-ink)", cursor: "pointer",
  };

  window.VaultViewAsBar = VaultViewAsBar;

  /* CONSOLE API. The pill is easy to miss in a full-bleed app, and typing a
     handle is faster than finding it anyway. app.jsx installs the setter; this
     is the front door.
         VaultViewAs()            -> list who you can become
         VaultViewAs('cswan')     -> become them
         VaultViewAs(null)        -> back to yourself                        */
  window.VaultViewAs = function (who) {
    var set = window.__vaultSetViewAs;
    if (!set) { console.warn('[vault] View As is developer-only, and you are not it.'); return; }
    if (who === undefined) {
      var accts = window.VAULT_ACCOUNTS || [];
      console.log('%cVaultViewAs("handle") — or VaultViewAs(null) to exit', 'font-weight:bold');
      console.table(accts.map(function (a) {
        var h = String(a.personId || '').replace(/^p_/, '').replace(/_gmail$/, '');
        // Pass the WHOLE account, not just personId. Passing an id alone drops
        // the one thing the org chart cannot express -- the developer override
        // -- so this table reported Brian as `module_lead`.
        var mods = window.VaultOrg ? window.VaultOrg.selectableModules(a) : [];
        return { handle: h, name: a.name,
                 tier: window.VaultOrg ? window.VaultOrg.viewerTier(a) : '?',
                 modules: mods.length > 3 ? mods.length + ' (all)' : mods.join(', ') };
      }));
      console.log('also: dharveyinc / briegler (administration, no module), notaperson (no team)');
      return;
    }
    if (who === null) { set(null); console.log('[vault] back to yourself'); return; }
    var key = String(who).replace(/^p_/, '');
    var found = (window.VAULT_ACCOUNTS || []).find(function (a) {
      var h = String(a.personId || '').replace(/^p_/, '').replace(/_gmail$/, '');
      return h === key || String(a.email || '').toLowerCase() === key.toLowerCase();
    });
    if (!found) {
      // Not a real login -- build a persona so the no-module and
      // administration cases can still be exercised.
      found = { email: key + '@example.invalid', name: key, role: 'Analyst I',
                access: 'user', personId: 'p_' + key };
    }
    set({ ...found });
    console.log('[vault] viewing as ' + (found.name || key)
      + ' — tier ' + (window.VaultOrg ? window.VaultOrg.viewerTier(found) : '?'));
  };
})();
