/**
 * Event Listener Manager
 * Manages all event listeners for file search functionality
 *
 * Responsibilities:
 * - Setting up event listeners for text input interactions
 * - Handling mouse events (click, move, leave)
 * - Handling keyboard events (keydown, keyup)
 * - Coordinating scroll synchronization
 * - Managing Cmd key interactions for link highlighting
 */

export interface EventListenerCallbacks {
  // Input change handlers
  checkForFileSearch: () => void;
  updateHighlightBackdrop: () => void;
  updateCursorPositionHighlight: () => void;

  // Keyboard handlers
  handleKeyDown: (e: KeyboardEvent) => void;
  handleBackspaceForAtPath: (e: KeyboardEvent) => boolean;
  handleBackspaceForAgentSkill?: (e: KeyboardEvent) => boolean;
  handleCtrlEnterOpenFile: (e: KeyboardEvent) => void;
  handleCtrlShiftEnterRevealDir: (e: KeyboardEvent) => void;

  // Mouse handlers
  handleCmdClickOnAtPath: (e: MouseEvent) => void;
  handleMouseMove: (e: MouseEvent) => void;

  // State checkers
  isVisible: () => boolean;

  // UI management
  hideSuggestions: () => void;
  syncBackdropScroll: () => void;

  // HighlightManager delegation
  clearFilePathHighlight: () => void;
  onCmdKeyDown: () => void;
  onCmdKeyUp: () => void;
  onMouseMove: (e: MouseEvent) => void;
}

export class EventListenerManager {
  private textInput: HTMLTextAreaElement | null = null;
  private suggestionsContainer: HTMLElement | null = null;
  private callbacks: EventListenerCallbacks;

  // Bound event handlers for add/remove listener
  private boundInputHandler: (() => void) | null = null;
  private boundSelectionChangeHandler: (() => void) | null = null;

  // Flag to track if listeners are suspended
  private listenersAreSuspended: boolean = false;

  // rAF-based input processing optimization
  private lastText: string = '';
  private pendingInputUpdate: number | null = null;
  private pendingSelectionUpdate: number | null = null;

  // IME composition tracking
  private isComposing: boolean = false;

  // Throttle state for mousemove handler
  private lastMouseMoveTime: number = 0;
  private static readonly MOUSE_MOVE_THROTTLE = 80;

  constructor(callbacks: EventListenerCallbacks) {
    this.callbacks = callbacks;
  }

  /**
   * Suspend input and selectionchange listeners temporarily
   * Used during @path deletion to prevent cursor interference
   */
  public suspendInputListeners(): void {
    if (this.listenersAreSuspended) return;
    this.listenersAreSuspended = true;

    // Cancel pending rAF callbacks to prevent them from firing
    if (this.pendingInputUpdate) {
      cancelAnimationFrame(this.pendingInputUpdate);
      this.pendingInputUpdate = null;
    }
    if (this.pendingSelectionUpdate) {
      cancelAnimationFrame(this.pendingSelectionUpdate);
      this.pendingSelectionUpdate = null;
    }

    if (this.textInput && this.boundInputHandler) {
      this.textInput.removeEventListener('input', this.boundInputHandler);
    }
    if (this.boundSelectionChangeHandler) {
      document.removeEventListener('selectionchange', this.boundSelectionChangeHandler);
    }
  }

  /**
   * Resume input and selectionchange listeners
   * Call this after @path deletion is complete
   */
  public resumeInputListeners(): void {
    if (!this.listenersAreSuspended) return;
    this.listenersAreSuspended = false;

    if (this.textInput && this.boundInputHandler) {
      this.textInput.addEventListener('input', this.boundInputHandler);
    }
    if (this.boundSelectionChangeHandler) {
      document.addEventListener('selectionchange', this.boundSelectionChangeHandler);
    }
  }

  /**
   * Initialize with DOM elements
   */
  public initialize(
    textInput: HTMLTextAreaElement,
    suggestionsContainer: HTMLElement | null
  ): void {
    this.textInput = textInput;
    this.suggestionsContainer = suggestionsContainer;
  }

