import { canonicalStringify } from "../utils/canonicalStringify.js";
import WarpError from "../errors/WarpError.js";
import { TRANSFER_OP_ATTACH_EDGE_CONTENT, TRANSFER_OP_ATTACH_NODE_CONTENT, } from "./transfer/transferOps.js";
/**
 * Returns true if the value is null or undefined.
 */
function isNullish(value) {
    return value === null || value === undefined;
}
/**
 * Asserts the value is a non-null, non-array object, throwing if not.
 */
function requireObject(value, label) {
    if (isNullish(value) || typeof value !== 'object' || Array.isArray(value)) {
        throw new WarpError(`${label} must be an object`, 'E_COORDINATE_FACT_NOT_OBJECT', { context: { label } });
    }
}
const COORDINATE_COMPARISON_FACT_EXPORT_VERSION = 'coordinate-comparison-fact/v1';
const COORDINATE_TRANSFER_PLAN_FACT_EXPORT_VERSION = 'coordinate-transfer-plan-fact/v1';
/**
 * Validates that a value is a non-empty string, throwing if not.
 */
function requireNonEmptyString(value, label) {
    if (typeof value !== 'string' || value.trim().length === 0) {
        throw new WarpError(`${label} must be a non-empty string`, 'E_COORDINATE_FACT_NOT_STRING', { context: { label } });
    }
    return value;
}
/**
 * Serializes a node content attach operation to its fact form.
 */
function serializeNodeContentOp(op) {
    return {
        op: op.op,
        nodeId: op['nodeId'],
        contentOid: op['contentOid'],
        mime: op['mime'] ?? null,
        size: op['size'] ?? null,
    };
}
/**
 * Serializes an edge content attach operation to its fact form.
 */
function serializeEdgeContentOp(op) {
    return {
        op: op.op,
        from: op['from'],
        to: op['to'],
        label: op['label'],
        contentOid: op['contentOid'],
        mime: op['mime'] ?? null,
        size: op['size'] ?? null,
    };
}
/**
 * Serializes a single transfer operation into its JSON-safe fact form.
 */
function serializeSingleTransferOp(op) {
    switch (op.op) {
        case TRANSFER_OP_ATTACH_NODE_CONTENT:
            return serializeNodeContentOp(op);
        case TRANSFER_OP_ATTACH_EDGE_CONTENT:
            return serializeEdgeContentOp(op);
        default:
            return { ...op };
    }
}
/**
 * Produces the JSON-safe transfer-op representation used for deterministic
 * transfer digests and higher-layer factual exports.
 */
function serializeTransferOpsForFact(ops) {
    if (!Array.isArray(ops)) {
        throw new WarpError('ops must be an array', 'E_COORDINATE_FACT_OPS_NOT_ARRAY');
    }
    return ops.map((op) => serializeSingleTransferOp(op));
}
/**
 * Builds the exact substrate fact payload hashed by `comparisonDigest`.
 */
export function buildCoordinateComparisonFact(comparison) {
    requireObject(comparison, 'comparison');
    requireNonEmptyString(comparison.comparisonVersion, 'comparison.comparisonVersion');
    return {
        comparisonVersion: comparison.comparisonVersion,
        ...(comparison.scope ? { scope: comparison.scope } : {}),
        left: comparison.left,
        right: comparison.right,
        visiblePatchDivergence: comparison.visiblePatchDivergence,
        visibleState: comparison.visibleState,
    };
}
/**
 * Builds the exact JSON-safe substrate fact payload hashed by `transferDigest`.
 */
export function buildCoordinateTransferPlanFact(transferPlan) {
    requireObject(transferPlan, 'transferPlan');
    requireNonEmptyString(transferPlan.transferVersion, 'transferPlan.transferVersion');
    requireNonEmptyString(transferPlan.comparisonDigest, 'transferPlan.comparisonDigest');
    return {
        transferVersion: transferPlan.transferVersion,
        comparisonDigest: transferPlan.comparisonDigest,
        ...(transferPlan.scope ? { scope: transferPlan.scope } : {}),
        changed: !!transferPlan.changed,
        source: transferPlan.source,
        target: transferPlan.target,
        summary: transferPlan.summary,
        ops: serializeTransferOpsForFact(transferPlan.ops),
    };
}
/**
 * Exports a coordinate comparison as a deterministic substrate fact envelope
 * suitable for higher-layer storage or attestation context.
 */
export function exportCoordinateComparisonFact(comparison) {
    const fact = buildCoordinateComparisonFact(comparison);
    const factDigest = requireNonEmptyString(comparison.comparisonDigest, 'comparison.comparisonDigest');
    return {
        exportVersion: COORDINATE_COMPARISON_FACT_EXPORT_VERSION,
        factKind: 'coordinate-comparison',
        factDigest,
        canonicalFactJson: canonicalStringify(fact),
        fact,
    };
}
/**
 * Exports a coordinate transfer plan as a deterministic substrate fact
 * envelope without embedding raw attachment bytes.
 */
export function exportCoordinateTransferPlanFact(transferPlan) {
    const fact = buildCoordinateTransferPlanFact(transferPlan);
    const factDigest = requireNonEmptyString(transferPlan.transferDigest, 'transferPlan.transferDigest');
    return {
        exportVersion: COORDINATE_TRANSFER_PLAN_FACT_EXPORT_VERSION,
        factKind: 'coordinate-transfer-plan',
        factDigest,
        canonicalFactJson: canonicalStringify(fact),
        fact,
    };
}
