/**
 * Quick Panel Search Input
 *
 * A scope-aware search input component with:
 * - PRD-defined scope prefixes (t/b/h/c/>)
 * - Scope chip for visual indication and cycling
 * - XSS-safe rendering (textContent/value only)
 * - IME composition handling
 * - Disposer-based cleanup
 */

import { Disposer } from '@/entrypoints/web-editor/utils/disposables';
import {
  DEFAULT_SCOPE,
  QUICK_PANEL_SCOPES,
  normalizeQuickPanelScope,
  parseScopePrefixedQuery,
  type QuickPanelScope,
} from '../core/types';

// ============================================================
// Types
// ============================================================

export interface SearchInputState {
  scope: QuickPanelScope;
  query: string;
}

export interface SearchInputOptions {
  /** Container to mount the search input */
  container: HTMLElement;
  /** Initial scope. Default: 'all' */
  initialScope?: QuickPanelScope;
  /** Initial query string */
  initialQuery?: string;
  /** Input placeholder. Default: 'Search...' */
  placeholder?: string;
  /** Auto-focus on mount. Default: true */
  autoFocus?: boolean;
  /**
   * Available scopes for cycling.
   * Default: all known scopes
   */
  availableScopes?: readonly QuickPanelScope[];

  /** Called when state changes (scope or query) */
  onChange?: (state: SearchInputState) => void;
  /** Called when scope changes */
  onScopeChange?: (scope: QuickPanelScope) => void;
  /** Called when query changes */
  onQueryChange?: (query: string) => void;
  /** Called when clear button is clicked */
  onClear?: () => void;
}

export interface SearchInputManager {
  /** Root DOM element */
  root: HTMLDivElement;
  /** Input element */
  input: HTMLInputElement;
  /** Get current state */
  getState: () => SearchInputState;
  /** Set scope programmatically */
  setScope: (scope: QuickPanelScope, options?: { emit?: boolean }) => void;
  /** Set query programmatically */
  setQuery: (query: string, options?: { emit?: boolean }) => void;
  /** Clear the input */
  clear: (options?: { emit?: boolean }) => void;
  /** Focus the input */
  focus: () => void;
  /** Clean up resources */
  dispose: () => void;
}

// ============================================================
// Helpers
// ============================================================

function isNonEmptyString(value: unknown): value is string {
  return typeof value === 'string' && value.trim().length > 0;
}

function safeFocus(el: HTMLElement): void {
  try {
    el.focus();
  } catch {
    // Best-effort
  }
}

function buildScopeCycleList(input: readonly QuickPanelScope[] | undefined): QuickPanelScope[] {
  const defaultList: QuickPanelScope[] = [
    'all',
    'tabs',
    'bookmarks',
    'history',
    'content',
    'commands',
  ];
  const list = (input?.length ? [...input] : defaultList).map((s) => normalizeQuickPanelScope(s));

  // Ensure 'all' exists as a stable fallback
  if (!list.includes('all')) {
    list.unshift('all');
  }

  // De-duplicate while preserving order
  const seen = new Set<QuickPanelScope>();
  return list.filter((s) => {
    if (seen.has(s)) return false;
    seen.add(s);
    return true;
  });
}

// ============================================================
// Main Factory
// ============================================================

/**
 * Create a Quick Panel search input component.
 *
 * @example
 * ```typescript
 * const searchInput = createSearchInput({
 *   container: headerSearchMount,
 *   initialScope: 'all',
 *   onChange: ({ scope, query }) => {
 *     controller.search(scope, query);
 *   },
 * });
 *
 * // Programmatically set scope
 * searchInput.setScope('tabs');
 *
 * // Cleanup
 * searchInput.dispose();
 * ```
 */
