/**
 * File watcher for hot-reloading stages and proxymodels.
 *
 * Watches ~/.mcpctl/stages/ and ~/.mcpctl/proxymodels/ for changes.
 * - Stage changes: clear + reload all custom stages (module cache busted)
 * - ProxyModel changes: no action needed (loader reads from disk each time)
 *   but we log for visibility and notify subscribers.
 */
import { watch, type FSWatcher } from 'node:fs';
import { join } from 'node:path';
import { loadCustomStages } from './stage-registry.js';

const HOME = process.env['HOME'] ?? '/tmp';
const STAGES_DIR = join(HOME, '.mcpctl', 'stages');
const PROXYMODELS_DIR = join(HOME, '.mcpctl', 'proxymodels');

let stageWatcher: FSWatcher | null = null;
let modelWatcher: FSWatcher | null = null;
let reloadDebounce: ReturnType<typeof setTimeout> | null = null;

type ReloadListener = (what: 'stages' | 'proxymodels') => void;
const listeners = new Set<ReloadListener>();

/** Subscribe to reload events. Returns unsubscribe function. */
export function onReload(cb: ReloadListener): () => void {
  listeners.add(cb);
  return () => {
    listeners.delete(cb);
  };
}

function notify(what: 'stages' | 'proxymodels'): void {
  for (const cb of listeners) {
    try {
      cb(what);
    } catch {
      // ignore
    }
  }
}

/**
 * Reload custom stages from disk.
 * Busts the Node module cache for changed files by appending a query string.
 */
export async function reloadStages(): Promise<{ loaded: number }> {
  await loadCustomStages(STAGES_DIR);
  const { listStages } = await import('./stage-registry.js');
  const stages = listStages();
  const localCount = stages.filter((s) => s.source === 'local').length;
  console.info(`[watcher] Reloaded stages: ${localCount} custom, ${stages.length} total`);
  notify('stages');
  return { loaded: localCount };
}

/** Start file watchers. Safe to call multiple times (idempotent). */
export function startWatchers(): void {
  if (!stageWatcher) {
    try {
      stageWatcher = watch(STAGES_DIR, { persistent: false }, (_eventType, filename) => {
        if (!filename || (!filename.endsWith('.js') && !filename.endsWith('.mjs'))) return;

        // Debounce: multiple events fire for a single save
        if (reloadDebounce) clearTimeout(reloadDebounce);
        reloadDebounce = setTimeout(() => {
          console.info(`[watcher] Stage file changed: ${filename}`);
          void reloadStages();
        }, 300);
      });
      console.info(`[watcher] Watching ${STAGES_DIR} for stage changes`);
    } catch {
      // Directory doesn't exist — that's fine
    }
  }

  if (!modelWatcher) {
    try {
      modelWatcher = watch(PROXYMODELS_DIR, { persistent: false }, (_eventType, filename) => {
        if (!filename || (!filename.endsWith('.yaml') && !filename.endsWith('.yml'))) return;
        console.info(`[watcher] ProxyModel file changed: ${filename} (will take effect on next use)`);
        notify('proxymodels');
      });
      console.info(`[watcher] Watching ${PROXYMODELS_DIR} for proxymodel changes`);
    } catch {
      // Directory doesn't exist — that's fine
    }
  }
}

/** Stop file watchers. */
export function stopWatchers(): void {
  if (reloadDebounce) clearTimeout(reloadDebounce);
  stageWatcher?.close();
  stageWatcher = null;
  modelWatcher?.close();
  modelWatcher = null;
}
