import { NodeLLM } from "../llm.js";
/**
 * Base class for creating reusable, class-configured agents.
 */
export class Agent {
    // Static configuration properties - override these in subclasses
    static model;
    static provider;
    static instructions;
    static tools;
    static temperature;
    static thinking;
    static schema;
    static params;
    static headers;
    static maxTokens;
    static maxToolCalls;
    static assumeModelExists;
    static middlewares;
    /**
     * Explicitly declare which inputs this agent expects.
     * Useful for introspection and validation.
     */
    static inputs;
    /**
     * Hook called when the agent starts a new session (ask/stream).
     */
    static onStart(_context) {
        // Override in subclass
    }
    static onThinking(_thinking, _result) {
        // Override in subclass
    }
    static onToolStart(_toolCall) {
        // Override in subclass
    }
    static onToolEnd(_toolCall, _result) {
        // Override in subclass
    }
    static onToolError(_toolCall, _error) {
        // Override in subclass
    }
    static onComplete(_result) {
        // Override in subclass
    }
    // --- Static Execution API ---
    /**
     * Run the agent immediately with a prompt.
     */
    static async ask(message, options) {
        const agent = new this({ ...options });
        return agent.ask(message, options);
    }
    /**
     * Stream the agent response immediately.
     */
    static stream(message, options) {
        const agent = new this({ ...options });
        return agent.stream(message, options);
    }
    /** The underlying Chat instance */
    chat;
    /** Private reference to configuration overrides for lazy resolution */
    config;
    /**
     * Create a new agent instance.
     * @param overrides - Optional configuration to override static properties
     */
    constructor(overrides = {}) {
        this.config = overrides;
        const ctor = this.constructor;
        // Build chat options from static properties + overrides
        const chatOptions = {
            provider: overrides.provider ?? ctor.provider,
            assumeModelExists: overrides.assumeModelExists ?? ctor.assumeModelExists,
            temperature: overrides.temperature ?? ctor.temperature,
            maxTokens: overrides.maxTokens ?? ctor.maxTokens,
            maxToolCalls: overrides.maxToolCalls ?? ctor.maxToolCalls,
            headers: { ...ctor.headers, ...overrides.headers },
            params: { ...ctor.params, ...overrides.params },
            thinking: overrides.thinking ?? ctor.thinking,
            messages: overrides.messages,
            middlewares: [...(ctor.middlewares || []), ...(overrides.middlewares || [])]
        };
        // Determine model
        const model = overrides.model ?? ctor.model;
        if (!model) {
            throw new Error(`[Agent] No model specified. Set static model property or pass model in constructor.`);
        }
        const llm = overrides.llm ?? NodeLLM;
        this.chat = llm.chat(model, chatOptions);
        // Initial resolution if inputs are provided in constructor
        if (overrides.inputs) {
            this.resolveLazyConfig(overrides.inputs);
        }
        else {
            // Fallback: apply static/direct instructions immediately if they aren't functions
            const instructions = overrides.instructions ?? ctor.instructions;
            if (instructions && typeof instructions !== "function") {
                this.chat.withInstructions(instructions);
            }
            const tools = overrides.tools ?? ctor.tools;
            if (tools && typeof tools !== "function" && tools.length > 0) {
                this.chat.withTools(tools);
            }
        }
        // Apply schema
        const schema = overrides.schema ?? ctor.schema;
        if (schema) {
            this.chat.withSchema(schema);
        }
        // Wire up telemetry hooks
        if (ctor.onStart) {
            this.chat.beforeRequest(async (messages) => {
                if (ctor.onStart)
                    await ctor.onStart({ messages });
            });
        }
        if (ctor.onToolStart) {
            this.chat.onToolCallStart(async (toolCall) => {
                if (ctor.onToolStart)
                    await ctor.onToolStart(toolCall);
            });
        }
        if (ctor.onToolEnd) {
            this.chat.onToolCallEnd(async (toolCall, result) => {
                if (ctor.onToolEnd)
                    await ctor.onToolEnd(toolCall, result);
            });
        }
        if (ctor.onToolError) {
            this.chat.onToolCallError(async (toolCall, error) => {
                if (ctor.onToolError)
                    await ctor.onToolError(toolCall, error);
            });
        }
        if (ctor.onComplete || ctor.onThinking) {
            this.chat.onEndMessage(async (msg) => {
                if (msg.thinking && ctor.onThinking) {
                    await ctor.onThinking(msg.thinking, msg);
                }
                if (ctor.onComplete) {
                    await ctor.onComplete(msg);
                }
            });
        }
    }
    /**
     * Helper to resolve lazy instructions and tools based on inputs.
     */
    resolveLazyConfig(inputs) {
        if (!inputs)
            return;
        const ctor = this.constructor;
        // 1. Resolve Instructions
        let instructions = this.config.instructions ?? ctor.instructions;
        if (typeof instructions === "function") {
            instructions = instructions(inputs);
        }
        if (typeof instructions === "string") {
            this.chat.withInstructions(instructions, { replace: true });
        }
        // 2. Resolve Tools
        let tools = this.config.tools ?? ctor.tools;
        if (typeof tools === "function") {
            tools = tools(inputs);
        }
        if (Array.isArray(tools)) {
            this.chat.withTools(tools, { replace: true });
        }
    }
    // --- Fluent Configuration ---
    /**
     * Add instructions to the agent (replaces or appends).
     */
    withInstructions(instructions, options) {
        this.chat.withInstructions(instructions, options);
        return this;
    }
    /**
     * Add tools to the agent.
     */
    withTools(tools, options) {
        this.chat.withTools(tools, options);
        return this;
    }
    /**
     * Alias for withTools([tool]).
     */
    use(tool) {
        return this.withTools([tool]);
    }
    /**
     * Send a message to the agent and get a response.
     */
    async ask(message, options) {
        if (options?.inputs) {
            this.resolveLazyConfig(options.inputs);
        }
        return this.chat.ask(message, options);
    }
    /**
     * Hook called when a tool call starts.
     */
    onToolCallStart(handler) {
        this.chat.onToolCallStart(handler);
        return this;
    }
    /**
     * Hook called when a tool call ends.
     */
    onToolCallEnd(handler) {
        this.chat.onToolCallEnd(handler);
        return this;
    }
    /**
     * Hook called when a tool call errors.
     */
    onToolCallError(handler) {
        this.chat.onToolCallError(handler);
        return this;
    }
    /**
     * Hook called before a request.
     */
    beforeRequest(handler) {
        this.chat.beforeRequest(handler);
        return this;
    }
    /**
     * Hook called after a response.
     */
    afterResponse(handler) {
        this.chat.afterResponse(handler);
        return this;
    }
    /**
     * Alias for ask()
     */
    async say(message, options) {
        return this.ask(message, options);
    }
    /**
     * Stream a response from the agent.
     */
    stream(message, options) {
        if (options?.inputs) {
            this.resolveLazyConfig(options.inputs);
        }
        return this.chat.stream(message, options);
    }
    /**
     * Get the conversation history.
     */
    get history() {
        return this.chat.history;
    }
    /**
     * Get the model ID being used.
     */
    get modelId() {
        return this.chat.modelId;
    }
    /**
     * Get aggregate token usage across the conversation.
     */
    get totalUsage() {
        return this.chat.totalUsage;
    }
    /**
     * Access the underlying Chat instance for advanced operations.
     */
    get underlyingChat() {
        return this.chat;
    }
}
/**
 * Helper function to define an agent inline without creating a class.
 */
export function defineAgent(config) {
    return class extends Agent {
        static model = config.model;
        static provider = config.provider;
        static instructions = config.instructions;
        static tools = config.tools;
        static temperature = config.temperature;
        static thinking = config.thinking;
        static schema = config.schema;
        static params = config.params;
        static headers = config.headers;
        static maxTokens = config.maxTokens;
        static maxToolCalls = config.maxToolCalls;
        static assumeModelExists = config.assumeModelExists;
        static middlewares = config.middlewares;
    };
}
