// App chrome: TopHeader, LeftRail, StatusBar.

const NAV_ITEMS = [
  { id: 'sources',    label: 'Sources',    icon: 'Upload',         badge: null },
  { id: 'draft',      label: 'Course architecture', icon: 'Layers',  badge: null },
  { id: 'library',    label: 'Layout library', icon: 'Grid',      badge: null },
  { id: 'roles',      label: 'Organisations & roles', icon: 'Users', badge: null },
  { id: 'content-mapping', label: 'Content mapping', icon: 'Table', badge: null },
  // No badge: `{ dot: 'warning', count: 2 }` was a literal that pinned a
  // permanent amber "2" on Localisation for every course — including a
  // single-language one with nothing to translate (removed 2026-07-28, same
  // reason as Assessments below). The real per-language status lives in the
  // Languages rail, counted from the course's own content.
  { id: 'localisation', label: 'Localisation', icon: 'Globe',      badge: null },
  // No badge: the old `{ dot: 'error', count: 1 }` was a literal that pinned a
  // permanent red dot on Assessments for every course, including empty ones.
  { id: 'assessments',label: 'Assessments',icon: 'ClipboardCheck', badge: null },
  { id: 'brand',      label: 'Course settings', icon: 'Palette',  badge: null },
  { id: 'export',     label: 'Export',     icon: 'Package',        badge: null },
  // Workspace-level, not course-level, and shown only to somebody who can act on
  // it. `requires` is read by NAV_ITEMS' consumer below; an entry with no
  // `requires` is visible to everyone, exactly as all nine above always were.
  //
  // Hidden rather than greyed, deliberately: a Reviewer cannot invite anybody,
  // and a menu item that answers 403 is a false affordance
  // (`feedback_no_false_affordance_toggles`). The route enforces the same rule —
  // this only stops the door being drawn.
  { id: 'members',    label: 'Members',    icon: 'Users',   badge: null,
    requires: 'member:manage' },
];

/**
 * The rail entries this caller may actually use.
 *
 * ⚠️ `window.dynamoCan` is published by app.jsx from the capability set
 * `GET /v1/me` returns — the SERVER's answer, not a role name matched here. A
 * second role list in this file is exactly the drift that put a hardcoded
 * `role === 'reviewer'` in app.jsx and would have given a Language Reviewer a
 * fully editable UI (`feedback_one_rule_one_place`).
 *
 * Before /v1/me answers, `dynamoCan` returns false, so a gated entry appears a
 * moment late rather than flashing and disappearing.
 */
function visibleNavItems() {
  const can = typeof window.dynamoCan === 'function' ? window.dynamoCan : () => false;
  return NAV_ITEMS.filter((item) => !item.requires || can(item.requires));
}

