/**
 * macOS 7–style Control Strip (content-script friendly).
 *
 * Compact fixed strip (default upper-left). Dark GUI Pro chrome matches the
 * floating keyboard reference panel. Segments: On/Off, move (expanded),
 * Keyboard, Settings, collapse, and close.
 *
 * Position uses the shared panel-position system (snap + margin + persist).
 * Light DOM + inline styles so the strip survives hostile page CSS.
 */
import { Z_INDEX } from '../config/constants.js';
import { applyPopupThemeVars } from './popup-theme-vars.js';
import { getActionIconDataUri } from './keybindings-ui-shared.js';
import {
  NCT_DARK_UI_FONT,
  NCT_DARK_UI_PANEL_BORDER,
  NCT_DARK_UI_PANEL_RADIUS,
  NCT_DARK_UI_PANEL_BOX_SHADOW,
  NCT_DARK_UI_TITLEBAR_GRADIENT,
  NCT_DARK_UI_TITLEBAR_TEXT_MODE_BACKGROUND,
  NCT_DARK_UI_BTN_GRADIENT,
  NCT_DARK_UI_BTN_LIT_GRADIENT,
  NCT_DARK_UI_HOVER_TINT,
  NCT_DARK_UI_SELECTED_TINT,
  NCT_DARK_UI_SELECTED_TEXT,
  NCT_DARK_UI_FOCUS_RING,
  NCT_DARK_UI_ICON_BUTTON_OUTLINE,
  NCT_DARK_UI_COLORS
} from './nct-dark-ui.js';
import { positionOnboardingBelowControlStrip } from './onboarding-shared.js';
import { ensureOpenChromeShadow, ensureChromeHostMounted } from './kp-chrome-shadow.js';
import { getSettings, setSettings, DEFAULT_SETTINGS, SETTINGS_STORAGE_KEY } from '../modules/settings-manager.js';
import {
  PANEL_POSITION_MARGIN_PX,
  applyPanelPosition,
  makePanelDraggable,
  normalizePanelPositionState
} from '../utils/panel-position.js';

const CONTROL_STRIP_ROOT_CLASS = 'kp-control-strip';
const DEFAULT_TOP_PX = 16;
const DEFAULT_LEFT_PX = 16;
const STRIP_HEIGHT_PX = 28;
/** Prefer a slightly larger dock margin for the strip (matches legacy 16px perch). */
const STRIP_POSITION_MARGIN_PX = Math.max(PANEL_POSITION_MARGIN_PX, 16);

/**
 * @typedef {{
 *   onToggleEnabled?: () => void,
 *   onToggleKeyboard?: () => void,
 *   onOpenSettings?: () => void,
 *   onCollapseChange?: (collapsed: boolean) => void,
 *   onClose?: () => void
 * }} ControlStripHandlers
 */

export class ControlStrip {
  /**
   * @param {ControlStripHandlers} [handlers]
   */
  constructor(handlers = {}) {
    this.root = null;
    this.shadowRoot = null;
    this._modulesEl = null;
    this._statusBtn = null;
    this._statusDot = null;
    this._statusLabel = null;
    this._moveBtn = null;
    this._keyboardBtn = null;
    this._settingsBtn = null;
    this._collapseBtn = null;
    this._closeBtn = null;

    this._enabled = true;
    this._textMode = false;
    this._collapsed = false;
    this._keyboardActive = false;
    this._desiredVisible = true;

    /** @type {import('../modules/settings-manager.js').PanelPositionSettings} */
    this._panelPosition = { ...DEFAULT_SETTINGS.panelPositions.controlStrip };
    this._suppressPositionPersist = false;
    /** @type {(() => void)|null} */
    this._dragDispose = null;
    this._dragBound = false;
    this._settingsBound = false;
    this._onStorageChanged = this._onStorageChanged.bind(this);

    /** @type {ControlStripHandlers} */
    this._handlers = {
      onToggleEnabled: typeof handlers.onToggleEnabled === 'function' ? handlers.onToggleEnabled : null,
      onToggleKeyboard: typeof handlers.onToggleKeyboard === 'function' ? handlers.onToggleKeyboard : null,
      onOpenSettings: typeof handlers.onOpenSettings === 'function' ? handlers.onOpenSettings : null,
      onCollapseChange: typeof handlers.onCollapseChange === 'function' ? handlers.onCollapseChange : null,
      onClose: typeof handlers.onClose === 'function' ? handlers.onClose : null
    };

    this._onStatusClick = this._onStatusClick.bind(this);
    this._onKeyboardClick = this._onKeyboardClick.bind(this);
    this._onSettingsClick = this._onSettingsClick.bind(this);
    this._onCollapseClick = this._onCollapseClick.bind(this);
    this._onCloseClick = this._onCloseClick.bind(this);
    this._onDocMutate = this._onDocMutate.bind(this);
    this._onWinResize = this._onWinResize.bind(this);

    this._observer = null;
    this._observeBound = false;
    /** @type {MutationObserver|null} */
    this._hostGuard = null;
  }

