import { readdir } from 'fs/promises';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
import { execSync } from 'child_process';
import { addSkillToDb, getSkillsFromDb, addEvent, addProceduralMemory, getProceduralMemories, getDb } from './db.mjs';
import { askLLM, pickModel } from './llm.mjs';
import { remember, recall, recallAll } from './memory.mjs';
import { writeWorkspaceFile } from './workspace.mjs';
import { getConfig } from './config.mjs';

const __dirname = dirname(fileURLToPath(import.meta.url));
const SKILLS_DIR = join(__dirname, 'skills');

const loadedSkills = new Map();
const FILE_CREATING_SKILLS = new Set(['build-page', 'write-doc', 'create-file', 'research', 'app-build', 'diagram', 'mockup']);

/**
 * Load all skills from the skills/ directory and register in DB.
 */
export async function loadSkills() {
  loadedSkills.clear();
  let files;
  try {
    files = await readdir(SKILLS_DIR);
  } catch {
    console.log('[skills] No skills directory found');
    return;
  }

  for (const file of files) {
    if (!file.endsWith('.mjs')) continue;
    try {
      const importUrl = pathToFileURL(join(SKILLS_DIR, file)).href + `?t=${Date.now()}`;
      const mod = await import(importUrl);
      const skill = mod.default || mod;
      if (skill.name && skill.execute) {
        loadedSkills.set(skill.name, skill);
        addSkillToDb(skill.name, skill.description || '', file);
      }
    } catch (err) {
      console.error(`[skills] Failed to load ${file}: ${err.message}`);
    }
  }

  // Prune DB entries for skills whose files no longer exist
  try {
    const dbSkills = getSkillsFromDb();
    for (const s of dbSkills) {
      if (!loadedSkills.has(s.name)) {
        getDb().prepare('DELETE FROM skills WHERE name = ?').run(s.name);
        console.log(`[skills] Pruned stale DB entry: ${s.name}`);
      }
    }
  } catch {}

  console.log(`[skills] Loaded ${loadedSkills.size} skills: ${[...loadedSkills.keys()].join(', ')}`);
}

/**
 * Run a skill by name.
 */
export async function runSkill(name, args = {}) {
  const skill = loadedSkills.get(name);
  if (!skill) {
    console.log(`[skills] Skill "${name}" not found`);
    return null;
  }

  const dbSkills = getSkillsFromDb();
  const dbEntry = dbSkills.find(s => s.name === name);
  if (dbEntry && !dbEntry.enabled) {
    console.log(`[skills] Skill "${name}" is disabled`);
    return null;
  }

  console.log(`[skills] Running: ${name}`);
  addEvent('skill', 'agent', `Running skill: ${name}`, { skill: name, args });

  // Query procedural memory for similar past executions
  const inputPattern = JSON.stringify(args).slice(0, 200);
  const pastMemories = getProceduralMemories(name, 3);
  const proceduralHint = pastMemories.length > 0
    ? `Previous successful approach for similar request: ${pastMemories[0].approach}`
    : null;

  const context = {
    // Default to rawCompletion so skills get direct LLM output (no agentic loop).
    // Skills needing the agentic tool loop can pass { rawCompletion: false }.
    askLLM: (prompt, opts = {}) => {
      const enrichedPrompt = proceduralHint
        ? `${prompt}\n\n[Memory] ${proceduralHint}`
        : prompt;
      return askLLM(enrichedPrompt, { rawCompletion: true, ...opts });
    },
    pickModel,
    remember,
    recall,
    recallAll,
    addEvent,
    args,
    async writeOutputFile(filename, content) {
      const wsId = getConfig().activeWorkspace || 'default';
      const written = await writeWorkspaceFile(filename, content, wsId);
      addEvent('file', 'agent', `Created file: ${written.name}`, { path: written.publicPath, destination: 'workspace', workspaceId: wsId });
      return written;
    },

    // v3.0 — Tools registry access
    getToolsRegistry() {
      try {
        return readFileSync(join(__dirname, 'tools.md'), 'utf-8');
      } catch {
        return '';
      }
    },

    // v3.0 — Install an npm package and register in tools.md
    async installTool(pkg) {
      const toolsPath = join(__dirname, 'tools.md');
      try {
        execSync(`npm install ${pkg}`, { cwd: __dirname, stdio: 'pipe', timeout: 60_000 });
        // Append to "Installed by Skills" section in tools.md
        try {
          let content = readFileSync(toolsPath, 'utf-8');
          if (!content.includes(`\`${pkg}\``)) {
            content = content.replace(
              /## Installed by Skills\n/,
              `## Installed by Skills\n- \`${pkg}\`\n`
            );
            const { writeFileSync } = await import('fs');
            writeFileSync(toolsPath, content, 'utf-8');
          }
        } catch {}
        return { success: true, package: pkg };
      } catch (err) {
        return { success: false, package: pkg, error: err.message };
      }
    },

    // v3.0 — Active workspace accessor
    getActiveWorkspaceId() {
      return getConfig().activeWorkspace || 'default';
    }
  };

  try {
    const result = await skill.execute(context);
    addProceduralMemory(name, inputPattern, 'standard', 'success', 1.0);
    return result;
  } catch (err) {
    console.error(`[skills] Error in "${name}": ${err.message}`);
    addEvent('error', 'system', `Skill "${name}" failed: ${err.message}`);
    return null;
  }
}

/**
 * Pick a random enabled skill to run during creative time.
 */
// Skills that should only run when explicitly requested by the user
const USER_ONLY_SKILLS = new Set(['self-improve', 'summarize', 'app-build']);

export function pickCreativeSkill() {
  const dbSkills = getSkillsFromDb();
  const enabled = dbSkills.filter(s => s.enabled && !USER_ONLY_SKILLS.has(s.name));
  if (!enabled.length) return null;
  const pick = enabled[Math.floor(Math.random() * enabled.length)];
  return pick.name;
}

export function listSkills() {
  return [...loadedSkills.entries()].map(([name, s]) => ({
    name,
    description: s.description || ''
  }));
}

export function skillCreatesFiles(name) {
  return FILE_CREATING_SKILLS.has(name);
}

/**
 * Reload skills (used after self-improve creates new ones).
 */
export async function reloadSkills() {
  await loadSkills();
}
