import GraphModelMigrationBasis from "../../domain/migrations/GraphModelMigrationBasis.js";
import GraphModelMigrationContentMapping from "../../domain/migrations/GraphModelMigrationContentMapping.js";
import GraphModelMigrationEdgeMapping from "../../domain/migrations/GraphModelMigrationEdgeMapping.js";
import GraphModelMigrationManifest from "../../domain/migrations/GraphModelMigrationManifest.js";
import GraphModelMigrationManifestVersion from "../../domain/migrations/GraphModelMigrationManifestVersion.js";
import GraphModelMigrationNodeMapping from "../../domain/migrations/GraphModelMigrationNodeMapping.js";
import GraphModelMigrationNotice from "../../domain/migrations/GraphModelMigrationNotice.js";
import GraphModelMigrationPropertyMapping from "../../domain/migrations/GraphModelMigrationPropertyMapping.js";
import AdapterValidationError from "../../domain/errors/AdapterValidationError.js";
const MANIFEST_KEYS = Object.freeze([
    'version',
    'sourceBasis',
    'targetBasis',
    'nodeMappings',
    'edgeMappings',
    'propertyMappings',
    'contentMappings',
    'warnings',
    'fatalErrors',
]);
/** Serializes a migration manifest as deterministic JSON text. */
export function serializeGraphModelMigrationManifest(manifest) {
    return `${JSON.stringify(manifestToJson(requireManifest(manifest)), null, 2)}\n`;
}
/** Parses migration manifest JSON into runtime-backed domain nouns. */
export function parseGraphModelMigrationManifest(raw) {
    return manifestFromJson(parseJson(raw));
}
/** Converts a manifest into its adapter-boundary JSON object. */
function manifestToJson(manifest) {
    return {
        version: manifest.version.value,
        sourceBasis: basisToJson(manifest.sourceBasis),
        targetBasis: basisToJson(manifest.targetBasis),
        nodeMappings: manifest.nodeMappings.map(nodeMappingToJson),
        edgeMappings: manifest.edgeMappings.map(edgeMappingToJson),
        propertyMappings: manifest.propertyMappings.map(propertyMappingToJson),
        contentMappings: manifest.contentMappings.map(contentMappingToJson),
        warnings: manifest.warnings.map(noticeToJson),
        fatalErrors: manifest.fatalErrors.map(noticeToJson),
    };
}
/** Converts parsed JSON into a migration manifest. */
function manifestFromJson(value) {
    const source = requireJsonObject(value, 'manifest');
    rejectUnknownKeys(source, MANIFEST_KEYS, 'manifest');
    return new GraphModelMigrationManifest({
        version: new GraphModelMigrationManifestVersion(readRequiredNumber(source, 'version')),
        sourceBasis: readBasis(source, 'sourceBasis'),
        targetBasis: readBasis(source, 'targetBasis'),
        nodeMappings: readNodeMappings(source),
        edgeMappings: readEdgeMappings(source),
        propertyMappings: readPropertyMappings(source),
        contentMappings: readContentMappings(source),
        warnings: readNotices(source, 'warnings'),
        fatalErrors: readNotices(source, 'fatalErrors'),
    });
}
/** Parses untrusted JSON text without leaking platform SyntaxError. */
function parseJson(raw) {
    try {
        return JSON.parse(raw);
    }
    catch {
        throw new AdapterValidationError('Graph model migration manifest JSON must be valid JSON');
    }
}
/** Requires a manifest instance before serialization. */
function requireManifest(manifest) {
    if (!(manifest instanceof GraphModelMigrationManifest)) {
        throw new AdapterValidationError('Graph model migration manifest must be a manifest instance');
    }
    return manifest;
}
/** Serializes a basis value. */
function basisToJson(basis) {
    return {
        graphId: basis.graphId,
        basisId: basis.basisId,
    };
}
/** Serializes a node mapping. */
function nodeMappingToJson(mapping) {
    return {
        legacyNodeId: mapping.legacyNodeId,
        targetNodeId: mapping.targetNodeId,
    };
}
/** Serializes an edge mapping. */
function edgeMappingToJson(mapping) {
    return {
        legacyEdgeId: mapping.legacyEdgeId,
        targetEdgeId: mapping.targetEdgeId,
    };
}
/** Serializes a property mapping. */
function propertyMappingToJson(mapping) {
    return {
        legacyOwnerId: mapping.legacyOwnerId,
        legacyPropertyKey: mapping.legacyPropertyKey,
        targetOwnerId: mapping.targetOwnerId,
        targetPropertyKey: mapping.targetPropertyKey,
    };
}
/** Serializes a content mapping. */
function contentMappingToJson(mapping) {
    return {
        legacyContentKey: mapping.legacyContentKey,
        targetAttachmentKey: mapping.targetAttachmentKey,
    };
}
/** Serializes a migration notice. */
function noticeToJson(notice) {
    return {
        kind: notice.kind,
        code: notice.code,
        message: notice.message,
    };
}
/** Reads a basis object from the manifest envelope. */
function readBasis(source, key) {
    const basis = requireJsonObject(source[key], key);
    rejectUnknownKeys(basis, ['graphId', 'basisId'], key);
    return new GraphModelMigrationBasis({
        graphId: readRequiredString(basis, `${key}.graphId`, 'graphId'),
        basisId: readRequiredString(basis, `${key}.basisId`, 'basisId'),
    });
}
/** Reads node mappings from the manifest envelope. */
function readNodeMappings(source) {
    return readObjectArray(source, 'nodeMappings').map((mapping, index) => {
        const label = `nodeMappings[${index}]`;
        rejectUnknownKeys(mapping, ['legacyNodeId', 'targetNodeId'], label);
        return new GraphModelMigrationNodeMapping({
            legacyNodeId: readRequiredString(mapping, `${label}.legacyNodeId`, 'legacyNodeId'),
            targetNodeId: readRequiredString(mapping, `${label}.targetNodeId`, 'targetNodeId'),
        });
    });
}
/** Reads edge mappings from the manifest envelope. */
function readEdgeMappings(source) {
    return readObjectArray(source, 'edgeMappings').map((mapping, index) => {
        const label = `edgeMappings[${index}]`;
        rejectUnknownKeys(mapping, ['legacyEdgeId', 'targetEdgeId'], label);
        return new GraphModelMigrationEdgeMapping({
            legacyEdgeId: readRequiredString(mapping, `${label}.legacyEdgeId`, 'legacyEdgeId'),
            targetEdgeId: readRequiredString(mapping, `${label}.targetEdgeId`, 'targetEdgeId'),
        });
    });
}
/** Reads property mappings from the manifest envelope. */
function readPropertyMappings(source) {
    return readObjectArray(source, 'propertyMappings').map((mapping, index) => {
        const label = `propertyMappings[${index}]`;
        rejectUnknownKeys(mapping, ['legacyOwnerId', 'legacyPropertyKey', 'targetOwnerId', 'targetPropertyKey'], label);
        return new GraphModelMigrationPropertyMapping({
            legacyOwnerId: readRequiredString(mapping, `${label}.legacyOwnerId`, 'legacyOwnerId'),
            legacyPropertyKey: readRequiredString(mapping, `${label}.legacyPropertyKey`, 'legacyPropertyKey'),
            targetOwnerId: readRequiredString(mapping, `${label}.targetOwnerId`, 'targetOwnerId'),
            targetPropertyKey: readRequiredString(mapping, `${label}.targetPropertyKey`, 'targetPropertyKey'),
        });
    });
}
/** Reads content mappings from the manifest envelope. */
function readContentMappings(source) {
    return readObjectArray(source, 'contentMappings').map((mapping, index) => {
        const label = `contentMappings[${index}]`;
        rejectUnknownKeys(mapping, ['legacyContentKey', 'targetAttachmentKey'], label);
        return new GraphModelMigrationContentMapping({
            legacyContentKey: readRequiredString(mapping, `${label}.legacyContentKey`, 'legacyContentKey'),
            targetAttachmentKey: readRequiredString(mapping, `${label}.targetAttachmentKey`, 'targetAttachmentKey'),
        });
    });
}
/** Reads notice arrays from the manifest envelope. */
function readNotices(source, key) {
    return readObjectArray(source, key).map((notice, index) => {
        const label = `${key}[${index}]`;
        rejectUnknownKeys(notice, ['kind', 'code', 'message'], label);
        return new GraphModelMigrationNotice({
            kind: readNoticeKind(notice, `${label}.kind`, 'kind'),
            code: readRequiredString(notice, `${label}.code`, 'code'),
            message: readRequiredString(notice, `${label}.message`, 'message'),
        });
    });
}
/** Requires an object array field. */
function readObjectArray(source, key) {
    const value = source[key];
    if (!Array.isArray(value)) {
        throw new AdapterValidationError(`Graph model migration manifest field "${key}" must be an array`);
    }
    const objects = [];
    value.forEach((entry, index) => {
        objects.push(requireJsonObject(entry, `${key}[${index}]`));
    });
    return Object.freeze(objects);
}
/** Reads a required string field. */
function readRequiredString(source, label, key) {
    const value = source[key];
    if (typeof value !== 'string' || value.length === 0) {
        throw new AdapterValidationError(`Graph model migration manifest field "${label}" must be a non-empty string`);
    }
    return value;
}
/** Reads a required finite number field. */
function readRequiredNumber(source, key) {
    const value = source[key];
    if (typeof value !== 'number' || !Number.isFinite(value)) {
        throw new AdapterValidationError(`Graph model migration manifest field "${key}" must be a finite number`);
    }
    return value;
}
/** Reads a notice kind without string casts. */
function readNoticeKind(source, label, key) {
    const value = source[key];
    if (value === 'warning' || value === 'fatal') {
        return value;
    }
    throw new AdapterValidationError(`Graph model migration manifest field "${label}" must be warning or fatal`);
}
/** Requires a non-array JSON object. */
function requireJsonObject(value, label) {
    if (!isJsonObject(value)) {
        throw new AdapterValidationError(`Graph model migration manifest field "${label}" must be an object`);
    }
    return value;
}
/** Returns true for non-array JSON object values. */
function isJsonObject(value) {
    return value !== null && typeof value === 'object' && !Array.isArray(value);
}
/** Rejects unexpected fields at the manifest JSON boundary. */
function rejectUnknownKeys(source, allowed, label) {
    for (const key of Object.keys(source)) {
        if (!allowed.includes(key)) {
            throw new AdapterValidationError(`Graph model migration manifest field "${label}.${key}" is not allowed`);
        }
    }
}
