/**
 * Computer Skills Module
 *
 * Skills library for reusable automations and workflows.
 * Inspired by Open Interpreter's computer.skills capabilities.
 */
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { EventEmitter } from 'events';
import { safeEval, safeEvalAsync, safeInterpolate } from '../../sandbox/safe-eval.js';
export const DEFAULT_SKILL_LIBRARY_CONFIG = {
    skillPaths: [
        '~/.codebuddy/skills',
        '.codebuddy/skills',
    ],
    enableBuiltin: true,
    cacheEnabled: true,
};
// ============================================================================
// Built-in Skills
// ============================================================================
const BUILTIN_SKILLS = [
    {
        id: 'web-search',
        name: 'Web Search',
        description: 'Search the web and return results',
        version: '1.0.0',
        tags: ['web', 'search', 'research'],
        builtin: true,
        parameters: [
            { name: 'query', type: 'string', description: 'Search query', required: true },
            { name: 'numResults', type: 'number', description: 'Number of results', default: 5 },
        ],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerBrowser } = await import('./browser.js');
          const browser = getComputerBrowser();
          return await browser.search(params.query, { numResults: params.numResults });
        `,
            },
        ],
        examples: [
            { input: { query: 'TypeScript best practices' }, description: 'Search for TypeScript articles' },
        ],
    },
    {
        id: 'read-file',
        name: 'Read File',
        description: 'Read contents of a file',
        version: '1.0.0',
        tags: ['file', 'read', 'io'],
        builtin: true,
        parameters: [
            { name: 'path', type: 'string', description: 'File path', required: true },
            { name: 'lines', type: 'number', description: 'Number of lines to read' },
        ],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerFiles } = await import('./files.js');
          const files = getComputerFiles();
          if (params.lines) {
            return await files.readLines(params.path, 0, params.lines);
          }
          return await files.read(params.path);
        `,
            },
        ],
    },
    {
        id: 'write-file',
        name: 'Write File',
        description: 'Write content to a file',
        version: '1.0.0',
        tags: ['file', 'write', 'io'],
        builtin: true,
        parameters: [
            { name: 'path', type: 'string', description: 'File path', required: true },
            { name: 'content', type: 'string', description: 'Content to write', required: true },
            { name: 'append', type: 'boolean', description: 'Append to file', default: false },
        ],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerFiles } = await import('./files.js');
          const files = getComputerFiles();
          await files.write(params.path, params.content, { append: params.append, createDirs: true });
          return { success: true, path: params.path };
        `,
            },
        ],
    },
    {
        id: 'run-shell',
        name: 'Run Shell Command',
        description: 'Execute a shell command',
        version: '1.0.0',
        tags: ['shell', 'command', 'exec'],
        builtin: true,
        capabilities: ['shellCommands'],
        parameters: [
            { name: 'command', type: 'string', description: 'Command to run', required: true },
            { name: 'cwd', type: 'string', description: 'Working directory' },
            { name: 'timeout', type: 'number', description: 'Timeout in ms', default: 30000 },
        ],
        steps: [
            {
                type: 'shell',
                content: '{{command}}',
            },
        ],
    },
    {
        id: 'get-clipboard',
        name: 'Get Clipboard',
        description: 'Get clipboard contents',
        version: '1.0.0',
        tags: ['clipboard', 'os'],
        builtin: true,
        parameters: [],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerOS } = await import('./os.js');
          const computerOS = getComputerOS();
          return await computerOS.getClipboardText();
        `,
            },
        ],
    },
    {
        id: 'set-clipboard',
        name: 'Set Clipboard',
        description: 'Set clipboard contents',
        version: '1.0.0',
        tags: ['clipboard', 'os'],
        builtin: true,
        parameters: [
            { name: 'text', type: 'string', description: 'Text to copy', required: true },
        ],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerOS } = await import('./os.js');
          const computerOS = getComputerOS();
          await computerOS.setClipboardText(params.text);
          return { success: true };
        `,
            },
        ],
    },
    {
        id: 'open-url',
        name: 'Open URL',
        description: 'Open a URL in the default browser',
        version: '1.0.0',
        tags: ['browser', 'url', 'open'],
        builtin: true,
        parameters: [
            { name: 'url', type: 'string', description: 'URL to open', required: true },
        ],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerOS } = await import('./os.js');
          const computerOS = getComputerOS();
          await computerOS.open(params.url);
          return { success: true, url: params.url };
        `,
            },
        ],
    },
    {
        id: 'summarize-webpage',
        name: 'Summarize Webpage',
        description: 'Fetch and summarize a webpage',
        version: '1.0.0',
        tags: ['web', 'summarize', 'research'],
        builtin: true,
        capabilities: ['webSearch'],
        parameters: [
            { name: 'url', type: 'string', description: 'URL to summarize', required: true },
            { name: 'maxLength', type: 'number', description: 'Max content length', default: 10000 },
        ],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerBrowser } = await import('./browser.js');
          const browser = getComputerBrowser();
          const page = await browser.fetch(params.url, { maxLength: params.maxLength });
          return { title: page.title, content: page.content.substring(0, 2000) };
        `,
            },
            {
                type: 'llm',
                content: 'Summarize the following webpage content in 3-5 bullet points:\n\nTitle: {{stepResults[0].title}}\n\n{{stepResults[0].content}}',
            },
        ],
    },
    {
        id: 'git-status',
        name: 'Git Status',
        description: 'Get git repository status',
        version: '1.0.0',
        tags: ['git', 'vcs', 'dev'],
        builtin: true,
        capabilities: ['shellCommands'],
        parameters: [
            { name: 'path', type: 'string', description: 'Repository path', default: '.' },
        ],
        steps: [
            {
                type: 'shell',
                content: 'cd "{{path}}" && git status --porcelain',
            },
        ],
    },
    {
        id: 'list-processes',
        name: 'List Processes',
        description: 'List running processes',
        version: '1.0.0',
        tags: ['os', 'process', 'system'],
        builtin: true,
        parameters: [
            { name: 'filter', type: 'string', description: 'Filter by name' },
        ],
        steps: [
            {
                type: 'code',
                language: 'javascript',
                content: `
          const { getComputerOS } = await import('./os.js');
          const computerOS = getComputerOS();
          let processes = await computerOS.listProcesses();
          if (params.filter) {
            const filter = params.filter.toLowerCase();
            processes = processes.filter(p =>
              p.name.toLowerCase().includes(filter) ||
              (p.command && p.command.toLowerCase().includes(filter))
            );
          }
          return processes.slice(0, 20);
        `,
            },
        ],
    },
];
// ============================================================================
// Skills Library Class
// ============================================================================
export class ComputerSkills extends EventEmitter {
    config;
    skills = new Map();
    loaded = false;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_SKILL_LIBRARY_CONFIG, ...config };
    }
    // ==========================================================================
    // Skill Discovery
    // ==========================================================================
    /**
     * Load all available skills
     */
    async load() {
        if (this.loaded && this.config.cacheEnabled) {
            return;
        }
        this.skills.clear();
        // Load built-in skills
        if (this.config.enableBuiltin) {
            for (const skill of BUILTIN_SKILLS) {
                this.skills.set(skill.id, skill);
            }
        }
        // Load skills from paths
        for (const skillPath of this.config.skillPaths) {
            await this.loadSkillsFromPath(skillPath);
        }
        this.loaded = true;
        this.emit('loaded', this.skills.size);
    }
    /**
     * Load skills from a directory
     */
    async loadSkillsFromPath(skillPath) {
        const resolved = this.resolvePath(skillPath);
        if (!fs.existsSync(resolved)) {
            return;
        }
        const files = await fs.promises.readdir(resolved);
        for (const file of files) {
            if (file.endsWith('.json') || file.endsWith('.yaml') || file.endsWith('.yml')) {
                try {
                    const fullPath = path.join(resolved, file);
                    const content = await fs.promises.readFile(fullPath, 'utf-8');
                    let skill;
                    if (file.endsWith('.json')) {
                        skill = JSON.parse(content);
                    }
                    else {
                        // YAML support would require yaml package
                        continue;
                    }
                    skill.sourcePath = fullPath;
                    this.skills.set(skill.id, skill);
                }
                catch (err) {
                    this.emit('error', { file, error: err });
                }
            }
        }
    }
    /**
     * List all available skills
     */
    list(filter) {
        let skills = Array.from(this.skills.values());
        if (filter?.tags && filter.tags.length > 0) {
            skills = skills.filter(s => filter.tags.some(tag => s.tags.includes(tag)));
        }
        if (filter?.search) {
            const search = filter.search.toLowerCase();
            skills = skills.filter(s => s.name.toLowerCase().includes(search) ||
                s.description.toLowerCase().includes(search) ||
                s.tags.some(t => t.toLowerCase().includes(search)));
        }
        return skills;
    }
    /**
     * Search for skills
     */
    search(query) {
        const queryLower = query.toLowerCase();
        const queryWords = queryLower.split(/\s+/);
        const results = [];
        for (const skill of this.skills.values()) {
            let score = 0;
            const matchedTags = [];
            let matchedDescription = false;
            // Check name
            if (skill.name.toLowerCase().includes(queryLower)) {
                score += 10;
            }
            // Check tags
            for (const tag of skill.tags) {
                if (queryWords.some(word => tag.toLowerCase().includes(word))) {
                    score += 5;
                    matchedTags.push(tag);
                }
            }
            // Check description
            for (const word of queryWords) {
                if (skill.description.toLowerCase().includes(word)) {
                    score += 2;
                    matchedDescription = true;
                }
            }
            if (score > 0) {
                results.push({ skill, score, matchedTags, matchedDescription });
            }
        }
        // Sort by score descending
        return results.sort((a, b) => b.score - a.score);
    }
    /**
     * Get a skill by ID
     */
    get(skillId) {
        return this.skills.get(skillId);
    }
    // ==========================================================================
    // Skill Execution
    // ==========================================================================
    /**
     * Run a skill
     */
    async run(skillId, params = {}) {
        const startTime = Date.now();
        const skill = this.skills.get(skillId);
        if (!skill) {
            return {
                skillId,
                success: false,
                output: null,
                error: `Skill not found: ${skillId}`,
                duration: Date.now() - startTime,
                steps: [],
            };
        }
        // Validate parameters
        const validationError = this.validateParams(skill, params);
        if (validationError) {
            return {
                skillId,
                success: false,
                output: null,
                error: validationError,
                duration: Date.now() - startTime,
                steps: [],
            };
        }
        // Apply defaults
        const fullParams = this.applyDefaults(skill, params);
        // Execute steps
        const stepResults = [];
        let lastOutput = null;
        for (let i = 0; i < skill.steps.length; i++) {
            const step = skill.steps[i];
            const stepId = step.id || `step-${i}`;
            const stepStart = Date.now();
            try {
                const output = await this.executeStep(step, fullParams, stepResults);
                lastOutput = output;
                stepResults.push({
                    stepId,
                    success: true,
                    output,
                    duration: Date.now() - stepStart,
                });
                this.emit('step:complete', { skillId, stepId, output });
            }
            catch (err) {
                const error = err instanceof Error ? err.message : String(err);
                stepResults.push({
                    stepId,
                    success: false,
                    output: null,
                    error,
                    duration: Date.now() - stepStart,
                });
                this.emit('step:error', { skillId, stepId, error });
                if (step.onError !== 'continue') {
                    return {
                        skillId,
                        success: false,
                        output: null,
                        error: `Step ${stepId} failed: ${error}`,
                        duration: Date.now() - startTime,
                        steps: stepResults,
                    };
                }
            }
        }
        const result = {
            skillId,
            success: true,
            output: lastOutput,
            duration: Date.now() - startTime,
            steps: stepResults,
        };
        this.emit('complete', result);
        return result;
    }
    /**
     * Execute a single step
     */
    async executeStep(step, params, previousResults) {
        const context = {
            params,
            stepResults: previousResults.map(r => r.output),
        };
        switch (step.type) {
            case 'code':
                return this.executeCodeStep(step, context);
            case 'shell':
                return this.executeShellStep(step, context);
            case 'llm':
                return this.executeLLMStep(step, context);
            case 'condition':
                return this.executeConditionStep(step, context);
            case 'loop':
                return this.executeLoopStep(step, context, params, previousResults);
            case 'skill':
                if (!step.skillId) {
                    throw new Error('Skill step requires skillId');
                }
                return this.run(step.skillId, { ...params, ...step.args });
            default:
                throw new Error(`Unknown step type: ${step.type}`);
        }
    }
    async executeCodeStep(step, context) {
        // Sandboxed JavaScript execution via vm.runInNewContext
        return safeEvalAsync(step.content, {
            context: {
                params: context.params,
                stepResults: context.stepResults,
            },
            timeout: 10000,
        });
    }
    async executeShellStep(step, context) {
        const { exec } = await import('child_process');
        const { promisify } = await import('util');
        const execAsync = promisify(exec);
        // Interpolate template variables
        const command = this.interpolate(step.content, context);
        const { stdout, stderr } = await execAsync(command, { timeout: 30000 });
        return { stdout: stdout.trim(), stderr: stderr.trim() };
    }
    async executeLLMStep(step, context) {
        // Interpolate template variables
        const prompt = this.interpolate(step.content, context);
        // TODO: Integrate with actual LLM client
        // For now, return the prompt
        return { prompt, note: 'LLM integration pending' };
    }
    async executeConditionStep(step, context) {
        if (!step.condition) {
            throw new Error('Condition step requires condition');
        }
        const condition = this.interpolate(step.condition, context);
        return safeEval(condition, {
            context: {
                params: context.params,
                stepResults: context.stepResults,
            },
        });
    }
    async executeLoopStep(step, context, params, _previousResults) {
        if (!step.items) {
            throw new Error('Loop step requires items');
        }
        const itemsExpr = this.interpolate(step.items, context);
        const items = safeEval(itemsExpr, {
            context: {
                params: context.params,
                stepResults: context.stepResults,
            },
        });
        const results = [];
        for (const item of items) {
            const itemParams = { ...params, _item: item };
            const innerContext = {
                params: itemParams,
                stepResults: context.stepResults,
            };
            const result = await this.executeCodeStep({ ...step, type: 'code' }, innerContext);
            results.push(result);
        }
        return results;
    }
    // ==========================================================================
    // Skill Management
    // ==========================================================================
    /**
     * Register a new skill
     */
    register(skill) {
        const validation = this.validateSkill(skill);
        if (!validation.valid) {
            throw new Error(`Invalid skill: ${validation.errors.join(', ')}`);
        }
        this.skills.set(skill.id, skill);
        this.emit('registered', skill.id);
    }
    /**
     * Unregister a skill
     */
    unregister(skillId) {
        const deleted = this.skills.delete(skillId);
        if (deleted) {
            this.emit('unregistered', skillId);
        }
        return deleted;
    }
    /**
     * Save a skill to disk
     */
    async save(skill, filePath) {
        const resolved = filePath
            ? this.resolvePath(filePath)
            : path.join(this.resolvePath(this.config.skillPaths[0]), `${skill.id}.json`);
        const dir = path.dirname(resolved);
        if (!fs.existsSync(dir)) {
            await fs.promises.mkdir(dir, { recursive: true });
        }
        await fs.promises.writeFile(resolved, JSON.stringify(skill, null, 2), 'utf-8');
        skill.sourcePath = resolved;
    }
    /**
     * Delete a skill from disk
     */
    async delete(skillId) {
        const skill = this.skills.get(skillId);
        if (!skill) {
            return false;
        }
        if (skill.builtin) {
            throw new Error('Cannot delete built-in skill');
        }
        if (skill.sourcePath && fs.existsSync(skill.sourcePath)) {
            await fs.promises.unlink(skill.sourcePath);
        }
        return this.unregister(skillId);
    }
    // ==========================================================================
    // Utility Methods
    // ==========================================================================
    validateParams(skill, params) {
        for (const param of skill.parameters) {
            if (param.required && !(param.name in params)) {
                return `Missing required parameter: ${param.name}`;
            }
            if (param.name in params) {
                const value = params[param.name];
                const actualType = Array.isArray(value) ? 'array' : typeof value;
                if (actualType !== param.type && value !== undefined) {
                    return `Invalid type for ${param.name}: expected ${param.type}, got ${actualType}`;
                }
                if (param.enum && !param.enum.includes(value)) {
                    return `Invalid value for ${param.name}: must be one of ${param.enum.join(', ')}`;
                }
            }
        }
        return null;
    }
    applyDefaults(skill, params) {
        const result = { ...params };
        for (const param of skill.parameters) {
            if (!(param.name in result) && param.default !== undefined) {
                result[param.name] = param.default;
            }
        }
        return result;
    }
    validateSkill(skill) {
        const errors = [];
        if (!skill.id)
            errors.push('ID is required');
        if (!skill.name)
            errors.push('Name is required');
        if (!skill.description)
            errors.push('Description is required');
        if (!skill.version)
            errors.push('Version is required');
        if (!skill.steps || skill.steps.length === 0)
            errors.push('At least one step is required');
        return { valid: errors.length === 0, errors };
    }
    interpolate(template, context) {
        return safeInterpolate(template, context);
    }
    resolvePath(p) {
        if (p.startsWith('~')) {
            return path.join(os.homedir(), p.slice(1));
        }
        return path.resolve(p);
    }
    /**
     * Get skill count
     */
    get count() {
        return this.skills.size;
    }
    /**
     * Get all tags
     */
    getTags() {
        const tags = new Set();
        for (const skill of this.skills.values()) {
            for (const tag of skill.tags) {
                tags.add(tag);
            }
        }
        return Array.from(tags).sort();
    }
}
// ============================================================================
// Singleton
// ============================================================================
let skillsInstance = null;
export function getComputerSkills(config) {
    if (!skillsInstance) {
        skillsInstance = new ComputerSkills(config);
    }
    return skillsInstance;
}
export function resetComputerSkills() {
    skillsInstance = null;
}
export default ComputerSkills;
//# sourceMappingURL=skills.js.map