import { 
  Component, 
  Input, 
  Output, 
  EventEmitter, 
  ChangeDetectionStrategy, 
  ViewEncapsulation, 
  OnInit, 
  OnDestroy, 
  ViewChild, 
  ElementRef,
  inject,
  signal,
  computed,
  effect
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faSearch, faClock, faFolder, faTerminal } from '@fortawesome/free-solid-svg-icons';
import { CommandPaletteService } from './command-palette.service';
import { CommandPaletteItemComponent } from './command-palette-item.component';
import { 
  CommandSearchResult, 
  CommandGroup, 
  CommandCategory,
  CommandExecutionContext
} from './command-palette.types';

export type CommandPaletteSize = 'md' | 'lg' | 'xl';

@Component({
  selector: 'ui-command-palette',
  standalone: true,
  imports: [CommonModule, FormsModule, FontAwesomeModule, CommandPaletteItemComponent],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  template: `
    <div 
      class="ui-command-palette"
      [class.ui-command-palette--{{size}}]="size"
      [class.ui-command-palette--open]="isOpen()"
      [class.ui-command-palette--entering]="isEntering()"
      [class.ui-command-palette--leaving]="isLeaving()"
      role="dialog"
      [attr.aria-modal]="isOpen()"
      [attr.aria-labelledby]="'command-palette-' + paletteId + '-label'"
      (keydown)="handleKeydown($event)"
    >
      @if (isVisible()) {
        <!-- Backdrop -->
        <div 
          class="ui-command-palette__backdrop"
          (click)="close()"
          aria-hidden="true"
        ></div>

        <!-- Modal Container -->
        <div class="ui-command-palette__container">
          <!-- Search Input -->
          <div class="ui-command-palette__search">
            <div class="ui-command-palette__search-icon" aria-hidden="true">
              <fa-icon [icon]="faSearch"></fa-icon>
            </div>
            
            <input
              #searchInput
              type="search"
              class="ui-command-palette__input"
              [placeholder]="commandService.config().placeholder"
              [value]="searchQuery()"
              (input)="handleSearchInput($event)"
              autocomplete="off"
              autocorrect="off"
              autocapitalize="off"
              spellcheck="false"
              [id]="'command-palette-' + paletteId + '-input'"
              [attr.aria-label]="'Search commands'"
              [attr.aria-describedby]="'command-palette-' + paletteId + '-results'"
              role="combobox"
              [attr.aria-expanded]="searchResults().length > 0"
              [attr.aria-activedescendant]="getActiveDescendantId()"
              aria-haspopup="listbox"
            />

            @if (searchQuery()) {
              <button
                type="button"
                class="ui-command-palette__clear"
                (click)="clearSearch()"
                aria-label="Clear search"
                tabindex="-1"
              >
                ×
              </button>
            }
          </div>

          <!-- Results -->
          <div 
            class="ui-command-palette__results"
            [id]="'command-palette-' + paletteId + '-results'"
            role="listbox"
            [attr.aria-label]="'Command results'"
          >
            @if (searchResults().length > 0) {
              @if (commandService.config().showCategories && !searchQuery()) {
                <!-- Grouped by category -->
                @for (group of groupedResults(); track group.category) {
                  <div class="ui-command-palette__group">
                    <div class="ui-command-palette__group-header">
                      <div class="ui-command-palette__group-icon" aria-hidden="true">
                        <fa-icon [icon]="getCategoryIcon(group.category)"></fa-icon>
                      </div>
                      <h3 class="ui-command-palette__group-title">{{ group.title }}</h3>
                      <span class="ui-command-palette__group-count">{{ group.commands.length }}</span>
                    </div>
                    
                    @for (result of getResultsForGroup(group); track result.command.id; let i = $index) {
                      <ui-command-palette-item
                        [result]="result"
                        [selected]="getGlobalIndex(group, i) === selectedIndex()"
                        [id]="'command-' + result.command.id"
                        (itemClick)="executeCommand(result)"
                        (itemHover)="setSelectedIndex(getGlobalIndex(group, i))"
                      />
                    }
                  </div>
                }
              } @else {
                <!-- Flat results list -->
                @for (result of searchResults(); track result.command.id; let i = $index) {
                  <ui-command-palette-item
                    [result]="result"
                    [selected]="i === selectedIndex()"
                    [id]="'command-' + result.command.id"
                    (itemClick)="executeCommand(result)"
                    (itemHover)="setSelectedIndex(i)"
                  />
                }
              }
            } @else {
              <div class="ui-command-palette__empty" role="status">
                <div class="ui-command-palette__empty-icon" aria-hidden="true">
                  <fa-icon [icon]="faTerminal"></fa-icon>
                </div>
                <p class="ui-command-palette__empty-text">
                  {{ commandService.config().noResultsMessage }}
                </p>
              </div>
            }
          </div>

          <!-- Footer -->
          @if (showFooter) {
            <div class="ui-command-palette__footer">
              <div class="ui-command-palette__footer-section">
                <kbd class="ui-command-palette__footer-kbd">↑↓</kbd>
                <span>Navigate</span>
              </div>
              <div class="ui-command-palette__footer-section">
                <kbd class="ui-command-palette__footer-kbd">Enter</kbd>
                <span>Select</span>
              </div>
              <div class="ui-command-palette__footer-section">
                <kbd class="ui-command-palette__footer-kbd">Esc</kbd>
                <span>Close</span>
              </div>
            </div>
          }
        </div>
      }
    </div>
  `,
  styleUrl: './command-palette.component.scss'
})
export class CommandPaletteComponent implements OnInit, OnDestroy {
  @ViewChild('searchInput') searchInput!: ElementRef<HTMLInputElement>;

