import { readFileSync, readdirSync, existsSync, lstatSync } from 'fs';
import { join } from 'path';
import { logger } from '../logger.js';
import matter from 'gray-matter';

export const VALID_SKILL_NAME = /^[a-z0-9_-]+$/;

export interface SkillEntry {
  name: string;
  description: string;
  triggers: string[];
  source: 'system' | 'user';
  filePath: string;
  dirPath: string | null;
}

function parseSkillFile(filePath: string, source: 'system' | 'user', dirPath: string | null = null): SkillEntry | null {
  try {
    const raw = readFileSync(filePath, 'utf-8');
    const { data } = matter(raw);
    if (!data || typeof data.name !== 'string' || !data.name) return null;
    if (!VALID_SKILL_NAME.test(data.name)) {
      logger.warn(`[skill-catalog] skipping skill with invalid name: ${data.name} in ${filePath}`);
      return null;
    }
    return {
      name: data.name,
      description: typeof data.description === 'string' ? data.description : '',
      triggers: Array.isArray(data.triggers) ? data.triggers : [],
      source,
      filePath,
      dirPath,
    };
  } catch (e) {
    logger.warn(`[skill-catalog] failed to parse ${filePath}: ${e}`);
    return null;
  }
}

function scanDir(dir: string, source: 'system' | 'user'): SkillEntry[] {
  if (!existsSync(dir)) return [];
  const entries = readdirSync(dir);
  const results: SkillEntry[] = [];
  const dirNames = new Set<string>();

  // Pass 1: directories — look for {dir}/SKILL.md
  for (const entry of entries) {
    const fullPath = join(dir, entry);
    let stat;
    try {
      stat = lstatSync(fullPath);
    } catch {
      continue;
    }
    // Skip symlinks
    if (stat.isSymbolicLink()) continue;
    if (!stat.isDirectory()) continue;

    const skillMdPath = join(fullPath, 'SKILL.md');
    if (!existsSync(skillMdPath)) continue;

    // Skip if SKILL.md itself is a symlink
    try {
      if (lstatSync(skillMdPath).isSymbolicLink()) continue;
    } catch {
      continue;
    }

    const parsed = parseSkillFile(skillMdPath, source, fullPath);
    if (parsed) {
      dirNames.add(entry);
      results.push(parsed);
    }
  }

  // Pass 2: flat .md files — skip if same-name directory was found in pass 1
  for (const entry of entries) {
    if (!entry.endsWith('.md')) continue;
    const baseName = entry.slice(0, -3);
    if (dirNames.has(baseName)) continue;

    const fullPath = join(dir, entry);
    let stat;
    try {
      stat = lstatSync(fullPath);
    } catch {
      continue;
    }
    // Skip symlinks
    if (stat.isSymbolicLink()) continue;
    if (!stat.isFile()) continue;

    const parsed = parseSkillFile(fullPath, source, null);
    if (parsed) {
      results.push(parsed);
    }
  }

  return results;
}

export class SkillCatalog {
  private systemSkills: SkillEntry[] = [];
  private cache = new Map<string, { ts: number; entries: SkillEntry[] }>();
  private readonly ttlMs = 60_000;

  constructor(
    private readonly systemDir: string,
    private readonly userRoot: string,
  ) {
    this.systemSkills = scanDir(systemDir, 'system');
    if (this.systemSkills.length > 0) {
      logger.info(`[skill-catalog] loaded ${this.systemSkills.length} system skills from ${systemDir}`);
    }
  }

  getForUser(userId: string): SkillEntry[] {
    const cached = this.cache.get(userId);
    if (cached && Date.now() - cached.ts < this.ttlMs) return cached.entries;

    const userDir = join(this.userRoot, userId, 'skills');
    const userSkills = scanDir(userDir, 'user');

    const byName = new Map<string, SkillEntry>(this.systemSkills.map(s => [s.name, s]));
    for (const u of userSkills) byName.set(u.name, u);
    const entries = Array.from(byName.values());

    this.cache.set(userId, { ts: Date.now(), entries });
    return entries;
  }

  getSkillContent(name: string, userId: string): { content: string; dirPath: string | null } | null {
    const entries = this.getForUser(userId);
    const entry = entries.find(e => e.name === name);
    if (!entry) return null;
    try {
      const raw = readFileSync(entry.filePath, 'utf-8');
      const { content } = matter(raw);
      return { content: content.trim(), dirPath: entry.dirPath };
    } catch {
      return null;
    }
  }

  buildIndex(userId: string, maxChars: number = 2000): string {
    const entries = this.getForUser(userId);
    if (entries.length === 0) return '';

    const lines: string[] = [];
    let totalLen = 0;
    let included = 0;

    for (const e of entries) {
      const line = `- **${e.name}**: ${e.description}`;
      if (totalLen + line.length + 1 > maxChars && included > 0) break;
      lines.push(line);
      totalLen += line.length + 1;
      included++;
    }

    const remaining = entries.length - included;
    if (remaining > 0) {
      lines.push(`... and ${remaining} more skills (use ListSkills to see all)`);
    }

    return lines.join('\n');
  }

  invalidate(userId: string): void {
    this.cache.delete(userId);
    this.systemSkills = scanDir(this.systemDir, 'system');
  }

  getSkillBinds(userId: string): Array<{ src: string; dest: string }> {
    const binds: Array<{ src: string; dest: string }> = [];
    if (existsSync(this.systemDir)) {
      binds.push({ src: this.systemDir, dest: '/skills' });
    }
    const userDir = join(this.userRoot, userId, 'skills');
    if (existsSync(userDir)) {
      binds.push({ src: userDir, dest: '/user-skills' });
    }
    return binds;
  }

  getSystemDir(): string { return this.systemDir; }
  getUserSkillDir(userId: string): string { return join(this.userRoot, userId, 'skills'); }

  refreshSystem(): void {
    this.systemSkills = scanDir(this.systemDir, 'system');
    this.cache.clear();
  }
}