  /**
   * @param {Partial<ControlStripHandlers>} handlers
   */
  setHandlers(handlers = {}) {
    if (typeof handlers.onToggleEnabled === 'function') this._handlers.onToggleEnabled = handlers.onToggleEnabled;
    if (typeof handlers.onToggleKeyboard === 'function') this._handlers.onToggleKeyboard = handlers.onToggleKeyboard;
    if (typeof handlers.onOpenSettings === 'function') this._handlers.onOpenSettings = handlers.onOpenSettings;
    if (typeof handlers.onCollapseChange === 'function') this._handlers.onCollapseChange = handlers.onCollapseChange;
    if (typeof handlers.onClose === 'function') this._handlers.onClose = handlers.onClose;
  }

  isVisible() {
    return !!(this.root && this.root.isConnected && this.root.hidden === false);
  }

  isCollapsed() {
    return !!this._collapsed;
  }

  show() {
    if (window !== window.top) return;
    this._desiredVisible = true;
    this._ensure();
    // Prefer body once available; reattach if React yanked an html-mounted host.
    try { ensureChromeHostMounted(this.root); } catch { /* ignore */ }
    this.root.hidden = false;
    this.root.style.display = 'flex';
    this.root.style.pointerEvents = 'auto';
    this._applyCollapsedLayout();
    this._bindDrag();
    this._bindSettingsSync();
    this._refreshPanelPosition(); // async
    this._applyPanelPositionNow();
    this._syncOnboardingOffset();
    this._bindOnboardingWatch();
    this._bindHostGuard();
  }

  hide() {
    this._desiredVisible = false;
    this._unbindHostGuard();
    if (this.root) {
      this.root.hidden = true;
      this.root.style.display = 'none';
      this.root.style.pointerEvents = 'none';
    }
    this._unbindOnboardingWatch();
  }

  /**
   * Apply desired visibility without changing the persisted "desired" flag when already set.
   * @param {boolean} visible
   */
  setVisible(visible) {
    if (visible) this.show();
    else this.hide();
  }

  /**
   * @param {boolean} collapsed
   * @param {{ notify?: boolean }} [opts]
   */
  setCollapsed(collapsed, opts = {}) {
    const next = !!collapsed;
    if (this._collapsed === next) {
      this._applyCollapsedLayout();
      this._bindDrag();
      return;
    }
    this._collapsed = next;
    this._applyCollapsedLayout();
    this._bindDrag();
    // Width changes when expanding/collapsing — reclamp so margin stays valid.
    this._applyPanelPositionNow();
    this._syncOnboardingOffset();
    if (opts.notify !== false && this._handlers.onCollapseChange) {
      try { this._handlers.onCollapseChange(this._collapsed); } catch { /* ignore */ }
    }
  }

  /**
   * @param {boolean} enabled
   */
  setEnabledState(enabled) {
    this._enabled = !!enabled;
    // Text mode only applies while KeyPilot is on.
    if (!this._enabled) this._textMode = false;
    this._renderStatus();
  }

  /**
   * Reflect text-field focus (text mode) on the On/Off status segment and strip chrome.
   * When active, the green ON indicator turns orange and the strip gets the same
   * orange titlebar cast as the Keyboard Reference window.
   * @param {boolean} active
   */
  setTextModeActive(active) {
    const next = !!active && !!this._enabled;
    if (this._textMode === next) {
      this._renderStatus();
      return;
    }
    this._textMode = next;
    this._renderStatus();
  }

  /**
   * Apply / clear the shared text-mode orange cast on the strip root.
   * Status segment stays transparent so the cast (and titlebar bevel) show through.
   * @param {boolean} textMode
   */
  _applyTextModeChrome(textMode) {
    if (!this.root || !this.root.style) return;
    try {
      if (textMode) this.root.setAttribute('data-kp-text-mode', 'true');
      else this.root.removeAttribute('data-kp-text-mode');
    } catch { /* ignore */ }
    try {
      this.root.style.background = textMode
        ? NCT_DARK_UI_TITLEBAR_TEXT_MODE_BACKGROUND
        : NCT_DARK_UI_TITLEBAR_GRADIENT;
    } catch { /* ignore */ }
    // Opaque status fills (legacy primary / early-inject) would mask the cast.
    try {
      if (this._statusBtn?.style) this._statusBtn.style.background = 'transparent';
    } catch { /* ignore */ }
  }

  /**
   * @param {boolean} active
   */
  setKeyboardHelpActive(active) {
    this._keyboardActive = !!active;
    this._renderKeyboard();
  }