export function createSearchInput(options: SearchInputOptions): SearchInputManager {
  const disposer = new Disposer();
  const scopes = buildScopeCycleList(options.availableScopes);

  let disposed = false;
  let isComposing = false;

  let state: SearchInputState = {
    scope: normalizeQuickPanelScope(options.initialScope, DEFAULT_SCOPE),
    query: (options.initialQuery ?? '').trim(),
  };

  // --------------------------------------------------------
  // DOM Construction
  // --------------------------------------------------------

  const root = document.createElement('div');
  root.className = 'qp-search';

  const brand = document.createElement('div');
  brand.className = 'qp-brand';
  brand.textContent = '\u2726'; // Star symbol

  const scopeBtn = document.createElement('button');
  scopeBtn.type = 'button';
  scopeBtn.className = 'qp-scope-chip ac-btn ac-focus-ring';
  scopeBtn.setAttribute('aria-label', 'Switch search scope');

  const input = document.createElement('input');
  input.type = 'text';
  input.className = 'qp-search-input ac-focus-ring';
  input.placeholder = options.placeholder?.trim() || 'Search\u2026';
  input.setAttribute('autocomplete', 'off');
  input.setAttribute('spellcheck', 'false');
  input.setAttribute('aria-label', 'Quick Panel search');

  const clearBtn = document.createElement('button');
  clearBtn.type = 'button';
  clearBtn.className = 'qp-icon-btn ac-btn ac-focus-ring';
  clearBtn.textContent = '\u00D7'; // ×
  clearBtn.setAttribute('aria-label', 'Clear search');

  root.append(brand, scopeBtn, input, clearBtn);
  options.container.append(root);
  disposer.add(() => root.remove());

  // --------------------------------------------------------
  // Rendering
  // --------------------------------------------------------

  function renderScopeChip(): void {
    const def = QUICK_PANEL_SCOPES[state.scope];
    const prefixHint = def.prefix ? def.prefix.trim() : '';

    scopeBtn.textContent = '';

    const iconEl = document.createElement('span');
    iconEl.className = 'qp-scope-chip__icon';
    iconEl.textContent = def.icon;

    const labelEl = document.createElement('span');
    labelEl.className = 'qp-scope-chip__label';
    labelEl.textContent = def.label;

    scopeBtn.append(iconEl, labelEl);

    if (prefixHint) {
      const prefixEl = document.createElement('span');
      prefixEl.className = 'qp-scope-chip__prefix';
      prefixEl.textContent = prefixHint;
      scopeBtn.append(prefixEl);
    }
  }

  function renderClearButton(): void {
    clearBtn.hidden = !isNonEmptyString(input.value);
  }

  function render(): void {
    renderScopeChip();
    renderClearButton();
  }

  // --------------------------------------------------------
  // State Change Emission
  // --------------------------------------------------------

  function emit(): void {
    try {
      options.onChange?.({ ...state });
    } catch {
      // Best-effort
    }
    try {
      options.onScopeChange?.(state.scope);
    } catch {
      // Best-effort
    }
    try {
      options.onQueryChange?.(state.query);
    } catch {
      // Best-effort
    }
  }

  // --------------------------------------------------------
  // State Mutators
  // --------------------------------------------------------

  function setScope(next: QuickPanelScope, opts: { emit?: boolean } = {}): void {
    if (disposed) return;

    const normalized = normalizeQuickPanelScope(next, DEFAULT_SCOPE);
    if (state.scope === normalized) return;

    // Only allow scopes in the cycle list
    if (!scopes.includes(normalized)) return;

    state = { ...state, scope: normalized };
    render();

    if (opts.emit !== false) emit();
  }

  function setQuery(nextQuery: string, opts: { emit?: boolean } = {}): void {
    if (disposed) return;

    const q = (nextQuery ?? '').trim();
    if (state.query === q && input.value === q) {
      render();
      return;
    }

    state = { ...state, query: q };
    input.value = q;
    render();

    if (opts.emit !== false) emit();
  }

  function clear(opts: { emit?: boolean } = {}): void {
    if (disposed) return;

    setQuery('', { emit: false });

    try {
      options.onClear?.();
    } catch {
      // Best-effort
    }

    if (opts.emit !== false) emit();
  }

  // --------------------------------------------------------
  // Prefix Parsing
  // --------------------------------------------------------

  function applyPrefixParsing(): void {
    if (disposed) return;
    if (isComposing) return;

    const parsed = parseScopePrefixedQuery(input.value, state.scope);

    if (parsed.consumedPrefix) {
      // Apply scope change if available
      if (scopes.includes(parsed.scope) && parsed.scope !== state.scope) {
        setScope(parsed.scope, { emit: false });
      }

      // Consume the prefix from the visible input
      if (input.value !== parsed.query) {
        input.value = parsed.query;
        // Move caret to end after rewrite for predictable UX
        try {
          input.setSelectionRange(input.value.length, input.value.length);
        } catch {
          // Ignore
        }
      }
    }

    // Always update query state from current input value
    setQuery(input.value, { emit: false });
    emit();
  }

  // --------------------------------------------------------
  // Event Handlers
  // --------------------------------------------------------

  disposer.listen(input, 'compositionstart', () => {
    isComposing = true;
  });

  disposer.listen(input, 'compositionend', () => {
    isComposing = false;
    applyPrefixParsing();
  });

  disposer.listen(input, 'input', () => {
    applyPrefixParsing();
  });

  disposer.listen(clearBtn, 'click', () => {
    clear();
    safeFocus(input);
  });

  disposer.listen(scopeBtn, 'click', () => {
    const idx = scopes.indexOf(state.scope);
    const next = scopes[(idx >= 0 ? idx + 1 : 0) % scopes.length] ?? 'all';
    setScope(next);
    safeFocus(input);
  });

  // --------------------------------------------------------
  // Initialization
  // --------------------------------------------------------

  input.value = state.query;
  render();

  if (options.autoFocus !== false) {
    safeFocus(input);
  }

  // --------------------------------------------------------
  // Public API
  // --------------------------------------------------------

  return {
    root,
    input,
    getState: () => ({ ...state }),
    setScope,
    setQuery,
    clear,
    focus: () => safeFocus(input),
    dispose: () => {
      if (disposed) return;
      disposed = true;
      disposer.dispose();
    },
  };
}
