/**
 * AI Profile / Config / Snapshot canonical types.
 *
 * Spec authority:
 *   P-AISC-001~005  AIScopeRef identity
 *   D-AIPC-001~012  Three-tier AI configuration
 *   K-AIEXEC-001~005 Runtime execution contract
 *   S-AICONF-001~006 SDK surface contract
 */
import { createNimiError } from '../runtime/errors.js';
import { createNimiUlid } from '../runtime/ids.js';
import { ReasonCode } from '../types/index.js';
export { computeAIConfigDiff, computeAIConfigVersion, createAIConfigEvidence, snapshotAIConfig, } from './ai-config-diff.js';
import { createAIConfigEvidence } from './ai-config-diff.js';
// ---------------------------------------------------------------------------
// Factory functions
// ---------------------------------------------------------------------------
const DEFAULT_SCOPE = { kind: 'app', ownerId: 'desktop', surfaceId: 'chat' };
const CANONICAL_MOD_SCOPE_SURFACE_ID = 'workspace';
// ---------------------------------------------------------------------------
// Built-in first-run chat scopes  (P-AISC-006 / S-AICONF-007)
// ---------------------------------------------------------------------------
/** Canonical owner of the two built-in first-run chat feature surfaces. */
const BUILT_IN_CHAT_FEATURE_OWNER_ID = 'desktop.chat';
const BUILT_IN_CHAT_SURFACE_IDS = ['nimi', 'agent'];
function normalizeRequiredId(value, label) {
    const normalized = String(value || '').trim();
    if (!normalized) {
        throw createNimiError({
            message: `${label} is required`,
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: `provide_${label.replace(/\s+/g, '_')}`,
            source: 'sdk',
        });
    }
    return normalized;
}
/** Create the default app-level scope ref for Phase 1. */
export function createDefaultAIScopeRef() {
    return { ...DEFAULT_SCOPE };
}
/** Create the canonical Phase 1 mod workspace scope ref. */
export function createCanonicalModAIScopeRef(modId) {
    return {
        kind: 'mod',
        ownerId: normalizeRequiredId(modId, 'mod id'),
        surfaceId: CANONICAL_MOD_SCOPE_SURFACE_ID,
    };
}
/** True when the scope matches the canonical Phase 1 mod workspace scope. */
export function isCanonicalModAIScopeRef(scopeRef, modId) {
    if (!scopeRef) {
        return false;
    }
    const normalizedModId = String(modId || '').trim();
    if (!normalizedModId) {
        return false;
    }
    return scopeRef.kind === 'mod'
        && String(scopeRef.ownerId || '').trim() === normalizedModId
        && String(scopeRef.surfaceId || '').trim() === CANONICAL_MOD_SCOPE_SURFACE_ID;
}
/** Assert that the caller provided the canonical Phase 1 mod workspace scope. */
export function assertCanonicalModAIScopeRef(scopeRef, modId) {
    const canonicalScopeRef = createCanonicalModAIScopeRef(modId);
    if (!scopeRef) {
        throw createNimiError({
            message: 'mod AIConfig scopeRef is required',
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'provide_explicit_mod_scope_ref',
            source: 'sdk',
        });
    }
    if (!isCanonicalModAIScopeRef(scopeRef, canonicalScopeRef.ownerId)) {
        throw createNimiError({
            message: `mod AIConfig scopeRef must equal ${canonicalScopeRef.kind}:${canonicalScopeRef.ownerId}:${canonicalScopeRef.surfaceId}`,
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'use_canonical_mod_workspace_scope_ref',
            source: 'sdk',
        });
    }
    return canonicalScopeRef;
}
/**
 * Create a canonical built-in first-run chat AIScopeRef (P-AISC-006).
 *
 * Produces the `feature` shape owned by `desktop.chat`:
 *   - `nimi`  -> { kind: 'feature', ownerId: 'desktop.chat', surfaceId: 'nimi' }
 *   - `agent` -> { kind: 'feature', ownerId: 'desktop.chat', surfaceId: 'agent' }
 *
 * The caller must pass an explicit surface id. There is no omitted-scope
 * inference and the result is never the generic `DEFAULT_SCOPE`.
 */
