/**
 * @fileoverview OrdoJS CLI - File Watcher
 *
 * Efficient file system monitoring with intelligent change detection
 * and dependency tracking for hot module replacement.
 */

import { EventEmitter } from 'events';
import { FSWatcher, watch } from 'fs';
import path from 'path';
import { fileExists } from '../utils/fs.js';
import { logger } from '../utils/index.js';

/**
 * File change event types
 */
export enum FileChangeType {
  ADDED = 'added',
  CHANGED = 'changed',
  DELETED = 'deleted'
}

/**
 * File change event data
 */
export interface FileChangeEvent {
  /** Type of change */
  type: FileChangeType;
  /** Absolute path to the changed file */
  filePath: string;
  /** Relative path from the watched directory */
  relativePath: string;
  /** Timestamp of the change */
  timestamp: number;
  /** File extension */
  extension: string;
}

/**
 * File watcher options
 */
export interface FileWatcherOptions {
  /** Directory to watch */
  watchDir: string;
  /** File patterns to watch (glob patterns) */
  patterns?: string[];
  /** File patterns to ignore */
  ignorePatterns?: string[];
  /** Debounce delay in milliseconds */
  debounceMs?: number;
  /** Enable recursive watching */
  recursive?: boolean;
}

/**
 * Dependency tracking information
 */
export interface DependencyInfo {
  /** Files that depend on this file */
  dependents: Set<string>;
  /** Files that this file depends on */
  dependencies: Set<string>;
  /** Last modification time */
  lastModified: number;
}
/*
*
 * FileWatcher class for efficient file system monitoring
 */
export class FileWatcher extends EventEmitter {
  private options: Required<FileWatcherOptions>;
  private watchers: Map<string, FSWatcher>;
  private dependencyGraph: Map<string, DependencyInfo>;
  private debounceTimers: Map<string, NodeJS.Timeout>;
  private isWatching: boolean;

  /**
   * Create a new FileWatcher instance
   */
  constructor(options: FileWatcherOptions) {
    super();

    this.options = {
      watchDir: '.',
              patterns: ['**/*.ordo', '**/*.ts', '**/*.js', '**/*.css', '**/*.html'],
      ignorePatterns: ['**/node_modules/**', '**/dist/**', '**/.git/**', '**/coverage/**'],
      debounceMs: 100,
      recursive: true,
      ...options
    };

    this.watchers = new Map();
    this.dependencyGraph = new Map();
    this.debounceTimers = new Map();
    this.isWatching = false;
  }

  /**
   * Start watching files
   */
  async start(): Promise<void> {
    if (this.isWatching) {
      logger.warn('FileWatcher is already watching');
      return;
    }

    logger.info(`Starting file watcher for: ${this.options.watchDir}`);

    try {
      // Verify the watch directory exists
      const dirExists = await fileExists(this.options.watchDir);
      if (!dirExists) {
        throw new Error(`Watch directory does not exist: ${this.options.watchDir}`);
      }

      // Initialize dependency graph
      await this.buildInitialDependencyGraph();

      // Start watching the directory
      await this.startWatching();

      this.isWatching = true;
      logger.success('File watcher started successfully');
    } catch (error) {
      logger.error(`Failed to start file watcher: ${error instanceof Error ? error.message : String(error)}`);
      throw error;
    }
  }

  /**
   * Stop watching files
   */
  async stop(): Promise<void> {
    if (!this.isWatching) {
      logger.info('FileWatcher is not watching');
      return;
    }

    logger.info('Stopping file watcher...');

    try {
      // Clear all debounce timers
      for (const timer of this.debounceTimers.values()) {
        clearTimeout(timer);
      }
      this.debounceTimers.clear();

      // Close all watchers
      for (const [path, watcher] of this.watchers) {
        watcher.close();
        logger.debug(`Closed watcher for: ${path}`);
      }
      this.watchers.clear();

      this.isWatching = false;
      logger.success('File watcher stopped');
    } catch (error) {
      logger.error(`Error stopping file watcher: ${error instanceof Error ? error.message : String(error)}`);
      throw error;
    }
  }
 /**
   * Get files that are affected by a change to the given file
   */
  getAffectedFiles(filePath: string): string[] {
    const absolutePath = path.resolve(filePath);
    const affected = new Set<string>();

    // Add the file itself
    affected.add(absolutePath);

    // Add all dependents recursively
    this.collectDependents(absolutePath, affected);

    return Array.from(affected);
  }

