/**
 * Buddy Script Built-in Functions
 *
 * Provides bindings to code-buddy features for scripting
 */
import * as fs from 'fs';
import * as path from 'path';
import { execSync, spawn } from 'child_process';
import { logger } from '../utils/logger.js';
// Lazy-loaded agent instance for AI operations
let cachedAgent = null;
/**
 * Get or create the AI agent for script execution
 */
async function getOrCreateAgent(config) {
    // Use provided agent if available
    if (config.agent) {
        return config.agent;
    }
    // Return cached agent if already created
    if (cachedAgent) {
        return cachedAgent;
    }
    // Try to create a new agent with lazy loading
    const apiKey = process.env.GROK_API_KEY;
    if (!apiKey) {
        logger.warn('GROK_API_KEY not set, AI operations will not be available');
        return null;
    }
    try {
        const { CodeBuddyAgent } = await import('../agent/codebuddy-agent.js');
        cachedAgent = new CodeBuddyAgent(apiKey, process.env.GROK_BASE_URL, process.env.GROK_MODEL || 'grok-3-latest');
        return cachedAgent;
    }
    catch (error) {
        logger.error('Failed to create AI agent for scripting', {
            error: error instanceof Error ? error.message : String(error)
        });
        return null;
    }
}
/**
 * Create all built-in functions for the Buddy Script runtime
 */