// ── TopHeader ───────────────────────────────────────────────────────────────
function TopHeader({ course, currentSurface, currentOrg, serverSync, onPreview, onLogoClick, onSwitchOrg }) {
  // ── The course title is READ-ONLY here (2026-08-14) ───────────────────────
  //
  // Omar: *"Do not allow the editing of the course title using the edit icon at
  // the top of the page… Since the Course Title is defined in the Course
  // Settings, the only place where to edit it should be only in the Course
  // Settings."* One place per rule (`feedback_one_rule_one_place`).
  //
  // ★ It was never an editor. The pencil opened an input bound to a local
  // `useState` with no `onSave`, no PATCH and no callback prop of any kind: the
  // author could rename the course, watch the header change, and lose it on the
  // next refresh — while the ZIP, the manifest and the courses list kept the old
  // name. So this removes a control that had FOUR layers of convincing UI over
  // nothing at all, which is the shape
  // `feedback_verify_the_control_not_just_the_pipeline` is about. Removing it is
  // strictly a repair, not only a scope decision.
  //
  // Kept: the title itself, as text. It is the breadcrumb — the answer to "which
  // course am I in" — and that job never needed the pencil.
  const [orgMenuOpen, setOrgMenuOpen] = React.useState(false);
  const isHome = currentSurface === 'home';
  const isDynamoHome = currentSurface === 'dynamo-home';
  const isNewCourse = currentSurface === 'new-course' || currentSurface === 'generating';
  const isLanding = isHome || isDynamoHome || isNewCourse;

  return (
    <header style={{
      gridArea: 'header',
      display: 'flex', alignItems: 'center',
      padding: '0 16px',
      background: 'var(--surface)',
      borderBottom: '1px solid var(--border)',
      height: 52,
      position: 'relative',
      zIndex: 10,
    }}>
      {/* Left: logo + course title */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, minWidth: 240 }}>
        <button onClick={onLogoClick} className="focusable"
          style={{
            display: 'inline-flex', alignItems: 'center', gap: 8,
            padding: '4px 6px', margin: '-4px -6px', borderRadius: 4,
            background: 'transparent', border: 0, cursor: 'default',
            color: 'var(--text)', fontFamily: 'inherit',
          }}
          onMouseOver={e => e.currentTarget.style.background = 'var(--surface-inset)'}
          onMouseOut={e => e.currentTarget.style.background = 'transparent'}
          title="Back to courses home">
          <DynamoLogo />
          <span style={{ fontWeight: 600, fontSize: 13, color: 'var(--text)' }}>Dynamo</span>
          <span style={{ fontSize: 11, color: 'var(--text-faint)', padding: '1px 5px',
            background: 'var(--surface-inset)', borderRadius: 3, fontWeight: 500 }}>Authoring</span>
        </button>
      </div>

      <div style={{ width: 1, height: 24, background: 'var(--border)', marginRight: 12 }} />

      {/* Org + Course breadcrumb */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flex: 1, minWidth: 0 }}>
        {/* Org switcher — hidden on Dynamo home (no org chosen yet) */}
        {!isDynamoHome && (
          <div style={{ position: 'relative' }}>
          <button onClick={() => setOrgMenuOpen(o => !o)}
            style={{
              display: 'inline-flex', alignItems: 'center', gap: 6,
              padding: '3px 6px 3px 4px', borderRadius: 4,
              background: 'transparent', border: 0, cursor: 'default',
              color: 'var(--text-muted)', fontFamily: 'inherit', fontSize: 12.5,
              fontWeight: 500,
            }}
            onMouseOver={e => e.currentTarget.style.background = 'var(--surface-inset)'}
            onMouseOut={e => e.currentTarget.style.background = 'transparent'}>
            <OrgGlyph org={currentOrg} size={20} />
            <span>{currentOrg.name}</span>
            <I.ChevronDown size={11} style={{ opacity: 0.6 }} />
          </button>
          {orgMenuOpen && <OrgSwitcherMenu currentOrg={currentOrg}
            onClose={() => setOrgMenuOpen(false)} onSwitch={onSwitchOrg} />}
        </div>
        )}

        {!isLanding && (
          <>
            <I.ChevronRight size={13} style={{ color: 'var(--text-faint)' }} />
            {/* Read straight off the prop — no local mirror. The mirror this
                replaced had to be re-synced by an effect on every course switch
                (`feedback_a_local_mirror_of_props_goes_stale`); with the editor
                gone there is nothing for it to hold. */}
            <span title={course.title} style={{
              padding: '4px 8px', color: 'var(--text)', fontSize: 14,
              fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden',
              textOverflow: 'ellipsis', maxWidth: 420,
            }}>{course.title}</span>
          </>
        )}
      </div>

      {/* Right-side header actions */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
        {!isLanding && (
          <>
            {/* NEVER hardcode this again. It read `state="saved"` from the day it
                was written until 2026-08-11, so the header promised the server had
                the author's work whatever the truth was — and on the two roles
                screens the truth was that nothing had ever been sent. The default
                when the prop is absent is the HONEST one, not the reassuring one.
                It renders NOTHING at rest since 2026-08-12 (Omar: "Remove also
                'Saved on this device'"), and it brings its own trailing divider so
                the separator disappears with it. */}
            <SaveIndicator state={serverSync || 'local'} timestamp={course.lastSaved} />
          </>
        )}
        {/* The divider below separates the STATUS cluster (save state, AI spend) from the
            ACTIONS cluster. It is NOT the magnifier's, and it stays.

            ★ I removed it along with the magnifier and `fe-save-doors.test.ts` caught it
            immediately: that test keeps this exact divider as its proof that its own
            narrower assertion is not merely satisfied, and says so in its comment. I had
            read it as "the separator before the Search button". It separates two GROUPS,
            and with Preview now first in the actions cluster it still has a job.

            This note sits ABOVE `<AiSpendIndicator>` rather than between it and the
            divider, because the same test asserts they are adjacent with only whitespace
            in between — a comment in that gap fails it. Worth knowing before "tidying". */}
        <AiSpendIndicator spend={course.aiSpend} />
        <div style={{ width: 1, height: 24, background: 'var(--border)' }} />
        {/* ── The magnifier button that used to sit here is GONE ────────────────
            Removed 2026-08-13 on Omar's instruction: "Let's remove from the top
            the 'Search'". It read as a search field and was not one — it opened
            the ⌘K command palette, which is a different promise than the icon
            makes (`feedback_no_false_affordance_toggles`).

            ★ The palette itself is NOT removed and is still reachable: the
            keydown handler at `app.jsx:421` owns ⌘K independently of this button,
            verified before deleting it. Removing a control whose only other door
            does not exist would have quietly deleted the feature
            (`feedback_a_new_gate_needs_its_remedy_to_exist`).

            The now-unused `onCmdK` prop was dropped from this component's
            signature and from its call site too, rather than left dangling to read
            as still-wired. The divider above is NOT part of this removal — see the
            note on it. */}
        {/* ── Course preview ────────────────────────────────────────────────────
            Renamed from "Preview" and filled with the brand red on 2026-08-13, when
            a SECOND preview arrived: "Module preview", in the module card over in
            Course architecture.

            ★ WHY THIS ONE STAYS UP HERE, and why that is the whole disambiguation.
            A control's scope is read from WHERE IT LIVES far more reliably than from
            its label. This band is the app's course-wide chrome — it already carries
            the organisation, the course title and the course's AI spend — so a button
            here is understood to act on the course. The module-scoped one lives INSIDE
            the module's own card, next to that module's other action. Same reasoning as
            keeping the language picker out of the preview window: the control belongs
            to whatever owns the thing it acts on (`feedback_one_rule_one_place`).

            Three further separations, so they cannot read as the same button twice:
              · WEIGHT — this is the only filled one. Module preview is a quiet ghost,
                peer to "+ Add layout" beside it, because that is its actual peer.
              · HUE — both use `#A53E53`, but only this one uses it as a FILL; the other
                wears it as text. Same family, obviously different rank.
              · LABEL — each names its own scope, so neither is just "Preview".

            The colour is a literal rather than a token because it is the one Omar
            specified; there is no brand-red token in this stylesheet to point at yet. */}
        {!isLanding && (
          <button className="btn sm" onClick={onPreview}
            style={{ background: '#A53E53', borderColor: '#A53E53', color: '#fff' }}>
            <I.Eye size={13} />
            Course preview
          </button>
        )}
        <UserMenu currentOrg={currentOrg} />
      </div>
    </header>
  );
}