  /**
   * Add a dependency relationship
   */
  addDependency(dependent: string, dependency: string): void {
    const dependentPath = path.resolve(dependent);
    const dependencyPath = path.resolve(dependency);

    // Initialize dependency info if not exists
    if (!this.dependencyGraph.has(dependencyPath)) {
      this.dependencyGraph.set(dependencyPath, {
        dependents: new Set(),
        dependencies: new Set(),
        lastModified: Date.now()
      });
    }

    if (!this.dependencyGraph.has(dependentPath)) {
      this.dependencyGraph.set(dependentPath, {
        dependents: new Set(),
        dependencies: new Set(),
        lastModified: Date.now()
      });
    }

    // Add the relationship
    const dependencyInfo = this.dependencyGraph.get(dependencyPath)!;
    const dependentInfo = this.dependencyGraph.get(dependentPath)!;

    dependencyInfo.dependents.add(dependentPath);
    dependentInfo.dependencies.add(dependencyPath);

    logger.debug(`Added dependency: ${dependent} -> ${dependency}`);
  }

  /**
   * Remove a dependency relationship
   */
  removeDependency(dependent: string, dependency: string): void {
    const dependentPath = path.resolve(dependent);
    const dependencyPath = path.resolve(dependency);

    const dependencyInfo = this.dependencyGraph.get(dependencyPath);
    const dependentInfo = this.dependencyGraph.get(dependentPath);

    if (dependencyInfo) {
      dependencyInfo.dependents.delete(dependentPath);
    }

    if (dependentInfo) {
      dependentInfo.dependencies.delete(dependencyPath);
    }

    logger.debug(`Removed dependency: ${dependent} -> ${dependency}`);
  }

