/**
 * Pipeline Compositor (Lobster-inspired)
 *
 * Composes skills and tools into pipelines using pipe syntax.
 * Each step receives the output of the previous step as input.
 *
 * Syntax:
 * ```
 * search "query" | summarize | save-to-file output.md
 * ```
 *
 * Features:
 * - Pipe operator (|) chains steps
 * - Each step can be a tool, skill, or function
 * - Steps receive previous output as context
 * - Conditional branching with ? operator
 * - Parallel execution with & operator
 * - Error handling with || (fallback) operator
 *
 * Usage:
 * ```typescript
 * const pipeline = getPipelineCompositor();
 *
 * // Parse and run a pipeline
 * const result = await pipeline.run('search "node.js" | summarize | save output.md');
 *
 * // Programmatic pipeline
 * const result = await pipeline.execute([
 *   { tool: 'web_search', args: { query: 'node.js' } },
 *   { tool: 'summarize', args: {} },
 *   { tool: 'write_file', args: { path: 'output.md' } },
 * ]);
 * ```
 */
import { EventEmitter } from 'events';
// ============================================================================
// Default Configuration
// ============================================================================
const DEFAULT_PIPELINE_CONFIG = {
    defaultTimeout: 30000,
    maxSteps: 20,
    maxDurationMs: 300000, // 5 minutes
};
// Built-in transform steps
const BUILTIN_TRANSFORMS = {
    'uppercase': (input) => input.toUpperCase(),
    'lowercase': (input) => input.toLowerCase(),
    'trim': (input) => input.trim(),
    'lines': (input) => input.split('\n').filter(l => l.trim()).join('\n'),
    'count': (input) => String(input.split('\n').filter(l => l.trim()).length),
    'head': (input, args) => {
        const n = Number(args.n) || 10;
        return input.split('\n').slice(0, n).join('\n');
    },
    'tail': (input, args) => {
        const n = Number(args.n) || 10;
        return input.split('\n').slice(-n).join('\n');
    },
    'grep': (input, args) => {
        const pattern = new RegExp(String(args.pattern || ''), 'gi');
        return input.split('\n').filter(l => pattern.test(l)).join('\n');
    },
    'json': (input) => {
        try {
            return JSON.stringify(JSON.parse(input), null, 2);
        }
        catch {
            return input;
        }
    },
    'wrap': (input, args) => {
        const prefix = String(args.prefix || '');
        const suffix = String(args.suffix || '');
        return `${prefix}${input}${suffix}`;
    },
};
// ============================================================================
// Pipeline Compositor
// ============================================================================
export class PipelineCompositor extends EventEmitter {
    config;
    approvalHistory = [];
    pendingApprovals = new Map();
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_PIPELINE_CONFIG, ...config };
    }
    // ==========================================================================
    // Parsing
    // ==========================================================================
    /**
     * Parse a pipeline string into steps
     *
     * Syntax: `step1 args | step2 args | step3 args`
     * Fallback: `step1 args || fallback-step args`
     * Sequential: `step1 args && step2 args`
     */
    parse(pipelineString) {
        const tokens = [];
        const parts = this.tokenize(pipelineString);
        for (const part of parts) {
            if (part === '|' || part === '||' || part === '&&' || part === '&') {
                tokens.push({ type: 'operator', operator: part });
            }
            else {
                const step = this.parseStep(part.trim());
                if (step) {
                    tokens.push({ type: 'step', step });
                }
            }
        }
        return tokens;
    }
    /**
     * Tokenize pipeline string, respecting quotes
     */
    tokenize(input) {
        const tokens = [];
        let current = '';
        let inQuote = false;
        let quoteChar = '';
        for (let i = 0; i < input.length; i++) {
            const char = input[i];
            if (inQuote) {
                if (char === quoteChar) {
                    inQuote = false;
                }
                current += char;
                continue;
            }
            if (char === '"' || char === "'") {
                inQuote = true;
                quoteChar = char;
                current += char;
                continue;
            }
            // Check for operators (|| must be checked before |)
            if (char === '|' && input[i + 1] === '|') {
                if (current.trim())
                    tokens.push(current.trim());
                tokens.push('||');
                current = '';
                i++;
                continue;
            }
            if (char === '&' && input[i + 1] === '&') {
                if (current.trim())
                    tokens.push(current.trim());
                tokens.push('&&');
                current = '';
                i++;
                continue;
            }
            if (char === '|') {
                if (current.trim())
                    tokens.push(current.trim());
                tokens.push('|');
                current = '';
                continue;
            }
            if (char === '&') {
                if (current.trim())
                    tokens.push(current.trim());
                tokens.push('&');
                current = '';
                continue;
            }
            current += char;
        }
        if (current.trim())
            tokens.push(current.trim());
        return tokens;
    }
    /**
     * Parse a single step string
     */
    parseStep(stepStr) {
        if (!stepStr)
            return null;
        // Split into name and args
        const match = stepStr.match(/^(\S+)(?:\s+(.*))?$/);
        if (!match)
            return null;
        const name = match[1];
        const rawArgs = match[2] || '';
        const args = this.parseArgs(rawArgs);
        // Determine type
        let type;
        if (name === 'approval' || name === 'approve') {
            type = 'approval';
        }
        else if (BUILTIN_TRANSFORMS[name]) {
            type = 'transform';
        }
        else {
            type = 'tool';
        }
        return {
            type,
            name,
            args,
            rawArgs,
        };
    }
    /**
     * Parse arguments from a string
     */
    parseArgs(argsStr) {
        const args = {};
        if (!argsStr)
            return args;
        // Handle key=value pairs
        const kvPattern = /(\w+)=(?:"([^"]*?)"|'([^']*?)'|(\S+))/g;
        let match;
        let hasKv = false;
        while ((match = kvPattern.exec(argsStr)) !== null) {
            const key = match[1];
            const value = match[2] ?? match[3] ?? match[4];
            args[key] = value;
            hasKv = true;
        }
        // If no key=value pairs, treat as positional
        if (!hasKv) {
            // Remove quotes
            const cleaned = argsStr.replace(/^["']|["']$/g, '');
            args['_input'] = cleaned;
            args['query'] = cleaned;
            args['path'] = cleaned;
            args['pattern'] = cleaned;
        }
        return args;
    }
    // ==========================================================================
    // Execution
    // ==========================================================================
    /**
     * Run a pipeline from a string
     */
    async run(pipelineString) {
        const tokens = this.parse(pipelineString);
        return this.executeTokens(tokens);
    }
    /**
     * Execute a pipeline from step definitions
     */
    async execute(steps) {
        const tokens = [];
        for (let i = 0; i < steps.length; i++) {
            tokens.push({ type: 'step', step: steps[i] });
            if (i < steps.length - 1) {
                tokens.push({ type: 'operator', operator: '|' });
            }
        }
        return this.executeTokens(tokens);
    }
    /**
     * Execute pipeline from tokens
     */
    async executeTokens(tokens) {
        const startTime = Date.now();
        const stepResults = [];
        let currentOutput = '';
        let success = true;
        let error;
        // Extract steps and operators
        const steps = [];
        const operators = [];
        for (const token of tokens) {
            if (token.type === 'step' && token.step) {
                steps.push(token.step);
            }
            else if (token.type === 'operator' && token.operator) {
                operators.push(token.operator);
            }
        }
        // Validate
        if (steps.length > this.config.maxSteps) {
            return {
                success: false,
                output: '',
                steps: [],
                totalDurationMs: Date.now() - startTime,
                error: `Pipeline exceeds maximum of ${this.config.maxSteps} steps`,
            };
        }
        // Execute steps
        for (let i = 0; i < steps.length; i++) {
            const step = steps[i];
            const operator = i > 0 ? (operators[i - 1] || '|') : '|';
            // Check total duration
            if (Date.now() - startTime > this.config.maxDurationMs) {
                error = 'Pipeline exceeded maximum duration';
                success = false;
                break;
            }
            this.emit('step:start', step, i);
            const stepStart = Date.now();
            let result;
            try {
                const output = await this.executeStep(step, currentOutput, i);
                result = {
                    step,
                    success: true,
                    output,
                    durationMs: Date.now() - stepStart,
                    index: i,
                };
                currentOutput = output;
            }
            catch (err) {
                const errMsg = err instanceof Error ? err.message : String(err);
                result = {
                    step,
                    success: false,
                    output: '',
                    error: errMsg,
                    durationMs: Date.now() - stepStart,
                    index: i,
                };
                // Handle operators
                if (operator === '||' && step.fallback) {
                    // Try fallback
                    try {
                        const fallbackOutput = await this.executeStep(step.fallback, currentOutput, i);
                        result.success = true;
                        result.output = fallbackOutput;
                        currentOutput = fallbackOutput;
                    }
                    catch {
                        success = false;
                        error = errMsg;
                        stepResults.push(result);
                        break;
                    }
                }
                else if (operator === '&&') {
                    // && requires previous success
                    success = false;
                    error = errMsg;
                    stepResults.push(result);
                    break;
                }
                else {
                    success = false;
                    error = errMsg;
                    stepResults.push(result);
                    break;
                }
            }
            stepResults.push(result);
            this.emit('step:complete', result);
        }
        const pipelineResult = {
            success,
            output: currentOutput,
            steps: stepResults,
            totalDurationMs: Date.now() - startTime,
            error,
        };
        this.emit('pipeline:complete', pipelineResult);
        return pipelineResult;
    }
    /**
     * Execute a single step
     */
    async executeStep(step, input, stepIndex = 0) {
        // Approval steps manage their own timeout (via approvalGate.timeoutMs)
        if (step.type === 'approval') {
            return this.runStep(step, input, stepIndex);
        }
        const timeout = step.timeout || this.config.defaultTimeout;
        return new Promise((resolve, reject) => {
            const timer = setTimeout(() => {
                reject(new Error(`Step '${step.name}' timed out after ${timeout}ms`));
            }, timeout);
            this.runStep(step, input, stepIndex)
                .then(output => {
                clearTimeout(timer);
                resolve(output);
            })
                .catch(err => {
                clearTimeout(timer);
                reject(err);
            });
        });
    }
    /**
     * Run a step based on its type
     */
    async runStep(step, input, stepIndex = 0) {
        switch (step.type) {
            case 'transform':
                return this.runTransform(step, input);
            case 'tool':
            case 'skill':
                return this.runTool(step, input);
            case 'function':
                return this.runTool(step, input);
            case 'approval':
                return this.runApprovalGate(step, input, stepIndex);
            default:
                throw new Error(`Unknown step type: ${step.type}`);
        }
    }
    /**
     * Run a built-in transform
     */
    runTransform(step, input) {
        const transform = BUILTIN_TRANSFORMS[step.name];
        if (!transform) {
            throw new Error(`Unknown transform: ${step.name}`);
        }
        return transform(input, step.args);
    }
    /**
     * Run a tool step using the configured executor
     */
    async runTool(step, input) {
        if (!this.config.toolExecutor) {
            throw new Error('No tool executor configured');
        }
        // Inject previous output as context
        const args = { ...step.args, _input: input, _context: input };
        const result = await this.config.toolExecutor(step.name, args, input);
        if (!result.success) {
            throw new Error(result.error || `Tool '${step.name}' failed`);
        }
        return result.output;
    }
    // ==========================================================================
    // Approval Gates (OpenClaw Lobster-inspired)
    // ==========================================================================
    /**
     * Run an approval gate step.
     *
     * Resolution order:
     * 1. If autoApproveCondition exists and returns true (and requireExplicit is not set), auto-approve.
     * 2. If an approvalHandler callback is configured, delegate to it.
     * 3. Otherwise, emit 'approval:required' and wait for manual resolution via approveStep().
     * 4. If the configured timeout elapses, reject with a timeout error.
     */
    async runApprovalGate(step, input, stepIndex = 0) {
        const gate = step.approvalGate || {
            timeoutMs: 300000,
            message: step.rawArgs || step.name || 'Approval required',
        };
        // 1. Auto-approve condition (skip if requireExplicit is set)
        if (gate.autoApproveCondition && !gate.requireExplicit) {
            try {
                if (gate.autoApproveCondition()) {
                    const result = {
                        approved: true,
                        approvedBy: 'auto',
                        timestamp: new Date(),
                        comment: 'Auto-approved by condition',
                    };
                    this.approvalHistory.push(result);
                    this.emit('approval:auto', result, step);
                    return input; // pass-through
                }
            }
            catch {
                // If the condition throws, fall through to manual approval
            }
        }
        // 2. Approval handler callback
        if (this.config.approvalHandler) {
            const result = await this.config.approvalHandler(gate, stepIndex, input);
            this.approvalHistory.push(result);
            if (!result.approved) {
                throw new Error(`Approval rejected${result.comment ? ': ' + result.comment : ''}`);
            }
            return input;
        }
        // 3. Emit event and wait for manual resolution
        return new Promise((resolve, reject) => {
            const timeoutMs = gate.timeoutMs ?? 300000;
            const timer = setTimeout(() => {
                this.pendingApprovals.delete(stepIndex);
                const timeoutResult = {
                    approved: false,
                    timestamp: new Date(),
                    comment: 'Approval timed out',
                };
                this.approvalHistory.push(timeoutResult);
                reject(new Error('Approval timed out'));
            }, timeoutMs);
            // Store the pending approval keyed by step index
            this.pendingApprovals.set(stepIndex, {
                resolve: (result) => {
                    clearTimeout(timer);
                    this.pendingApprovals.delete(stepIndex);
                    this.approvalHistory.push(result);
                    if (result.approved) {
                        resolve(input);
                    }
                    else {
                        reject(new Error(`Approval rejected${result.comment ? ': ' + result.comment : ''}`));
                    }
                },
                reject: (err) => {
                    clearTimeout(timer);
                    this.pendingApprovals.delete(stepIndex);
                    reject(err);
                },
            });
            this.emit('approval:required', gate, stepIndex, input);
        });
    }
    /**
     * Manually approve or reject a pending approval gate step.
     * Call this in response to the 'approval:required' event.
     */
    approveStep(stepIndex, result) {
        const pending = this.pendingApprovals.get(stepIndex);
        if (!pending) {
            throw new Error(`No pending approval for step index ${stepIndex}`);
        }
        pending.resolve(result);
    }
    /**
     * Get the history of all approval decisions made during this pipeline's lifetime.
     */
    getApprovalHistory() {
        return [...this.approvalHistory];
    }
    // ==========================================================================
    // Configuration
    // ==========================================================================
    /**
     * Set the tool executor
     */
    setToolExecutor(executor) {
        this.config.toolExecutor = executor;
    }
    /**
     * Register a custom transform
     */
    registerTransform(name, fn) {
        BUILTIN_TRANSFORMS[name] = fn;
    }
    /**
     * List available transforms
     */
    listTransforms() {
        return Object.keys(BUILTIN_TRANSFORMS);
    }
    // ==========================================================================
    // File Loading & Validation
    // ==========================================================================
    /**
     * Load a pipeline definition from a JSON or YAML file.
     * Returns parsed PipelineStep array ready for execution.
     */
    async loadFromFile(filePath) {
        const fs = await import('fs');
        const path = await import('path');
        const resolvedPath = path.default.resolve(filePath);
        if (!fs.default.existsSync(resolvedPath)) {
            throw new Error(`Pipeline file not found: ${resolvedPath}`);
        }
        const content = fs.default.readFileSync(resolvedPath, 'utf-8');
        const ext = path.default.extname(resolvedPath).toLowerCase();
        let raw;
        if (ext === '.json') {
            raw = JSON.parse(content);
        }
        else if (ext === '.yaml' || ext === '.yml') {
            const yaml = await import('js-yaml');
            raw = yaml.default.load(content);
        }
        else {
            throw new Error(`Unsupported file format: ${ext}`);
        }
        const rawSteps = raw.steps;
        if (!rawSteps || !Array.isArray(rawSteps)) {
            throw new Error('Pipeline file must contain a "steps" array');
        }
        const steps = rawSteps.map((s) => ({
            type: s.type || 'tool',
            name: String(s.name || ''),
            args: s.args || {},
            rawArgs: s.rawArgs,
            label: s.label,
            timeout: s.timeout,
            approvalGate: s.approvalGate,
        }));
        return {
            name: String(raw.name || ''),
            description: raw.description,
            steps,
        };
    }
    /**
     * Validate a pipeline definition (steps array).
     * Returns a list of error/warning messages.
     */
    validateDefinition(steps) {
        const errors = [];
        const warnings = [];
        if (!steps || steps.length === 0) {
            errors.push('Pipeline must have at least one step');
            return { valid: false, errors, warnings };
        }
        if (steps.length > this.config.maxSteps) {
            errors.push(`Pipeline exceeds maximum of ${this.config.maxSteps} steps (has ${steps.length})`);
        }
        const names = new Set();
        for (let i = 0; i < steps.length; i++) {
            const step = steps[i];
            if (!step.name) {
                errors.push(`Step ${i + 1}: missing name`);
            }
            if (names.has(step.name)) {
                warnings.push(`Step ${i + 1}: duplicate name "${step.name}"`);
            }
            names.add(step.name);
            if (step.type && !['tool', 'skill', 'function', 'transform', 'approval'].includes(step.type)) {
                errors.push(`Step ${i + 1} ("${step.name}"): invalid type "${step.type}"`);
            }
            if (step.type === 'transform' && !BUILTIN_TRANSFORMS[step.name]) {
                warnings.push(`Step ${i + 1} ("${step.name}"): unknown transform (not built-in)`);
            }
        }
        return { valid: errors.length === 0, errors, warnings };
    }
    // ==========================================================================
    // Lifecycle
    // ==========================================================================
    dispose() {
        // Reject any pending approvals
        for (const [idx, pending] of this.pendingApprovals) {
            pending.reject(new Error('Pipeline disposed'));
            this.pendingApprovals.delete(idx);
        }
        this.approvalHistory = [];
        this.removeAllListeners();
    }
}
// ============================================================================
// Singleton
// ============================================================================
let compositorInstance = null;
export function getPipelineCompositor(config) {
    if (!compositorInstance) {
        compositorInstance = new PipelineCompositor(config);
    }
    return compositorInstance;
}
export function resetPipelineCompositor() {
    if (compositorInstance) {
        compositorInstance.dispose();
    }
    compositorInstance = null;
}
//# sourceMappingURL=pipeline.js.map