  cleanup() {
    this._unbindHostGuard();
    this._unbindOnboardingWatch();
    this._unbindDrag();
    this._unbindSettingsSync();
    try {
      if (this._statusBtn) this._statusBtn.removeEventListener('click', this._onStatusClick);
      if (this._keyboardBtn) this._keyboardBtn.removeEventListener('click', this._onKeyboardClick);
      if (this._settingsBtn) this._settingsBtn.removeEventListener('click', this._onSettingsClick);
      if (this._collapseBtn) this._collapseBtn.removeEventListener('click', this._onCollapseClick);
      if (this._closeBtn) this._closeBtn.removeEventListener('click', this._onCloseClick);
    } catch { /* ignore */ }
    try {
      if (this.root && this.root.parentNode) this.root.parentNode.removeChild(this.root);
    } catch { /* ignore */ }
    this.root = null;
    this.shadowRoot = null;
    this._modulesEl = null;
    this._statusBtn = null;
    this._statusDot = null;
    this._statusLabel = null;
    this._moveBtn = null;
    this._keyboardBtn = null;
    this._settingsBtn = null;
    this._collapseBtn = null;
    this._closeBtn = null;
  }

  _ensure() {
    if (this.root && this.root.isConnected) {
      this._ensureMoveHandle();
      this._bindDrag();
      return;
    }

    // If early-inject created the shell at document_start, adopt it to avoid flicker.
    try {
      const existing = document.querySelector('.kp-control-strip[data-kp-early-control-strip="true"]');
      if (existing && existing.isConnected) {
        const shadowRoot = ensureOpenChromeShadow(existing, { id: 'control-strip' });
        const shell = shadowRoot || existing;
        const statusBtn = shell.querySelector('[data-kp-control-strip-status="true"]');
        const statusDot = shell.querySelector('[data-kp-control-strip-status-dot="true"]');
        const statusLabel = shell.querySelector('[data-kp-control-strip-status-label="true"]');
        const modules = shell.querySelector('[data-kp-control-strip-modules="true"]');
        const keyboardBtn = shell.querySelector('[data-kp-control-strip-keyboard="true"]');
        const settingsBtn = shell.querySelector('[data-kp-control-strip-settings="true"]');
        const collapseBtn = shell.querySelector('[data-kp-control-strip-collapse="true"]');
        const closeBtn = shell.querySelector('[data-kp-control-strip-close="true"]');

        if (statusBtn && modules && keyboardBtn && settingsBtn && collapseBtn && closeBtn) {
          // Enrich early shell with icons if it only has text labels.
          try { this._ensureSegmentIcon(keyboardBtn, 'TOGGLE_KEYBOARD_HELP'); } catch { /* ignore */ }
          try { this._ensureSegmentIcon(settingsBtn, 'OPEN_SETTINGS_POPOVER'); } catch { /* ignore */ }

          this.root = existing;
          this.shadowRoot = shadowRoot;
          this._modulesEl = modules;
          this._statusBtn = statusBtn;
          this._statusDot = statusDot;
          this._statusLabel = statusLabel;
          this._keyboardBtn = keyboardBtn;
          this._settingsBtn = settingsBtn;
          this._collapseBtn = collapseBtn;
          this._closeBtn = closeBtn;

          // Keep shell rim + titlebar bevel in sync with current chrome
          // (early inject may still have a flat near-black fill / opaque ON segment).
          try {
            this.root.style.border = NCT_DARK_UI_PANEL_BORDER;
            this.root.style.borderRadius = NCT_DARK_UI_PANEL_RADIUS;
            this.root.style.boxShadow = NCT_DARK_UI_PANEL_BOX_SHADOW;
            this.root.style.fontFamily = NCT_DARK_UI_FONT;
          } catch { /* ignore */ }
          try {
            if (statusBtn && statusBtn.style) {
              statusBtn.style.background = 'transparent';
              statusBtn.style.color = NCT_DARK_UI_COLORS.fg;
            }
          } catch { /* ignore */ }

          this._ensureMoveHandle();
          this._bindButtonHandlers();
          this._renderStatus();
          this._renderKeyboard();
          this._applyCollapsedLayout();
          this._bindDrag();
          this._applyPanelPositionNow();
          return;
        }
      }
    } catch { /* ignore */ }

    const root = document.createElement('div');
    root.className = CONTROL_STRIP_ROOT_CLASS;
    root.hidden = true;
    root.setAttribute('role', 'toolbar');
    root.setAttribute('aria-label', 'KeyPilot control strip');
    root.setAttribute('data-kp-control-strip', 'true');
    const shadowRoot = ensureOpenChromeShadow(root, { id: 'control-strip' });
    const shell = shadowRoot || root;

    Object.assign(root.style, {
      position: 'fixed',
      left: `${DEFAULT_LEFT_PX}px`,
      top: `${DEFAULT_TOP_PX}px`,
      height: `${STRIP_HEIGHT_PX}px`,
      minHeight: `${STRIP_HEIGHT_PX}px`,
      maxHeight: `${STRIP_HEIGHT_PX}px`,
      display: 'none',
      flexDirection: 'row',
      alignItems: 'stretch',
      zIndex: String(Z_INDEX.CONTROL_STRIP || 2147483025),
      background: NCT_DARK_UI_TITLEBAR_GRADIENT,
      color: 'rgba(248, 250, 252, 0.95)',
      // NCT dark UI panel rim (dark outer edge).
      border: NCT_DARK_UI_PANEL_BORDER,
      borderRadius: NCT_DARK_UI_PANEL_RADIUS,
      boxShadow: NCT_DARK_UI_PANEL_BOX_SHADOW,
      fontFamily: NCT_DARK_UI_FONT,
      pointerEvents: 'auto',
      overflow: 'hidden',
      boxSizing: 'border-box',
      userSelect: 'none',
      WebkitUserSelect: 'none'
    });
    applyPopupThemeVars(root);

    // Status (On/Off) — always visible. Transparent like other segments so the
    // strip titlebar bevel (and text-mode orange cast) shows through; ON/OFF is
    // signaled by the dot + label color, not an opaque segment fill.
    const statusBtn = this._createSegmentButton({
      ariaLabel: 'Toggle KeyPilot on or off',
      title: 'Toggle KeyPilot (Alt+K)'
    });
    statusBtn.setAttribute('data-kp-control-strip-status', 'true');

    const statusInner = document.createElement('span');
    Object.assign(statusInner.style, {
      display: 'inline-flex',
      alignItems: 'center',
      gap: '6px',
      pointerEvents: 'none'
    });

    const statusDot = document.createElement('span');
    Object.assign(statusDot.style, {
      width: '7px',
      height: '7px',
      borderRadius: '50%',
      flex: '0 0 auto',
      background: 'rgba(16, 185, 129, 0.95)',
      boxShadow: '0 0 0 2px rgba(16, 185, 129, 0.2)'
    });
    statusDot.setAttribute('aria-hidden', 'true');
    statusDot.setAttribute('data-kp-control-strip-status-dot', 'true');

    const statusLabel = document.createElement('span');
    statusLabel.textContent = 'ON';
    statusLabel.setAttribute('data-kp-control-strip-status-label', 'true');
    Object.assign(statusLabel.style, {
      fontSize: '11px',
      fontWeight: '700',
      letterSpacing: '0.06em',
      lineHeight: '1'
    });

    statusInner.appendChild(statusDot);
    statusInner.appendChild(statusLabel);
    statusBtn.appendChild(statusInner);

    // Expandable modules (hidden when collapsed)
    const modules = document.createElement('div');
    modules.setAttribute('data-kp-control-strip-modules', 'true');
    Object.assign(modules.style, {
      display: 'flex',
      flexDirection: 'row',
      alignItems: 'stretch',
      flex: '0 0 auto'
    });

    const moveBtn = this._createMoveHandleButton();

    const keyboardBtn = this._createSegmentButton({
      ariaLabel: 'Toggle keyboard reference',
      title: 'Keyboard reference',
      text: 'KB',
      iconActionId: 'TOGGLE_KEYBOARD_HELP'
    });
    keyboardBtn.setAttribute('data-kp-control-strip-keyboard', 'true');

    const settingsBtn = this._createSegmentButton({
      ariaLabel: 'Open KeyPilot settings',
      title: 'Settings',
      text: 'Settings',
      iconActionId: 'OPEN_SETTINGS_POPOVER'
    });
    settingsBtn.setAttribute('data-kp-control-strip-settings', 'true');

    modules.appendChild(moveBtn);
    modules.appendChild(keyboardBtn);
    modules.appendChild(settingsBtn);

    // Collapse / expand
    const collapseBtn = this._createSegmentButton({
      ariaLabel: 'Collapse control strip',
      title: 'Collapse',
      text: '◀',
      compact: true
    });
    collapseBtn.setAttribute('data-kp-control-strip-collapse', 'true');

    // Close
    const closeBtn = this._createSegmentButton({
      ariaLabel: 'Close control strip',
      title: 'Close (Alt+J to show again)',
      text: '×',
      compact: true,
      last: true
    });
    closeBtn.setAttribute('data-kp-control-strip-close', 'true');

    shell.appendChild(statusBtn);
    shell.appendChild(modules);
    shell.appendChild(collapseBtn);
    shell.appendChild(closeBtn);

    (document.body || document.documentElement).appendChild(root);
    try { ensureChromeHostMounted(root); } catch { /* ignore */ }

    this.root = root;
    this.shadowRoot = shadowRoot;
    this._modulesEl = modules;
    this._statusBtn = statusBtn;
    this._statusDot = statusDot;
    this._statusLabel = statusLabel;
    this._moveBtn = moveBtn;
    this._keyboardBtn = keyboardBtn;
    this._settingsBtn = settingsBtn;
    this._collapseBtn = collapseBtn;
    this._closeBtn = closeBtn;

    this._bindButtonHandlers();
    this._renderStatus();
    this._renderKeyboard();
    this._applyCollapsedLayout();
    this._bindDrag();
    this._applyPanelPositionNow();
  }

