/*
 * Shared omni operation core.
 *
 * omniBase owns the model-callable contract. Runtime/modelAdapter owns media bytes,
 * artifact references, provider lowering, uploads, native codecs, and capability routing.
 */
export function createOmniOperationDescriptor(config) {
    return {
        toolId: config.toolId,
        capability: config.capability,
        route: config.route,
        defaultDryRun: true,
        tapOwnsApproval: true,
        permissionsRequired: config.permissionsRequired,
        runtimeEntry: 'BaseToolExecutorPort.omni.transformMedia',
        runtimeOwnsMaterial: true,
        baseToolOwnsProviderBodyLowering: false,
        dependencyProfile: config.dependencyProfile,
    };
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function isOperationResult(value) {
    return isRecord(value) && typeof value.ok === 'boolean' && typeof value.toolId === 'string' && Array.isArray(value.events);
}
function cleanString(value) {
    return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
}
function cleanNumber(value) {
    return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
function cleanList(value) {
    if (value === undefined)
        return undefined;
    if (!Array.isArray(value))
        return undefined;
    const cleaned = [];
    for (const item of value) {
        const text = cleanString(item);
        if (text === undefined)
            return undefined;
        if (!cleaned.includes(text))
            cleaned.push(text);
    }
    return cleaned;
}
function cleanGate(value) {
    if (value === undefined)
        return undefined;
    if (!isRecord(value))
        return undefined;
    const gate = {};
    if (typeof value.accepted === 'boolean')
        gate.accepted = value.accepted;
    if (typeof value.allowed === 'boolean')
        gate.allowed = value.allowed;
    const reason = cleanString(value.reason);
    if (reason !== undefined)
        gate.reason = reason;
    return gate;
}
function normalizeContext(value, config) {
    if (value === undefined)
        return {};
    if (!isRecord(value))
        return failure(config, 'INVALID_CONTEXT', config.toolId + ' context must be an object', 'input');
    const allowedInputRoots = cleanList(value.allowedInputRoots);
    const allowedOutputRoots = cleanList(value.allowedOutputRoots);
    const grantedPermissions = cleanList(value.grantedPermissions);
    const requestedScopes = cleanList(value.requestedScopes);
    const allowedScopes = cleanList(value.allowedScopes);
    const guard = cleanGate(value.guard);
    const contract = cleanGate(value.contract);
    const governance = cleanGate(value.governance);
    if ((value.allowedInputRoots !== undefined && allowedInputRoots === undefined) ||
        (value.allowedOutputRoots !== undefined && allowedOutputRoots === undefined) ||
        (value.grantedPermissions !== undefined && grantedPermissions === undefined) ||
        (value.requestedScopes !== undefined && requestedScopes === undefined) ||
        (value.allowedScopes !== undefined && allowedScopes === undefined) ||
        (value.guard !== undefined && guard === undefined) ||
        (value.contract !== undefined && contract === undefined) ||
        (value.governance !== undefined && governance === undefined)) {
        return failure(config, 'INVALID_CONTEXT', config.toolId + ' context contains invalid governance metadata', 'input');
    }
    return {
        runtimeId: cleanString(value.runtimeId),
        sessionId: cleanString(value.sessionId),
        invocationId: cleanString(value.invocationId),
        dryRun: typeof value.dryRun === 'boolean' ? value.dryRun : undefined,
        guard,
        allowedInputRoots,
        allowedOutputRoots,
        grantedPermissions,
        requestedScopes,
        allowedScopes,
        contract,
        governance,
        auditMetadata: isRecord(value.auditMetadata) ? value.auditMetadata : undefined,
    };
}
function normalizeTarget(value, config) {
    if (value === undefined)
        return failure(config, 'MISSING_TARGET', config.toolId + ' requires target', 'input');
    if (!isRecord(value))
        return failure(config, 'INVALID_TARGET', config.toolId + ' target must be an object', 'input');
    const target = {};
    for (const [key, item] of Object.entries(value)) {
        if (item !== undefined)
            target[key] = item;
    }
    const prompt = cleanString(target.prompt);
    if (config.requiresPrompt && prompt === undefined) {
        return failure(config, 'MISSING_PROMPT', config.toolId + ' requires target.prompt', 'input');
    }
    if (prompt !== undefined)
        target.prompt = prompt;
    const inputPath = cleanString(target.inputPath) ?? cleanString(target.audioPath) ?? cleanString(target.imagePath) ?? cleanString(target.videoPath);
    const inputRef = cleanString(target.inputRef) ?? cleanString(target.inputArtifactId) ?? cleanString(target.audioRef) ?? cleanString(target.imageRef) ?? cleanString(target.videoRef);
    if (config.requiresInput && inputPath === undefined && inputRef === undefined) {
        return failure(config, 'MISSING_INPUT_TARGET', config.toolId + ' requires an input path or artifact ref', 'input');
    }
    if (inputPath !== undefined)
        target.inputPath = inputPath;
    if (inputRef !== undefined)
        target.inputRef = inputRef;
    const outputPath = cleanString(target.outputPath);
    const outputRef = cleanString(target.outputRef) ?? cleanString(target.outputArtifactId);
    if (config.requiresOutput && outputPath === undefined && outputRef === undefined) {
        return failure(config, 'MISSING_OUTPUT_TARGET', config.toolId + ' requires target.outputPath or target.outputRef', 'input');
    }
    if (outputPath !== undefined)
        target.outputPath = outputPath;
    if (outputRef !== undefined)
        target.outputRef = outputRef;
    const targetFormat = cleanString(target.targetFormat) ?? cleanString(target.format) ?? cleanString(target.mimeType);
    if (targetFormat !== undefined)
        target.targetFormat = targetFormat;
    const maxBytes = cleanNumber(target.maxBytes);
    if (target.maxBytes !== undefined && maxBytes === undefined) {
        return failure(config, 'INVALID_MAX_BYTES', config.toolId + ' target.maxBytes must be a finite number', 'input');
    }
    if (maxBytes !== undefined)
        target.maxBytes = maxBytes;
    const durationSeconds = cleanNumber(target.durationSeconds);
    if (target.durationSeconds !== undefined && (durationSeconds === undefined || durationSeconds <= 0)) {
        return failure(config, 'INVALID_DURATION', config.toolId + ' target.durationSeconds must be a positive finite number', 'input');
    }
    if (durationSeconds !== undefined)
        target.durationSeconds = durationSeconds;
    return target;
}
function invocationId(config, context) {
    return context.invocationId ?? config.toolId + ':dry-run';
}
function auditEvent(config, context, type, metadata) {
    return {
        type,
        toolId: config.toolId,
        invocationId: invocationId(config, context ?? {}),
        dryRun: context?.dryRun !== false,
        metadata: {
            ...(context?.auditMetadata ?? {}),
            ...(metadata ?? {}),
        },
    };
}
function failure(config, code, message, boundary) {
    return {
        ok: false,
        toolId: config.toolId,
        error: { code, message },
        events: [config.toolId + ':' + boundary + ':rejected:' + code],
    };
}
function publicSafeProviderError(error) {
    if (!isRecord(error))
        return undefined;
    const code = cleanString(error.code);
    const message = cleanString(error.message);
    if (code === undefined || message === undefined)
        return undefined;
    if (error.publicSafe !== true)
        return undefined;
    return { code, message };
}
function gateRejected(gate) {
    return gate?.accepted === false || gate?.allowed === false;
}
function gateAccepted(gate) {
    return gate?.accepted === true || gate?.allowed === true;
}
function withinRoots(value, roots) {
    if (roots === undefined || roots.length === 0)
        return true;
    return roots.some((root) => value === root || value.startsWith(root.endsWith('/') ? root : root + '/'));
}
function governanceCheck(config, target, context, dryRun) {
    const inputPath = cleanString(target.inputPath);
    const outputPath = cleanString(target.outputPath);
    if (inputPath !== undefined && !withinRoots(inputPath, context.allowedInputRoots)) {
        return failure(config, 'INPUT_PATH_OUT_OF_SCOPE', config.toolId + ' input path is outside allowed roots', 'scope');
    }
    if (outputPath !== undefined && !withinRoots(outputPath, context.allowedOutputRoots)) {
        return failure(config, 'OUTPUT_PATH_OUT_OF_SCOPE', config.toolId + ' output path is outside allowed roots', 'scope');
    }
    if (context.grantedPermissions !== undefined) {
        const missing = config.permissionsRequired.filter((permission) => !context.grantedPermissions?.includes(permission));
        if (missing.length > 0) {
            return failure(config, 'PERMISSION_DENIED', config.toolId + ' missing required permission: ' + missing.join(', '), 'permission');
        }
    }
    if (context.requestedScopes !== undefined && context.allowedScopes !== undefined) {
        const denied = context.requestedScopes.filter((scope) => !context.allowedScopes?.includes(scope));
        if (denied.length > 0) {
            return failure(config, 'SCOPE_DENIED', config.toolId + ' requested scope is not allowed: ' + denied.join(', '), 'scope');
        }
    }
    if (gateRejected(context.contract)) {
        return failure(config, 'CONTRACT_REJECTED', context.contract?.reason ?? config.toolId + ' contract rejected the request', 'contract');
    }
    if (gateRejected(context.governance)) {
        return failure(config, 'GOVERNANCE_REJECTED', context.governance?.reason ?? config.toolId + ' governance rejected the request', 'governance');
    }
    if (!dryRun && !gateAccepted(context.guard)) {
        return failure(config, 'GOVERNANCE_REJECTED', config.toolId + ' live omni execution requires an accepted runtime guard', 'governance');
    }
    return undefined;
}
function buildOutput(config, target, dryRun, artifact) {
    return {
        kind: 'agentCore.basicTool.' + config.toolId,
        target,
        dispatch: dryRun ? 'dry-run' : 'runtime-omni',
        dryRun,
        providerCalled: !dryRun,
        executionBlocked: dryRun,
        unsafeSideEffects: config.unsafeSideEffects,
        permissionsRequired: config.permissionsRequired,
        requiresTapApproval: true,
        runtimeEntry: {
            port: 'BaseToolExecutorPort.omni.transformMedia',
            operation: config.runtimeOperation,
            runtimeOwnsMaterial: true,
            baseToolOwnsProviderBodyLowering: false,
        },
        operationEnvelope: {
            mediaKind: config.mediaKind,
            action: config.action,
            outputResource: config.outputResource,
            plannedOnly: dryRun,
            materialized: artifact !== undefined,
            artifactId: artifact?.artifactId,
            mimeType: artifact?.mimeType,
        },
        dependencyProfile: config.dependencyProfile,
        providerMetadata: artifact?.metadata,
    };
}
export async function executeOmniOperationCore(config, request = {}) {
    const context = normalizeContext(request.context, config);
    if (isOperationResult(context))
        return context;
    const target = normalizeTarget(request.target, config);
    if (isOperationResult(target))
        return target;
    const dryRun = context.dryRun !== false;
    const governanceFailure = governanceCheck(config, target, context, dryRun);
    if (governanceFailure !== undefined)
        return governanceFailure;
    if (dryRun) {
        return {
            ok: true,
            toolId: config.toolId,
            output: buildOutput(config, target, true),
            audit: [auditEvent(config, context, 'omni.operation.planned', { action: config.action })],
            events: [config.toolId + ':planned'],
        };
    }
    if (request.provider === undefined) {
        return failure(config, 'PROVIDER_UNAVAILABLE', config.toolId + ' requires runtime executor.omni.transformMedia for live execution', 'provider');
    }
    try {
        const inputArtifactId = cleanString(target.inputRef);
        const artifact = await request.provider({
            operation: config.runtimeOperation,
            inputArtifactId,
            parameters: {
                ...target,
                runtimeId: context.runtimeId,
                sessionId: context.sessionId,
                invocationId: invocationId(config, context),
                auditMetadata: context.auditMetadata ?? {},
            },
        });
        if (!isRecord(artifact) || cleanString(artifact.artifactId) === undefined) {
            return failure(config, 'PROVIDER_REJECTED', config.toolId + ' provider returned an invalid artifact envelope', 'provider');
        }
        return {
            ok: true,
            toolId: config.toolId,
            output: buildOutput(config, target, false, artifact),
            audit: [auditEvent(config, context, 'omni.operation.executed', { action: config.action })],
            events: [config.toolId + ':runtime-omni:executed'],
        };
    }
    catch (error) {
        const providerError = publicSafeProviderError(error);
        return failure(config, providerError?.code ?? 'PROVIDER_REJECTED', providerError?.message ?? config.toolId + ' runtime provider rejected the request', 'provider');
    }
}
export function createOmniOperationPlanner(config) {
    return (request = {}) => executeOmniOperationCore(config, {
        ...request,
        context: request.context === undefined
            ? { dryRun: true }
            : isRecord(request.context)
                ? { ...request.context, dryRun: true }
                : request.context,
    });
}