  @Input() size: CommandPaletteSize = 'lg';
  @Input() showFooter = true;
  @Input() autoFocus = true;

  @Output() opened = new EventEmitter<void>();
  @Output() closed = new EventEmitter<void>();
  @Output() commandExecuted = new EventEmitter<{ commandId: string; context: CommandExecutionContext }>();

  // Inject services
  readonly commandService = inject(CommandPaletteService);

  // Icons
  readonly faSearch = faSearch;
  readonly faClock = faClock;
  readonly faFolder = faFolder;
  readonly faTerminal = faTerminal;

  // State signals
  private _isOpen = signal(false);
  private _isEntering = signal(false);
  private _isLeaving = signal(false);
  private _searchQuery = signal('');
  private _selectedIndex = signal(0);
  private _searchResults = signal<CommandSearchResult[]>([]);

  // Computed signals
  readonly isOpen = this._isOpen.asReadonly();
  readonly isEntering = this._isEntering.asReadonly();
  readonly isLeaving = this._isLeaving.asReadonly();
  readonly searchQuery = this._searchQuery.asReadonly();
  readonly selectedIndex = this._selectedIndex.asReadonly();
  readonly searchResults = this._searchResults.asReadonly();
  readonly isVisible = computed(() => this._isOpen() || this._isEntering() || this._isLeaving());

  readonly groupedResults = computed(() => {
    const results = this._searchResults();
    if (!results.length || this._searchQuery()) {
      return [];
    }

    const groups = new Map<CommandCategory, CommandSearchResult[]>();
    
    results.forEach(result => {
      const category = result.command.category;
      const categoryResults = groups.get(category) || [];
      categoryResults.push(result);
      groups.set(category, categoryResults);
    });

    return Array.from(groups.entries()).map(([category, commands]) => ({
      category,
      title: this.getCategoryTitle(category),
      commands: []
    })).sort((a, b) => this.getCategoryOrder(a.category) - this.getCategoryOrder(b.category));
  });

  // Internal properties
  readonly paletteId = `palette-${Math.random().toString(36).substr(2, 9)}`;
  private previousActiveElement: Element | null = null;

  constructor() {
    // Auto-update search results when query changes
    effect(() => {
      const query = this._searchQuery();
      const results = this.commandService.searchCommands(query);
      this._searchResults.set(results);
      this._selectedIndex.set(0);
    });
  }

  ngOnInit(): void {
    // Initial search to show recent commands
    this.updateSearchResults();
  }

  ngOnDestroy(): void {
    this.restoreFocus();
  }

  open(): void {
    if (this._isOpen()) return;

    this.storePreviousFocus();
    this._isOpen.set(true);
    this._isEntering.set(true);
    this.updateSearchResults();
    
    // Focus input after animation
    setTimeout(() => {
      this._isEntering.set(false);
      if (this.autoFocus && this.searchInput) {
        this.searchInput.nativeElement.focus();
      }
      this.opened.emit();
    }, 150);
  }

  close(): void {
    if (!this._isOpen()) return;

    this._isLeaving.set(true);
    
    setTimeout(() => {
      this._isOpen.set(false);
      this._isLeaving.set(false);
      this._searchQuery.set('');
      this._selectedIndex.set(0);
      this.restoreFocus();
      this.closed.emit();
    }, 150);
  }

  toggle(): void {
    if (this._isOpen()) {
      this.close();
    } else {
      this.open();
    }
  }

  handleSearchInput(event: Event): void {
    const target = event.target as HTMLInputElement;
    this._searchQuery.set(target.value);
  }

  clearSearch(): void {
    this._searchQuery.set('');
    if (this.searchInput) {
      this.searchInput.nativeElement.focus();
    }
  }

