import { createNimiError } from '../errors.js';
import { ReasonCode } from '../../types/index.js';
import { RuntimeMethodIds } from '../method-ids.js';
import { RoutePolicy, } from '../generated/runtime/v1/ai.js';
import { AuthorizationPreset, PolicyMode, } from '../generated/runtime/v1/grant.js';
import { ConnectorAuthKind, ConnectorStatus, } from '../generated/runtime/v1/connector.js';
import { CONNECTOR_AUTH_PROFILES } from '../connector-auth-profiles.generated.js';
import { ExternalPrincipalType } from '../generated/runtime/v1/common.js';
import { normalizeText } from '../../internal/utils.js';
export function ensureAppId(appId) {
    const normalized = String(appId || '').trim();
    if (!normalized) {
        throw createNimiError({
            message: 'createRuntimeClient requires appId',
            reasonCode: ReasonCode.SDK_APP_ID_REQUIRED,
            actionHint: 'set_app_id',
            source: 'sdk',
        });
    }
    return normalized;
}
export { normalizeText };
function hasOwnField(value, key) {
    return Object.prototype.hasOwnProperty.call(value, key);
}
function throwValidationError(reasonCode, message, actionHint) {
    throw createNimiError({
        message,
        reasonCode,
        actionHint,
        source: 'sdk',
    });
}
function requireNonEmptyField(value, fieldName, reasonCode, actionHint) {
    const normalized = normalizeText(value);
    if (!normalized) {
        throwValidationError(reasonCode, `${fieldName} is required`, actionHint);
    }
    return normalized;
}
function withAiRouteValidation(methodId, request) {
    const routePolicy = request.routePolicy ?? request.head?.routePolicy ?? RoutePolicy.UNSPECIFIED;
    if (routePolicy === RoutePolicy.UNSPECIFIED) {
        throwValidationError(ReasonCode.SDK_RUNTIME_AI_ROUTE_POLICY_REQUIRED, `${methodId} requires explicit routePolicy`, 'set_route_policy_local_or_cloud');
    }
    return request;
}
function validateAiCredentialMetadata(methodId, request, options) {
    const source = normalizeText(options?.metadata?.keySource).toLowerCase();
    const apiKey = normalizeText(options?.metadata?.providerApiKey);
    if (source && source !== 'managed' && source !== 'inline') {
        throwValidationError(ReasonCode.SDK_RUNTIME_AI_CREDENTIAL_SOURCE_INVALID, `${methodId} metadata.keySource is invalid`, 'set_key_source_managed_or_inline');
    }
    const routePolicy = request.routePolicy ?? request.head?.routePolicy ?? RoutePolicy.UNSPECIFIED;
    if (routePolicy === RoutePolicy.CLOUD && source === 'inline' && !apiKey) {
        throwValidationError(ReasonCode.SDK_RUNTIME_AI_CREDENTIAL_MISSING, `${methodId} inline source requires metadata.providerApiKey`, 'set_provider_api_key');
    }
    if (routePolicy === RoutePolicy.LOCAL && source === 'inline') {
        throwValidationError(ReasonCode.SDK_RUNTIME_AI_CREDENTIAL_SCOPE_FORBIDDEN, `${methodId} local route does not allow inline keySource`, 'use_managed_key_source');
    }
}
function hasDecisionAtValue(decisionAt) {
    if (!decisionAt) {
        return false;
    }
    const seconds = normalizeText(decisionAt.seconds);
    if (seconds) {
        return true;
    }
    const nanosRaw = decisionAt.nanos;
    const nanos = typeof nanosRaw === 'number' ? nanosRaw : Number(nanosRaw);
    return Number.isFinite(nanos) && nanos !== 0;
}
function validateAuthorizeExternalPrincipalRequest(request) {
    requireNonEmptyField(request.domain, 'domain', ReasonCode.SDK_RUNTIME_APP_AUTH_DOMAIN_REQUIRED, 'set_domain_app_auth');
    requireNonEmptyField(request.appId, 'appId', ReasonCode.SDK_RUNTIME_APP_AUTH_APP_ID_REQUIRED, 'set_app_id');
    requireNonEmptyField(request.externalPrincipalId, 'externalPrincipalId', ReasonCode.SDK_RUNTIME_APP_AUTH_EXTERNAL_PRINCIPAL_ID_REQUIRED, 'set_external_principal_id');
    if (request.externalPrincipalType === ExternalPrincipalType.UNSPECIFIED) {
        throwValidationError(ReasonCode.SDK_RUNTIME_APP_AUTH_EXTERNAL_PRINCIPAL_TYPE_REQUIRED, 'externalPrincipalType is required', 'set_external_principal_type');
    }
    requireNonEmptyField(request.subjectUserId, 'subjectUserId', ReasonCode.SDK_RUNTIME_APP_AUTH_SUBJECT_USER_ID_REQUIRED, 'set_subject_user_id');
    requireNonEmptyField(request.consentId, 'consentId', ReasonCode.SDK_RUNTIME_APP_AUTH_CONSENT_ID_REQUIRED, 'set_consent_id');
    requireNonEmptyField(request.consentVersion, 'consentVersion', ReasonCode.SDK_RUNTIME_APP_AUTH_CONSENT_VERSION_REQUIRED, 'set_consent_version');
    if (!hasDecisionAtValue(request.decisionAt)) {
        throwValidationError(ReasonCode.SDK_RUNTIME_APP_AUTH_DECISION_AT_REQUIRED, 'decisionAt is required', 'set_decision_at');
    }
    requireNonEmptyField(request.policyVersion, 'policyVersion', ReasonCode.SDK_RUNTIME_APP_AUTH_POLICY_VERSION_REQUIRED, 'set_policy_version');
    if (request.policyMode === PolicyMode.UNSPECIFIED) {
        throwValidationError(ReasonCode.SDK_RUNTIME_APP_AUTH_POLICY_MODE_REQUIRED, 'policyMode is required', 'set_policy_mode_preset_or_custom');
    }
    if (request.policyMode === PolicyMode.PRESET
        && request.preset === AuthorizationPreset.UNSPECIFIED) {
        throwValidationError(ReasonCode.SDK_RUNTIME_APP_AUTH_PRESET_REQUIRED, 'preset is required when policyMode is preset', 'set_authorization_preset');
    }
    if (request.policyMode === PolicyMode.CUSTOM
        && (!Array.isArray(request.scopes) || request.scopes.length === 0)) {
        throwValidationError(ReasonCode.SDK_RUNTIME_APP_AUTH_CUSTOM_SCOPES_REQUIRED, 'custom policy requires scopes', 'set_custom_policy_scopes');
    }
    if (request.policyMode === PolicyMode.CUSTOM) {
        if (typeof request.ttlSeconds !== 'number' || request.ttlSeconds <= 0) {
            throwValidationError(ReasonCode.SDK_RUNTIME_APP_AUTH_CUSTOM_TTL_REQUIRED, 'custom policy requires ttlSeconds > 0', 'set_ttl_seconds');
        }
        if (typeof request.canDelegate !== 'boolean') {
            throwValidationError(ReasonCode.SDK_RUNTIME_APP_AUTH_CUSTOM_DELEGATE_REQUIRED, 'custom policy requires explicit canDelegate boolean', 'set_can_delegate');
        }
    }
    requireNonEmptyField(request.scopeCatalogVersion, 'scopeCatalogVersion', ReasonCode.SDK_RUNTIME_APP_AUTH_SCOPE_CATALOG_VERSION_REQUIRED, 'publish_scope_catalog_before_authorize');
    return request;
}
function validateCreateConnectorRequest(request) {
    const authKind = request.authKind ?? ConnectorAuthKind.UNSPECIFIED;
    const provider = normalizeText(request.provider);
    const apiKey = normalizeText(request.apiKey);
    const providerAuthProfile = normalizeText(request.providerAuthProfile);
    const credentialJson = normalizeText(request.credentialJson);
    if (apiKey && credentialJson) {
        throwValidationError(ReasonCode.SDK_RUNTIME_CONNECTOR_UPDATE_EMPTY, 'createConnector does not allow apiKey and credentialJson together', 'set_single_connector_credential_shape');
    }
    if (authKind === ConnectorAuthKind.OAUTH_MANAGED || credentialJson) {
        requireNonEmptyField(providerAuthProfile, 'providerAuthProfile', ReasonCode.SDK_RUNTIME_CONNECTOR_API_KEY_REQUIRED, 'set_provider_auth_profile');
        requireNonEmptyField(credentialJson, 'credentialJson', ReasonCode.SDK_RUNTIME_CONNECTOR_API_KEY_REQUIRED, 'set_connector_credential_json');
        const profileSpec = CONNECTOR_AUTH_PROFILES[providerAuthProfile];
        if (!profileSpec) {
            throwValidationError(ReasonCode.SDK_RUNTIME_CONNECTOR_API_KEY_REQUIRED, `providerAuthProfile "${providerAuthProfile}" is not admitted`, 'set_supported_provider_auth_profile');
        }
        if (!profileSpec.allowedProviders.includes(provider)) {
            throwValidationError(ReasonCode.SDK_RUNTIME_CONNECTOR_API_KEY_REQUIRED, `providerAuthProfile "${providerAuthProfile}" is incompatible with provider "${provider}"`, 'align_provider_and_auth_profile');
        }
    }
    else {
        requireNonEmptyField(apiKey, 'apiKey', ReasonCode.SDK_RUNTIME_CONNECTOR_API_KEY_REQUIRED, 'set_connector_api_key');
    }
    return request;
}
function validateUpdateConnectorRequest(request) {
    const hasMutableField = request.status !== ConnectorStatus.UNSPECIFIED
        || hasOwnField(request, 'label')
        || hasOwnField(request, 'endpoint')
        || hasOwnField(request, 'apiKey')
        || hasOwnField(request, 'authKind')
        || hasOwnField(request, 'providerAuthProfile')
        || hasOwnField(request, 'credentialJson');
    if (!hasMutableField) {
        throwValidationError(ReasonCode.SDK_RUNTIME_CONNECTOR_UPDATE_EMPTY, 'updateConnector requires at least one mutable field', 'set_connector_update_fields');
    }
    const nextAuthKind = request.authKind;
    const nextProviderAuthProfile = normalizeText(request.providerAuthProfile);
    if (nextAuthKind === ConnectorAuthKind.OAUTH_MANAGED && nextProviderAuthProfile) {
        if (!CONNECTOR_AUTH_PROFILES[nextProviderAuthProfile]) {
            throwValidationError(ReasonCode.SDK_RUNTIME_CONNECTOR_UPDATE_EMPTY, `providerAuthProfile "${nextProviderAuthProfile}" is not admitted`, 'set_supported_provider_auth_profile');
        }
    }
    return request;
}
export function normalizeRequestForMethod(methodId, request, options) {
    switch (methodId) {
        case RuntimeMethodIds.ai.executeScenario:
        case RuntimeMethodIds.ai.streamScenario:
        case RuntimeMethodIds.ai.submitScenarioJob: {
            const normalized = withAiRouteValidation(methodId, request);
            validateAiCredentialMetadata(methodId, normalized, options);
            return normalized;
        }
        case RuntimeMethodIds.appAuth.authorizeExternalPrincipal:
            return validateAuthorizeExternalPrincipalRequest(request);
        case RuntimeMethodIds.connector.createConnector:
            return validateCreateConnectorRequest(request);
        case RuntimeMethodIds.connector.updateConnector:
            return validateUpdateConnectorRequest(request);
        default:
            return request;
    }
}
//# sourceMappingURL=client-validation.js.map