  /**
   * Drag grip shown only when the strip is expanded.
   * @returns {HTMLElement}
   */
  _createMoveHandleButton() {
    const btn = this._createSegmentButton({
      ariaLabel: 'Move control strip',
      title: 'Drag to move',
      text: '⠿',
      compact: true
    });
    btn.setAttribute('data-kp-control-strip-move', 'true');
    try {
      btn.style.cursor = 'grab';
      btn.style.touchAction = 'none';
      btn.style.letterSpacing = '0';
      btn.style.fontSize = '12px';
      btn.style.opacity = '0.85';
    } catch { /* ignore */ }
    // Prevent accidental click activation — drag handle only.
    btn.addEventListener('click', (e) => {
      try { e.preventDefault(); e.stopPropagation(); } catch { /* ignore */ }
    });
    return btn;
  }

  /**
   * Ensure early-inject shells gain a move handle without rebuilding the strip.
   */
  _ensureMoveHandle() {
    if (!this.root || !this._modulesEl) return;
    let moveBtn = this.shadowRoot?.querySelector('[data-kp-control-strip-move="true"]');
    if (!moveBtn) {
      moveBtn = this._createMoveHandleButton();
      try {
        this._modulesEl.insertBefore(moveBtn, this._modulesEl.firstChild);
      } catch {
        try { this._modulesEl.appendChild(moveBtn); } catch { /* ignore */ }
      }
    }
    this._moveBtn = moveBtn;
  }