export function createBuiltins(config, print) {
    const builtins = {};
    // ============================================
    // Core Functions
    // ============================================
    builtins.print = (...args) => {
        const message = args.map(a => stringify(a)).join(' ');
        print(message);
        return null;
    };
    builtins.println = (...args) => {
        builtins.print(...args);
        return null;
    };
    builtins.typeof = (value) => {
        if (value === null)
            return 'null';
        if (Array.isArray(value))
            return 'array';
        return typeof value;
    };
    builtins.len = (value) => {
        if (typeof value === 'string')
            return value.length;
        if (Array.isArray(value))
            return value.length;
        if (typeof value === 'object' && value !== null)
            return Object.keys(value).length;
        return 0;
    };
    builtins.str = (value) => stringify(value);
    builtins.num = (value) => Number(value);
    builtins.bool = (value) => Boolean(value);
    builtins.int = (value) => Math.floor(Number(value));
    builtins.float = (value) => Number(value);
    // ============================================
    // Array/Object Functions
    // ============================================
    builtins.range = (start, end, step) => {
        const s = Number(start);
        const e = end !== undefined ? Number(end) : s;
        const st = step !== undefined ? Number(step) : 1;
        const actualStart = end !== undefined ? s : 0;
        const result = [];
        if (st > 0) {
            for (let i = actualStart; i < e; i += st)
                result.push(i);
        }
        else if (st < 0) {
            for (let i = actualStart; i > e; i += st)
                result.push(i);
        }
        return result;
    };
    builtins.keys = (obj) => {
        if (typeof obj === 'object' && obj !== null) {
            return Object.keys(obj);
        }
        return [];
    };
    builtins.values = (obj) => {
        if (typeof obj === 'object' && obj !== null) {
            return Object.values(obj);
        }
        return [];
    };
    builtins.entries = (obj) => {
        if (typeof obj === 'object' && obj !== null) {
            return Object.entries(obj);
        }
        return [];
    };
    builtins.push = (arr, ...items) => {
        if (Array.isArray(arr)) {
            arr.push(...items);
            return arr; // Return array for chaining
        }
        throw new Error('push() requires an array as first argument. Got: ' + (typeof arr));
    };
    builtins.pop = (arr) => {
        if (Array.isArray(arr)) {
            return arr.pop();
        }
        throw new Error('pop() requires an array as argument. Got: ' + (typeof arr));
    };
    builtins.join = (arr, separator) => {
        if (Array.isArray(arr)) {
            return arr.map(stringify).join(separator !== undefined ? String(separator) : ',');
        }
        throw new Error('join() requires an array as first argument. Got: ' + (typeof arr));
    };
    builtins.split = (str, separator) => {
        return String(str).split(separator !== undefined ? String(separator) : '');
    };
    builtins.slice = (arr, start, end) => {
        if (Array.isArray(arr) || typeof arr === 'string') {
            return arr.slice(start !== undefined ? Number(start) : undefined, end !== undefined ? Number(end) : undefined);
        }
        throw new Error('slice() requires an array or string as first argument. Got: ' + (typeof arr));
    };
    builtins.map = async (arr, fn) => {
        if (!Array.isArray(arr))
            throw new Error('map() requires an array as first argument. Got: ' + (typeof arr));
        if (typeof fn !== 'function')
            throw new Error('map() requires a function as second argument. Got: ' + (typeof fn));
        const results = [];
        for (let i = 0; i < arr.length; i++) {
            results.push(await fn(arr[i], i));
        }
        return results;
    };
    builtins.filter = async (arr, fn) => {
        if (!Array.isArray(arr))
            throw new Error('filter() requires an array as first argument. Got: ' + (typeof arr));
        if (typeof fn !== 'function')
            throw new Error('filter() requires a function as second argument. Got: ' + (typeof fn));
        const results = [];
        for (let i = 0; i < arr.length; i++) {
            if (await fn(arr[i], i)) {
                results.push(arr[i]);
            }
        }
        return results;
    };
    builtins.find = async (arr, fn) => {
        if (!Array.isArray(arr))
            throw new Error('find() requires an array as first argument. Got: ' + (typeof arr));
        if (typeof fn !== 'function')
            throw new Error('find() requires a function as second argument. Got: ' + (typeof fn));
        for (let i = 0; i < arr.length; i++) {
            if (await fn(arr[i], i)) {
                return arr[i];
            }
        }
        return null;
    };
    builtins.includes = (arr, item) => {
        if (Array.isArray(arr))
            return arr.includes(item);
        if (typeof arr === 'string')
            return arr.includes(String(item));
        return false;
    };
    // ============================================
    // String Functions
    // ============================================
    builtins.trim = (str) => String(str).trim();
    builtins.lower = (str) => String(str).toLowerCase();
    builtins.upper = (str) => String(str).toUpperCase();
    builtins.replace = (str, search, replacement) => {
        return String(str).replace(String(search), String(replacement));
    };
    builtins.replaceAll = (str, search, replacement) => {
        return String(str).replaceAll(String(search), String(replacement));
    };
    builtins.startsWith = (str, prefix) => String(str).startsWith(String(prefix));
    builtins.endsWith = (str, suffix) => String(str).endsWith(String(suffix));
    builtins.contains = (str, search) => String(str).includes(String(search));
    builtins.match = (str, pattern) => {
        const match = String(str).match(new RegExp(String(pattern)));
        return match ? Array.from(match) : null;
    };
    // ============================================
    // Math Functions
    // ============================================
    builtins.min = (...args) => {
        const nums = args.flat().map(Number);
        return Math.min(...nums);
    };
    builtins.max = (...args) => {
        const nums = args.flat().map(Number);
        return Math.max(...nums);
    };
    builtins.abs = (n) => Math.abs(Number(n));
    builtins.floor = (n) => Math.floor(Number(n));
    builtins.ceil = (n) => Math.ceil(Number(n));
    builtins.round = (n) => Math.round(Number(n));
    builtins.sqrt = (n) => Math.sqrt(Number(n));
    builtins.pow = (base, exp) => Math.pow(Number(base), Number(exp));
    builtins.random = (max) => {
        if (max !== undefined) {
            return Math.floor(Math.random() * Number(max));
        }
        return Math.random();
    };
    // ============================================
    // Time Functions
    // ============================================
    builtins.time = () => Date.now();
    builtins.now = () => new Date().toISOString();
    builtins.sleep = async (ms) => {
        await new Promise(resolve => setTimeout(resolve, Number(ms)));
        return null;
    };
    // ============================================
    // File Operations (grok.file namespace)
    // ============================================
    const file = {
        read: (filePath) => {
            if (!config.enableFileOps)
                throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
            const fullPath = resolvePath(String(filePath), config.workdir);
            if (config.dryRun) {
                print(`[DRY RUN] file.read: ${fullPath}`);
                return '';
            }
            return fs.readFileSync(fullPath, 'utf-8');
        },
        write: (filePath, content) => {
            if (!config.enableFileOps)
                throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
            const fullPath = resolvePath(String(filePath), config.workdir);
            if (config.dryRun) {
                print(`[DRY RUN] file.write: ${fullPath}`);
                return true;
            }
            fs.writeFileSync(fullPath, String(content));
            return true;
        },
        append: (filePath, content) => {
            if (!config.enableFileOps)
                throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
            const fullPath = resolvePath(String(filePath), config.workdir);
            if (config.dryRun) {
                print(`[DRY RUN] file.append: ${fullPath}`);
                return true;
            }
            fs.appendFileSync(fullPath, String(content));
            return true;
        },
        exists: (filePath) => {
            const fullPath = resolvePath(String(filePath), config.workdir);
            return fs.existsSync(fullPath);
        },
        delete: (filePath) => {
            if (!config.enableFileOps)
                throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
            const fullPath = resolvePath(String(filePath), config.workdir);
            if (config.dryRun) {
                print(`[DRY RUN] file.delete: ${fullPath}`);
                return true;
            }
            fs.unlinkSync(fullPath);
            return true;
        },
        copy: (src, dest) => {
            if (!config.enableFileOps)
                throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
            const srcPath = resolvePath(String(src), config.workdir);
            const destPath = resolvePath(String(dest), config.workdir);
            if (config.dryRun) {
                print(`[DRY RUN] file.copy: ${srcPath} -> ${destPath}`);
                return true;
            }
            fs.copyFileSync(srcPath, destPath);
            return true;
        },
        move: (src, dest) => {
            if (!config.enableFileOps)
                throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
            const srcPath = resolvePath(String(src), config.workdir);
            const destPath = resolvePath(String(dest), config.workdir);
            if (config.dryRun) {
                print(`[DRY RUN] file.move: ${srcPath} -> ${destPath}`);
                return true;
            }
            fs.renameSync(srcPath, destPath);
            return true;
        },
        list: (dirPath) => {
            const fullPath = resolvePath(String(dirPath), config.workdir);
            return fs.readdirSync(fullPath);
        },
        mkdir: (dirPath) => {
            if (!config.enableFileOps)
                throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
            const fullPath = resolvePath(String(dirPath), config.workdir);
            if (config.dryRun) {
                print(`[DRY RUN] file.mkdir: ${fullPath}`);
                return true;
            }
            fs.mkdirSync(fullPath, { recursive: true });
            return true;
        },
        stat: (filePath) => {
            const fullPath = resolvePath(String(filePath), config.workdir);
            const stats = fs.statSync(fullPath);
            return {
                size: stats.size,
                isFile: stats.isFile(),
                isDirectory: stats.isDirectory(),
                created: stats.birthtime.toISOString(),
                modified: stats.mtime.toISOString(),
            };
        },
        glob: (pattern) => {
            // Simple glob implementation
            const fullPath = resolvePath(String(pattern), config.workdir);
            const dir = path.dirname(fullPath);
            const base = path.basename(fullPath);
            if (!fs.existsSync(dir))
                return [];
            const files = fs.readdirSync(dir);
            const regex = new RegExp('^' + base.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
            return files.filter(f => regex.test(f)).map(f => path.join(dir, f));
        },
    };
    builtins.file = file;
    // ============================================
    // Bash/Shell Operations
    // ============================================
    const bash = {
        run: (command, options) => {
            if (!config.enableBash)
                throw new Error('Bash/shell commands are disabled in the current script configuration. Enable them with { enableBash: true }.');
            const opts = options || {};
            if (config.dryRun) {
                print(`[DRY RUN] bash: ${command}`);
                return { stdout: '', stderr: '', code: 0 };
            }
            try {
                const stdout = execSync(String(command), {
                    cwd: opts.cwd ? String(opts.cwd) : config.workdir,
                    encoding: 'utf-8',
                    timeout: opts.timeout ? Number(opts.timeout) : 30000,
                    maxBuffer: 10 * 1024 * 1024,
                });
                return {
                    stdout: stdout.trim(),
                    stderr: '',
                    code: 0,
                };
            }
            catch (error) {
                const execError = error;
                return {
                    stdout: execError.stdout || '',
                    stderr: execError.stderr || String(error),
                    code: execError.status || 1,
                };
            }
        },
        exec: (command) => {
            const result = bash.run(command);
            if (result.code !== 0) {
                throw new Error(`Command failed with code ${result.code}`);
            }
            return result.stdout;
        },
        spawn: async (command, args) => {
            if (!config.enableBash)
                throw new Error('Bash/shell commands are disabled in the current script configuration. Enable them with { enableBash: true }.');
            const argList = Array.isArray(args) ? args.map(String) : [];
            return new Promise((resolve, reject) => {
                const proc = spawn(String(command), argList, {
                    cwd: config.workdir,
                    shell: true,
                });
                let stdout = '';
                let stderr = '';
                proc.stdout?.on('data', (data) => {
                    stdout += data.toString();
                    if (config.verbose)
                        print(data.toString());
                });
                proc.stderr?.on('data', (data) => {
                    stderr += data.toString();
                });
                proc.on('close', (code) => {
                    resolve({ stdout, stderr, code });
                });
                proc.on('error', reject);
            });
        },
    };
    builtins.bash = bash;
    // ============================================
    // AI Operations (placeholder - will integrate with CodeBuddyAgent)
    // ============================================
    const ai = {
        ask: async (prompt) => {
            if (!config.enableAI)
                throw new Error('AI operations are disabled in the current script configuration. Enable them with { enableAI: true }.');
            if (config.dryRun) {
                print(`[DRY RUN] ai.ask: ${prompt}`);
                return '[AI Response Placeholder]';
            }
            const agent = await getOrCreateAgent(config);
            if (!agent) {
                throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
            }
            const result = await agent.processUserInput(String(prompt));
            return result.content || '';
        },
        chat: async (message) => {
            if (!config.enableAI)
                throw new Error('AI operations are disabled in the current script configuration. Enable them with { enableAI: true }.');
            if (config.dryRun) {
                print(`[DRY RUN] ai.chat: ${message}`);
                return '[AI Response Placeholder]';
            }
            const agent = await getOrCreateAgent(config);
            if (!agent) {
                throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
            }
            const result = await agent.processUserInput(String(message));
            return result.content || '';
        },
        complete: async (prompt, options) => {
            if (!config.enableAI)
                throw new Error('AI operations are disabled in the current script configuration. Enable them with { enableAI: true }.');
            if (config.dryRun) {
                print(`[DRY RUN] ai.complete: ${prompt}`);
                return '[AI Response Placeholder]';
            }
            const agent = await getOrCreateAgent(config);
            if (!agent) {
                throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
            }
            const opts = typeof options === 'object' && options !== null ? options : {};
            const result = await agent.processUserInput(String(prompt), opts);
            return result.content || '';
        },
    };
    builtins.ai = ai;
    // ============================================
    // Environment & Config
    // ============================================
    const env = {
        get: (name) => process.env[String(name)] || null,
        set: (name, value) => {
            process.env[String(name)] = String(value);
            return true;
        },
        all: () => ({ ...process.env }),
    };
    builtins.env = env;
    const grok = {
        workdir: () => config.workdir,
        verbose: () => config.verbose,
        dryRun: () => config.dryRun,
        version: () => '1.0.0',
    };
    builtins.grok = grok;
    // ============================================
    // JSON
    // ============================================
    const json = {
        parse: (str) => JSON.parse(String(str)),
        stringify: (value, indent) => {
            return JSON.stringify(value, null, indent ? Number(indent) : undefined);
        },
    };
    builtins.json = json;
    builtins.JSON = json;
    // ============================================
    // Console (alias for print functions)
    // ============================================
    const console = {
        log: builtins.print,
        info: builtins.print,
        warn: (...args) => {
            print('[WARN] ' + args.map(stringify).join(' '));
            return null;
        },
        error: (...args) => {
            print('[ERROR] ' + args.map(stringify).join(' '));
            return null;
        },
    };
    builtins.console = console;
    // ============================================
    // Math namespace (for compatibility)
    // ============================================
    builtins.Math = {
        min: builtins.min,
        max: builtins.max,
        abs: builtins.abs,
        floor: builtins.floor,
        ceil: builtins.ceil,
        round: builtins.round,
        sqrt: builtins.sqrt,
        pow: builtins.pow,
        random: () => Math.random(),
        PI: Math.PI,
        E: Math.E,
    };
    builtins.Date = {
        now: () => Date.now(),
        parse: (str) => Date.parse(String(str)),
    };
    return builtins;
}
// ============================================
// Utility Functions
// ============================================
function stringify(value) {
    if (value === null)
        return 'null';
    if (value === undefined)
        return 'undefined';
    if (typeof value === 'object')
        return JSON.stringify(value);
    return String(value);
}
function resolvePath(filePath, workdir) {
    if (path.isAbsolute(filePath))
        return filePath;
    return path.resolve(workdir, filePath);
}
//# sourceMappingURL=builtins.js.map