import { watch, type FSWatcher } from 'node:fs';
import { dirname } from 'node:path';
import { MUSIC_ROOT, reindexFolder } from './indexer';

// Recursive file watcher on ~/Music (chokidar-style). Any change — add / modify (mtime delta) / delete —
// is logged and, after a short debounce, triggers a LOCALIZED reindex of just the affected folder(s)
// (reindexFolder recomputes that album/artist's cache entry; its mtime-based signature decides whether a
// rebuild is actually needed, so irrelevant touches are cheap no-ops). Full correctness is backstopped by
// the nightly full reindex — a new folder whose files land after the debounce is caught by that.
//
// Notes: max_user_watches is ~483k here (>> the folder count), and Bun's recursive watch fires through
// the ~/Music symlink and for new-dir creation (so reindexFolder reads the new folder's contents).

const DEBOUNCE_MS = 3000;

let watcher: FSWatcher | null = null;
let flushTimer: ReturnType<typeof setTimeout> | null = null;
const pending = new Map<string, boolean>(); // music-relative folder → reindex recursively?

function enqueue(rel: string, recursive: boolean): void {
  pending.set(rel, (pending.get(rel) ?? false) || recursive);
}

function scheduleFlush(): void {
  if (flushTimer) clearTimeout(flushTimer);
  flushTimer = setTimeout(() => void flush(), DEBOUNCE_MS);
}

async function flush(): Promise<void> {
  flushTimer = null;
  const folders = [...pending];
  pending.clear();
  console.log(`[music] watch: reindexing ${folders.length} changed folder(s)`);
  for (const [rel, recursive] of folders) {
    try {
      await reindexFolder(rel, { recursive });
    } catch (err) {
      console.error(`[music] watch: reindex "${rel}" failed:`, err instanceof Error ? err.message : err);
    }
  }
}

export function startMusicWatcher(): void {
  try {
    watcher = watch(MUSIC_ROOT, { recursive: true, persistent: true }, (eventType, filename) => {
      if (!filename) return;
      const rel = filename.toString(); // path relative to MUSIC_ROOT (posix)
      console.log(`[music] watch: ${eventType} — ${rel}`);
      // Two enqueues: the containing folder SHALLOW — rebuilds that album on a file change, and prunes any
      // renamed/removed-away child it no longer sees (e.g. an old-cased artist dir); and the path itself
      // RECURSIVE — so if it's a new or renamed-in container, its album children get indexed. A recursive
      // reindex of a plain file or leaf album is a cheap no-op / single-folder rebuild.
      const folder = dirname(rel);
      enqueue(folder === '.' ? '' : folder, false);
      enqueue(rel, true);
      scheduleFlush();
    });
    console.log(`[music] watching ${MUSIC_ROOT} (recursive) for changes`);
  } catch (err) {
    console.error('[music] failed to start file watcher:', err instanceof Error ? err.message : err);
  }
}

export function stopMusicWatcher(): void {
  if (flushTimer) {
    clearTimeout(flushTimer);
    flushTimer = null;
  }
  if (watcher) {
    watcher.close();
    watcher = null;
  }
  pending.clear();
}