  _applyPanelPositionNow() {
    if (!this.root) return;
    try {
      applyPanelPosition(this.root, this._panelPosition, {
        margin: STRIP_POSITION_MARGIN_PX,
        defaultAnchor: 'top-left'
      });
    } catch { /* ignore */ }
  }

  /**
   * @param {import('../modules/settings-manager.js').PanelPositionSettings|null|undefined} next
   * @param {{ persist?: boolean }} [opts]
   */
  _setPanelPosition(next, opts = {}) {
    const normalized = normalizePanelPositionState(
      next,
      DEFAULT_SETTINGS.panelPositions.controlStrip
    ) || { ...DEFAULT_SETTINGS.panelPositions.controlStrip };
    this._panelPosition = {
      left: normalized.left,
      top: normalized.top,
      anchor: normalized.anchor === undefined ? null : normalized.anchor
    };
    this._applyPanelPositionNow();
    this._syncOnboardingOffset();
    if (opts.persist && !this._suppressPositionPersist) {
      this._persistPanelPosition(this._panelPosition);
    }
  }

  async _refreshPanelPosition() {
    try {
      const settings = await getSettings();
      const stored = settings?.panelPositions?.controlStrip;
      this._suppressPositionPersist = true;
      this._setPanelPosition(stored, { persist: false });
      this._suppressPositionPersist = false;
    } catch {
      this._suppressPositionPersist = false;
    }
  }

  /**
   * @param {import('../modules/settings-manager.js').PanelPositionSettings} position
   */
  async _persistPanelPosition(position) {
    try {
      await setSettings({
        panelPositions: {
          controlStrip: {
            left: position.left,
            top: position.top,
            anchor: position.anchor === undefined ? null : position.anchor
          }
        }
      });
    } catch { /* ignore */ }
  }

  _bindDrag() {
    if (!this.root || !this._moveBtn) return;
    // Only draggable while expanded (move icon visible).
    if (this._collapsed) {
      this._unbindDrag();
      return;
    }
    if (this._dragBound) return;
    try {
      const api = makePanelDraggable(this.root, this._moveBtn, {
        margin: STRIP_POSITION_MARGIN_PX,
        // Keep width/height content-driven so expand/collapse still works.
        pinWidth: false,
        pinHeight: false,
        onMove: () => {
          this._syncOnboardingOffset();
        },
        onMoveEnd: (state) => {
          if (!state?.moved) return;
          this._setPanelPosition(
            {
              left: state.left,
              top: state.top,
              anchor: state.anchor
            },
            { persist: true }
          );
        }
      });
      this._dragDispose = api?.dispose || null;
      this._dragBound = true;
    } catch (err) {
      console.warn('[KeyPilot] Failed to make control strip draggable:', err?.message || err);
      this._dragDispose = null;
      this._dragBound = false;
    }
  }

  _unbindDrag() {
    try { this._dragDispose?.(); } catch { /* ignore */ }
    this._dragDispose = null;
    this._dragBound = false;
  }

