'use strict';
/**
 * AI Tools Capability Scanner
 *
 * Discovers skills, commands, plugins, MCP servers, and extensions from all
 * AI coding tools installed on the system:
 *   - Claude Code (~/.claude)
 *   - Codex (~/.codex, AGENTS.md)
 *   - Gemini CLI (~/.gemini, GEMINI.md)
 *   - Cursor (~/.cursor)
 *   - Wall-E (own bundled + user skills)
 */
const fs = require('fs');
const path = require('path');

const HOME = process.env.HOME || '';
const CLAUDE_DIR = path.join(HOME, '.claude');
const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
const SETTINGS_LOCAL_PATH = path.join(CLAUDE_DIR, 'settings.local.json');

// ── MCP Servers ─────────────────────────────────────────────────────

function readMcpConfig() {
  const configs = [];
  for (const settingsPath of [SETTINGS_PATH, SETTINGS_LOCAL_PATH]) {
    try {
      if (!fs.existsSync(settingsPath)) continue;
      const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
      const mcpServers = settings.mcpServers || {};
      for (const [name, config] of Object.entries(mcpServers)) {
        configs.push({
          name,
          command: config.command,
          args: config.args || [],
          env: Object.keys(config.env || {}), // keys only, not values (security)
          source: settingsPath,
        });
      }
    } catch {}
  }
  return configs;
}

// ── Helpers ─────────────────────────────────────────────────────────

