/** Filesystem scanner — walks a directory, filters by whitelist, computes delta vs DB manifest. */

import { readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { getDb } from '../db';
import { projectFolderFiles } from '../db/schema';
import { eq } from 'drizzle-orm';
import {
  TEXT_EXTS, IMAGE_EXTS, PDF_EXTS, DOCX_EXTS,
  MAX_TEXT_FILE_BYTES, MAX_IMAGE_FILE_BYTES,
} from './constants';

export type FileKind = 'text' | 'image' | 'pdf' | 'docx' | 'skipped';

export interface ScannedFile {
  relativePath: string;
  ext: string;
  kind: FileKind;
  sizeBytes: number;
  mtimeMs: number;
}

export interface ScanDelta {
  newFiles: ScannedFile[];
  changedFiles: ScannedFile[];
  unchangedCount: number;
  deletedRelPaths: string[];
}

function classify(ext: string, sizeBytes: number): FileKind | null {
  const e = ext.toLowerCase();
  if (TEXT_EXTS.has(e)) return sizeBytes <= MAX_TEXT_FILE_BYTES ? 'text' : 'skipped';
  if (IMAGE_EXTS.has(e)) return sizeBytes <= MAX_IMAGE_FILE_BYTES ? 'image' : 'skipped';
  if (PDF_EXTS.has(e)) return sizeBytes <= MAX_TEXT_FILE_BYTES ? 'pdf' : 'skipped';
  if (DOCX_EXTS.has(e)) return sizeBytes <= MAX_TEXT_FILE_BYTES ? 'docx' : 'skipped';
  return null;  // not indexable
}

async function walk(root: string): Promise<ScannedFile[]> {
  const out: ScannedFile[] = [];
  async function recurse(dir: string) {
    let entries;
    try {
      entries = await readdir(dir, { withFileTypes: true });
    } catch {
      return; // permission denied — skip silently
    }
    for (const e of entries) {
      if (e.name.startsWith('.')) continue;     // skip dot dirs / files
      if (e.name === 'node_modules') continue;  // common noise
      const full = path.join(dir, e.name);
      if (e.isDirectory()) {
        await recurse(full);
      } else if (e.isFile()) {
        let s;
        try { s = await stat(full); } catch { continue; }
        const ext = path.extname(e.name);
        const kind = classify(ext, s.size);
        if (kind === null) continue;
        out.push({
          relativePath: path.relative(root, full),
          ext,
          kind,
          sizeBytes: s.size,
          mtimeMs: Math.floor(s.mtimeMs),
        });
      }
    }
  }
  await recurse(root);
  return out;
}

export async function scanFolder(projectId: string, folderPath: string): Promise<ScanDelta> {
  const scanned = await walk(folderPath);
  const existing = getDb()
    .select()
    .from(projectFolderFiles)
    .where(eq(projectFolderFiles.projectId, projectId))
    .all();

  const existingMap = new Map(existing.map(r => [r.relativePath, r]));
  const newFiles: ScannedFile[] = [];
  const changedFiles: ScannedFile[] = [];
  let unchanged = 0;
  for (const f of scanned) {
    const prev = existingMap.get(f.relativePath);
    if (!prev) newFiles.push(f);
    else if (prev.mtimeMs !== f.mtimeMs || prev.sizeBytes !== f.sizeBytes) changedFiles.push(f);
    else unchanged++;
    existingMap.delete(f.relativePath);
  }
  const deletedRelPaths = Array.from(existingMap.keys());
  return { newFiles, changedFiles, unchangedCount: unchanged, deletedRelPaths };
}