  _bindSettingsSync() {
    if (this._settingsBound) return;
    try {
      if (chrome?.storage?.onChanged?.addListener) {
        chrome.storage.onChanged.addListener(this._onStorageChanged);
        this._settingsBound = true;
      }
    } catch { /* ignore */ }
  }

  _unbindSettingsSync() {
    if (!this._settingsBound) return;
    try { chrome?.storage?.onChanged?.removeListener?.(this._onStorageChanged); } catch { /* ignore */ }
    this._settingsBound = false;
  }

  _onStorageChanged(changes, area) {
    try {
      if (area !== 'sync' && area !== 'local') return;
      const entry = changes && changes[SETTINGS_STORAGE_KEY];
      if (!entry || !entry.newValue) return;
      const nextPos = entry.newValue.panelPositions?.controlStrip;
      if (nextPos && typeof nextPos === 'object') {
        this._suppressPositionPersist = true;
        this._setPanelPosition(nextPos, { persist: false });
        this._suppressPositionPersist = false;
      }
    } catch { /* ignore */ }
  }

  /**
   * Attach click handlers (safe to call after early-inject adoption).
   */
  _bindButtonHandlers() {
    try {
      if (this._statusBtn) {
        this._statusBtn.removeEventListener('click', this._onStatusClick);
        this._statusBtn.addEventListener('click', this._onStatusClick);
      }
      if (this._keyboardBtn) {
        this._keyboardBtn.removeEventListener('click', this._onKeyboardClick);
        this._keyboardBtn.addEventListener('click', this._onKeyboardClick);
      }
      if (this._settingsBtn) {
        this._settingsBtn.removeEventListener('click', this._onSettingsClick);
        this._settingsBtn.addEventListener('click', this._onSettingsClick);
      }
      if (this._collapseBtn) {
        this._collapseBtn.removeEventListener('click', this._onCollapseClick);
        this._collapseBtn.addEventListener('click', this._onCollapseClick);
      }
      if (this._closeBtn) {
        this._closeBtn.removeEventListener('click', this._onCloseClick);
        this._closeBtn.addEventListener('click', this._onCloseClick);
      }
    } catch { /* ignore */ }
  }

  /**
   * Prepend action icon into a segment button if missing (early shell is text-only).
   * @param {HTMLElement|null} btn
   * @param {string} actionId
   */
  _ensureSegmentIcon(btn, actionId) {
    if (!btn || !actionId) return;
    try {
      if (btn.querySelector('img[data-kp-control-strip-icon="true"]')) return;
      const icon = this._createActionIcon(actionId);
      if (!icon) return;
      icon.setAttribute('data-kp-control-strip-icon', 'true');
      btn.insertBefore(icon, btn.firstChild);
    } catch { /* ignore */ }
  }

  /**
   * Inline monochrome icon for a KeyPilot action (keyboard, gear, etc.).
   * @param {string} actionId
   * @param {string} [color]
   * @returns {HTMLElement|null}
   */
  _createActionIcon(actionId, color = NCT_DARK_UI_COLORS.fg) {
    try {
      const cssUri = getActionIconDataUri(actionId, { fill: color });
      if (!cssUri) return null;
      // cssUri is CSS url("data:...") — extract the raw data URL for <img src>.
      const match = String(cssUri).match(/^url\("(.+)"\)$/);
      const src = match ? match[1] : null;
      if (!src) return null;
      const img = document.createElement('img');
      img.src = src;
      img.alt = '';
      img.setAttribute('aria-hidden', 'true');
      Object.assign(img.style, {
        width: '14px',
        height: '14px',
        display: 'block',
        pointerEvents: 'none',
        flex: '0 0 auto',
        opacity: '0.95'
      });
      return img;
    } catch {
      return null;
    }
  }

