import { ActionNotFoundError, ActionRegistrationError, ActionValidationError } from './errors.js';
import { validateJsonSchemaLite } from './json-schema-lite.js';
export class InMemoryAgentRelayActions {
    actions = new Map();
    eventHandlers = new Set();
    onEvent(handler) {
        this.eventHandlers.add(handler);
        return () => {
            this.eventHandlers.delete(handler);
        };
    }
    emitListenerEvent(event) {
        for (const handler of this.eventHandlers) {
            try {
                handler(event);
            }
            catch {
                // Listener errors must not break action invocation.
            }
        }
    }
    register(definition) {
        const name = normalizeActionName(definition.name);
        if (!name) {
            throw new ActionRegistrationError('Action name is required');
        }
        if (this.actions.has(name)) {
            throw new ActionRegistrationError(`Action already registered: ${name}`);
        }
        this.actions.set(name, {
            ...definition,
            name,
            description: definition.description ?? name,
            inputSchema: definition.inputSchema ?? definition.input,
            outputSchema: definition.outputSchema ?? definition.output,
            visibility: definition.visibility ?? 'agent',
        });
        return {
            unregister: () => {
                this.actions.delete(name);
            },
        };
    }
    async invoke(input) {
        const name = normalizeActionName(input.name);
        const definition = this.actions.get(name);
        const context = actionContext(input);
        if (!definition) {
            return {
                action: name,
                ok: false,
                error: { code: 'action_not_found', message: `Unknown action: ${name}` },
            };
        }
        const at = new Date().toISOString();
        await context.emit?.({
            type: 'action.invoked',
            action: name,
            caller: context.caller.name,
            at,
        });
        this.emitListenerEvent({
            type: 'action.invoked',
            action: name,
            caller: context.caller,
            input: input.input,
            at,
        });
        const inputValidation = validateActionSchema(input.input, definition.inputSchema);
        if (!inputValidation.valid) {
            const message = formatValidationIssues(inputValidation.issues);
            await context.emit?.({
                type: 'action.failed',
                action: name,
                caller: context.caller.name,
                at: new Date().toISOString(),
                error: message,
            });
            return { action: name, ok: false, error: { code: 'invalid_input', message } };
        }
        const actionInput = inputValidation.value;
        const decision = await definition.policy?.(actionInput, context);
        if (decision && !decision.allowed) {
            await context.emit?.({
                type: 'action.denied',
                action: name,
                caller: context.caller.name,
                at: new Date().toISOString(),
                reason: decision.reason,
            });
            this.emitListenerEvent({
                type: 'action.denied',
                action: name,
                caller: context.caller,
                input: actionInput,
                reason: decision.reason,
                at: new Date().toISOString(),
            });
            return {
                action: name,
                ok: false,
                error: { code: 'action_denied', message: decision.reason ?? 'Action denied' },
            };
        }
        try {
            const output = await definition.handler(actionInput, context);
            const outputValidation = validateActionSchema(output, definition.outputSchema);
            if (!outputValidation.valid) {
                const message = formatValidationIssues(outputValidation.issues);
                await context.emit?.({
                    type: 'action.failed',
                    action: name,
                    caller: context.caller.name,
                    at: new Date().toISOString(),
                    error: message,
                });
                return { action: name, ok: false, error: { code: 'invalid_output', message } };
            }
            await context.emit?.({
                type: 'action.completed',
                action: name,
                caller: context.caller.name,
                at: new Date().toISOString(),
            });
            this.emitListenerEvent({
                type: 'action.completed',
                action: name,
                caller: context.caller,
                input: actionInput,
                output: outputValidation.value,
                at: new Date().toISOString(),
            });
            return { action: name, ok: true, output: outputValidation.value };
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            await context.emit?.({
                type: 'action.failed',
                action: name,
                caller: context.caller.name,
                at: new Date().toISOString(),
                error: message,
            });
            this.emitListenerEvent({
                type: 'action.failed',
                action: name,
                caller: context.caller,
                input: actionInput,
                error: message,
                at: new Date().toISOString(),
            });
            return { action: name, ok: false, error: { code: 'action_failed', message } };
        }
    }
    async list(input) {
        return [...this.actions.values()]
            .filter((definition) => !input?.visibility || (definition.visibility ?? 'agent') === input.visibility)
            .map((definition) => ({
            name: definition.name,
            description: definition.description ?? definition.name,
            inputSchema: definition.inputSchema,
            outputSchema: definition.outputSchema,
            visibility: definition.visibility ?? 'agent',
        }));
    }
    get(name) {
        const definition = this.actions.get(normalizeActionName(name));
        if (!definition) {
            return undefined;
        }
        return {
            name: definition.name,
            description: definition.description ?? definition.name,
            inputSchema: definition.inputSchema,
            outputSchema: definition.outputSchema,
            visibility: definition.visibility ?? 'agent',
        };
    }
    has(name) {
        return this.actions.has(normalizeActionName(name));
    }
    unregister(name) {
        return this.actions.delete(normalizeActionName(name));
    }
    clear() {
        this.actions.clear();
    }
    async execute(name, input, context = { caller: { name: 'sdk' } }) {
        const result = await this.invoke({ name, input, context });
        if (result.ok) {
            return result.output;
        }
        const actionName = normalizeActionName(name);
        if (result.error?.code === 'action_not_found') {
            throw new ActionNotFoundError(actionName);
        }
        if (result.error?.code === 'invalid_input') {
            throw new ActionValidationError(actionName, 'input', [{ path: '$', message: result.error.message }]);
        }
        if (result.error?.code === 'invalid_output') {
            throw new ActionValidationError(actionName, 'output', [{ path: '$', message: result.error.message }]);
        }
        throw new Error(result.error?.message ?? `Action failed: ${actionName}`);
    }
}
export class ActionRegistry extends InMemoryAgentRelayActions {
}
function normalizeActionName(name) {
    return name.trim();
}
function formatValidationIssues(issues) {
    return issues.map((issue) => `${issue.path}: ${issue.message}`).join('; ');
}
function validateActionSchema(input, schema) {
    if (isZodLikeSchema(schema)) {
        const result = schema.safeParse(input);
        if (result.success) {
            return { valid: true, issues: [], value: result.data };
        }
        return {
            valid: false,
            issues: zodIssues(result.error),
            value: input,
        };
    }
    // Anything with a `safeParse` method was handled above, so the remainder is
    // a JSON-schema-lite definition.
    return {
        ...validateJsonSchemaLite(input, schema),
        value: input,
    };
}
function isZodLikeSchema(schema) {
    return Boolean(schema &&
        typeof schema === 'object' &&
        !Array.isArray(schema) &&
        typeof schema.safeParse === 'function');
}
function zodIssues(error) {
    const issues = error.issues ?? [];
    if (issues.length === 0) {
        return [{ path: '$', message: 'invalid value' }];
    }
    return issues.map((issue) => ({
        path: formatZodPath(issue.path ?? []),
        message: issue.message,
    }));
}
function formatZodPath(path) {
    if (path.length === 0)
        return '$';
    let result = '$';
    for (const part of path) {
        result = typeof part === 'number' ? `${result}[${part}]` : `${result}.${part}`;
    }
    return result;
}
function actionContext(input) {
    return {
        ...input.context,
        caller: input.context?.caller ?? input.caller ?? { name: 'sdk' },
        workspaceId: input.context?.workspaceId ?? input.workspaceId,
        messaging: input.context?.messaging ?? input.messaging,
        emit: input.context?.emit ?? input.emit,
    };
}
//# sourceMappingURL=registry.js.map