/**
 * Dispatch table that routes HostToolRequest/HostToolResult operations to the
 * per-tool ToolContract by `kind`. Construct once and pass around.
 */
import { HostToolResultStatus } from './host-tool.js';
export function createToolRegistry(contracts) {
    const contractsByKind = new Map();
    for (const contract of contracts) {
        if (contractsByKind.has(contract.kind)) {
            throw new Error(`Duplicate host tool kind in registry: ${contract.kind}`);
        }
        contractsByKind.set(contract.kind, contract);
    }
    const kinds = Object.freeze([...contractsByKind.keys()]);
    const lookup = (kind) => contractsByKind.get(kind);
    const isApprovalRequirement = (value) => {
        if (!value || typeof value !== 'object') {
            return false;
        }
        const candidate = value;
        return (typeof candidate.required === 'boolean' &&
            (candidate.reason === undefined || typeof candidate.reason === 'string'));
    };
    const isHostToolHeartbeatMeta = (value) => {
        if (value === undefined)
            return true;
        if (!value || typeof value !== 'object') {
            return false;
        }
        const candidate = value;
        return ((candidate.toolName === undefined || typeof candidate.toolName === 'string') &&
            (candidate.label === undefined || typeof candidate.label === 'string'));
    };
    const hasHostToolResultBaseFields = (value) => {
        if (!value || typeof value !== 'object') {
            return false;
        }
        const candidate = value;
        const statusIsValid = candidate.status === HostToolResultStatus.Succeeded ||
            candidate.status === HostToolResultStatus.Failed ||
            candidate.status === HostToolResultStatus.Cancelled;
        const userMessageValid = candidate.userMessage === undefined || typeof candidate.userMessage === 'string';
        return (typeof candidate.kind === 'string' &&
            typeof candidate.requestId === 'string' &&
            statusIsValid &&
            userMessageValid);
    };
    return {
        kinds,
        get: lookup,
        evaluateApproval(kind, params, context) {
            const contract = lookup(kind);
            if (!contract) {
                throw new Error(`Unknown host tool kind: ${kind}`);
            }
            if (!contract.validateParams(params)) {
                throw new Error(`Invalid params for host tool "${kind}"`);
            }
            return contract.evaluateApproval(params, context);
        },
        isHostToolRequest(value) {
            if (!value || typeof value !== 'object') {
                return false;
            }
            const candidate = value;
            if (typeof candidate.kind !== 'string' ||
                typeof candidate.requestId !== 'string' ||
                !isApprovalRequirement(candidate.approval) ||
                !isHostToolHeartbeatMeta(candidate.heartbeat)) {
                return false;
            }
            const contract = lookup(candidate.kind);
            if (!contract) {
                return false;
            }
            return contract.validateParams(candidate.params);
        },
        isHostToolResult(value) {
            if (!hasHostToolResultBaseFields(value)) {
                return false;
            }
            const contract = lookup(value.kind);
            if (!contract) {
                return false;
            }
            // Succeeded results carry the tool's typed payload — validate strictly.
            // Failed/Cancelled rides the user-facing reason on the top-level
            // `userMessage` field (validated above), and the tool's typed schema
            // doesn't apply, so `details` must be either undefined or pass the
            // tool's own validator (no special-case shapes here).
            if (value.status === HostToolResultStatus.Succeeded) {
                if (value.details === undefined)
                    return false;
                return contract.validateResultDetails(value.details);
            }
            if (value.details === undefined)
                return true;
            return contract.validateResultDetails(value.details);
        },
        normalizeHostToolResult(result) {
            const contract = lookup(result.kind);
            if (!contract) {
                throw new Error(`Cannot normalize unknown host tool kind: ${result.kind}`);
            }
            // Failed/Cancelled rides the user-facing reason on top-level `userMessage`;
            // `details` is undefined for synthetic infra failures and may be a valid
            // partial typed payload otherwise. Running the per-tool normalizer over a
            // non-success result is unsafe because per-tool normalizers are written
            // against the success-shape invariants (e.g. `value.updated_items.map(...)`)
            // and don't have to defend against absent fields.
            if (result.status !== HostToolResultStatus.Succeeded) {
                return result;
            }
            return {
                ...result,
                details: contract.normalizeResultDetails(result.details)
            };
        }
    };
}
//# sourceMappingURL=host-tool-registry.js.map