  /**
   * @param {{
   *   ariaLabel: string,
   *   title?: string,
   *   text?: string,
   *   iconActionId?: string,
   *   primary?: boolean,
   *   compact?: boolean,
   *   last?: boolean
   * }} opts
   */
  _createSegmentButton(opts) {
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.setAttribute('aria-label', opts.ariaLabel);
    if (opts.title) btn.title = opts.title;

    if (opts.iconActionId) {
      const icon = this._createActionIcon(opts.iconActionId);
      if (icon) {
        try { icon.setAttribute('data-kp-control-strip-icon', 'true'); } catch { /* ignore */ }
        btn.appendChild(icon);
      }
    }
    if (opts.text) {
      const label = document.createElement('span');
      label.textContent = opts.text;
      Object.assign(label.style, {
        pointerEvents: 'none',
        lineHeight: '1'
      });
      btn.appendChild(label);
    }

    Object.assign(btn.style, {
      display: 'inline-flex',
      alignItems: 'center',
      justifyContent: 'center',
      gap: '4px',
      height: '100%',
      minHeight: `${STRIP_HEIGHT_PX - 2}px`,
      margin: '0',
      padding: opts.compact ? '0 8px' : '0 10px',
      border: 'none',
      borderRight: opts.last ? 'none' : `1px solid ${NCT_DARK_UI_COLORS.panelEdgeDark}`,
      borderRadius: '0',
      background: opts.primary
        ? NCT_DARK_UI_BTN_LIT_GRADIENT
        : 'transparent',
      color: opts.primary ? NCT_DARK_UI_SELECTED_TEXT : NCT_DARK_UI_COLORS.fg,
      fontSize: '11px',
      fontWeight: '600',
      letterSpacing: '0.01em',
      lineHeight: '1',
      cursor: 'pointer',
      flex: '0 0 auto',
      boxShadow: 'none',
      outline: 'none',
      whiteSpace: 'nowrap'
    });

    btn.addEventListener('mouseenter', () => {
      try {
        btn.style.background = opts.primary
          ? NCT_DARK_UI_BTN_GRADIENT
          : NCT_DARK_UI_HOVER_TINT;
      } catch { /* ignore */ }
    });
    btn.addEventListener('mouseleave', () => {
      try {
        if (btn === this._keyboardBtn && this._keyboardActive) {
          btn.style.background = NCT_DARK_UI_SELECTED_TINT;
        } else {
          btn.style.background = opts.primary
            ? NCT_DARK_UI_BTN_LIT_GRADIENT
            : 'transparent';
        }
      } catch { /* ignore */ }
    });
    btn.addEventListener('focus', () => {
      try { btn.style.boxShadow = NCT_DARK_UI_FOCUS_RING; } catch { /* ignore */ }
    });
    btn.addEventListener('blur', () => {
      try { btn.style.boxShadow = 'none'; } catch { /* ignore */ }
    });

    return btn;
  }

  _applyCollapsedLayout() {
    if (!this.root) return;
    const collapsed = !!this._collapsed;

    if (this._modulesEl) {
      this._modulesEl.style.display = collapsed ? 'none' : 'flex';
    }
    if (this._moveBtn) {
      this._moveBtn.style.display = collapsed ? 'none' : 'inline-flex';
    }
    if (this._closeBtn) {
      this._closeBtn.style.display = collapsed ? 'none' : 'inline-flex';
      this._closeBtn.style.boxShadow = NCT_DARK_UI_ICON_BUTTON_OUTLINE;
    }
    if (this._collapseBtn) {
      this._collapseBtn.textContent = collapsed ? '▶' : '◀';
      this._collapseBtn.setAttribute(
        'aria-label',
        collapsed ? 'Expand control strip' : 'Collapse control strip'
      );
      this._collapseBtn.title = collapsed ? 'Expand' : 'Collapse';
      // When collapsed, collapse control is last visible segment.
      this._collapseBtn.style.borderRight = 'none';
      if (!collapsed && this._closeBtn) {
        this._collapseBtn.style.borderRight = `1px solid ${NCT_DARK_UI_COLORS.panelEdgeDark}`;
      }
      this._collapseBtn.style.boxShadow = NCT_DARK_UI_ICON_BUTTON_OUTLINE;
    }

    try {
      this.root.setAttribute('data-kp-collapsed', collapsed ? 'true' : 'false');
    } catch { /* ignore */ }
  }

  _renderStatus() {
    if (!this._statusLabel || !this._statusDot) return;
    const on = !!this._enabled;
    const textMode = on && !!this._textMode;
    this._statusLabel.textContent = on ? 'ON' : 'OFF';

    // Green when on; orange while a text field has focus (text mode); gray when off.
    let dotBg;
    let dotShadow;
    let labelColor;
    if (!on) {
      dotBg = 'rgba(148, 163, 184, 0.85)';
      dotShadow = '0 0 0 2px rgba(148, 163, 184, 0.15)';
      labelColor = 'rgba(148, 163, 184, 0.95)';
    } else if (textMode) {
      // Matches KeyPilot text-mode orange (#ff8c00 / COLORS.ORANGE).
      dotBg = 'rgba(255, 140, 0, 0.95)';
      dotShadow = '0 0 0 2px rgba(255, 140, 0, 0.25)';
      labelColor = 'rgba(253, 186, 116, 0.98)';
    } else {
      dotBg = 'rgba(16, 185, 129, 0.95)';
      dotShadow = '0 0 0 2px rgba(16, 185, 129, 0.2)';
      labelColor = 'rgba(167, 243, 208, 0.95)';
    }

    this._statusDot.style.background = dotBg;
    this._statusDot.style.boxShadow = dotShadow;
    this._statusLabel.style.color = labelColor;
    this._applyTextModeChrome(textMode);
    if (this._statusBtn) {
      this._statusBtn.setAttribute('aria-pressed', on ? 'true' : 'false');
      try {
        this._statusBtn.setAttribute('data-kp-text-mode', textMode ? 'true' : 'false');
      } catch { /* ignore */ }
      this._statusBtn.title = !on
        ? 'KeyPilot is off — click to turn on (Alt+K)'
        : textMode
          ? 'KeyPilot is on (text mode) — click to turn off (Alt+K)'
          : 'KeyPilot is on — click to turn off (Alt+K)';
    }
  }