  /**
   * Setup all event listeners
   */
  public setupEventListeners(): void {
    if (!this.textInput) {
      console.warn('[EventListenerManager] setupEventListeners: textInput is null, skipping');
      return;
    }

    // Create bound input handler with rAF optimization
    // Skip processing if text hasn't changed to avoid redundant work
    this.boundInputHandler = () => {
      // Skip during IME composition to avoid filtering with intermediate text
      // (e.g., hiragana "しすとれ" before katakana conversion "シストレ")
      if (this.isComposing) return;

      // Debounce with rAF - skip if already pending
      if (this.pendingInputUpdate) return;

      this.pendingInputUpdate = requestAnimationFrame(() => {
        this.pendingInputUpdate = null;

        // Guard: skip if listeners are suspended or composing
        if (this.listenersAreSuspended || this.isComposing) return;

        if (!this.textInput) return;
        const currentText = this.textInput.value;

        // Skip if text hasn't changed since last check
        if (currentText === this.lastText) return;
        this.lastText = currentText;

        // Execute callbacks only when text has actually changed
        this.callbacks.checkForFileSearch();
        // Defer non-critical updates when suggestions are visible to avoid blocking the critical path
        if (!this.callbacks.isVisible()) {
          this.callbacks.updateHighlightBackdrop();
          this.callbacks.updateCursorPositionHighlight();
        }
      });
    };

    // Listen for input changes to detect @ mentions and update highlights
    this.textInput.addEventListener('input', this.boundInputHandler);

    // Track IME composition state
    this.textInput.addEventListener('compositionstart', () => {
      this.isComposing = true;
    });

    // After IME composition ends, process the confirmed text synchronously.
    // Using rAF here is unreliable: if the user quickly cycles conversion candidates
    // (compositionend → compositionstart), the rAF fires after the new compositionstart
    // and gets skipped by the isComposing guard.
    this.textInput.addEventListener('compositionend', () => {
      this.isComposing = false;
      if (this.listenersAreSuspended || !this.textInput) return;

      // Cancel pending rAF to prevent stale processing
      if (this.pendingInputUpdate) {
        cancelAnimationFrame(this.pendingInputUpdate);
        this.pendingInputUpdate = null;
      }

      const currentText = this.textInput.value;
      if (currentText === this.lastText) return;
      this.lastText = currentText;
      this.callbacks.checkForFileSearch();
    });

    // Listen for keydown for navigation and backspace handling
    this.textInput.addEventListener('keydown', (e) => {
      if (this.callbacks.isVisible()) {
        this.callbacks.handleKeyDown(e);
      } else if (e.key === 'Backspace') {
        // Don't override Shift+Backspace or when text is selected
        if (e.shiftKey) return;
        if (this.textInput && this.textInput.selectionStart !== this.textInput.selectionEnd) return;

        // Try @path deletion first
        if (this.callbacks.handleBackspaceForAtPath(e)) return;

        // Then try agent skill deletion
        if (this.callbacks.handleBackspaceForAgentSkill?.(e)) return;
      } else if (e.key === 'Enter' && e.ctrlKey && e.shiftKey) {
        // Ctrl+Shift+Enter: reveal file directory in Finder
        this.callbacks.handleCtrlShiftEnterRevealDir(e);
      } else if (e.key === 'Enter' && e.ctrlKey) {
        // Ctrl+Enter: open file at cursor position
        this.callbacks.handleCtrlEnterOpenFile(e);
      }
    });

    // Listen for cursor position changes (click, arrow keys)
    this.textInput.addEventListener('click', () => {
      this.callbacks.updateCursorPositionHighlight();
    });

    this.textInput.addEventListener('keyup', (e) => {
      // Update on arrow keys that move cursor
      if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End'].includes(e.key)) {
        this.callbacks.updateCursorPositionHighlight();
      }
      // Force highlight redraw after Tab key to fix position alignment
      // Tab character rendering can differ between textarea and backdrop
      if (e.key === 'Tab') {
        // Use requestAnimationFrame to ensure the DOM has updated
        requestAnimationFrame(() => {
          this.callbacks.updateHighlightBackdrop();
          this.callbacks.syncBackdropScroll();
        });
      }
    });

    // Create bound selectionchange handler with rAF optimization
    // Only process if textarea is active to avoid unnecessary work
    this.boundSelectionChangeHandler = () => {
      if (document.activeElement !== this.textInput) return;

      // Debounce with rAF - skip if already pending
      if (this.pendingSelectionUpdate) return;

      this.pendingSelectionUpdate = requestAnimationFrame(() => {
        this.pendingSelectionUpdate = null;

        // Guard: skip if listeners are suspended
        if (this.listenersAreSuspended) return;

        this.callbacks.updateCursorPositionHighlight();
      });
    };

    // Also listen for selectionchange on document (handles all cursor movements)
    document.addEventListener('selectionchange', this.boundSelectionChangeHandler);

    // Sync scroll position between textarea and backdrop immediately
    this.textInput.addEventListener('scroll', () => {
      this.callbacks.syncBackdropScroll();
    }, { passive: true });

    // Hide suggestions on blur (with small delay to allow click selection)
    this.textInput.addEventListener('blur', () => {
      setTimeout(() => {
        if (!this.suggestionsContainer?.contains(document.activeElement)) {
          this.callbacks.hideSuggestions();
        }
      }, 150);
    });

    // Handle click outside
    document.addEventListener('click', (e) => {
      if (this.callbacks.isVisible() &&
          !this.suggestionsContainer?.contains(e.target as Node) &&
          !this.textInput?.contains(e.target as Node)) {
        this.callbacks.hideSuggestions();
      }
    });

    // Handle Cmd+click on @path in textarea to open in editor
    this.textInput.addEventListener('click', (e) => {
      if (e.metaKey) {
        this.callbacks.handleCmdClickOnAtPath(e);
      }
    });

    // Handle Cmd+hover for link style on @paths (throttled to 80ms)
    this.textInput.addEventListener('mousemove', (e) => {
      const now = Date.now();
      if (now - this.lastMouseMoveTime < EventListenerManager.MOUSE_MOVE_THROTTLE) return;
      this.lastMouseMoveTime = now;
      this.callbacks.handleMouseMove(e);
    });

    // Clear link style when mouse leaves textarea
    this.textInput.addEventListener('mouseleave', () => {
      // Delegate to HighlightManager
      this.callbacks.clearFilePathHighlight();
    });

    // Handle Cmd key press/release for link style
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Meta') {
        // Delegate to HighlightManager
        this.callbacks.onCmdKeyDown();
      }
    });

    document.addEventListener('keyup', (e) => {
      if (e.key === 'Meta') {
        // Delegate to HighlightManager
        this.callbacks.onCmdKeyUp();
      }
    });

    // Clear on window blur (Cmd key release detection may fail)
    window.addEventListener('blur', () => {
      // Delegate to HighlightManager
      this.callbacks.onCmdKeyUp();
    });
  }
}