// ── OrgSwitcherMenu ─────────────────────────────────────────────────────────
function OrgSwitcherMenu({ currentOrg, onClose, onSwitch }) {
  return (
    <>
      <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 50 }} />
      <div className="slide-in" style={{
        position: 'absolute', top: 'calc(100% + 6px)', left: -4,
        width: 360, background: 'var(--surface)',
        border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
        boxShadow: 'var(--shadow-xl)', zIndex: 51, padding: 8,
      }}>
        <div style={{ padding: '6px 8px', fontSize: 10.5, fontWeight: 600,
          color: 'var(--text-faint)', letterSpacing: '.06em', textTransform: 'uppercase' }}>
          Your organizations
        </div>
        {window.SAMPLE_ORGS.map(org => (
          <button key={org.id} onClick={() => { onSwitch(org.id); onClose(); }}
            style={{
              display: 'flex', alignItems: 'center', gap: 10, width: '100%',
              padding: '8px 8px', textAlign: 'left',
              background: org.id === currentOrg.id ? 'var(--surface-inset)' : 'transparent',
              border: 0, borderRadius: 'var(--radius)', cursor: 'default',
              fontFamily: 'inherit', color: 'var(--text)', marginBottom: 2,
            }}
            onMouseOver={e => e.currentTarget.style.background = 'var(--surface-inset)'}
            onMouseOut={e => e.currentTarget.style.background = org.id === currentOrg.id ? 'var(--surface-inset)' : 'transparent'}>
            <OrgGlyph org={org} size={32} />
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13, fontWeight: 600 }}>{org.full}</div>
              <div style={{ fontSize: 11, color: 'var(--text-muted)' }} className="truncate">
                {org.stats.courses} courses · {org.stats.editors} editors
              </div>
            </div>
            {org.id === currentOrg.id && <I.Check size={14} style={{ color: 'var(--accent)' }} />}
          </button>
        ))}
        <div style={{ borderTop: '1px solid var(--border)', margin: '4px -8px 0', padding: 8 }}>
          <button className="btn sm ghost" style={{ width: '100%', justifyContent: 'flex-start' }}>
            <I.Plus size={12} />Join another organization
          </button>
          <button className="btn sm ghost" style={{ width: '100%', justifyContent: 'flex-start' }}>
            <I.Settings size={12} />Organization settings
          </button>
        </div>
      </div>
    </>
  );
}

