import { KGCommand } from './KGCommand';
import { DEBUG_MODE } from '../../constants';

/**
 * Manages the command history for undo/redo functionality
 * Implements a singleton pattern to ensure single source of truth
 */
export class KGCommandHistory {
  private static _instance: KGCommandHistory | null = null;

  private undoStack: KGCommand[] = [];
  private redoStack: KGCommand[] = [];
  private maxHistorySize: number = 30;
  
  // Callbacks for UI updates
  private onHistoryChanged?: () => void;

  private constructor() {
    // Private constructor for singleton pattern
  }

  /**
   * Get the singleton instance
   */
  public static instance(): KGCommandHistory {
    if (!this._instance) {
      this._instance = new KGCommandHistory();
    }
    return this._instance;
  }

  /**
   * Execute a command and add it to the history
   * @param command The command to execute
   */
  public executeCommand(command: KGCommand, options?: { rethrow?: boolean }): void {
    try {
      // Execute the command
      command.execute();

      // Check if we can merge with the last command
      if (this.undoStack.length > 0) {
        const lastCommand = this.undoStack[this.undoStack.length - 1];
        if (lastCommand.canMergeWith(command)) {
          const mergedCommand = lastCommand.mergeWith(command);
          if (mergedCommand) {
            // Replace the last command with the merged one
            this.undoStack[this.undoStack.length - 1] = mergedCommand;
            
            if (DEBUG_MODE.CORE) {
              console.log(`Merged command: ${mergedCommand.getDescription()}`);
            }
            
            this.notifyHistoryChanged();
            return;
          }
        }
      }

      // Add to undo stack
      this.undoStack.push(command);

      // Clear redo stack since we're executing a new command
      this.redoStack = [];

      // Maintain history size limit
      if (this.undoStack.length > this.maxHistorySize) {
        this.undoStack.shift();
      }

      if (DEBUG_MODE.CORE) {
        console.log(`Executed command: ${command.getDescription()}`);
        console.log(`Undo stack size: ${this.undoStack.length}`);
      }

      this.notifyHistoryChanged();
    } catch (error) {
      console.error('Failed to execute command:', error);
      if (options?.rethrow) {
        throw error;
      }
      // Don't add failed commands to history
    }
  }

  /**
   * Undo the last command
   * @returns true if undo was successful, false otherwise
   */
  public undo(): boolean {
    if (this.undoStack.length === 0) {
      return false;
    }

    const command = this.undoStack.pop()!;
    
    try {
      command.undo();
      this.redoStack.push(command);

      if (DEBUG_MODE.CORE) {
        console.log(`Undid command: ${command.getDescription()}`);
        console.log(`Undo stack size: ${this.undoStack.length}, Redo stack size: ${this.redoStack.length}`);
      }

      this.notifyHistoryChanged();
      return true;
    } catch (error) {
      console.error('Failed to undo command:', error);
      // Put the command back on the stack if undo fails
      this.undoStack.push(command);
      return false;
    }
  }

  /**
   * Redo the last undone command
   * @returns true if redo was successful, false otherwise
   */
  public redo(): boolean {
    if (this.redoStack.length === 0) {
      return false;
    }

    const command = this.redoStack.pop()!;
    
    try {
      command.execute();
      this.undoStack.push(command);

      if (DEBUG_MODE.CORE) {
        console.log(`Redid command: ${command.getDescription()}`);
        console.log(`Undo stack size: ${this.undoStack.length}, Redo stack size: ${this.redoStack.length}`);
      }

      this.notifyHistoryChanged();
      return true;
    } catch (error) {
      console.error('Failed to redo command:', error);
      // Put the command back on the redo stack if redo fails
      this.redoStack.push(command);
      return false;
    }
  }

  /**
   * Check if undo is available
   */
  public canUndo(): boolean {
    return this.undoStack.length > 0;
  }

  /**
   * Check if redo is available
   */
  public canRedo(): boolean {
    return this.redoStack.length > 0;
  }

  /**
   * Get description of the next command that would be undone
   */
  public getUndoDescription(): string | null {
    if (this.undoStack.length === 0) {
      return null;
    }
    return this.undoStack[this.undoStack.length - 1].getDescription();
  }

  /**
   * Get description of the next command that would be redone
   */
  public getRedoDescription(): string | null {
    if (this.redoStack.length === 0) {
      return null;
    }
    return this.redoStack[this.redoStack.length - 1].getDescription();
  }

  /**
   * Clear all command history
   */
  public clear(): void {
    this.undoStack = [];
    this.redoStack = [];
    
    if (DEBUG_MODE.CORE) {
      console.log('Cleared command history');
    }

    this.notifyHistoryChanged();
  }

  /**
   * Set the maximum number of commands to keep in history
   * @param size Maximum history size (default: 30)
   */
  public setMaxHistorySize(size: number): void {
    this.maxHistorySize = Math.max(1, size);
    
    // Trim history if needed
    while (this.undoStack.length > this.maxHistorySize) {
      this.undoStack.shift();
    }
    
    this.notifyHistoryChanged();
  }

  /**
   * Get current history size limit
   */
  public getMaxHistorySize(): number {
    return this.maxHistorySize;
  }

  /**
   * Set callback for when history changes (for UI updates)
   */
  public setOnHistoryChanged(callback: () => void): void {
    this.onHistoryChanged = callback;
  }

  /**
   * Get current history statistics for debugging
   */
  public getHistoryStats(): { undoCount: number; redoCount: number; maxSize: number } {
    return {
      undoCount: this.undoStack.length,
      redoCount: this.redoStack.length,
      maxSize: this.maxHistorySize
    };
  }

  /**
   * Notify listeners that history has changed
   */
  private notifyHistoryChanged(): void {
    if (this.onHistoryChanged) {
      this.onHistoryChanged();
    }
  }
}