export function createBuiltInChatAIScopeRef(surfaceId) {
    const normalized = String(surfaceId || '').trim();
    if (normalized !== 'nimi' && normalized !== 'agent') {
        throw createNimiError({
            message: "built-in chat surface id must be 'nimi' or 'agent'",
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'provide_canonical_built_in_chat_surface_id',
            source: 'sdk',
        });
    }
    return {
        kind: 'feature',
        ownerId: BUILT_IN_CHAT_FEATURE_OWNER_ID,
        surfaceId: normalized,
    };
}
/** True when the scope is exactly one of the two canonical built-in chat scopes. */
export function isBuiltInChatAIScopeRef(scopeRef) {
    if (!scopeRef) {
        return false;
    }
    const surfaceId = String(scopeRef.surfaceId || '').trim();
    return scopeRef.kind === 'feature'
        && String(scopeRef.ownerId || '').trim() === BUILT_IN_CHAT_FEATURE_OWNER_ID
        && (surfaceId === 'nimi' || surfaceId === 'agent');
}
/**
 * Assert that the caller provided an exact canonical built-in chat scope.
 *
 * Rejects: omitted/null scope, generic `app:desktop:chat`, the retired
 * `app:desktop.chat.nimi|agent` shape, a merged `desktop.chat` scope with no
 * `surfaceId`, and any other non-canonical key. SDK never infers the scope.
 */
export function assertBuiltInChatAIScopeRef(scopeRef) {
    if (!scopeRef) {
        throw createNimiError({
            message: 'built-in chat AIScopeRef is required and must be provided explicitly',
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'provide_explicit_built_in_chat_scope_ref',
            source: 'sdk',
        });
    }
    if (!isBuiltInChatAIScopeRef(scopeRef)) {
        throw createNimiError({
            message: "built-in chat AIScopeRef must equal feature:desktop.chat:nimi or feature:desktop.chat:agent",
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'use_canonical_built_in_chat_scope_ref',
            source: 'sdk',
        });
    }
    return createBuiltInChatAIScopeRef(scopeRef.surfaceId);
}
/** The canonical first-run built-in chat scope set, in stable order (P-AISC-006). */
export function builtInChatAIScopeRefs() {
    return BUILT_IN_CHAT_SURFACE_IDS.map((surfaceId) => createBuiltInChatAIScopeRef(surfaceId));
}
/**
 * Apply the selected first-run baseline AIProfile to a single explicit
 * canonical built-in chat scope, delegating durable evidence minting to the
 * host AIConfig service (S-AICONF-007).
 *
 * The `applyHostAiConfig` callback is the Desktop host AIConfig authority seam:
 * it owns atomic apply (D-AIPC-005) and returns the host/backend-issued durable
 * evidence ref. The SDK never mints the ref, never accepts a caller-provided
 * string as evidence, and never infers the scope from an omitted argument.
 */
export async function applyFirstRunBuiltInChatAIConfig(input) {
    const boundScopeRef = assertBuiltInChatAIScopeRef(input.scopeRef);
    const validation = validateAIProfile(input.profile);
    if (!validation.valid) {
        throw createNimiError({
            message: `first-run built-in chat AIProfile is invalid: ${validation.errors.join('; ')}`,
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'provide_valid_first_run_built_in_chat_ai_profile',
            source: 'sdk',
        });
    }
    if (typeof input.applyHostAiConfig !== 'function') {
        throw createNimiError({
            message: 'first-run built-in chat AIConfig requires a host AIConfig apply authority',
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'provide_host_ai_config_apply_authority',
            source: 'sdk',
        });
    }
    const ref = String(await input.applyHostAiConfig(boundScopeRef, input.profile) || '').trim();
    if (!ref) {
        throw createNimiError({
            message: 'host AIConfig service did not return a durable built-in AIConfig ref',
            reasonCode: ReasonCode.ACTION_INPUT_INVALID,
            actionHint: 'host_ai_config_service_must_return_durable_ref',
            source: 'sdk',
        });
    }
    return { scopeRef: boundScopeRef, ref };
}
/**
 * Apply the selected first-run baseline AIProfile to BOTH canonical built-in
 * chat scopes (`desktop.chat.nimi` and `desktop.chat.agent`) and collect the
 * host-issued durable evidence refs (S-AICONF-007 / D-AIPC-013).
 *
 * Fails closed if either scope's apply fails — no partial built-in set is
 * returned. The generic `DEFAULT_SCOPE` is never reachable from this path.
 */