function DynamoLogo() {
  return (
    <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <rect x="2" y="2" width="20" height="20" rx="5" fill="var(--text)" />
      <path d="M8 7 L8 17 L13 17 C15.5 17 17 15 17 12 C17 9 15.5 7 13 7 Z" fill="var(--bg)" />
      <circle cx="17" cy="7" r="2" fill="var(--accent)" />
    </svg>
  );
}

function AiSpendIndicator({ spend }) {
  const pct = (spend.used / spend.cap) * 100;
  const color = pct > 90 ? 'var(--error)' : pct > 70 ? 'var(--warning)' : 'var(--accent)';
  return (
    <button className="btn sm ghost" title={`AI spend this session: $${spend.used.toFixed(2)} of $${spend.cap.toFixed(2)}`}
      style={{ display: 'inline-flex', alignItems: 'center', gap: 8, height: 28 }}>
      <I.Sparkle size={12} style={{ color }} />
      <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11.5 }}>
        ${spend.used.toFixed(2)}<span style={{ color: 'var(--text-faint)' }}> / ${spend.cap.toFixed(0)}</span>
      </span>
      <div style={{ width: 36, height: 3, background: 'var(--border)', borderRadius: 2 }}>
        <div style={{ width: `${Math.min(100,pct)}%`, height: '100%', background: color, borderRadius: 2 }} />
      </div>
    </button>
  );
}

