// V2/src/chrome.jsx
// Top nav + sidebar + mobile bottom-bar + module router.
// Renders panel shells (#panel-strategy / #panel-compete / #panel-opp);
// modules (Tier 4) mount their content INTO them.
// No import statements. Reads icons + primitives from window.
// Exposes Chrome, switchModule, MODULES, MODULE_KEYS, DEFAULT_MODULE, STATE on window.

// ── Module registry (source of truth for keys, labels, icons, atmosphere) ──
const MODULES = {
  strategy: {
    key: 'strategy',
    label: 'PartnerIQ',
    short: 'Partner​IQ',
    desc: 'Strategic partnership intelligence engine.',
    get Icon() { return window.HandshakeIcon; },
    iconClass: 'piq-shake',
    accent: '#0d9488',
    accentGlow: 'rgba(13,148,136,0.10)',
    title: 'CollabIQ — PartnerIQ',
  },
  compete: {
    key: 'compete',
    label: 'CompeteIQ',
    short: 'Compete​IQ',
    desc: 'Competitive landscape and drug profile intelligence.',
    get Icon() { return window.TargetIcon; },
    iconClass: '',
    accent: '#d97706',
    accentGlow: 'rgba(217,119,6,0.10)',
    title: 'CollabIQ — CompeteIQ',
  },
  opp: {
    key: 'opp',
    label: 'WhitespaceIQ',
    short: 'Whitespace​IQ',
    desc: "The opportunity your competitors can't see is already on your map.",
    get Icon() { return window.MapIcon; },
    iconClass: '',
    accent: '#3b82f6',
    accentGlow: 'rgba(59,130,246,0.10)',
    title: 'CollabIQ — WhitespaceIQ',
  },
  // ExploreIQ — knowledge-graph schema explorer (iframe-mounted, React-rendered).
  // Distinct from the other 3: no imperative window.renderXxx function — the
  // panel is populated by mounting <ExploreIQModule/> directly (see Chrome()'s
  // panel map). Excluded from MODULE_KEYS so the sidebar/bottom-bar tab
  // mappings keep their 3-module layout; the sidebar entry is hand-rolled
  // below the divider (see Sidebar() L727-746).
  exploreiq: {
    key: 'exploreiq',
    label: 'ExploreIQ',
    short: 'Explore​IQ',
    desc: 'Knowledge graph schema explorer.',
    get Icon() { return null; },
    iconClass: '',
    accent: '#8b5cf6',
    accentGlow: 'rgba(139,92,246,0.10)',
    title: 'CollabIQ — ExploreIQ',
  },
};

// MODULE_KEYS drives the sidebar / bottom-bar tab mapping + panel containers.
// ExploreIQ is intentionally excluded — it has its own hand-rolled sidebar
// entry (see Sidebar L727-746) and its panel is mounted via <ExploreIQModule/>
// rather than an imperative window.renderXxx function.
const MODULE_KEYS = Object.keys(MODULES).filter(k => k !== 'exploreiq');
const DEFAULT_MODULE = 'strategy';

// ── Shared module-level state bag ──
// Modules read STATE.activeModule; they extend this with their own keys.
// _mountedModules tracks which panels have already rendered (first-mount guard).
const STATE = window.STATE || {
  activeModule: DEFAULT_MODULE,
  _mountedModules: new Set(),
  comp_view: 'landscape',
  strat_subview: 'partners',
  strat_anchor_org: 'Sanofi',
  opp_matrix: 'target-disease',
};

// Ensure _mountedModules is always a Set even if STATE was pre-seeded without it.
if (!(STATE._mountedModules instanceof Set)) {
  STATE._mountedModules = new Set();
}

// ── Theme toggle: two-segment Light/Dark pill (Stripe/Linear style) ──
// Reads/writes `document.documentElement.dataset.theme` and persists to
// localStorage under `dashiq.theme`. Mirrors the existing token system —
// tokens.css already defines [data-theme="dark"] (L52) and
// [data-theme="light"] (L103) variants for every color token.
function ThemeToggle() {
  const [theme, setTheme] = React.useState(() => {
    if (typeof document === 'undefined') return 'dark';
    return document.documentElement.dataset.theme || 'dark';
  });

  // On mount: re-hydrate from localStorage and apply to <html>
  React.useEffect(() => {
    try {
      const stored = window.localStorage.getItem('dashiq.theme');
      if (stored === 'light' || stored === 'dark') {
        document.documentElement.dataset.theme = stored;
        setTheme(stored);
      } else {
        // No stored pref — sync state to whatever <html> currently has
        const current = document.documentElement.dataset.theme || 'dark';
        setTheme(current);
      }
    } catch (e) {
      // localStorage may be blocked (private mode etc.) — fail silent
    }
  }, []);

  const setThemeMode = React.useCallback((next) => {
    if (next !== 'light' && next !== 'dark') return;
    document.documentElement.dataset.theme = next;
    setTheme(next);
    try {
      window.localStorage.setItem('dashiq.theme', next);
    } catch (e) {
      // localStorage may be blocked — fail silent
    }
    // Tell theme-aware modules (e.g. sonar SVG) to rebuild their inline
    // light/dark-baked color attributes against the new tokens.
    window.dispatchEvent(new CustomEvent('theme:change', { detail: { theme: next } }));
  }, []);

  const isLight = theme === 'light';

  // Sun icon (inline SVG) — used in Light segment
  const sunIcon = (
    <svg
      width="11"
      height="11"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      style={{ flexShrink: 0 }}
    >
      <circle cx="12" cy="12" r="4" />
      <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
    </svg>
  );

  // Moon icon (inline SVG) — used in Dark segment
  const moonIcon = (
    <svg
      width="11"
      height="11"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      style={{ flexShrink: 0 }}
    >
      <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
    </svg>
  );

  return (
    <div
      role="group"
      aria-label="Theme"
      title={isLight ? 'Switch to dark theme' : 'Switch to light theme'}
      style={{
        position: 'relative',
        display: 'inline-flex',
        alignItems: 'center',
        height: '28px',
        minHeight: '28px',
        padding: '2px',
        background: 'var(--g-spatial)',
        border: '1px solid var(--g-border)',
        borderRadius: 'var(--r-pill)',
        flexShrink: 0,
        // Expand interactive hit area to 44px without growing visual size
        boxSizing: 'content-box',
      }}
    >
      {/* Sliding active indicator */}
      <span
        aria-hidden="true"
        style={{
          position: 'absolute',
          top: '2px',
          left: '2px',
          width: 'calc(50% - 2px)',
          height: 'calc(100% - 4px)',
          background: 'var(--g-elevated)',
          border: '1px solid var(--g-border)',
          borderRadius: 'var(--r-pill)',
          transform: isLight ? 'translateX(0)' : 'translateX(100%)',
          transition: 'transform var(--t-fast) var(--ease-out)',
          boxShadow: 'var(--sh-1)',
        }}
      />

      {/* Light segment */}
      <button
        type="button"
        onClick={() => setThemeMode('light')}
        aria-pressed={isLight}
        aria-label="Light theme"
        style={{
          position: 'relative',
          zIndex: 1,
          display: 'inline-flex',
          alignItems: 'center',
          justifyContent: 'center',
          gap: '4px',
          // Visual: tight; hit area expanded via padding wrapper
          minHeight: '44px',
          minWidth: '32px',
          height: '24px',
          padding: '0 8px',
          margin: '-10px 0', // negative margin keeps the visible row 24px
          fontFamily: 'var(--font-mono)',
          fontSize: '9px',
          fontWeight: 700,
          letterSpacing: '0.08em',
          textTransform: 'uppercase',
          color: isLight ? 'var(--text)' : 'var(--text-muted)',
          background: 'transparent',
          border: 'none',
          borderRadius: 'var(--r-pill)',
          cursor: 'pointer',
          transition: 'color var(--t-fast) var(--ease-out)',
        }}
      >
        {sunIcon}
        <span>L</span>
      </button>

      {/* Dark segment */}
      <button
        type="button"
        onClick={() => setThemeMode('dark')}
        aria-pressed={!isLight}
        aria-label="Dark theme"
        style={{
          position: 'relative',
          zIndex: 1,
          display: 'inline-flex',
          alignItems: 'center',
          justifyContent: 'center',
          gap: '4px',
          minHeight: '44px',
          minWidth: '32px',
          height: '24px',
          padding: '0 8px',
          margin: '-10px 0',
          fontFamily: 'var(--font-mono)',
          fontSize: '9px',
          fontWeight: 700,
          letterSpacing: '0.08em',
          textTransform: 'uppercase',
          color: isLight ? 'var(--text-muted)' : 'var(--text)',
          background: 'transparent',
          border: 'none',
          borderRadius: 'var(--r-pill)',
          cursor: 'pointer',
          transition: 'color var(--t-fast) var(--ease-out)',
        }}
      >
        {moonIcon}
        <span>D</span>
      </button>
    </div>
  );
}

