import { existsSync, statSync, watch } from 'fs';
import { join, resolve } from 'path';
import type { FSWatcher } from 'fs';

export interface SkillsWatcherEvent {
  changedPaths: string[];
  triggeredAt: Date;
}

export interface SkillsWatcherConfig {
  skillDirs: string[];
  debounceMs?: number;
  onSkillsChanged?: (event: SkillsWatcherEvent) => void;
}

export class SkillsWatcher {
  private readonly _skillDirs: string[];
  private readonly _debounceMs: number;
  private readonly _onSkillsChanged?: (event: SkillsWatcherEvent) => void;
  private readonly _watchers: FSWatcher[] = [];
  private readonly _pendingPaths = new Set<string>();
  private _timer?: NodeJS.Timeout;
  private _running = false;

  constructor(config: SkillsWatcherConfig) {
    this._skillDirs = [...new Set(config.skillDirs.map((dir) => resolve(dir)))];
    this._debounceMs = config.debounceMs ?? 250;
    this._onSkillsChanged = config.onSkillsChanged;
  }

  get isRunning(): boolean {
    return this._running;
  }

  get watchedDirectoryCount(): number {
    return this._watchers.length;
  }

  start(): void {
    if (this._running) {
      return;
    }

    for (const dir of this._skillDirs) {
      if (!existsSync(dir)) {
        continue;
      }
      try {
        if (!statSync(dir).isDirectory()) {
          continue;
        }
      } catch {
        continue;
      }

      const watcher = watch(dir, (_eventType, filename) => {
        if (!this._running) {
          return;
        }
        const changedPath = filename ? join(dir, String(filename)) : dir;
        this.notifyPathChanged(changedPath);
      });
      this._watchers.push(watcher);
    }

    this._running = true;
  }

  stop(): void {
    if (!this._running) {
      return;
    }

    for (const watcher of this._watchers) {
      watcher.close();
    }
    this._watchers.length = 0;

    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = undefined;
    }
    this._pendingPaths.clear();
    this._running = false;
  }

  notifyPathChanged(path: string): void {
    if (!this._running) {
      return;
    }

    this._pendingPaths.add(resolve(path));
    if (this._timer) {
      clearTimeout(this._timer);
    }

    this._timer = setTimeout(() => {
      this._flushPending();
    }, this._debounceMs);
  }

  private _flushPending(): void {
    this._timer = undefined;
    if (this._pendingPaths.size === 0) {
      return;
    }

    const changedPaths = Array.from(this._pendingPaths).sort();
    this._pendingPaths.clear();
    this._onSkillsChanged?.({
      changedPaths,
      triggeredAt: new Date(),
    });
  }
}