function UserMenu({ currentOrg }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{ position: 'relative' }}>
      <button onClick={() => setOpen(o => !o)} style={{
        width: 30, height: 30, padding: 0, border: 0, background: 'transparent', cursor: 'default',
        display: 'inline-flex', alignItems: 'center', justifyContent: 'center', borderRadius: '50%',
      }}>
        <div style={{
          width: 28, height: 28, borderRadius: '50%',
          background: 'linear-gradient(135deg, #818cf8 0%, #c084fc 100%)',
          color: '#fff', fontSize: 11.5, fontWeight: 600,
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          boxShadow: open ? 'inset 0 0 0 1px rgba(255,255,255,.15), 0 0 0 2px var(--accent)' : 'inset 0 0 0 1px rgba(255,255,255,.15)',
        }}>LP</div>
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 50 }} />
          <div className="slide-in" style={{
            position: 'absolute', top: 'calc(100% + 8px)', right: -4,
            width: 260, background: 'var(--surface)',
            border: '1px solid var(--border)', borderRadius: 'var(--radius-md)',
            boxShadow: 'var(--shadow-xl)', zIndex: 51, padding: 8,
          }}>
            <div style={{ padding: '8px 10px', display: 'flex', gap: 10, alignItems: 'center' }}>
              <div style={{
                width: 36, height: 36, borderRadius: '50%',
                background: 'linear-gradient(135deg, #818cf8, #c084fc)',
                color: '#fff', fontSize: 13, fontWeight: 700,
                display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              }}>LP</div>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 600 }}>Lisa Park</div>
                <div style={{ fontSize: 11, color: 'var(--text-muted)' }} className="truncate">
                  lisa@{currentOrg.id}.com
                </div>
              </div>
            </div>
            <div style={{ height: 1, background: 'var(--border)', margin: '6px -8px' }} />
            {[
              { icon: 'Settings', label: 'Profile & preferences' },
              { icon: 'Sparkle',  label: 'AI spend & limits' },
              { icon: 'Bell',     label: 'Notifications' },
              { icon: 'Command',  label: 'Keyboard shortcuts', shortcut: '⌘/' },
            ].map((it, i) => {
              const Ic = window.I[it.icon];
              return (
                <button key={i} className="btn sm ghost" style={{
                  width: '100%', justifyContent: 'flex-start', height: 30, padding: '0 10px',
                }}>
                  <Ic size={13} /><span style={{ flex: 1, textAlign: 'left' }}>{it.label}</span>
                  {it.shortcut && <kbd>{it.shortcut}</kbd>}
                </button>
              );
            })}
            <div style={{ height: 1, background: 'var(--border)', margin: '6px -8px' }} />
            <button className="btn sm ghost"
              onClick={() => { setOpen(false); window.dynamoSignOut && window.dynamoSignOut(); }}
              style={{
                width: '100%', justifyContent: 'flex-start', height: 30, padding: '0 10px',
                color: 'var(--error-text)',
              }}>
              <I.ArrowLeft size={13} />Sign out
            </button>
          </div>
        </>
      )}
    </div>
  );
}