// ── FaviconMark + FaviconMarkGlassOrb ──
// Now extracted to /logos/FaviconMark.jsx as a reusable, drop-in component.
// That file exposes window.FaviconMark (bare glyph), window.FaviconMarkGlassOrb
// (glass capsule home button), and window.FaviconMarkSVG (raw markup string).
// We reference window.FaviconMarkGlassOrb directly below.

// ── FloatingChrome: V1-style floating icons (no header bar) ──
// V1 reference: NO topbar, only floating elements anchored to viewport.
//   - Top-left: glass capsule home button — clicking returns to default module (PartnerIQ)
//   - Top-right: theme toggle (Light/Dark segmented pill)
//
// UR-016 fix: previously the home orb was a marketing-site link
// (https://www.collabiqcore.com), which navigated AWAY from the dashboard
// rather than acting as an in-app home. Reworked as an in-app button that
// resets activeModule to DEFAULT_MODULE and clears any drawer/overlay state.
// Also adds an explicit FaviconMark glyph fallback so the button always
// renders (previously the orb was conditionally null when GlassOrb was
// missing — leaving the user with NO visible home button).
function FloatingChrome() {
  const FaviconMark = window.FaviconMark;

  const onHomeClick = React.useCallback((e) => {
    e.preventDefault();
    // Close drawers/overlays so the user returns to a clean default state.
    const evDrw = document.getElementById('ev-drawer');
    if (evDrw) evDrw.classList.remove('open');
    const ptDrw = document.getElementById('part-drawer');
    if (ptDrw) ptDrw.classList.remove('open');
    const bd = document.getElementById('drawer-backdrop');
    if (bd) bd.classList.remove('visible');
    if (typeof window._hideMobileBackdrop === 'function') {
      window._hideMobileBackdrop();
    }
    // Return to default module with forceRender so it re-paints from
    // a clean state (e.g. resets PartnerIQ to its default Sanofi anchor view).
    if (typeof window.switchModule === 'function') {
      window.switchModule(DEFAULT_MODULE, true);
    } else {
      // Fallback — fire the event the chrome listener handles directly.
      window.dispatchEvent(new CustomEvent('chrome:switch', {
        detail: { key: DEFAULT_MODULE, forceRender: true },
      }));
    }
  }, []);

  return (
    <React.Fragment>
      {/* Top-left home button — glass capsule with the CollabIQ favicon glyph.
          Clicking returns to the default module (PartnerIQ landscape view). */}
      <button
        type="button"
        onClick={onHomeClick}
        aria-label="Home — return to default module"
        title="Home"
        className="floating-home-orb favicon-glass-orb"
        style={{
          position: 'fixed',
          top: '12px',
          left: '12px',
          zIndex: 220,
          display: 'inline-flex',
          alignItems: 'center',
          justifyContent: 'center',
          width: '40px',
          height: '40px',
          padding: 0,
          borderRadius: '50%',
          background: 'var(--g-frosted)',
          border: '1px solid var(--g-border)',
          backdropFilter: 'blur(20px) saturate(160%)',
          WebkitBackdropFilter: 'blur(20px) saturate(160%)',
          boxShadow: 'var(--sh-1)',
          color: 'var(--text)',
          cursor: 'pointer',
          transition: 'border-color var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out), box-shadow var(--t-fast) var(--ease-out)',
        }}
        onMouseEnter={e => {
          e.currentTarget.style.borderColor = 'var(--teal)';
          e.currentTarget.style.transform = 'scale(1.05)';
          e.currentTarget.style.boxShadow = '0 0 0 3px rgba(45,212,191,0.18), var(--sh-1)';
        }}
        onMouseLeave={e => {
          e.currentTarget.style.borderColor = 'var(--g-border)';
          e.currentTarget.style.transform = 'scale(1)';
          e.currentTarget.style.boxShadow = 'var(--sh-1)';
        }}
      >
        {FaviconMark ? (
          <FaviconMark size={20} aria-hidden="true" />
        ) : (
          // Inline fallback glyph so the home button never disappears even
          // if logos/FaviconMark.jsx fails to load.
          <svg width="20" height="20" viewBox="0 0 16 16" aria-hidden="true" style={{ display: 'block' }}>
            <circle cx="5.5" cy="6" r="4.2" fill="rgba(45,212,191,0.55)" />
            <circle cx="10.5" cy="6" r="4.2" fill="rgba(45,212,191,0.55)" />
            <circle cx="8" cy="10" r="4.2" fill="rgba(56,189,248,0.45)" />
            <circle cx="8" cy="7.6" r="2.8" fill="none" stroke="#FFFFFF" strokeWidth="0.6" />
            <circle cx="8" cy="7.6" r="1.7" fill="#2DD4BF" />
          </svg>
        )}
      </button>

      {/* Top-right theme toggle */}
      <div
        className="floating-theme-toggle"
        style={{
          position: 'fixed',
          top: '12px',
          right: '12px',
          zIndex: 220,
        }}
      >
        <ThemeToggle />
      </div>
    </React.Fragment>
  );
}