  /**
   * Get dependency information for a file
   */
  getDependencyInfo(filePath: string): DependencyInfo | undefined {
    const absolutePath = path.resolve(filePath);
    return this.dependencyGraph.get(absolutePath);
  }  /*
*
   * Check if a file should be watched based on patterns
   */
  private shouldWatchFile(filePath: string): boolean {
    const relativePath = path.relative(this.options.watchDir, filePath);

    // Check ignore patterns first
    for (const ignorePattern of this.options.ignorePatterns) {
      if (this.matchesPattern(relativePath, ignorePattern)) {
        return false;
      }
    }

    // Check include patterns
    for (const pattern of this.options.patterns) {
      if (this.matchesPattern(relativePath, pattern)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Simple glob pattern matching
   */
  private matchesPattern(filePath: string, pattern: string): boolean {
    // Normalize paths for comparison
    const normalizedPath = filePath.replace(/\\/g, '/');
    const normalizedPattern = pattern.replace(/\\/g, '/');

    // Convert glob pattern to regex
    const regexPattern = normalizedPattern
      .replace(/\*\*/g, '§DOUBLESTAR§')  // Temporary placeholder
      .replace(/\*/g, '[^/]*')
      .replace(/\?/g, '[^/]')
      .replace(/§DOUBLESTAR§/g, '.*');   // Replace placeholder with .*

    const regex = new RegExp(`^${regexPattern}$`);
    return regex.test(normalizedPath);
  }

  /**
   * Start watching the directory
   */
  private async startWatching(): Promise<void> {
    const watchPath = path.resolve(this.options.watchDir);

    logger.debug(`Setting up watcher for: ${watchPath}`);

    const watcher = watch(
      watchPath,
      { recursive: this.options.recursive },
      (eventType, filename) => {
        if (!filename) return;

        const fullPath = path.join(watchPath, filename);
        this.handleFileChange(eventType, fullPath);
      }
    );

    watcher.on('error', (error) => {
      logger.error(`File watcher error: ${error.message}`);
      this.emit('error', error);
    });

    this.watchers.set(watchPath, watcher);
  }  /**

   * Handle file change events with debouncing
   */
  private handleFileChange(eventType: string, filePath: string): void {
    if (!this.shouldWatchFile(filePath)) {
      return;
    }

    // Clear existing debounce timer
    const existingTimer = this.debounceTimers.get(filePath);
    if (existingTimer) {
      clearTimeout(existingTimer);
    }

    // Set new debounce timer
    const timer = setTimeout(async () => {
      this.debounceTimers.delete(filePath);
      await this.processFileChange(eventType, filePath);
    }, this.options.debounceMs);

    this.debounceTimers.set(filePath, timer);
  }

  /**
   * Process a file change after debouncing
   */
  private async processFileChange(eventType: string, filePath: string): Promise<void> {
    try {
      const relativePath = path.relative(this.options.watchDir, filePath);
      const extension = path.extname(filePath);
      const timestamp = Date.now();

      // Determine change type
      let changeType: FileChangeType;
      const exists = await fileExists(filePath);

      if (eventType === 'rename') {
        changeType = exists ? FileChangeType.ADDED : FileChangeType.DELETED;
      } else {
        changeType = FileChangeType.CHANGED;
      }

      // Update dependency graph
      if (changeType === FileChangeType.DELETED) {
        this.removeDependencyInfo(filePath);
      } else {
        await this.updateDependencyInfo(filePath);
      }

      // Create change event
      const changeEvent: FileChangeEvent = {
        type: changeType,
        filePath: path.resolve(filePath),
        relativePath,
        timestamp,
        extension
      };

      logger.debug(`File ${changeType}: ${relativePath}`);

      // Emit the change event
      this.emit('change', changeEvent);

    } catch (error) {
      logger.error(`Error processing file change for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
      this.emit('error', error);
    }
  }

  /**
   * Build initial dependency graph by scanning existing files
   */
  private async buildInitialDependencyGraph(): Promise<void> {
    logger.debug('Building initial dependency graph...');

    // This is a placeholder for dependency analysis
    // In a real implementation, this would scan all files and analyze imports/dependencies
    // For now, we'll just initialize the graph structure

    logger.debug('Initial dependency graph built');
  }

  /**
   * Update dependency information for a file
   */
  private async updateDependencyInfo(filePath: string): Promise<void> {
    const absolutePath = path.resolve(filePath);

    try {
      // Get or create dependency info
      let depInfo = this.dependencyGraph.get(absolutePath);
      if (!depInfo) {
        depInfo = {
          dependents: new Set(),
          dependencies: new Set(),
          lastModified: Date.now()
        };
        this.dependencyGraph.set(absolutePath, depInfo);
      }

      // Update last modified time
      depInfo.lastModified = Date.now();

      // TODO: Analyze file content to extract dependencies
      // This would involve parsing imports, requires, etc.
      // For now, we'll just update the timestamp

      logger.debug(`Updated dependency info for: ${path.relative(this.options.watchDir, filePath)}`);
    } catch (error) {
      logger.error(`Failed to update dependency info for ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
    }
  }

  /**
   * Remove dependency information for a deleted file
   */
  private removeDependencyInfo(filePath: string): void {
    const absolutePath = path.resolve(filePath);
    const depInfo = this.dependencyGraph.get(absolutePath);

    if (!depInfo) return;

    // Remove this file from all its dependencies' dependents lists
    for (const dependency of depInfo.dependencies) {
      const dependencyInfo = this.dependencyGraph.get(dependency);
      if (dependencyInfo) {
        dependencyInfo.dependents.delete(absolutePath);
      }
    }

    // Remove this file from all its dependents' dependencies lists
    for (const dependent of depInfo.dependents) {
      const dependentInfo = this.dependencyGraph.get(dependent);
      if (dependentInfo) {
        dependentInfo.dependencies.delete(absolutePath);
      }
    }

    // Remove the file from the graph
    this.dependencyGraph.delete(absolutePath);

    logger.debug(`Removed dependency info for: ${path.relative(this.options.watchDir, filePath)}`);
  }

  /**
   * Recursively collect all dependents of a file
   */
  private collectDependents(filePath: string, collected: Set<string>): void {
    const depInfo = this.dependencyGraph.get(filePath);
    if (!depInfo) return;

    for (const dependent of depInfo.dependents) {
      if (!collected.has(dependent)) {
        collected.add(dependent);
        this.collectDependents(dependent, collected);
      }
    }
  }
}