// ── LeftRail ────────────────────────────────────────────────────────────────
function LeftRail({ collapsed, currentSurface, onNavigate, onToggleCollapse }) {
  return (
    <nav style={{
      gridArea: 'rail',
      background: 'var(--surface)',
      borderRight: '1px solid var(--border)',
      width: collapsed ? 56 : 212,
      transition: 'width 200ms ease-out',
      display: 'flex', flexDirection: 'column',
      overflow: 'hidden',
    }}>
      <div style={{ flex: 1, padding: '8px 6px', display: 'flex', flexDirection: 'column', gap: 2 }}>
        {visibleNavItems().map((item, i) => {
          const Icon = I[item.icon];
          const active = currentSurface === item.id;
          return (
            <React.Fragment key={item.id}>
              {i === 1 && <div style={{ height: 1, background: 'var(--border)', margin: '4px 6px' }} />}
              {i === 3 && <div style={{ height: 1, background: 'var(--border)', margin: '4px 6px' }} />}
              <button onClick={() => onNavigate(item.id)}
                title={collapsed ? item.label : ''}
                style={{
                  display: 'flex', alignItems: 'center', gap: 10,
                  padding: collapsed ? '0' : '0 10px',
                  height: 32, width: '100%',
                  justifyContent: collapsed ? 'center' : 'flex-start',
                  border: 0, borderRadius: 'var(--radius)',
                  background: active ? 'var(--accent-bg)' : 'transparent',
                  color: active ? 'var(--accent-text)' : 'var(--text-muted)',
                  cursor: 'default', fontSize: 13, fontWeight: active ? 600 : 500,
                  fontFamily: 'inherit', position: 'relative',
                }}
                onMouseOver={e => { if (!active) e.currentTarget.style.background = 'var(--surface-inset)'; }}
                onMouseOut={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}>
                <Icon size={16} />
                {!collapsed && <span style={{ flex: 1, textAlign: 'left' }}>{item.label}</span>}
                {item.badge && !collapsed && (
                  <span style={{
                    minWidth: 16, height: 16, padding: '0 4px',
                    borderRadius: 8,
                    background: item.badge.dot === 'error' ? 'var(--error)' : 'var(--warning)',
                    color: '#fff', fontSize: 10, fontWeight: 700,
                    display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                  }}>{item.badge.count}</span>
                )}
                {item.badge && collapsed && (
                  <span style={{
                    position: 'absolute', top: 4, right: 6,
                    width: 7, height: 7, borderRadius: '50%',
                    background: item.badge.dot === 'error' ? 'var(--error)' : 'var(--warning)',
                  }} />
                )}
              </button>
            </React.Fragment>
          );
        })}
      </div>
      {/* Footer of rail */}
      <div style={{ padding: '8px 6px', borderTop: '1px solid var(--border)',
        display: 'flex', flexDirection: 'column', gap: 2 }}>
        <button onClick={onToggleCollapse} className="btn sm ghost"
          style={{ width: '100%', justifyContent: collapsed ? 'center' : 'flex-start',
            height: 32, padding: collapsed ? 0 : '0 10px', borderRadius: 'var(--radius)' }}>
          {collapsed ? <I.ChevronRight size={14} /> : <><I.ChevronLeft size={14} /><span>Collapse</span></>}
        </button>
      </div>
    </nav>
  );
}

// ── StatusBar ───────────────────────────────────────────────────────────────
function StatusBar({ course, jobs = [], onValidationClick }) {
  return (
    <footer style={{
      gridArea: 'status',
      height: 26,
      background: 'var(--surface)',
      borderTop: '1px solid var(--border)',
      display: 'flex', alignItems: 'center',
      padding: '0 12px',
      gap: 12,
      fontSize: 11.5,
      color: 'var(--text-muted)',
    }}>
      <button className="focusable"
        style={{ display: 'inline-flex', alignItems: 'center', gap: 5,
          color: 'inherit', background: 'transparent', border: 0, cursor: 'default',
          fontSize: 'inherit', padding: '2px 6px', borderRadius: 3, fontFamily: 'inherit' }}>
        <I.AlertTriangle size={11} style={{ color: 'var(--warning)' }} />
        <span>{course.validation.items.length} issues</span>
      </button>
      <Sep />
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
        <I.Clock size={11} />Last save {course.lastSaved}
      </span>
      <Sep />
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
        <I.Database size={11} />SCORM {course.scormVersion}
      </span>
      <Sep />
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
        <I.Globe size={11} />{course.languages.length} langs
      </span>
      <div style={{ flex: 1 }} />
      {/* Background jobs */}
      {jobs.length > 0 && (
        <>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
            <I.Loader size={11} className="spin" />{jobs.length} jobs running
          </span>
          <Sep />
        </>
      )}
      <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
        <I.Activity size={11} style={{ color: 'var(--success)' }} />Connected
      </span>
      <Sep />
      <kbd>?</kbd> <span>Shortcuts</span>
    </footer>
  );
}

function Sep() {
  return <span style={{ width: 1, height: 12, background: 'var(--border)' }} />;
}

Object.assign(window, { TopHeader, LeftRail, StatusBar, NAV_ITEMS });
