import { afterEach, describe, expect, it, vi } from 'vitest';
import { mkdtempSync, mkdirSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import { SkillsWatcher } from './watcher.js';

describe('SkillsWatcher', () => {
  const roots: string[] = [];

  afterEach(() => {
    vi.useRealTimers();
    for (const root of roots.splice(0)) {
      rmSync(root, { recursive: true, force: true });
    }
  });

  function makeDir(prefix: string): string {
    const root = mkdtempSync(join(tmpdir(), prefix));
    roots.push(root);
    return root;
  }

  it('starts only on existing directories', () => {
    const existing = makeDir('flynn-watcher-');
    const missing = join(existing, 'missing');

    const watcher = new SkillsWatcher({ skillDirs: [existing, missing] });
    watcher.start();

    expect(watcher.isRunning).toBe(true);
    expect(watcher.watchedDirectoryCount).toBe(1);

    watcher.stop();
  });

  it('debounces multiple change notifications into one callback', () => {
    vi.useFakeTimers();
    const dir = makeDir('flynn-watcher-');
    const events: string[][] = [];

    const watcher = new SkillsWatcher({
      skillDirs: [dir],
      debounceMs: 100,
      onSkillsChanged: (event) => {
        events.push(event.changedPaths);
      },
    });
    watcher.start();

    watcher.notifyPathChanged(join(dir, 'one/SKILL.md'));
    watcher.notifyPathChanged(join(dir, 'two/manifest.json'));
    vi.advanceTimersByTime(99);
    expect(events.length).toBe(0);

    vi.advanceTimersByTime(1);
    expect(events.length).toBe(1);
    expect(events[0]?.length).toBe(2);

    watcher.stop();
  });

  it('clears pending events on stop', () => {
    vi.useFakeTimers();
    const dir = makeDir('flynn-watcher-');
    const onSkillsChanged = vi.fn();

    const watcher = new SkillsWatcher({
      skillDirs: [dir],
      debounceMs: 50,
      onSkillsChanged,
    });
    watcher.start();
    watcher.notifyPathChanged(join(dir, 'sample/SKILL.md'));
    watcher.stop();

    vi.advanceTimersByTime(60);
    expect(onSkillsChanged).not.toHaveBeenCalled();
    expect(watcher.isRunning).toBe(false);
  });

  it('ignores notifyPathChanged calls before start', () => {
    const dir = makeDir('flynn-watcher-');
    const onSkillsChanged = vi.fn();
    const watcher = new SkillsWatcher({ skillDirs: [dir], onSkillsChanged });

    watcher.notifyPathChanged(join(dir, 'ignored'));
    expect(onSkillsChanged).not.toHaveBeenCalled();
  });

  it('handles repeated start calls safely', () => {
    const dir = makeDir('flynn-watcher-');
    mkdirSync(join(dir, 'nested'), { recursive: true });
    const watcher = new SkillsWatcher({ skillDirs: [dir] });

    watcher.start();
    watcher.start();

    expect(watcher.watchedDirectoryCount).toBe(1);
    watcher.stop();
  });
});