function extractDescription(content) {
  const lines = content.split('\n').filter(l => l.trim());
  for (const line of lines) {
    const trimmed = line.trim();
    if (trimmed.startsWith('#')) return trimmed.replace(/^#+\s*/, '');
    if (trimmed.length > 20 && !trimmed.startsWith('```') && !trimmed.startsWith('-')) {
      return trimmed.slice(0, 200);
    }
  }
  return '';
}

/** Scan a directory of .md files and skill subdirectories as skills/commands. */
function scanMdDir(dir, source, skills) {
  if (!dir || !fs.existsSync(dir)) return;
  try {
    for (const entry of fs.readdirSync(dir)) {
      const fullPath = path.join(dir, entry);
      try {
        const stat = fs.statSync(fullPath);
        if (stat.isFile() && entry.endsWith('.md')) {
          const content = fs.readFileSync(fullPath, 'utf8');
          const name = entry.replace('.md', '');
          const description = extractDescription(content);
          skills.push({ name, description, source, path: fullPath });
        } else if (stat.isDirectory()) {
          // Check for SKILL.md or README.md inside subdirectory
          for (const mdFile of ['SKILL.md', 'skill.md', 'README.md']) {
            const mdPath = path.join(fullPath, mdFile);
            if (fs.existsSync(mdPath)) {
              const content = fs.readFileSync(mdPath, 'utf8');
              const description = extractDescription(content);
              skills.push({ name: entry, description, source, path: mdPath });
              break;
            }
          }
        }
      } catch {}
    }
  } catch {}
}

/** Recursively scan plugin directories for skill definitions. */
function scanPluginSkills(dir, skills, depth, parentName) {
  if ((depth || 0) > 4) return;
  try {
    for (const entry of fs.readdirSync(dir)) {
      const fullPath = path.join(dir, entry);
      const stat = fs.statSync(fullPath);
      if (stat.isDirectory()) {
        for (const sf of ['skill.md', 'README.md', 'index.md']) {
          const sfPath = path.join(fullPath, sf);
          if (fs.existsSync(sfPath)) {
            try {
              const content = fs.readFileSync(sfPath, 'utf8');
              const description = extractDescription(content);
              const isVersion = /^[\da-f]{6,}$|^\d+\.\d+/.test(entry);
              const name = isVersion ? (parentName || entry) : entry;
              const pathParts = sfPath.split(path.sep);
              const pluginIdx = pathParts.findIndex(p => p.includes('plugins'));
              const group = pluginIdx >= 0 && pluginIdx + 1 < pathParts.length ? pathParts[pluginIdx + 1] : '';
              skills.push({ name, description, source: 'plugin', path: sfPath, group });
            } catch {}
          }
        }
        scanPluginSkills(fullPath, skills, (depth || 0) + 1, entry);
      }
    }
  } catch {}
}

/** Read a single instruction file (CLAUDE.md, AGENTS.md, etc.) as a skill entry. */
function readInstructionFile(filePath, source, toolName) {
  if (!fs.existsSync(filePath)) return null;
  try {
    const content = fs.readFileSync(filePath, 'utf8');
    const description = extractDescription(content);
    return { name: `${toolName}-instructions`, description: description || `${toolName} project instructions`, source, path: filePath };
  } catch {}
  return null;
}

// ── Claude Code ─────────────────────────────────────────────────────

function readClaudeSkills() {
  const skills = [];

  // ~/.claude/skills/
  scanMdDir(path.join(CLAUDE_DIR, 'skills'), 'claude-skills', skills);

  // ~/.claude/commands/ (user-level slash commands)
  scanMdDir(path.join(CLAUDE_DIR, 'commands'), 'claude-commands', skills);

  // Project-level .claude/commands/
  const cwd = process.env.CTM_PROJECT_DIR || process.cwd();
  scanMdDir(path.join(cwd, '.claude', 'commands'), 'claude-project-commands', skills);

  // ~/.claude/plugins/ (installed plugins with skills)
  const pluginsDir = path.join(CLAUDE_DIR, 'plugins');
  if (fs.existsSync(pluginsDir)) {
    try { scanPluginSkills(pluginsDir, skills); } catch {}
  }

  return skills;
}

// ── Codex (OpenAI) ──────────────────────────────────────────────────

function readCodexSkills() {
  const skills = [];
  const codexDir = path.join(HOME, '.codex');

  // ~/.codex/ config
  if (fs.existsSync(codexDir)) {
    // Codex instructions file
    const instrPath = path.join(codexDir, 'instructions.md');
    const entry = readInstructionFile(instrPath, 'codex', 'codex');
    if (entry) skills.push(entry);
  }

  // Project-level AGENTS.md (Codex convention)
  const cwd = process.env.CTM_PROJECT_DIR || process.cwd();
  const agentsMd = path.join(cwd, 'AGENTS.md');
  const entry = readInstructionFile(agentsMd, 'codex-project', 'codex');
  if (entry) skills.push(entry);

  // Project-level codex.md
  const codexMd = path.join(cwd, 'codex.md');
  const entry2 = readInstructionFile(codexMd, 'codex-project', 'codex-config');
  if (entry2) skills.push(entry2);

  return skills;
}

// ── Gemini CLI ──────────────────────────────────────────────────────

function readGeminiSkills() {
  const skills = [];
  const geminiDir = path.join(HOME, '.gemini');

  if (fs.existsSync(geminiDir)) {
    // ~/.gemini/instructions.md (user-level)
    const instrPath = path.join(geminiDir, 'instructions.md');
    const entry = readInstructionFile(instrPath, 'gemini', 'gemini');
    if (entry) skills.push(entry);

    // ~/.gemini/tools/ if it exists
    const toolsDir = path.join(geminiDir, 'tools');
    scanMdDir(toolsDir, 'gemini-tools', skills);
  }

  // Project-level .gemini/ directory
  const cwd = process.env.CTM_PROJECT_DIR || process.cwd();
  const projectGeminiDir = path.join(cwd, '.gemini');
  if (fs.existsSync(projectGeminiDir)) {
    scanMdDir(projectGeminiDir, 'gemini-project', skills);
  }

  // GEMINI.md (project instructions, like CLAUDE.md)
  const geminiMd = path.join(cwd, 'GEMINI.md');
  const entry = readInstructionFile(geminiMd, 'gemini-project', 'gemini');
  if (entry) skills.push(entry);

  return skills;
}

// ── Cursor ──────────────────────────────────────────────────────────

function readCursorSkills() {
  const skills = [];
  const cursorDir = path.join(HOME, '.cursor');

  // Cursor extensions (list installed extensions as capabilities)
  const extDir = path.join(cursorDir, 'extensions');
  if (fs.existsSync(extDir)) {
    try {
      for (const entry of fs.readdirSync(extDir)) {
        if (entry === 'extensions.json') continue;
        const pkgPath = path.join(extDir, entry, 'package.json');
        if (fs.existsSync(pkgPath)) {
          try {
            const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
            skills.push({
              name: pkg.name || entry,
              description: (pkg.description || '').slice(0, 200),
              source: 'cursor-extension',
              path: pkgPath,
            });
          } catch {}
        }
      }
    } catch {}
  }

  // Project-level .cursorrules
  const cwd = process.env.CTM_PROJECT_DIR || process.cwd();
  const cursorrules = path.join(cwd, '.cursorrules');
  const entry = readInstructionFile(cursorrules, 'cursor-project', 'cursor-rules');
  if (entry) skills.push(entry);

  // Cursor project rules directory
  const rulesDir = path.join(cwd, '.cursor', 'rules');
  scanMdDir(rulesDir, 'cursor-rules', skills);

  return skills;
}

// ── Wall-E (self) ───────────────────────────────────────────────────

function readWalleSkills() {
  const skills = [];

  // Bundled skills
  const bundledDir = path.join(__dirname, '_bundled');
  if (fs.existsSync(bundledDir)) {
    try {
      for (const entry of fs.readdirSync(bundledDir)) {
        const skillMd = path.join(bundledDir, entry, 'SKILL.md');
        if (fs.existsSync(skillMd)) {
          try {
            const content = fs.readFileSync(skillMd, 'utf8');
            const description = extractDescription(content);
            skills.push({ name: entry, description, source: 'wall-e-bundled', path: skillMd });
          } catch {}
        }
      }
    } catch {}
  }

  // User skills (~/.wall-e/skills/)
  const userSkillsDir = path.join(HOME, '.wall-e', 'skills');
  if (fs.existsSync(userSkillsDir)) {
    try {
      for (const entry of fs.readdirSync(userSkillsDir)) {
        const skillMd = path.join(userSkillsDir, entry, 'SKILL.md');
        if (fs.existsSync(skillMd)) {
          try {
            const content = fs.readFileSync(skillMd, 'utf8');
            const description = extractDescription(content);
            skills.push({ name: entry, description, source: 'wall-e-user', path: skillMd });
          } catch {}
        }
      }
    } catch {}
  }

  return skills;
}

// ── Main API ────────────────────────────────────────────────────────

/**
 * Scan all AI tools and return their capabilities.
 * Each tool's skills are returned in a separate array for the UI to group.
 */
function suggestSkillsFromClaudeCode() {
  const mcpServers = readMcpConfig();

  // Scan all tools
  const claudeSkills = readClaudeSkills();
  const codexSkills = readCodexSkills();
  const geminiSkills = readGeminiSkills();
  const cursorSkills = readCursorSkills();
  const walleSkills = readWalleSkills();

  // Deduplicate within each tool's skills
  function dedup(arr) {
    const seen = new Map();
    for (const s of arr) seen.set(s.name, s);
    return Array.from(seen.values());
  }

  // MCP-based suggestions
  const suggestions = [];
  for (const mcp of mcpServers) {
    if (/slack/i.test(mcp.name)) {
      suggestions.push({ name: 'fetch-slack-messages', description: `Fetch recent Slack messages using ${mcp.name} MCP server`, source: 'mcp', mcp_server: mcp.name });
    }
    if (/glean/i.test(mcp.name)) {
      suggestions.push({ name: 'search-company-docs', description: `Search company documents via ${mcp.name}`, source: 'mcp', mcp_server: mcp.name });
    }
    if (/github/i.test(mcp.name)) {
      suggestions.push({ name: 'fetch-github-activity', description: `Fetch GitHub PRs, commits, and reviews via ${mcp.name}`, source: 'mcp', mcp_server: mcp.name });
    }
  }

  return {
    mcpServers,
    claudeSkills: dedup(claudeSkills),
    codexSkills: dedup(codexSkills),
    geminiSkills: dedup(geminiSkills),
    cursorSkills: dedup(cursorSkills),
    walleSkills: dedup(walleSkills),
    suggestions,
  };
}

/**
 * Sync all discovered external skills as skill_reference memories in the brain.
 * This enables the Learn+Reference system — Wall-E knows about all external skills
 * and can recommend them contextually in chat.
 */
function syncSkillReferences() {
  let brain;
  try { brain = require('../brain'); brain.getDb(); } catch { return { synced: 0 }; }

  const allSkills = [
    ...readClaudeSkills().map(s => ({ ...s, tool: 'claude' })),
    ...readCodexSkills().map(s => ({ ...s, tool: 'codex' })),
    ...readGeminiSkills().map(s => ({ ...s, tool: 'gemini' })),
    ...readCursorSkills().map(s => ({ ...s, tool: 'cursor' })),
    ...readWalleSkills().map(s => ({ ...s, tool: 'wall-e' })),
  ];

  const crypto = require('crypto');
  let synced = 0;

  for (const skill of allSkills) {
    if (!skill.name || !skill.description) continue;
    const sourceId = `skill_ref:${skill.tool}:${skill.name}`;
    const content = `${skill.name} — ${skill.description}`;
    const contentHash = skill.path ? crypto.createHash('sha256').update(
      fs.existsSync(skill.path) ? fs.readFileSync(skill.path, 'utf8') : content
    ).digest('hex') : null;

    // Check if this reference already exists.
    // NOTE: filter on `source` too — `WHERE source_id = ?` alone can't use the compound
    // index idx_memories_source(source, source_id) and full-scans the (multi-GB) memories
    // table on EVERY skill, which made boot take ~3min. Skill refs are always stored with
    // source='skill_reference', so this is both correct and index-eligible (SEARCH not SCAN).
    try {
      const existing = brain.getDb().prepare(
        "SELECT id, content FROM memories WHERE source = 'skill_reference' AND source_id = ? LIMIT 1"
      ).get(sourceId);

      if (existing && existing.content === content) continue; // no change

      if (existing) {
        // Update
        brain.getDb().prepare(
          'UPDATE memories SET content = ?, metadata = ?, timestamp = ? WHERE id = ?'
        ).run(content, JSON.stringify({
          tool: skill.tool, name: skill.name, description: skill.description,
          source_path: skill.path || null, content_hash: contentHash, source_type: skill.source,
        }), new Date().toISOString(), existing.id);
      } else {
        // Insert
        brain.insertMemory({
          source: 'skill_reference',
          source_id: sourceId,
          memory_type: 'skill_reference',
          direction: 'observed',
          content,
          timestamp: new Date().toISOString(),
          metadata: JSON.stringify({
            tool: skill.tool, name: skill.name, description: skill.description,
            source_path: skill.path || null, content_hash: contentHash, source_type: skill.source,
          }),
        });
      }
      synced++;
    } catch {}
  }

  // Remove stale references (skills that no longer exist)
  try {
    const allIds = new Set(allSkills.filter(s => s.name && s.description).map(s => `skill_ref:${s.tool}:${s.name}`));
    const existing = brain.getDb().prepare(
      "SELECT id, source_id FROM memories WHERE source = 'skill_reference'"
    ).all();
    for (const row of existing) {
      if (!allIds.has(row.source_id)) {
        brain.deleteMemory(row.id); // cascades to memory_index + embeddings (no orphan leak)
      }
    }
  } catch {}

  return { synced, total: allSkills.length };
}

/**
 * Check for updates to imported skills. Returns list of skills with available updates.
 */
function checkImportedSkillUpdates() {
  let brain;
  try { brain = require('../brain'); brain.getDb(); } catch { return []; }

  const crypto = require('crypto');
  const importedSkills = brain.getDb().prepare(
    "SELECT * FROM skills WHERE import_source_path IS NOT NULL"
  ).all();

  const updates = [];
  for (const skill of importedSkills) {
    if (!skill.import_source_path || !fs.existsSync(skill.import_source_path)) continue;
    try {
      const currentContent = fs.readFileSync(skill.import_source_path, 'utf8');
      const currentHash = crypto.createHash('sha256').update(currentContent).digest('hex');
      if (currentHash !== skill.import_content_hash) {
        if (!skill.import_customized) {
          // Auto-update
          brain.updateSkill(skill.id, {
            prompt_template: currentContent,
            import_content_hash: currentHash,
            import_updated_at: new Date().toISOString(),
            import_update_available: 0,
          });
          updates.push({ id: skill.id, name: skill.name, action: 'updated' });
        } else {
          // Mark update available
          brain.updateSkill(skill.id, { import_update_available: 1 });
          updates.push({ id: skill.id, name: skill.name, action: 'update_available' });
        }
      }
    } catch {}
  }
  return updates;
}

module.exports = { readMcpConfig, readClaudeSkills, suggestSkillsFromClaudeCode, syncSkillReferences, checkImportedSkillUpdates };