// ── Top Nav: home orb + tab bar + search trigger + theme toggle + avatar ──
function TopNav({ activeModule, onSwitchModule, onOpenSearch }) {
  return (
    <nav
      id="chrome-nav"
      className="chrome-topbar"
      role="navigation"
      aria-label="Module navigation"
      style={{
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        zIndex: 200,
        display: 'flex',
        alignItems: 'center',
        gap: 'var(--s-3)',
        padding: '0 var(--s-5)',
        height: '56px',
        background: 'var(--g-frosted)',
        borderBottom: '1px solid var(--g-border)',
        backdropFilter: 'blur(20px) saturate(160%)',
        WebkitBackdropFilter: 'blur(20px) saturate(160%)',
        boxShadow: '0 1px 0 var(--g-border)',
        flexShrink: 0,
      }}
    >
      {/* Home orb — CollabIQ favicon mark, links to collabiqcore.com */}
      <a
        href="https://www.collabiqcore.com"
        className="chrome-home-orb"
        aria-label="Home — collabiqcore.com"
        title="Home"
        style={{
          display: 'inline-flex',
          alignItems: 'center',
          justifyContent: 'center',
          minHeight: '44px',
          minWidth: '36px',
          borderRadius: 'var(--r-md)',
          color: 'var(--text-muted)',
          background: 'transparent',
          border: '1px solid transparent',
          flexShrink: 0,
          textDecoration: 'none',
          transition: 'background var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out)',
        }}
        onMouseEnter={e => {
          e.currentTarget.style.background = 'var(--g-ground)';
          e.currentTarget.style.borderColor = 'var(--g-border)';
        }}
        onMouseLeave={e => {
          e.currentTarget.style.background = 'transparent';
          e.currentTarget.style.borderColor = 'transparent';
        }}
      >
        {window.FaviconMark
          ? <window.FaviconMark size={22} aria-hidden="true" />
          : window.HomeIcon
            ? <window.HomeIcon size={18} aria-hidden="true" />
            : <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M3 9.5L12 3l9 6.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1V9.5z"/></svg>}
      </a>

      {/* CollabIQ wordmark */}
      <span
        aria-label="CollabIQ"
        style={{
          fontFamily: 'var(--font-mono)',
          fontSize: 'var(--text-xs)',
          fontWeight: 700,
          letterSpacing: '0.08em',
          textTransform: 'uppercase',
          color: 'var(--text)',
          flexShrink: 0,
          userSelect: 'none',
        }}
      >
        Collab<span style={{ color: 'var(--teal)' }}>IQ</span>
      </span>

      {/* Divider */}
      <div
        aria-hidden="true"
        style={{
          width: '1px',
          height: '20px',
          background: 'var(--g-border)',
          flexShrink: 0,
        }}
      />

      {/* Module tab bar — 3 tabs */}
      <div
        role="tablist"
        aria-label="Module tabs"
        style={{
          display: 'flex',
          alignItems: 'center',
          gap: 'var(--s-1)',
          flex: 1,
        }}
      >
        {MODULE_KEYS.map(key => {
          const mod = MODULES[key];
          const isActive = activeModule === key;
          const Icon = mod.Icon;
          return (
            <button
              key={key}
              role="tab"
              aria-selected={isActive}
              aria-controls={`panel-${key}`}
              data-module={key}
              onClick={() => onSwitchModule(key)}
              style={{
                display: 'inline-flex',
                alignItems: 'center',
                gap: 'var(--s-2)',
                height: '30px',
                minWidth: '44px',
                padding: '0 var(--s-3)',
                fontFamily: 'var(--font-mono)',
                fontSize: 'var(--text-xs)',
                fontWeight: isActive ? 700 : 500,
                letterSpacing: '0.02em',
                color: isActive ? 'var(--bg-deep)' : 'var(--text-muted)',
                background: isActive ? mod.accent : 'transparent',
                border: isActive ? `1px solid ${mod.accent}` : '1px solid transparent',
                borderRadius: 'var(--r-pill)',
                cursor: 'pointer',
                whiteSpace: 'nowrap',
                transition: 'background var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out)',
                flexShrink: 0,
              }}
              onMouseEnter={e => {
                if (!isActive) {
                  e.currentTarget.style.background = 'var(--g-ground)';
                  e.currentTarget.style.color = 'var(--text)';
                  e.currentTarget.style.borderColor = 'var(--g-border)';
                }
              }}
              onMouseLeave={e => {
                if (!isActive) {
                  e.currentTarget.style.background = 'transparent';
                  e.currentTarget.style.color = 'var(--text-muted)';
                  e.currentTarget.style.borderColor = 'transparent';
                }
              }}
            >
              {Icon && (
                <Icon
                  size={14}
                  className={mod.iconClass || undefined}
                  aria-hidden="true"
                  style={{ flexShrink: 0 }}
                />
              )}
              {mod.label}
            </button>
          );
        })}
      </div>

      {/* Hidden bridge-island kept for module DOM API compatibility */}
      <div id="bridge-island" aria-live="polite" style={{ display: 'none' }}>
        <span id="bridge-island-label" />
      </div>

      {/* Search trigger — styled as a compact search input field */}
      <button
        onClick={onOpenSearch}
        aria-label="Open search (⌘K)"
        title="Search & Filter (⌘K)"
        style={{
          display: 'inline-flex',
          alignItems: 'center',
          gap: 'var(--s-2)',
          height: '30px',
          width: '180px',
          padding: '0 var(--s-3)',
          fontFamily: 'var(--font-body)',
          fontSize: 'var(--text-xs)',
          color: 'var(--text-faint)',
          background: 'var(--g-spatial)',
          border: '1px solid var(--g-border)',
          borderRadius: 'var(--r-md)',
          cursor: 'text',
          flexShrink: 0,
          transition: 'background var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out)',
          whiteSpace: 'nowrap',
          textAlign: 'left',
        }}
        onMouseEnter={e => {
          e.currentTarget.style.background = 'var(--g-ground)';
          e.currentTarget.style.borderColor = 'var(--g-border-strong)';
        }}
        onMouseLeave={e => {
          e.currentTarget.style.background = 'var(--g-spatial)';
          e.currentTarget.style.borderColor = 'var(--g-border)';
        }}
      >
        {window.SearchIcon && (
          <window.SearchIcon size={12} aria-hidden="true" style={{ flexShrink: 0, opacity: 0.5 }} />
        )}
        <span style={{ flex: 1 }}>Search...</span>
        <span
          style={{
            fontFamily: 'var(--font-mono)',
            fontSize: '9px',
            color: 'var(--text-faint)',
            background: 'var(--g-ground)',
            border: '1px solid var(--g-border)',
            borderRadius: '3px',
            padding: '1px 4px',
            letterSpacing: '0.02em',
            flexShrink: 0,
          }}
        >
          ⌘K
        </span>
      </button>

      {/* Theme toggle: two-segment Light/Dark pill */}
      <ThemeToggle />

      {/* User avatar placeholder — matches V1 nav right-end element */}
      <div
        aria-hidden="true"
        title="Account"
        style={{
          display: 'inline-flex',
          alignItems: 'center',
          justifyContent: 'center',
          width: '28px',
          height: '28px',
          borderRadius: '50%',
          background: 'var(--g-ground)',
          border: '1px solid var(--g-border)',
          flexShrink: 0,
          overflow: 'hidden',
          cursor: 'default',
        }}
      >
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={{ color: 'var(--text-muted)', opacity: 0.6 }}>
          <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
          <circle cx="12" cy="7" r="4" />
        </svg>
      </div>
    </nav>
  );
}

// ── Module-specific animated SVG glyphs (PIQ handshake / CIQ radar / WS scan / EIQ graph) ──
// 100x100 viewBox; CSS in styles/sidebar.css drives state colors + animations.
// Verbatim from sidebar-standalone_updated.html reference.
function PIQGlyph() {
  return (
    <svg viewBox="0 0 100 100" fill="none" aria-hidden="true">
      <g className="piq-shake">
        <g transform="translate(12 22) scale(0.30)">
          <path d="M254.3,107.91,228.78,56.85a16,16,0,0,0-21.47-7.15L182.44,62.13,130.05,48.27a8.14,8.14,0,0,0-4.1,0L73.56,62.13,48.69,49.7a16,16,0,0,0-21.47,7.15L1.7,107.9a16,16,0,0,0,7.15,21.47l27,13.51,55.49,39.63a8.06,8.06,0,0,0,2.71,1.25l64,16a8,8,0,0,0,7.6-2.1l40-40,15.08-15.08,26.42-13.21a16,16,0,0,0,7.15-21.46Zm-54.89,33.37L165,113.72a8,8,0,0,0-10.68.61C136.51,132.27,116.66,130,104,122L147.24,80h31.81l27.21,54.41Zm-41.87,41.86L99.42,168.61l-49.2-35.14,28-56L128,64.28l9.8,2.59-45,43.68-.08.09a16,16,0,0,0,2.72,24.81c20.56,13.13,45.37,11,64.91-5L188,152.66Zm-25.72,34.8a8,8,0,0,1-7.75,6.06,8.13,8.13,0,0,1-1.95-.24L80.41,213.33a7.89,7.89,0,0,1-2.71-1.25L51.35,193.26a8,8,0,0,1,9.3-13l25.11,17.94L126,208.24A8,8,0,0,1,131.82,217.94Z"/>
        </g>
      </g>
      <circle cx="50" cy="26" r="9" className="piq-ring-track"/>
      <circle cx="50" cy="26" r="9" transform="rotate(-90 50 26)" className="piq-ring-prog"/>
      <circle cx="50" cy="26" r="9" className="piq-fill"/>
      <polyline points="45,26 48,29 55,22" className="piq-check"/>
      <circle cx="50" cy="26" r="9" className="piq-pulse"/>
    </svg>
  );
}
// CompeteIQ glyph — target with offset competitor blips. Read-at-22px.
// Three concentric rings (visible, not opacity-faded), a centered "you"
// dot, three competitor blips at varying threat distances. No animated
// sweep — sidebar is always-on, motion is a distraction. The blips ARE
// the meaning of CompeteIQ: who else is in the space, where they sit
// relative to you.
function CIQGlyph() {
  return (
    <svg viewBox="0 0 100 100" fill="none" aria-hidden="true">
      <circle className="ciq-ring" cx="50" cy="50" r="40" strokeWidth="3.5" />
      <circle className="ciq-ring" cx="50" cy="50" r="26" strokeWidth="3" />
      <circle className="ciq-ring" cx="50" cy="50" r="13" strokeWidth="2.5" />
      <line className="ciq-tick" x1="50" y1="2"  x2="50" y2="9"  strokeWidth="3" strokeLinecap="round" />
      <line className="ciq-tick" x1="50" y1="91" x2="50" y2="98" strokeWidth="3" strokeLinecap="round" />
      <line className="ciq-tick" x1="2"  y1="50" x2="9"  y2="50" strokeWidth="3" strokeLinecap="round" />
      <line className="ciq-tick" x1="91" y1="50" x2="98" y2="50" strokeWidth="3" strokeLinecap="round" />
      <circle className="ciq-center" cx="50" cy="50" r="5" />
      <circle className="ciq-blip ciq-blip-1" cx="71" cy="28" r="5.5" />
      <circle className="ciq-blip ciq-blip-2" cx="26" cy="64" r="4.5" />
      <circle className="ciq-blip ciq-blip-3" cx="68" cy="74" r="4" />
    </svg>
  );
}
function WSGlyph() {
  return (
    <svg viewBox="0 0 100 100" fill="none" aria-hidden="true">
      <rect className="ws-inner" x="24" y="24" width="52" height="52" rx="6" />
      <g className="ws-frame-g">
        <path className="ws-bracket" d="M20,12 L12,12 L12,20" />
        <path className="ws-bracket" d="M80,12 L88,12 L88,20" />
        <path className="ws-bracket" d="M20,88 L12,88 L12,80" />
        <path className="ws-bracket" d="M80,88 L88,88 L88,80" />
      </g>
      <line className="ws-scan-line" x1="22" y1="50" x2="78" y2="50" />
      <circle className="ws-dot ws-dot-1" cx="38" cy="38" r="3" />
      <circle className="ws-dot ws-dot-2" cx="62" cy="54" r="3" />
      <circle className="ws-dot ws-dot-3" cx="48" cy="70" r="2.5" />
    </svg>
  );
}

// ── ExploreIQ glyph — knowledge graph (two clusters bridged) ──
// Verbatim from sidebar-standalone_updated.html reference.
function EIQGlyph() {
  return (
    <svg viewBox="0 0 100 100" fill="none" aria-hidden="true">
      <circle className="eiq-pulse" cx="50" cy="50" r="0"/>
      <line className="eiq-bridge eiq-bridge-1" x1="28" y1="32" x2="72" y2="32" strokeWidth="2.5" strokeDasharray="5 5"/>
      <line className="eiq-bridge eiq-bridge-2" x1="22" y1="52" x2="78" y2="52" strokeWidth="2" strokeDasharray="5 5"/>
      <line className="eiq-bridge eiq-bridge-3" x1="30" y1="70" x2="70" y2="70" strokeWidth="2" strokeDasharray="5 5"/>
      <g className="eiq-cluster-l">
        <line className="eiq-edge" x1="28" y1="32" x2="22" y2="52"/>
        <line className="eiq-edge" x1="28" y1="32" x2="30" y2="70"/>
        <line className="eiq-edge" x1="22" y1="52" x2="30" y2="70"/>
        <circle className="eiq-node" cx="28" cy="32" r="7" opacity="0.85"/>
        <circle className="eiq-node" cx="22" cy="52" r="5.5" opacity="0.6"/>
        <circle className="eiq-node" cx="30" cy="70" r="5.5" opacity="0.6"/>
      </g>
      <g className="eiq-cluster-r">
        <line className="eiq-edge-r" x1="72" y1="32" x2="78" y2="52"/>
        <line className="eiq-edge-r" x1="72" y1="32" x2="70" y2="70"/>
        <line className="eiq-edge-r" x1="78" y1="52" x2="70" y2="70"/>
        <circle className="eiq-node-r" cx="72" cy="32" r="7" opacity="0.85"/>
        <circle className="eiq-node-r" cx="78" cy="52" r="5.5" opacity="0.6"/>
        <circle className="eiq-node-r" cx="70" cy="70" r="5.5" opacity="0.6"/>
      </g>
    </svg>
  );
}

const MODULE_GLYPHS = { strategy: PIQGlyph, compete: CIQGlyph, opp: WSGlyph };

// ── Sidebar: standalone vertical floating glass capsule ──
// Class names match sidebar-standalone_updated.html reference exactly.
// Features:
//   - 54px collapsed → 172px on hover/pinned
//   - sidebar-active-pill: absolutely positioned morphing highlight
//   - Pin button in footer toggles .pinned class (persists localStorage)
//   - Section title "Module" above the 3 module items + ExploreIQ link
//   - Per-module animated SVG glyphs matching reference
//   - sb-ctx-* containers preserved for module context injection
function Sidebar({ activeModule, onSwitchModule, onOpenSearch }) {
  const [pinned, setPinned] = React.useState(() => {
    try { return window.localStorage.getItem('dashiq.sidebar.pinned') === '1'; }
    catch (e) { return false; }
  });
  const navRef = React.useRef(null);
  const pillRef = React.useRef(null);

  const togglePin = React.useCallback(() => {
    setPinned(prev => {
      const next = !prev;
      try { window.localStorage.setItem('dashiq.sidebar.pinned', next ? '1' : '0'); } catch (e) {}
      return next;
    });
  }, []);

  // Position the active pill to track the active sidebar item.
  //
  // V1 PARITY + LAYOUT-RESPONSIVE: V1 sets only top + height; CSS handles
  // full width via `left:1px; right:1px`. Use offsetTop/offsetHeight (CSS
  // layout metrics) so transforms (sb-icon-pop entrance) don't confuse
  // measurement.
  //
  // CRITICAL: when the user HOVERS or PINS the sidebar, buttons reflow
  // from column to row layout — height drops from ~37px collapsed to
  // ~28px expanded. Previously the pill only re-positioned on
  // [activeModule, pinned] changes, so on hover the buttons would shrink
  // but the pill would stay at collapsed height — visibly misaligned
  // (pill 9-36px taller than button, offset by 9-36px from button top
  // as items above also shrank). Add ResizeObserver on the nav so any
  // layout-affecting change (hover, font load, pin toggle, etc.)
  // re-positions the pill in lockstep.
  React.useEffect(() => {
    const pill = pillRef.current;
    const nav = navRef.current;
    if (!pill || !nav) return;
    const apply = () => {
      const activeBtn = nav.querySelector('.sidebar-item.active');
      if (!activeBtn) { pill.style.height = '0'; return; }
      pill.style.top    = activeBtn.offsetTop    + 'px';
      pill.style.height = activeBtn.offsetHeight + 'px';
    };
    apply();
    const raf = requestAnimationFrame(apply);

    // ResizeObserver fires whenever the nav (or any descendant) changes
    // size — including hover expansion, pin toggle, viewport changes.
    // Use a small RO-on-nav approach (one observer, fires on any child
    // resize). Browsers debounce RO callbacks to the next frame so this
    // is cheap.
    let ro = null;
    if (typeof ResizeObserver !== 'undefined') {
      ro = new ResizeObserver(apply);
      ro.observe(nav);
      // Also observe each sidebar-item so reflowing items trigger updates.
      nav.querySelectorAll('.sidebar-item').forEach(b => ro.observe(b));
    }

    // Belt-and-suspenders: explicit hover handlers in case RO is throttled
    // during the CSS transition.
    const onHoverChange = () => requestAnimationFrame(apply);
    nav.addEventListener('mouseenter', onHoverChange);
    nav.addEventListener('mouseleave', onHoverChange);
    // The hover transition is ~0.35s; reapply at 100ms and 400ms to catch
    // the start and end of the layout shift.
    const t1 = setTimeout(apply, 100);
    const t2 = setTimeout(apply, 400);

    return () => {
      cancelAnimationFrame(raf);
      if (ro) ro.disconnect();
      nav.removeEventListener('mouseenter', onHoverChange);
      nav.removeEventListener('mouseleave', onHoverChange);
      clearTimeout(t1);
      clearTimeout(t2);
    };
  }, [activeModule, pinned]);

  return (
    <nav
      id="sidebar-nav"
      ref={navRef}
      className={`sidebar-nav chrome-sidebar${pinned ? ' pinned' : ''}`}
      role="navigation"
      aria-label="Main navigation"
    >
      {/* Morphing active highlight */}
      <div className="sidebar-active-pill" id="sidebar-active-pill" ref={pillRef} />

      <div className="sidebar-divider" />
      <span className="sidebar-section-title">Module</span>

      {MODULE_KEYS.map(key => {
        const mod = MODULES[key];
        const isActive = activeModule === key;
        const Glyph = MODULE_GLYPHS[key];
        return (
          <React.Fragment key={key}>
            <button
              className={`sidebar-item${isActive ? ' active' : ''}`}
              data-module={key}
              aria-current={isActive ? 'page' : undefined}
              aria-label={mod.label}
              title={mod.label}
              onClick={() => onSwitchModule(key)}
            >
              <span className="sidebar-item-icon">
                {Glyph ? <Glyph /> : (mod.Icon ? <mod.Icon size={18} aria-hidden="true" /> : null)}
              </span>
              <span className="sidebar-item-name">{mod.short || mod.label}</span>
              <span className="sidebar-item-content">
                <span className="sidebar-item-label">{mod.label}</span>
              </span>
            </button>
            <div
              id={`sb-ctx-${key}`}
              className="sb-context"
              aria-live="polite"
            />
          </React.Fragment>
        );
      })}

      {/* ── ExploreIQ — embedded knowledge graph module ── */}
      <div className="sidebar-divider" />
      <span className="sidebar-section-title">Schema</span>
      <button
        type="button"
        id="sidebar-exploreiq"
        className={`sidebar-item${activeModule === 'exploreiq' ? ' active' : ''}`}
        onClick={() => onSwitchModule('exploreiq')}
        aria-label="ExploreIQ — knowledge graph explorer"
        aria-current={activeModule === 'exploreiq' ? 'page' : undefined}
        title="ExploreIQ"
      >
        <span className="sidebar-item-icon">
          <EIQGlyph />
        </span>
        <span className="sidebar-item-name">Explore&#8203;IQ</span>
        <span className="sidebar-item-content">
          <span className="sidebar-item-label">ExploreIQ</span>
        </span>
      </button>

      <div className="sidebar-footer">
        <button
          type="button"
          className={`sidebar-pin-btn${pinned ? ' is-pinned' : ''}`}
          onClick={togglePin}
          aria-pressed={pinned}
          aria-label={pinned ? 'Unpin sidebar' : 'Pin sidebar open'}
          title={pinned ? 'Unpin' : 'Pin open'}
        >
          <svg className="pin-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <line x1="12" y1="17" x2="12" y2="22" />
            <path d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24Z" />
          </svg>
          <span className="sidebar-pin-label">{pinned ? 'Pinned' : 'Pin'}</span>
        </button>
      </div>
    </nav>
  );
}

// ── Mobile bottom bar (≤768px): Partner / Compete / WhiteSpace tabs ──
// Matches V1 #sidebar-bottom-bar with bottom-tab-item buttons.
// Fixed bottom, 3 tabs, icons + short labels. 44px hit-target.
function BottomBar({ activeModule, onSwitchModule }) {
  return (
    <div
      id="sidebar-bottom-bar"
      className="sidebar-bottom-bar"
      role="tablist"
      aria-label="Module navigation"
      style={{
        position: 'fixed',
        bottom: 0,
        left: 0,
        right: 0,
        zIndex: 300,
        display: 'flex',
        flexDirection: 'row',
        alignItems: 'stretch',
        height: '56px',
        background: 'var(--g-frosted)',
        borderTop: '1px solid var(--g-border)',
        backdropFilter: 'blur(20px) saturate(160%)',
        WebkitBackdropFilter: 'blur(20px) saturate(160%)',
        paddingBottom: 'env(safe-area-inset-bottom, 0px)',
      }}
    >
      {MODULE_KEYS.map(key => {
        const mod = MODULES[key];
        const isActive = activeModule === key;
        const Icon = mod.Icon;
        return (
          <button
            key={key}
            role="tab"
            aria-selected={isActive}
            aria-controls={`panel-${key}`}
            data-module={key}
            className={`bottom-tab-item${isActive ? ' active' : ''}`}
            onClick={() => onSwitchModule(key)}
            style={{
              flex: 1,
              display: 'flex',
              flexDirection: 'column',
              alignItems: 'center',
              justifyContent: 'center',
              gap: '2px',
              minHeight: '44px',
              minWidth: '44px',
              padding: 'var(--s-1) var(--s-2)',
              fontFamily: 'var(--font-mono)',
              fontSize: '9px',
              fontWeight: isActive ? 700 : 500,
              letterSpacing: '0.04em',
              textTransform: 'uppercase',
              color: isActive ? 'var(--teal)' : 'var(--text-muted)',
              background: 'transparent',
              border: 'none',
              borderTop: isActive ? '2px solid var(--teal)' : '2px solid transparent',
              cursor: 'pointer',
              transition: 'color var(--t-fast) var(--ease-out), border-top-color var(--t-fast) var(--ease-out)',
            }}
          >
            <span
              className="bottom-tab-icon"
              style={{
                display: 'inline-flex',
                alignItems: 'center',
                justifyContent: 'center',
                width: '20px',
                height: '20px',
                color: isActive ? 'var(--teal)' : 'var(--text-muted)',
              }}
            >
              {Icon && <Icon size={16} aria-hidden="true" />}
            </span>
            <span className="bottom-tab-label">{mod.short}</span>
          </button>
        );
      })}
    </div>
  );
}

// ── Search popover (overlay): org + compare inputs, closes on Escape / X ──
// Input values are passed to window.STATE and then call switchModule('strategy', true).
// Uses window.DASHIQ for live autocomplete. Escape key handled by Chrome root useEffect.
function SearchPopover({ open, onClose }) {
  const [orgVal, setOrgVal] = React.useState('');
  const [cmpVal, setCmpVal] = React.useState('');
  const [orgResults, setOrgResults] = React.useState([]);
  const [cmpResults, setCmpResults] = React.useState([]);
  const orgRef = React.useRef(null);

  React.useEffect(() => {
    if (open && orgRef.current) {
      orgRef.current.focus();
    }
    if (!open) {
      setOrgResults([]);
      setCmpResults([]);
    }
  }, [open]);

  const fetchOrgSuggestions = React.useCallback((query, setter) => {
    if (!query || query.length < 2) { setter([]); return; }
    if (window.DASHIQ && typeof window.DASHIQ.fetchOrgs === 'function') {
      // DEFECT-001 fix: previously passed `query` as opts (broken) AND tried
      // to `.slice` the response object (broken). Now passes `{ q: query }`
      // as the params object and extracts `.results` from the envelope shape
      // `{total, results: [...]}` that `/v2/orgs?q=…` returns.
      window.DASHIQ.fetchOrgs({ q: query }).then(res => {
        const list = Array.isArray(res) ? res
                   : Array.isArray(res && res.results) ? res.results
                   : [];
        setter(list.slice(0, 8));
      }).catch(() => setter([]));
    } else {
      setter([]);
    }
  }, []);

  const handleAnalyze = () => {
    if (!orgVal.trim()) return;
    STATE.strat_anchor_org = orgVal.trim();
    if (cmpVal.trim()) STATE.strat_org_b = cmpVal.trim();
    onClose();
    window.dispatchEvent(new CustomEvent('chrome:switch', {
      detail: { key: 'strategy', forceRender: true },
    }));
  };

  const handleKeyDown = (e) => {
    if (e.key === 'Enter') handleAnalyze();
  };

  if (!open) return null;

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label="Search organizations"
      onClick={e => { if (e.target === e.currentTarget) onClose(); }}
      style={{
        position: 'fixed',
        inset: 0,
        zIndex: 500,
        background: 'var(--g-dark)',
        display: 'flex',
        alignItems: 'flex-start',
        justifyContent: 'center',
        paddingTop: '15vh',
      }}
    >
      <div
        id="sidebar-search-popover"
        className="sidebar-search-popover"
        style={{
          position: 'relative',
          width: '100%',
          maxWidth: '400px',
          background: 'var(--g-elevated)',
          border: '1px solid var(--g-border-strong)',
          borderRadius: 'var(--r-xl)',
          backdropFilter: 'blur(28px) saturate(180%)',
          WebkitBackdropFilter: 'blur(28px) saturate(180%)',
          boxShadow: 'var(--sh-4)',
          padding: 'var(--s-5)',
          display: 'flex',
          flexDirection: 'column',
          gap: 'var(--s-4)',
        }}
      >
        {/* Close button */}
        <button
          id="search-pop-close"
          onClick={onClose}
          aria-label="Close search"
          style={{
            position: 'absolute',
            top: 'var(--s-3)',
            right: 'var(--s-3)',
            display: 'inline-flex',
            alignItems: 'center',
            justifyContent: 'center',
            minHeight: '44px',
            minWidth: '44px',
            background: 'transparent',
            border: 'none',
            borderRadius: 'var(--r-sm)',
            color: 'var(--text-muted)',
            cursor: 'pointer',
            fontSize: '18px',
            lineHeight: 1,
            transition: 'background var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out)',
          }}
          onMouseEnter={e => {
            e.currentTarget.style.background = 'var(--g-ground)';
            e.currentTarget.style.color = 'var(--text)';
          }}
          onMouseLeave={e => {
            e.currentTarget.style.background = 'transparent';
            e.currentTarget.style.color = 'var(--text-muted)';
          }}
        >
          {window.CloseIcon ? <window.CloseIcon size={16} aria-hidden="true" /> : '×'}
        </button>

        {/* Title */}
        <div
          id="search-pop-title"
          style={{
            fontFamily: 'var(--font-mono)',
            fontSize: 'var(--text-xs)',
            fontWeight: 700,
            letterSpacing: '0.06em',
            textTransform: 'uppercase',
            color: 'var(--text-muted)',
          }}
        >
          Explore by
        </div>

        <div className="search-pop-group" style={{ display: 'flex', flexDirection: 'column', gap: 'var(--s-3)' }}>
          {/* Org A input */}
          <div id="search-pop-row-org" style={{ display: 'flex', flexDirection: 'column', gap: 'var(--s-2)' }}>
            <div
              id="search-pop-label-org"
              className="search-pop-label"
              style={{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-3xs)', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}
            >
              Organization
            </div>
            <div
              className="search-pop-input-wrap"
              style={{ position: 'relative', display: 'flex', alignItems: 'center' }}
            >
              {window.SearchIcon && (
                <window.SearchIcon
                  size={14}
                  aria-hidden="true"
                  style={{ position: 'absolute', left: '10px', color: 'var(--text-faint)', pointerEvents: 'none' }}
                />
              )}
              <input
                ref={orgRef}
                id="search-pop-org"
                className="search-pop-input diq-search-input"
                type="text"
                placeholder="e.g. Sanofi"
                autoComplete="off"
                value={orgVal}
                onChange={e => {
                  setOrgVal(e.target.value);
                  fetchOrgSuggestions(e.target.value, setOrgResults);
                }}
                onKeyDown={handleKeyDown}
                style={{
                  width: '100%',
                  paddingLeft: '32px',
                  // UR-013 fix: explicit box-sizing so paddingLeft doesn't push
                  // the input width past 100% and create a horizontal-shift
                  // illusion as the dropdown opens.
                  boxSizing: 'border-box',
                }}
              />
              {orgResults.length > 0 && (
                <div
                  className="diq-search-dropdown"
                  // UR-013 fix: explicit width/background/border/radius so the
                  // dropdown has a stable, fully-styled surface independent of
                  // the .diq-search-dropdown class definition. Locked to the
                  // wrapper width via left:0/right:0; max-height + scroll keeps
                  // the dropdown from resizing as result counts change.
                  style={{
                    position: 'absolute',
                    top: 'calc(100% + 4px)',
                    left: 0,
                    right: 0,
                    zIndex: 600,
                    maxHeight: '256px',
                    overflowY: 'auto',
                    background: 'var(--g-elevated)',
                    border: '1px solid var(--g-border-strong)',
                    borderRadius: 'var(--r-md)',
                    boxShadow: 'var(--sh-3)',
                    boxSizing: 'border-box',
                  }}
                >
                  {orgResults.map((org, i) => (
                    <div
                      key={i}
                      style={{
                        padding: 'var(--s-2) var(--s-3)',
                        fontFamily: 'var(--font-body)',
                        fontSize: 'var(--text-sm)',
                        color: 'var(--text)',
                        cursor: 'pointer',
                        transition: 'background var(--t-fast) var(--ease-out)',
                      }}
                      onMouseEnter={e => { e.currentTarget.style.background = 'var(--g-frosted)'; }}
                      onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }}
                      onMouseDown={() => {
                        setOrgVal(org.name || org.org_name || String(org));
                        setOrgResults([]);
                      }}
                    >
                      {org.name || org.org_name || String(org)}
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>

          {/* Org B comparison input */}
          <div id="search-pop-row-cmp" style={{ display: 'flex', flexDirection: 'column', gap: 'var(--s-2)' }}>
            <div
              id="search-pop-label-cmp"
              className="search-pop-label"
              style={{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-3xs)', color: 'var(--text-muted)', letterSpacing: '0.04em', textTransform: 'uppercase' }}
            >
              Compare with (optional)
            </div>
            <div
              className="search-pop-input-wrap"
              style={{ position: 'relative', display: 'flex', alignItems: 'center' }}
            >
              {window.SearchIcon && (
                <window.SearchIcon
                  size={14}
                  aria-hidden="true"
                  style={{ position: 'absolute', left: '10px', color: 'var(--text-faint)', pointerEvents: 'none' }}
                />
              )}
              <input
                id="search-pop-cmp"
                className="search-pop-input diq-search-input"
                type="text"
                placeholder="e.g. Novartis"
                autoComplete="off"
                value={cmpVal}
                onChange={e => {
                  setCmpVal(e.target.value);
                  fetchOrgSuggestions(e.target.value, setCmpResults);
                }}
                onKeyDown={handleKeyDown}
                style={{ width: '100%', paddingLeft: '32px', boxSizing: 'border-box' }}
              />
              {cmpResults.length > 0 && (
                <div
                  className="diq-search-dropdown"
                  style={{
                    position: 'absolute',
                    top: 'calc(100% + 4px)',
                    left: 0,
                    right: 0,
                    zIndex: 600,
                    maxHeight: '256px',
                    overflowY: 'auto',
                    background: 'var(--g-elevated)',
                    border: '1px solid var(--g-border-strong)',
                    borderRadius: 'var(--r-md)',
                    boxShadow: 'var(--sh-3)',
                    boxSizing: 'border-box',
                  }}
                >
                  {cmpResults.map((org, i) => (
                    <div
                      key={i}
                      style={{
                        padding: 'var(--s-2) var(--s-3)',
                        fontFamily: 'var(--font-body)',
                        fontSize: 'var(--text-sm)',
                        color: 'var(--text)',
                        cursor: 'pointer',
                        transition: 'background var(--t-fast) var(--ease-out)',
                      }}
                      onMouseEnter={e => { e.currentTarget.style.background = 'var(--g-frosted)'; }}
                      onMouseLeave={e => { e.currentTarget.style.background = 'transparent'; }}
                      onMouseDown={() => {
                        setCmpVal(org.name || org.org_name || String(org));
                        setCmpResults([]);
                      }}
                    >
                      {org.name || org.org_name || String(org)}
                    </div>
                  ))}
                </div>
              )}
            </div>
          </div>

          {/* Analyze button */}
          <button
            id="search-pop-go"
            className="search-pop-btn diq-search-btn"
            onClick={handleAnalyze}
            style={{
              minHeight: '44px',
              minWidth: '44px',
            }}
          >
            Analyze
          </button>
        </div>
      </div>
    </div>
  );
}

// ── Org cross-module menu (#org-xmenu) ──
// Rendered as a portal-like fixed div. Populated imperatively by showOrgXMenu (V1 pattern
// reproduced as React component). Shown on orgXLink click, hidden on outside click / Escape.
// The div id="org-xmenu" must exist in DOM for V1-compatible orgXLink callers.
function OrgXMenu() {
  return (
    <div
      id="org-xmenu"
      role="menu"
      aria-label="Cross-module navigation"
      style={{
        display: 'none',
        position: 'fixed',
        zIndex: 800,
        minWidth: '240px',
        maxWidth: '300px',
        background: 'var(--g-elevated)',
        border: '1px solid var(--g-border-strong)',
        borderRadius: 'var(--r-lg)',
        backdropFilter: 'blur(24px) saturate(180%)',
        WebkitBackdropFilter: 'blur(24px) saturate(180%)',
        boxShadow: 'var(--sh-4)',
        overflow: 'hidden',
        fontFamily: 'var(--font-body)',
        fontSize: 'var(--text-sm)',
        color: 'var(--text)',
      }}
    />
  );
}

// ── ExploreIQModule — knowledge-graph schema explorer (iframe) ──
// Embeds the live explorer.collabiqcore.com Vercel deploy (the schema explorer
// has its own deploy pipeline at /Users/ctk/Desktop/CollabIQ V2.0/schema explorer/).
// Iframing the live URL means schema updates auto-propagate without rebuilding
// the dashboard. The explorer sets `x-frame-options: ALLOWALL` and CSP
// `frame-ancestors *` so any origin can embed it.
//
// Note: `allow-same-origin` is REQUIRED in the sandbox. It lets the explorer's
// scripts access their OWN origin (explorer.collabiqcore.com) — specifically
// localStorage/sessionStorage which the Vite/React bundle uses for layout state.
// Without it, the explorer renders as a black void (scripts run but hydration
// silently fails). It does NOT grant the iframe access to the parent's origin
// or cookies — those remain cross-origin per browser policy.
const ExploreIQModule = () => (
  <iframe
    src="https://explorer.collabiqcore.com/"
    title="ExploreIQ — Knowledge Graph"
    allow="fullscreen"
    sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
    style={{
      width: '100%',
      height: 'calc(100vh - 80px)', // panel container has no explicit height,
      // so 100% collapses to ~150px. Anchor iframe to viewport minus the
      // chrome header so it actually fills the available area.
      minHeight: '600px',
      border: 0,
      display: 'block',
      // --bg is theme-aware (light: #f1f4f9, dark: #06101e). Previous fallback
      // referenced --color-bg which is undefined in tokens.css, so the literal
      // #06101e dark navy was painted into the light-mode container as a
      // black bar above the iframe before its content loaded.
      background: 'var(--bg, #06101e)',
    }}
  />
);

// ── Panel container: the mount target each Tier 4 module renders into ──
// id must be exactly panel-{key}: #panel-strategy, #panel-compete, #panel-opp.
// role="tabpanel" for accessibility. active class = visible.
function PanelContainer({ moduleKey, isActive }) {
  return (
    <div
      id={`panel-${moduleKey}`}
      className={`panel${isActive ? ' active' : ''}`}
      role="tabpanel"
      aria-labelledby={`tab-${moduleKey}`}
      style={{
        display: isActive ? 'block' : 'none',
        flex: 1,
        minWidth: 0,
        minHeight: 0,
        overflow: 'auto',
      }}
    />
  );
}

// ── Chrome root ──
// Manages activeModule + searchOpen state.
// Bridges imperative window.switchModule (CustomEvent) ↔ React state.
// Calls module render functions on first mount or forceRender.
function Chrome() {
  const [activeModule, setActiveModule] = React.useState(DEFAULT_MODULE);
  const [searchOpen, setSearchOpen] = React.useState(false);

  // Internal switchModule that drives React state, atmosphere, document.title, render calls.
  const _switchModuleInternal = React.useCallback((key, forceRender = false) => {
    if (!MODULES[key]) return;

    // UR-017 fix: flip panel display attributes synchronously BEFORE the
    // module's imperative renderFn writes innerHTML. Previously
    // setActiveModule(key) below would queue a React re-render of
    // <PanelContainer/> for the next frame, but renderFn runs SYNC
    // immediately after — so the module rendered into a panel with
    // `display: none` and width:0/height:0 dimensions. Visualizations
    // measured wrong, layouts misaligned. By eagerly toggling the DOM
    // panels here, renderFn always operates on a panel that is already
    // visible at the time it computes layout.
    MODULE_KEYS.forEach(k => {
      const panel = document.getElementById('panel-' + k);
      if (panel) panel.style.display = (k === key) ? 'block' : 'none';
    });
    const explorePanel = document.getElementById('panel-exploreiq');
    if (explorePanel) {
      explorePanel.style.display = (key === 'exploreiq') ? 'block' : 'none';
    }

    // Update React state (drives panel visibility + nav visual state)
    setActiveModule(key);

    // Update shared STATE bag (imperative modules read this)
    STATE.activeModule = key;

    // Document title swap (constraint 12)
    document.title = MODULES[key].title;

    // Atmosphere accent swap — sets --accent and --accent-glow CSS custom props on :root
    document.documentElement.style.setProperty('--accent', MODULES[key].accent);
    document.documentElement.style.setProperty('--accent-glow', MODULES[key].accentGlow);

    // data-active-module attribute on html element (CSS can target this for per-module styling)
    document.documentElement.dataset.activeModule = key;

    // Close mobile drawers on module switch (matches V1 L19527-19531 pattern)
    const evDrw = document.getElementById('ev-drawer');
    if (evDrw) evDrw.classList.remove('open');
    const ptDrw = document.getElementById('part-drawer');
    if (ptDrw) ptDrw.classList.remove('open');
    if (typeof window._hideMobileBackdrop === 'function') {
      window._hideMobileBackdrop();
    }

    // ExploreIQ short-circuit: panel is React-mounted (<ExploreIQModule/>),
    // not imperative. Skip the renderFn lookup + deferred-retry branches —
    // setActiveModule() above is enough for the iframe to appear.
    if (key === 'exploreiq') {
      STATE._mountedModules.add(key);
      if (typeof window.refreshAllXmodBars === 'function') window.refreshAllXmodBars();
      return;
    }

    // Trigger module render — first mount or forceRender
    const renderFn = key === 'strategy' ? window.renderStrategy
                   : key === 'compete'  ? window.renderCompeteIQ
                   : key === 'opp'      ? window.renderOpp
                   : null;

    if (renderFn && (forceRender || !STATE._mountedModules.has(key))) {
      try {
        renderFn();
        STATE._mountedModules.add(key);
      } catch (renderErr) {
        console.warn('[chrome] module render error for key=' + key, renderErr);
      }
      // All module render fns set innerHTML synchronously, so xmod-island-wrap-*
      // elements exist in the DOM the moment renderFn() returns. Call directly —
      // no setTimeout needed and no race condition possible.
      if (typeof window.refreshAllXmodBars === 'function') window.refreshAllXmodBars();
    } else if (!renderFn && (forceRender || !STATE._mountedModules.has(key))) {
      // Render function not yet available — defer until it loads
      const deferMs = 100;
      setTimeout(() => {
        const deferredFn = key === 'strategy' ? window.renderStrategy
                         : key === 'compete'  ? window.renderCompeteIQ
                         : key === 'opp'      ? window.renderOpp
                         : null;
        if (deferredFn) {
          try {
            deferredFn();
            STATE._mountedModules.add(key);
          } catch (deferErr) {
            console.warn('[chrome] deferred render error for key=' + key, deferErr);
          }
          if (typeof window.refreshAllXmodBars === 'function') window.refreshAllXmodBars();
        }
      }, deferMs);
    } else {
      // Module already mounted — still refresh xmod bars in case STATE changed
      // (e.g. user ran a new analysis and strat_anchor_org updated).
      if (typeof window.refreshAllXmodBars === 'function') window.refreshAllXmodBars();
    }
  }, []);

  // Listen for chrome:switch events dispatched by the imperative window.switchModule().
  // This keeps React state in sync with Tier 4 module calls.
  React.useEffect(() => {
    const handler = (e) => {
      if (e.detail && e.detail.key) {
        _switchModuleInternal(e.detail.key, !!e.detail.forceRender);
      }
    };
    window.addEventListener('chrome:switch', handler);
    return () => window.removeEventListener('chrome:switch', handler);
  }, [_switchModuleInternal]);

  // Close search on Escape key
  React.useEffect(() => {
    const onKey = (e) => {
      if (e.key === 'Escape') setSearchOpen(false);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  // Open search on Cmd+K / Ctrl+K
  React.useEffect(() => {
    const onCmdK = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
        e.preventDefault();
        setSearchOpen(prev => !prev);
      }
    };
    window.addEventListener('keydown', onCmdK);
    return () => window.removeEventListener('keydown', onCmdK);
  }, []);

  // Initialize on mount: activate default module
  React.useEffect(() => {
    _switchModuleInternal(DEFAULT_MODULE, false);
  }, [_switchModuleInternal]);

  // Hide OrgXMenu on outside click (matches V1 L18364 document.addEventListener pattern)
  React.useEffect(() => {
    const onDocClick = (e) => {
      const menu = document.getElementById('org-xmenu');
      if (menu && menu.style.display !== 'none') {
        if (!menu.contains(e.target) && !e.target.closest('.org-xlink')) {
          menu.style.display = 'none';
        }
      }
    };
    document.addEventListener('click', onDocClick);
    return () => document.removeEventListener('click', onDocClick);
  }, []);

  // Expose search opener globally so the standalone search bar (which lives
  // inside each module's content) can trigger the same popover as ⌘K.
  React.useEffect(() => {
    window._openSearch = () => setSearchOpen(true);
    return () => { delete window._openSearch; };
  }, []);

  return (
    <React.Fragment>
      {/* Floating chrome — logo orb (top-left) + theme toggle (top-right) */}
      <FloatingChrome />

      {/* Sidebar — vertical glass capsule nav (left-center), handles module switching + ExploreIQ */}
      <Sidebar
        activeModule={activeModule}
        onSwitchModule={(key) => _switchModuleInternal(key)}
        onOpenSearch={() => setSearchOpen(true)}
      />

      {/* Main content area — panel shells mount here */}
      <main
        id="chrome-main"
        className="chrome-main"
        style={{
          flex: 1,
          minWidth: 0,
          minHeight: 0,
          display: 'flex',
          flexDirection: 'column',
          paddingTop: '0',
          paddingBottom: '56px',
          overflow: 'auto',
        }}
      >
        {/* Panel containers (Tier 4 modules render INTO these) */}
        {MODULE_KEYS.map(k => (
          <PanelContainer
            key={k}
            moduleKey={k}
            isActive={activeModule === k}
          />
        ))}

        {/* ExploreIQ panel — React-mounted iframe (no imperative renderFn). */}
        <div
          id="panel-exploreiq"
          className={`panel${activeModule === 'exploreiq' ? ' active' : ''}`}
          role="tabpanel"
          aria-labelledby="tab-exploreiq"
          style={{
            display: activeModule === 'exploreiq' ? 'block' : 'none',
            flex: 1,
            minWidth: 0,
            minHeight: 0,
            overflow: 'hidden',
          }}
        >
          {activeModule === 'exploreiq' ? <ExploreIQModule /> : null}
        </div>
      </main>

      {/* Mobile bottom bar */}
      <BottomBar
        activeModule={activeModule}
        onSwitchModule={(key) => _switchModuleInternal(key)}
      />

      {/* Search popover overlay */}
      <SearchPopover
        open={searchOpen}
        onClose={() => setSearchOpen(false)}
      />

      {/* Org cross-module menu (populated imperatively by V1 orgXLink pattern) */}
      <OrgXMenu />

      {/* Mobile drawer backdrop (used by PartnerIQ / WhitespaceIQ evidence drawers) */}
      <div
        className="drawer-backdrop"
        id="drawer-backdrop"
        onClick={() => {
          if (typeof window._hideMobileBackdrop === 'function') {
            window._hideMobileBackdrop();
          } else {
            const bd = document.getElementById('drawer-backdrop');
            if (bd) bd.classList.remove('visible');
            const evDrw = document.getElementById('ev-drawer');
            if (evDrw) evDrw.classList.remove('open');
            const ptDrw = document.getElementById('part-drawer');
            if (ptDrw) ptDrw.classList.remove('open');
          }
        }}
      />

      {/* Evidence drawer — mobile companion pane (populated by partneriq/whitespaceiq modules) */}
      <div className="ev-drawer" id="ev-drawer">
        <div className="ev-drawer-head" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: 'var(--s-3) var(--s-4)', borderBottom: '1px solid var(--g-border)' }}>
          <div className="ev-drawer-title" id="ev-drawer-title" style={{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-sm)', fontWeight: 600, color: 'var(--text)' }}>Evidence</div>
          <button
            className="ev-drawer-close"
            onClick={() => { if (typeof window.closeEvDrawer === 'function') { window.closeEvDrawer(); } else { const d = document.getElementById('ev-drawer'); if (d) d.classList.remove('open'); } }}
            aria-label="Close evidence drawer"
            style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', minHeight: '44px', minWidth: '44px', background: 'transparent', border: 'none', borderRadius: 'var(--r-sm)', color: 'var(--text-muted)', cursor: 'pointer', fontSize: '18px' }}
          >
            {window.CloseIcon ? <window.CloseIcon size={16} aria-hidden="true" /> : '×'}
          </button>
        </div>
        <div className="ev-drawer-body" id="ev-drawer-body" />
      </div>

      {/* Strategy/PartnerIQ drawer — mobile evidence pane (populated by partneriq module) */}
      <div className="part-drawer" id="part-drawer">
        <div className="part-drawer-head" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: 'var(--s-3) var(--s-4)', borderBottom: '1px solid var(--g-border)' }}>
          <div className="part-drawer-title" id="part-drawer-title" style={{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-sm)', fontWeight: 600, color: 'var(--text)' }}>Strategy Details</div>
          <button
            className="part-drawer-close"
            onClick={() => { if (typeof window.closePartDrawer === 'function') { window.closePartDrawer(); } else { const d = document.getElementById('part-drawer'); if (d) d.classList.remove('open'); } }}
            aria-label="Close strategy drawer"
            style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', minHeight: '44px', minWidth: '44px', background: 'transparent', border: 'none', borderRadius: 'var(--r-sm)', color: 'var(--text-muted)', cursor: 'pointer', fontSize: '18px' }}
          >
            {window.CloseIcon ? <window.CloseIcon size={16} aria-hidden="true" /> : '×'}
          </button>
        </div>
        <div className="part-drawer-body" id="part-drawer-body" />
      </div>

      {/* Toast notification container */}
      <div id="toast" className="toast" aria-live="polite" aria-atomic="true" />
    </React.Fragment>
  );
}

// ── Imperative switchModule dispatcher — exposed on window ──
// Tier 4 modules call this directly (e.g. switchModule('opp', true)).
// Dispatches chrome:switch CustomEvent which Chrome's useEffect handles.
// Also syncs STATE.activeModule immediately for callers that read it synchronously.
function switchModule(key, forceRender) {
  if (!MODULES[key]) return;
  STATE.activeModule = key;
  window.dispatchEvent(new CustomEvent('chrome:switch', {
    detail: { key: key, forceRender: !!forceRender },
  }));
}

// ── showOrgXMenu / hideOrgXMenu — V1-compatible imperative API ──
// Modules generate orgXLink HTML strings that call showOrgXMenu(event, orgName, currentModule).
// Chrome provides this globally so the imperative pattern from V1 continues to work.
function showOrgXMenu(evt, orgName, currentModule) {
  const menu = document.getElementById('org-xmenu');
  if (!menu) return;

  const display = (typeof window.fixOrgName === 'function')
    ? window.fixOrgName(orgName)
    : orgName;
  const esc = String(orgName).replace(/'/g, "\\'");
  const displayShort = display.substring(0, 20);

  const items = [];

  if (currentModule === 'strategy') {
    const anchorEsc = (STATE.strat_anchor_org || '').replace(/'/g, "\\'");
    const anchorDisplay = (typeof window.fixOrgName === 'function')
      ? window.fixOrgName(STATE.strat_anchor_org || '')
      : (STATE.strat_anchor_org || '');

    items.push({
      cls: 'xm-compete',
      dotColor: 'var(--rose)',
      label: 'View Profile in CompeteIQ',
      sub: 'Competitive landscape & portfolio',
      action: `STATE.comp_selected_org='${esc}';STATE._xmod_org=true;switchModule('compete',true)`,
    });
    items.push({
      cls: 'xm-compete',
      dotColor: 'var(--rose)',
      label: 'Head-to-Head Comparison',
      sub: anchorDisplay + ' vs ' + display,
      action: `STATE.comp_selected_org='${anchorEsc}';STATE.comp_vs_org='${esc}';switchModule('compete',true)`,
    });
    const pairData = (STATE.strat_data || []).find(d => d.org_b_name === orgName || d.org_a_name === orgName);
    const pairTA = pairData ? (pairData.top_shared_tas || '').split('|').filter(Boolean)[0] || '' : '';
    if (pairTA) {
      const taEsc = pairTA.replace(/'/g, "\\'");
      const taDisplay = (typeof window.formatTA === 'function') ? window.formatTA(pairTA) : pairTA;
      items.push({
        cls: 'xm-opp',
        dotColor: 'var(--amber)',
        label: 'Explore Whitespace in ' + taDisplay,
        sub: 'Open targets & gaps in WhiteSpaceIQ',
        action: `STATE.opp_ta='${taEsc}';switchModule('opp',true)`,
      });
    }
  } else if (currentModule === 'compete') {
    items.push({
      cls: 'xm-strategy',
      dotColor: 'var(--amber)',
      label: 'Evaluate Partnership Strategy',
      sub: 'Find partners in PartnerIQ',
      action: `STATE.strat_anchor_org='${esc}';STATE.strat_subview='partners';switchModule('strategy',true)`,
    });
  } else {
    items.push({
      cls: 'xm-compete',
      dotColor: 'var(--rose)',
      label: 'View in CompeteIQ',
      sub: 'Competitive landscape & assets',
      action: `STATE.comp_selected_org='${esc}';STATE._xmod_org=true;switchModule('compete',true)`,
    });
    items.push({
      cls: 'xm-strategy',
      dotColor: 'var(--amber)',
      label: 'View in PartnerIQ',
      sub: 'Partnership strategy & deals',
      action: `STATE.strat_anchor_org='${esc}';STATE.strat_subview='partners';switchModule('strategy',true)`,
    });
    items.push({
      cls: 'xm-opp',
      dotColor: 'var(--blue)',
      label: 'Explore in WhiteSpaceIQ',
      sub: 'Open targets & whitespace gaps',
      action: `switchModule('opp',true)`,
    });
  }

  let html = `<div class="xm-header" style="padding:var(--s-3) var(--s-4);border-bottom:1px solid var(--g-border)">`;
  html += `<div class="xm-name" style="font-family:var(--font-body);font-size:var(--text-sm);font-weight:700;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${display}</div>`;
  html += `<div class="xm-sub" style="font-family:var(--font-mono);font-size:var(--text-3xs);color:var(--text-muted);margin-top:2px;letter-spacing:0.04em;text-transform:uppercase">Cross-Intelligence Navigation</div>`;
  html += `</div>`;

  items.forEach(item => {
    html += `<div class="xm-item ${item.cls}" onclick="${item.action};hideOrgXMenu()" role="menuitem" style="display:flex;align-items:center;gap:var(--s-3);padding:var(--s-3) var(--s-4);cursor:pointer;transition:background var(--t-fast) var(--ease-out)"`;
    html += ` onmouseover="this.style.background='var(--g-frosted)'" onmouseout="this.style.background='transparent'">`;
    html += `<div class="xm-dot" style="width:8px;height:8px;border-radius:50%;background:${item.dotColor};flex-shrink:0"></div>`;
    html += `<div style="flex:1;min-width:0"><div style="font-size:var(--text-sm);font-weight:500;color:var(--text)">${item.label}</div>`;
    html += `<div style="font-size:var(--text-3xs);color:var(--text-muted);margin-top:1px;font-family:var(--font-mono)">${item.sub}</div></div>`;
    html += `<span class="xm-arrow" style="color:var(--text-muted);font-size:12px">&rarr;</span>`;
    html += `</div>`;
  });

  menu.innerHTML = html;
  menu.style.display = 'block';

  const x = evt.clientX;
  const y = evt.clientY;
  const mw = 260;
  const mh = menu.offsetHeight || 180;
  const safeX = (x + mw > window.innerWidth) ? Math.max(8, window.innerWidth - mw - 8) : x;
  const safeY = (y + mh > window.innerHeight) ? Math.max(8, y - mh) : y;
  menu.style.left = safeX + 'px';
  menu.style.top = safeY + 'px';
}

function hideOrgXMenu() {
  const m = document.getElementById('org-xmenu');
  if (m) m.style.display = 'none';
}

// ── orgXLink — returns an HTML string for a clickable org name ──
// Used by all three modules: orgXLink(orgName, 'strategy') → '<span class="org-xlink" ...>'
function orgXLink(orgName, currentModule) {
  const safe = String(orgName).replace(/'/g, "\\'").replace(/"/g, '&quot;');
  const display = (typeof window.fixOrgName === 'function')
    ? window.fixOrgName(orgName)
    : orgName;
  const safeDisplay = String(display).replace(/"/g, '&quot;');
  return `<span class="org-xlink" onclick="event.stopPropagation();showOrgXMenu(event,'${safe}','${currentModule || ''}')" title="Navigate ${safeDisplay} across modules" style="cursor:pointer;color:var(--teal);font-weight:600;text-decoration:none;transition:opacity var(--t-fast) var(--ease-out)">${display}</span>`;
}

// ── showToast — lightweight toast notification (V1 compatibility shim) ──
function showToast(message, duration) {
  const toast = document.getElementById('toast');
  if (!toast) return;
  toast.textContent = message;
  toast.classList.add('is-visible');
  clearTimeout(toast._toastTimeout);
  toast._toastTimeout = setTimeout(() => {
    toast.classList.remove('is-visible');
  }, duration || 3000);
}

// ── Window exposure ──
// All imperative callers (Tier 4 modules, embedded scripts) read from window.
if (!window.STATE) window.STATE = STATE;

Object.assign(window, {
  Chrome,
  ExploreIQModule,
  switchModule,
  showOrgXMenu,
  hideOrgXMenu,
  orgXLink,
  showToast,
  MODULES,
  MODULE_KEYS,
  DEFAULT_MODULE,
  STATE,
});