export async function applyFirstRunBuiltInChatAIConfigs(input) {
    const builtInAiConfigRefs = [];
    for (const scopeRef of builtInChatAIScopeRefs()) {
        builtInAiConfigRefs.push(await applyFirstRunBuiltInChatAIConfig({
            scopeRef,
            profile: input.profile,
            applyHostAiConfig: input.applyHostAiConfig,
        }));
    }
    return { builtInAiConfigRefs };
}
/** Create an empty AIConfig for a given scope. */
export function createEmptyAIConfig(scopeRef) {
    return {
        scopeRef: scopeRef || createDefaultAIScopeRef(),
        capabilities: { selectedBindings: {}, localProfileRefs: {}, selectedParams: {} },
        profileOrigin: null,
    };
}
/** Create an empty adjacent memory-embedding config for a given scope. */
export function createEmptyMemoryEmbeddingConfig(scopeRef) {
    const now = new Date().toISOString();
    return {
        scopeRef: scopeRef || createDefaultAIScopeRef(),
        sourceKind: null,
        bindingRef: null,
        revisionToken: now,
        updatedAt: now,
    };
}
// ---------------------------------------------------------------------------
// Factory functions
// ---------------------------------------------------------------------------
/**
 * Apply an AIProfile to an AIConfig via atomic overwrite (D-AIPC-005).
 * Returns a new AIConfig with the profile's capability intents materialized.
 * The original config's scopeRef is preserved.
 */
export function applyAIProfileToConfig(config, profile) {
    const selectedBindings = {};
    const localProfileRefs = {};
    const selectedParams = {};
    for (const [capability, intent] of Object.entries(profile.capabilities)) {
        if (!intent)
            continue;
        if (intent.binding !== undefined) {
            selectedBindings[capability] = intent.binding;
        }
        if (intent.localProfileRef !== undefined) {
            localProfileRefs[capability] = intent.localProfileRef;
        }
        if (intent.params !== undefined && intent.params !== null) {
            selectedParams[capability] = intent.params;
        }
    }
    return {
        scopeRef: config.scopeRef,
        capabilities: { selectedBindings, localProfileRefs, selectedParams },
        profileOrigin: {
            profileId: profile.profileId,
            title: profile.title,
            appliedAt: new Date().toISOString(),
        },
    };
}
/** Static schema validation for AIProfile (D-AIPC-012 layer 1). */
export function validateAIProfile(profile) {
    const errors = [];
    if (!profile || typeof profile !== 'object' || Array.isArray(profile)) {
        return { valid: false, errors: ['profile must be a non-null object'] };
    }
    const p = profile;
    if (typeof p.profileId !== 'string' || !p.profileId)
        errors.push('profileId is required');
    if (typeof p.title !== 'string' || !p.title)
        errors.push('title is required');
    if (typeof p.description !== 'string')
        errors.push('description must be a string');
    if (!Array.isArray(p.tags))
        errors.push('tags must be an array');
    if (!p.capabilities || typeof p.capabilities !== 'object' || Array.isArray(p.capabilities)) {
        errors.push('capabilities must be a non-null object');
    }
    return { valid: errors.length === 0, errors };
}
/** Create a canonical execution ID for AISnapshot records. */
export function createAISnapshotExecutionId(nowMs = Date.now()) {
    return createNimiUlid(nowMs);
}
/** Create a canonical AISnapshot record using the published SDK schema. */
export function createAISnapshotRecord(input) {
    const executionId = String(input.executionId || '').trim() || createAISnapshotExecutionId();
    const createdAt = String(input.createdAt || '').trim() || new Date().toISOString();
    return {
        executionId,
        scopeRef: input.scopeRef || input.config.scopeRef,
        configEvidence: createAIConfigEvidence(input.config),
        conversationCapabilitySlice: {
            executionId,
            createdAt,
            capability: String(input.capability || '').trim(),
            selectedBinding: input.selectedBinding || null,
            resolvedBinding: input.resolvedBinding ?? null,
            health: input.health ?? null,
            metadata: input.metadata ?? null,
            agentResolution: input.agentResolution ?? null,
        },
        runtimeEvidence: input.runtimeEvidence ?? null,
        createdAt,
    };
}
//# sourceMappingURL=ai-config.js.map