import { Injectable, signal } from '@angular/core';
import { 
  Command, 
  CommandGroup, 
  CommandSearchResult, 
  CommandPaletteConfig, 
  CommandCategory, 
  RecentCommand,
  CommandExecutionContext
} from './command-palette.types';

@Injectable({
  providedIn: 'root'
})
export class CommandPaletteService {
  private _commands = signal<Command[]>([]);
  private _recentCommands = signal<RecentCommand[]>([]);
  private _config = signal<CommandPaletteConfig>({
    maxResults: 20,
    showCategories: true,
    showShortcuts: true,
    showRecent: true,
    recentLimit: 5,
    placeholder: 'Search commands...',
    noResultsMessage: 'No commands found',
    fuzzySearchThreshold: 0.3
  });

  readonly commands = this._commands.asReadonly();
  readonly recentCommands = this._recentCommands.asReadonly();
  readonly config = this._config.asReadonly();

  private readonly RECENT_COMMANDS_KEY = 'command-palette-recent';

  constructor() {
    this.loadRecentCommands();
  }

  /**
   * Register a new command
   */
  registerCommand(command: Command): void {
    const commands = this._commands();
    const existingIndex = commands.findIndex(c => c.id === command.id);
    
    if (existingIndex >= 0) {
      // Update existing command
      const updated = [...commands];
      updated[existingIndex] = command;
      this._commands.set(updated);
    } else {
      // Add new command
      this._commands.set([...commands, command]);
    }
  }

  /**
   * Register multiple commands at once
   */
  registerCommands(commands: Command[]): void {
    commands.forEach(command => this.registerCommand(command));
  }

  /**
   * Unregister a command by ID
   */
  unregisterCommand(commandId: string): void {
    const commands = this._commands();
    this._commands.set(commands.filter(c => c.id !== commandId));
  }

  /**
   * Get command by ID
   */
  getCommand(commandId: string): Command | undefined {
    return this._commands().find(c => c.id === commandId);
  }