  _renderKeyboard() {
    if (!this._keyboardBtn) return;
    const active = !!this._keyboardActive;
    this._keyboardBtn.setAttribute('aria-pressed', active ? 'true' : 'false');
    this._keyboardBtn.style.background = active
      ? NCT_DARK_UI_SELECTED_TINT
      : 'transparent';
    this._keyboardBtn.style.color = active
      ? NCT_DARK_UI_SELECTED_TEXT
      : NCT_DARK_UI_COLORS.fg;
    this._keyboardBtn.title = active
      ? 'Hide keyboard reference'
      : 'Show keyboard reference';
  }

  _onStatusClick(e) {
    try { e.preventDefault(); e.stopPropagation(); } catch { /* ignore */ }
    if (this._handlers.onToggleEnabled) {
      try { this._handlers.onToggleEnabled(); } catch { /* ignore */ }
    }
  }

  _onKeyboardClick(e) {
    try { e.preventDefault(); e.stopPropagation(); } catch { /* ignore */ }
    if (this._handlers.onToggleKeyboard) {
      try { this._handlers.onToggleKeyboard(); } catch { /* ignore */ }
    }
  }

  _onSettingsClick(e) {
    try { e.preventDefault(); e.stopPropagation(); } catch { /* ignore */ }
    if (this._handlers.onOpenSettings) {
      try { this._handlers.onOpenSettings(); } catch { /* ignore */ }
    }
  }

  _onCollapseClick(e) {
    try { e.preventDefault(); e.stopPropagation(); } catch { /* ignore */ }
    this.setCollapsed(!this._collapsed, { notify: true });
  }

  _onCloseClick(e) {
    try { e.preventDefault(); e.stopPropagation(); } catch { /* ignore */ }
    this.hide();
    if (this._handlers.onClose) {
      try { this._handlers.onClose(); } catch { /* ignore */ }
    }
  }

  /**
   * Reposition the walkthrough relative to the strip.
   * Does not force the strip back to the default top-left — user position is preserved.
   */
  _syncOnboardingOffset() {
    if (!this.root) return;
    try {
      const panel = document.querySelector('.kp-onboarding-panel');
      if (panel && panel.isConnected) {
        const hidden = panel.hidden === true
          || panel.getAttribute('hidden') !== null
          || panel.style.display === 'none';
        if (!hidden) positionOnboardingBelowControlStrip(panel);
      }
    } catch { /* ignore */ }
  }

  _bindOnboardingWatch() {
    if (this._observeBound) return;
    try {
      if (typeof MutationObserver !== 'undefined') {
        this._observer = new MutationObserver(() => {
          this._syncOnboardingOffset();
        });
        this._observer.observe(document.documentElement || document.body, {
          childList: true,
          subtree: true,
          attributes: true,
          attributeFilter: ['hidden', 'style', 'class']
        });
      }
    } catch { /* ignore */ }
    try {
      window.addEventListener('resize', this._onWinResize, true);
    } catch { /* ignore */ }
    this._observeBound = true;
  }

  /**
   * React/Next can remove foreign chrome hosts from <html>/<body>.
   * Reattach while the strip is still supposed to be visible.
   */
  _bindHostGuard() {
    if (this._hostGuard || typeof MutationObserver === 'undefined') return;
    const root = this.root;
    if (!root) return;
    try {
      this._hostGuard = new MutationObserver(() => {
        if (!this._desiredVisible || !this.root || this.root.isConnected) return;
        try {
          ensureChromeHostMounted(this.root);
          this.root.hidden = false;
          this.root.style.display = 'flex';
          this.root.style.pointerEvents = 'auto';
        } catch { /* ignore */ }
      });
      this._hostGuard.observe(document.documentElement, { childList: true, subtree: true });
    } catch {
      this._hostGuard = null;
    }
  }

  _unbindHostGuard() {
    try { this._hostGuard?.disconnect?.(); } catch { /* ignore */ }
    this._hostGuard = null;
  }

  _unbindOnboardingWatch() {
    if (!this._observeBound) return;
    try {
      if (this._observer) {
        this._observer.disconnect();
        this._observer = null;
      }
    } catch { /* ignore */ }
    try {
      window.removeEventListener('resize', this._onWinResize, true);
    } catch { /* ignore */ }
    this._observeBound = false;
  }

  _onDocMutate() {
    this._syncOnboardingOffset();
  }

  _onWinResize() {
    // Re-resolve anchors / reclamp free positions when the viewport changes.
    this._applyPanelPositionNow();
    this._syncOnboardingOffset();
  }
}