  handleKeydown(event: KeyboardEvent): void {
    const results = this._searchResults();
    
    switch (event.key) {
      case 'ArrowDown':
        event.preventDefault();
        this.navigateDown();
        break;
        
      case 'ArrowUp':
        event.preventDefault();
        this.navigateUp();
        break;
        
      case 'Enter':
        event.preventDefault();
        this.executeSelectedCommand();
        break;
        
      case 'Escape':
        event.preventDefault();
        this.close();
        break;
        
      case 'Tab':
        event.preventDefault();
        // Tab could cycle through results or close
        if (event.shiftKey) {
          this.navigateUp();
        } else {
          this.navigateDown();
        }
        break;
    }
  }

  private navigateDown(): void {
    const results = this._searchResults();
    if (results.length === 0) return;
    
    const currentIndex = this._selectedIndex();
    const nextIndex = currentIndex >= results.length - 1 ? 0 : currentIndex + 1;
    this._selectedIndex.set(nextIndex);
  }

  private navigateUp(): void {
    const results = this._searchResults();
    if (results.length === 0) return;
    
    const currentIndex = this._selectedIndex();
    const prevIndex = currentIndex <= 0 ? results.length - 1 : currentIndex - 1;
    this._selectedIndex.set(prevIndex);
  }

  executeSelectedCommand(): void {
    const results = this._searchResults();
    const selectedResult = results[this._selectedIndex()];
    
    if (selectedResult) {
      this.executeCommand(selectedResult);
    }
  }

  async executeCommand(result: CommandSearchResult): Promise<void> {
    const context: CommandExecutionContext = {
      source: 'click',
      timestamp: new Date()
    };

    try {
      await this.commandService.executeCommand(result.command.id, context);
      this.commandExecuted.emit({ commandId: result.command.id, context });
      this.close();
    } catch (error) {
      console.error('Failed to execute command:', error);
    }
  }

  setSelectedIndex(index: number): void {
    this._selectedIndex.set(index);
  }

  getActiveDescendantId(): string | null {
    const results = this._searchResults();
    const selectedResult = results[this._selectedIndex()];
    return selectedResult ? `command-${selectedResult.command.id}` : null;
  }

  getCategoryIcon(category: CommandCategory) {
    const icons = {
      [CommandCategory.RECENT]: faClock,
      [CommandCategory.NAVIGATION]: faFolder,
      [CommandCategory.ACTIONS]: faTerminal,
      [CommandCategory.SETTINGS]: faTerminal,
      [CommandCategory.SEARCH]: faSearch,
      [CommandCategory.HELP]: faTerminal,
      [CommandCategory.FILE]: faFolder,
      [CommandCategory.EDIT]: faTerminal,
      [CommandCategory.VIEW]: faTerminal,
      [CommandCategory.TOOLS]: faTerminal
    };
    return icons[category] || faTerminal;
  }

  getCategoryTitle(category: CommandCategory): string {
    const titles = {
      [CommandCategory.RECENT]: 'Recent',
      [CommandCategory.NAVIGATION]: 'Navigation',
      [CommandCategory.ACTIONS]: 'Actions',
      [CommandCategory.SETTINGS]: 'Settings',
      [CommandCategory.SEARCH]: 'Search',
      [CommandCategory.HELP]: 'Help',
      [CommandCategory.FILE]: 'File',
      [CommandCategory.EDIT]: 'Edit',
      [CommandCategory.VIEW]: 'View',
      [CommandCategory.TOOLS]: 'Tools'
    };
    return titles[category] || 'Other';
  }

  getCategoryOrder(category: CommandCategory): number {
    const orders = {
      [CommandCategory.RECENT]: 0,
      [CommandCategory.NAVIGATION]: 1,
      [CommandCategory.FILE]: 2,
      [CommandCategory.EDIT]: 3,
      [CommandCategory.VIEW]: 4,
      [CommandCategory.ACTIONS]: 5,
      [CommandCategory.TOOLS]: 6,
      [CommandCategory.SEARCH]: 7,
      [CommandCategory.SETTINGS]: 8,
      [CommandCategory.HELP]: 9
    };
    return orders[category] || 99;
  }

  getResultsForGroup(group: { category: CommandCategory }): CommandSearchResult[] {
    return this._searchResults().filter(result => result.command.category === group.category);
  }

  getGlobalIndex(group: { category: CommandCategory }, localIndex: number): number {
    const results = this._searchResults();
    let globalIndex = 0;
    
    for (const result of results) {
      if (result.command.category === group.category) {
        if (localIndex === 0) {
          return globalIndex;
        }
        localIndex--;
      }
      globalIndex++;
    }
    
    return 0;
  }

  private updateSearchResults(): void {
    const query = this._searchQuery();
    const results = this.commandService.searchCommands(query);
    this._searchResults.set(results);
    this._selectedIndex.set(0);
  }

  private storePreviousFocus(): void {
    this.previousActiveElement = document.activeElement;
  }

  private restoreFocus(): void {
    if (this.previousActiveElement && 'focus' in this.previousActiveElement) {
      (this.previousActiveElement as HTMLElement).focus();
    }
  }
}