/**
 * Unified Script Built-in Functions
 *
 * Merged from FCS builtins (100+ functions, date/time, format, enumerate/zip)
 * and Buddy Script builtins (ai.*, console.*, lazy agent loading, sandboxing)
 *
 * Extensions: .bs (primary), .fcs (backward-compatible alias)
 */
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) {
    if (config.agent) {
        return config.agent;
    }
    if (cachedAgent) {
        return cachedAgent;
    }
    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 unified scripting runtime
 */
export function createBuiltins(config, print) {
    const builtins = {};
    // ============================================
    // Core I/O
    // ============================================
    builtins.print = (...args) => {
        const message = args.map(a => stringify(a)).join(' ');
        print(message);
        return null;
    };
    builtins.println = (...args) => {
        builtins.print(...args);
        return null;
    };
    builtins.input = async (prompt) => {
        if (prompt)
            print(prompt);
        return '';
    };
    // ============================================
    // Type Conversion
    // ============================================
    builtins.int = (value) => {
        if (typeof value === 'string') {
            if (value.startsWith('0x') || value.startsWith('0X')) {
                return parseInt(value, 16);
            }
            if (value.startsWith('0b') || value.startsWith('0B')) {
                return parseInt(value.substring(2), 2);
            }
            return parseInt(value, 10);
        }
        return Math.floor(Number(value));
    };
    builtins.float = (value) => parseFloat(String(value));
    builtins.num = (value) => Number(value);
    builtins.str = (value) => stringify(value);
    builtins.bool = (value) => {
        if (value === null || value === undefined || value === false || value === 0 || value === '') {
            return false;
        }
        return true;
    };
    builtins.array = (value) => {
        if (Array.isArray(value))
            return value;
        if (typeof value === 'string')
            return value.split('');
        if (typeof value === 'object' && value !== null)
            return Object.values(value);
        return [value];
    };
    // ============================================
    // Collection Functions
    // ============================================
    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.range = (...args) => {
        let start = 0, end = 0, step = 1;
        if (args.length === 1) {
            end = args[0];
        }
        else if (args.length === 2) {
            start = args[0];
            end = args[1];
        }
        else if (args.length >= 3) {
            start = args[0];
            end = args[1];
            step = args[2];
        }
        const result = [];
        if (step > 0) {
            for (let i = start; i < end; i += step)
                result.push(i);
        }
        else if (step < 0) {
            for (let i = start; i > end; i += step)
                result.push(i);
        }
        return result;
    };
    builtins.enumerate = (arr) => {
        return arr.map((item, index) => [index, item]);
    };
    builtins.zip = (...arrays) => {
        const minLen = Math.min(...arrays.map(a => a.length));
        const result = [];
        for (let i = 0; i < minLen; i++) {
            result.push(arrays.map(a => a[i]));
        }
        return result;
    };
    builtins.map = async (arr, fn) => {
        if (!Array.isArray(arr))
            throw new Error('map() requires an array as first argument');
        if (typeof fn !== 'function')
            throw new Error('map() requires a function as second argument');
        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');
        if (typeof fn !== 'function')
            throw new Error('filter() requires a function as second argument');
        const results = [];
        for (let i = 0; i < arr.length; i++) {
            if (await fn(arr[i], i))
                results.push(arr[i]);
        }
        return results;
    };
    builtins.reduce = async (arr, fn, initial) => {
        let acc = initial !== undefined ? initial : arr[0];
        const startIdx = initial !== undefined ? 0 : 1;
        for (let i = startIdx; i < arr.length; i++) {
            acc = await fn(acc, arr[i], i);
        }
        return acc;
    };
    builtins.find = async (arr, fn) => {
        if (!Array.isArray(arr))
            throw new Error('find() requires an array as first argument');
        if (typeof fn !== 'function')
            throw new Error('find() requires a function as second argument');
        for (let i = 0; i < arr.length; i++) {
            if (await fn(arr[i], i))
                return arr[i];
        }
        return null;
    };
    builtins.every = async (arr, fn) => {
        for (const item of arr) {
            if (!(await fn(item)))
                return false;
        }
        return true;
    };
    builtins.some = async (arr, fn) => {
        for (const item of arr) {
            if (await fn(item))
                return true;
        }
        return false;
    };
    builtins.sort = (arr, fn) => {
        const copy = [...arr];
        if (fn) {
            copy.sort((a, b) => {
                const result = fn(a, b);
                return typeof result === 'number' ? result : 0;
            });
        }
        else {
            copy.sort((a, b) => {
                if (typeof a === 'number' && typeof b === 'number')
                    return a - b;
                return String(a).localeCompare(String(b));
            });
        }
        return copy;
    };
    builtins.reverse = (arr) => [...arr].reverse();
    builtins.slice = (arr, start, end) => arr.slice(start, end);
    builtins.concat = (...arrays) => arrays.flat();
    builtins.push = (arr, ...items) => {
        if (!Array.isArray(arr))
            throw new Error('push() requires an array as first argument');
        arr.push(...items);
        return arr;
    };
    builtins.pop = (arr) => {
        if (!Array.isArray(arr))
            throw new Error('pop() requires an array as first argument');
        return arr.pop();
    };
    builtins.shift = (arr) => arr.shift();
    builtins.unshift = (arr, ...items) => { arr.unshift(...items); return arr; };
    builtins.includes = (arr, item) => {
        if (Array.isArray(arr))
            return arr.includes(item);
        if (typeof arr === 'string')
            return arr.includes(String(item));
        return false;
    };
    builtins.indexOf = (arr, item) => arr.indexOf(item);
    builtins.join = (arr, separator = ',') => {
        if (!Array.isArray(arr))
            throw new Error('join() requires an array as first argument');
        return arr.map(a => stringify(a)).join(separator);
    };
    builtins.split = (str, separator) => String(str).split(separator);
    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 [];
    };
    // ============================================
    // String Functions
    // ============================================
    builtins.upper = (str) => String(str).toUpperCase();
    builtins.lower = (str) => String(str).toLowerCase();
    builtins.trim = (str) => String(str).trim();
    builtins.ltrim = (str) => String(str).trimStart();
    builtins.rtrim = (str) => String(str).trimEnd();
    builtins.startsWith = (str, prefix) => String(str).startsWith(prefix);
    builtins.endsWith = (str, suffix) => String(str).endsWith(suffix);
    builtins.contains = (str, substr) => String(str).includes(substr);
    builtins.replace = (str, search, replace) => String(str).split(search).join(replace);
    builtins.replaceAll = (str, search, replace) => String(str).split(search).join(replace);
    builtins.substr = (str, start, length) => {
        if (length !== undefined)
            return String(str).substring(start, start + length);
        return String(str).substring(start);
    };
    builtins.charAt = (str, index) => String(str).charAt(index);
    builtins.repeat = (str, count) => String(str).repeat(count);
    builtins.padStart = (str, length, char = ' ') => String(str).padStart(length, char);
    builtins.padEnd = (str, length, char = ' ') => String(str).padEnd(length, char);
    builtins.format = (template, ...args) => {
        let result = template;
        for (let i = 0; i < args.length; i++) {
            result = result.replace(`{${i}}`, stringify(args[i]));
            result = result.replace('{}', stringify(args[i]));
        }
        return result;
    };
    builtins.match = (str, pattern) => {
        const m = String(str).match(new RegExp(String(pattern)));
        return m ? Array.from(m) : null;
    };
    // ============================================
    // Math Functions
    // ============================================
    builtins.abs = Math.abs;
    builtins.ceil = Math.ceil;
    builtins.floor = Math.floor;
    builtins.round = Math.round;
    builtins.sqrt = Math.sqrt;
    builtins.pow = Math.pow;
    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.sin = Math.sin;
    builtins.cos = Math.cos;
    builtins.tan = Math.tan;
    builtins.log = Math.log;
    builtins.log10 = Math.log10;
    builtins.exp = Math.exp;
    builtins.random = (max) => {
        if (max !== undefined)
            return Math.floor(Math.random() * Number(max));
        return Math.random();
    };
    builtins.randomInt = (min, max) => {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    };
    builtins.sum = (arr) => arr.reduce((a, b) => a + b, 0);
    builtins.avg = (arr) => arr.length > 0 ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
    // Constants
    builtins.PI = Math.PI;
    builtins.E = Math.E;
    // ============================================
    // Date/Time Functions
    // ============================================
    builtins.now = () => Date.now();
    builtins.date = () => new Date().toISOString().split('T')[0];
    builtins.time = () => new Date().toISOString().split('T')[1].split('.')[0];
    builtins.datetime = () => new Date().toISOString();
    builtins.timestamp = () => Math.floor(Date.now() / 1000);
    builtins.formatDate = (timestamp, format) => {
        const d = new Date(timestamp);
        if (!format)
            return d.toISOString();
        return format
            .replace('YYYY', String(d.getFullYear()))
            .replace('MM', String(d.getMonth() + 1).padStart(2, '0'))
            .replace('DD', String(d.getDate()).padStart(2, '0'))
            .replace('HH', String(d.getHours()).padStart(2, '0'))
            .replace('mm', String(d.getMinutes()).padStart(2, '0'))
            .replace('ss', String(d.getSeconds()).padStart(2, '0'));
    };
    // ============================================
    // JSON Functions
    // ============================================
    builtins.jsonEncode = (value) => JSON.stringify(value, null, 2);
    builtins.jsonDecode = (str) => JSON.parse(str);
    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 (from Buddy Script)
    // ============================================
    builtins.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; },
    };
    // ============================================
    // Math namespace (from Buddy Script)
    // ============================================
    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)),
    };
    // ============================================
    // File Operations (merged FCS flat + BS namespaced)
    // ============================================
    if (config.enableFileOps) {
        // Flat FCS-style file functions
        builtins.readFile = (filePath) => {
            const fullPath = resolvePath(String(filePath), config.workdir);
            return fs.readFileSync(fullPath, 'utf-8');
        };
        builtins.writeFile = (filePath, content) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would write to: ${filePath}`);
                return true;
            }
            const fullPath = resolvePath(String(filePath), config.workdir);
            fs.writeFileSync(fullPath, content);
            return true;
        };
        builtins.appendFile = (filePath, content) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would append to: ${filePath}`);
                return true;
            }
            const fullPath = resolvePath(String(filePath), config.workdir);
            fs.appendFileSync(fullPath, content);
            return true;
        };
        builtins.fileExists = (filePath) => fs.existsSync(resolvePath(String(filePath), config.workdir));
        builtins.isFile = (filePath) => {
            try {
                return fs.statSync(resolvePath(String(filePath), config.workdir)).isFile();
            }
            catch {
                return false;
            }
        };
        builtins.isDir = (filePath) => {
            try {
                return fs.statSync(resolvePath(String(filePath), config.workdir)).isDirectory();
            }
            catch {
                return false;
            }
        };
        builtins.mkdir = (dirPath) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would create directory: ${dirPath}`);
                return true;
            }
            fs.mkdirSync(resolvePath(String(dirPath), config.workdir), { recursive: true });
            return true;
        };
        builtins.rmdir = (dirPath) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would remove directory: ${dirPath}`);
                return true;
            }
            fs.rmSync(resolvePath(String(dirPath), config.workdir), { recursive: true });
            return true;
        };
        builtins.remove = (filePath) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would remove: ${filePath}`);
                return true;
            }
            fs.unlinkSync(resolvePath(String(filePath), config.workdir));
            return true;
        };
        builtins.rename = (oldPath, newPath) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would rename: ${oldPath} -> ${newPath}`);
                return true;
            }
            fs.renameSync(resolvePath(String(oldPath), config.workdir), resolvePath(String(newPath), config.workdir));
            return true;
        };
        builtins.copy = (src, dest) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would copy: ${src} -> ${dest}`);
                return true;
            }
            fs.copyFileSync(resolvePath(String(src), config.workdir), resolvePath(String(dest), config.workdir));
            return true;
        };
        builtins.listDir = (dirPath) => fs.readdirSync(resolvePath(String(dirPath), config.workdir));
        builtins.glob = (pattern) => {
            const dir = path.dirname(pattern);
            const filePattern = path.basename(pattern);
            const fullDir = resolvePath(dir, config.workdir);
            if (!fs.existsSync(fullDir))
                return [];
            const regex = new RegExp('^' + filePattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
            return fs.readdirSync(fullDir).filter(f => regex.test(f)).map(f => path.join(dir, f));
        };
        builtins.fileSize = (filePath) => fs.statSync(resolvePath(String(filePath), config.workdir)).size;
        builtins.fileMtime = (filePath) => fs.statSync(resolvePath(String(filePath), config.workdir)).mtimeMs;
        // Path utilities
        builtins.basename = path.basename;
        builtins.dirname = path.dirname;
        builtins.extname = path.extname;
        builtins.joinPath = (...parts) => path.join(...parts);
        builtins.resolvePath = (...parts) => path.resolve(config.workdir, ...parts);
        // BS-style namespaced file object
        builtins.file = {
            read: (filePath) => builtins.readFile(String(filePath)),
            write: (filePath, content) => builtins.writeFile(String(filePath), String(content)),
            append: (filePath, content) => builtins.appendFile(String(filePath), String(content)),
            exists: (filePath) => builtins.fileExists(String(filePath)),
            delete: (filePath) => builtins.remove(String(filePath)),
            copy: (src, dest) => builtins.copy(String(src), String(dest)),
            move: (src, dest) => builtins.rename(String(src), String(dest)),
            list: (dirPath) => builtins.listDir(String(dirPath)),
            mkdir: (dirPath) => builtins.mkdir(String(dirPath)),
            stat: (filePath) => {
                const stats = fs.statSync(resolvePath(String(filePath), config.workdir));
                return { size: stats.size, isFile: stats.isFile(), isDirectory: stats.isDirectory(), created: stats.birthtime.toISOString(), modified: stats.mtime.toISOString() };
            },
            glob: (pattern) => builtins.glob(String(pattern)),
        };
    }
    // ============================================
    // Shell/Bash Functions (merged FCS flat + BS namespaced)
    // ============================================
    if (config.enableBash) {
        builtins.exec = (command) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would execute: ${command}`);
                return '';
            }
            try {
                return execSync(command, { cwd: config.workdir, encoding: 'utf-8', timeout: config.timeout });
            }
            catch (err) {
                throw new Error(`Command failed: ${err.message}`);
            }
        };
        builtins.shell = async (command) => {
            if (config.dryRun) {
                print(`[DRY RUN] Would execute: ${command}`);
                return { code: 0, stdout: '', stderr: '' };
            }
            return new Promise((resolve) => {
                const child = spawn(command, { shell: true, cwd: config.workdir });
                let stdout = '', stderr = '';
                child.stdout.on('data', (data) => { stdout += data.toString(); });
                child.stderr.on('data', (data) => { stderr += data.toString(); });
                child.on('close', (code) => { resolve({ code: code ?? 0, stdout, stderr }); });
            });
        };
        // BS-style namespaced bash object
        builtins.bash = {
            run: (command, options) => {
                if (!config.enableBash)
                    throw new Error('Bash commands are disabled');
                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 = builtins.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 commands are disabled');
                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 = '', 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);
                });
            },
        };
    }
    // ============================================
    // AI Operations (from Buddy Script with lazy agent)
    // ============================================
    if (config.enableAI) {
        const ai = {
            ask: async (prompt) => {
                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.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.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;
        // FCS-style flat ai/grok function
        builtins.grok = async (prompt) => {
            return ai.ask(prompt);
        };
    }
    // ============================================
    // Environment & Config
    // ============================================
    builtins.env = {
        get: (name) => process.env[String(name)] || null,
        set: (name, value) => { process.env[String(name)] = String(value); return true; },
        all: () => ({ ...process.env }),
    };
    builtins.cwd = () => config.workdir;
    // ============================================
    // Utility Functions
    // ============================================
    builtins.typeof = (value) => {
        if (value === null)
            return 'null';
        if (Array.isArray(value))
            return 'array';
        return typeof value;
    };
    builtins.isNull = (value) => value === null;
    builtins.isNumber = (value) => typeof value === 'number';
    builtins.isString = (value) => typeof value === 'string';
    builtins.isBool = (value) => typeof value === 'boolean';
    builtins.isArray = (value) => Array.isArray(value);
    builtins.isDict = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
    builtins.isFunction = (value) => typeof value === 'function';
    builtins.sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    builtins.exit = (code = 0) => process.exit(code);
    builtins.assert = (condition, message) => {
        if (!condition)
            throw new Error(message || 'Assertion failed');
    };
    builtins.expect = (actual, expected, message) => {
        if (actual !== expected)
            throw new Error(message || `Expected ${stringify(expected)}, got ${stringify(actual)}`);
    };
    return builtins;
}
// ============================================
// Utility Functions
// ============================================
function stringify(value) {
    if (value === null)
        return 'null';
    if (value === undefined)
        return 'undefined';
    if (typeof value === 'string')
        return value;
    if (typeof value === 'function')
        return '[Function]';
    if (Array.isArray(value)) {
        return '[' + value.map(v => stringify(v)).join(', ') + ']';
    }
    if (typeof value === 'object') {
        try {
            return JSON.stringify(value);
        }
        catch {
            return '[Object]';
        }
    }
    return String(value);
}
function resolvePath(filePath, workdir) {
    if (path.isAbsolute(filePath))
        return filePath;
    return path.resolve(workdir, filePath);
}
/** @deprecated Use createBuiltins instead */
export const createFCSBuiltins = createBuiltins;
//# sourceMappingURL=builtins.js.map