  /**
   * Get all commands grouped by category
   */
  getCommandsByCategory(): CommandGroup[] {
    const commands = this._commands().filter(c => c.visible !== false);
    const groups = new Map<CommandCategory, Command[]>();

    commands.forEach(command => {
      const categoryCommands = groups.get(command.category) || [];
      categoryCommands.push(command);
      groups.set(command.category, categoryCommands);
    });

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

  /**
   * Search commands with fuzzy matching
   */
  searchCommands(query: string): CommandSearchResult[] {
    if (!query.trim()) {
      return this.getRecentCommandResults();
    }

    const commands = this._commands().filter(c => c.visible !== false && !c.disabled);
    const results: CommandSearchResult[] = [];
    const normalizedQuery = query.toLowerCase().trim();

    for (const command of commands) {
      const searchText = this.buildSearchText(command);
      const score = this.calculateFuzzyScore(normalizedQuery, searchText);
      
      if (score >= (this._config().fuzzySearchThreshold || 0.3)) {
        const highlightRanges = this.findHighlightRanges(normalizedQuery, command.title);
        results.push({
          command,
          score,
          highlightRanges
        });
      }
    }

    return results
      .sort((a, b) => b.score - a.score)
      .slice(0, this._config().maxResults || 20);
  }

  /**
   * Execute a command and track usage
   */
  async executeCommand(commandId: string, context: CommandExecutionContext): Promise<void> {
    const command = this.getCommand(commandId);
    if (!command || command.disabled) {
      return;
    }

    try {
      await command.handler();
      this.trackCommandUsage(commandId);
    } catch (error) {
      console.error('Command execution failed:', error);
      throw error;
    }
  }

  /**
   * Update configuration
   */
  updateConfig(config: Partial<CommandPaletteConfig>): void {
    this._config.set({ ...this._config(), ...config });
  }

  /**
   * Clear all commands
   */
  clearCommands(): void {
    this._commands.set([]);
  }

  /**
   * Clear recent commands
   */
  clearRecentCommands(): void {
    this._recentCommands.set([]);
    localStorage.removeItem(this.RECENT_COMMANDS_KEY);
  }

  private buildSearchText(command: Command): string {
    const parts = [
      command.title,
      command.description || '',
      ...command.keywords,
      ...(command.aliases || [])
    ];
    return parts.join(' ').toLowerCase();
  }

  private calculateFuzzyScore(query: string, text: string): number {
    const queryChars = query.split('');
    const textLower = text.toLowerCase();
    let score = 0;
    let queryIndex = 0;
    let consecutiveMatches = 0;

    // Exact title match gets highest score
    if (text.includes(query)) {
      score += 1;
      if (text.startsWith(query)) {
        score += 0.5; // Bonus for prefix match
      }
    }

    // Character by character fuzzy matching
    for (let i = 0; i < textLower.length && queryIndex < queryChars.length; i++) {
      if (textLower[i] === queryChars[queryIndex]) {
        score += 0.1;
        consecutiveMatches++;
        queryIndex++;
        
        // Bonus for consecutive matches
        if (consecutiveMatches > 1) {
          score += 0.05 * consecutiveMatches;
        }
      } else {
        consecutiveMatches = 0;
      }
    }

    // Penalty for incomplete matches
    const completionRatio = queryIndex / queryChars.length;
    score *= completionRatio;

    return score;
  }

  private findHighlightRanges(query: string, text: string): Array<{ start: number; end: number }> {
    const ranges: Array<{ start: number; end: number }> = [];
    const textLower = text.toLowerCase();
    const queryLower = query.toLowerCase();

    let startIndex = 0;
    let matchIndex = textLower.indexOf(queryLower, startIndex);

    while (matchIndex !== -1) {
      ranges.push({
        start: matchIndex,
        end: matchIndex + queryLower.length
      });
      startIndex = matchIndex + 1;
      matchIndex = textLower.indexOf(queryLower, startIndex);
    }

    return ranges;
  }

  private getRecentCommandResults(): CommandSearchResult[] {
    if (!this._config().showRecent) {
      return [];
    }

    const recentCommands = this._recentCommands()
      .sort((a, b) => b.lastUsed.getTime() - a.lastUsed.getTime())
      .slice(0, this._config().recentLimit || 5);

    const results: CommandSearchResult[] = [];
    
    for (const recent of recentCommands) {
      const command = this.getCommand(recent.commandId);
      if (command) {
        results.push({
          command: { ...command, category: CommandCategory.RECENT },
          score: 1,
          highlightRanges: []
        });
      }
    }
    
    return results;
  }

  private trackCommandUsage(commandId: string): void {
    const recent = this._recentCommands();
    const existingIndex = recent.findIndex(r => r.commandId === commandId);
    const now = new Date();

    let updated: RecentCommand[];
    if (existingIndex >= 0) {
      updated = [...recent];
      updated[existingIndex] = {
        ...updated[existingIndex],
        lastUsed: now,
        useCount: updated[existingIndex].useCount + 1
      };
    } else {
      updated = [...recent, {
        commandId,
        lastUsed: now,
        useCount: 1
      }];
    }

    // Keep only the most recent commands
    const maxRecent = (this._config().recentLimit || 5) * 2;
    if (updated.length > maxRecent) {
      updated = updated
        .sort((a, b) => b.lastUsed.getTime() - a.lastUsed.getTime())
        .slice(0, maxRecent);
    }

    this._recentCommands.set(updated);
    this.saveRecentCommands();
  }

  private loadRecentCommands(): void {
    try {
      const stored = localStorage.getItem(this.RECENT_COMMANDS_KEY);
      if (stored) {
        const parsed = JSON.parse(stored);
        const recent = parsed.map((item: any) => ({
          ...item,
          lastUsed: new Date(item.lastUsed)
        }));
        this._recentCommands.set(recent);
      }
    } catch (error) {
      console.warn('Failed to load recent commands:', error);
    }
  }

  private saveRecentCommands(): void {
    try {
      const recent = this._recentCommands();
      localStorage.setItem(this.RECENT_COMMANDS_KEY, JSON.stringify(recent));
    } catch (error) {
      console.warn('Failed to save recent commands:', error);
    }
  }

  private getCategoryTitle(category: CommandCategory): string {
    const titles: Record<CommandCategory, string> = {
      [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];
  }

  private getCategoryOrder(category: CommandCategory): number {
    const orders: Record<CommandCategory, number> = {
      [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;
  }
}