"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.getNodeEncoder = exports.getFieldEncoder = exports.buildContext = exports.schemaCompressedEncodeV2 = exports.schemaCompressedEncodeV1 = void 0;
const internal_1 = require("@fluidframework/core-utils/internal");
const index_js_1 = require("../../../core/index.js");
const index_js_2 = require("../../../util/index.js");
const compressedEncode_js_1 = require("./compressedEncode.js");
const index_js_3 = require("./format/index.js");
const incrementalEncodingPolicy_js_1 = require("./incrementalEncodingPolicy.js");
const nodeEncoder_js_1 = require("./nodeEncoder.js");
/**
 * Encode data from `fieldBatch` in into an `EncodedChunk` using {@link FieldBatchFormatVersion.v1}.
 * @remarks See {@link schemaCompressedEncode} for more details.
 * This version does not support incremental encoding.
 */
function schemaCompressedEncodeV1(schema, policy, fieldBatch, idCompressor) {
    const encoded = schemaCompressedEncode(schema, policy, fieldBatch, idCompressor, undefined /* incrementalEncoder */, (0, index_js_2.brand)(index_js_3.FieldBatchFormatVersion.v1));
    // Since incrementalEncoder was not provided, no V2 features should be used, and this cast should be safe.
    return encoded;
}
exports.schemaCompressedEncodeV1 = schemaCompressedEncodeV1;
/**
 * Encode data from `fieldBatch` in into an `EncodedChunk` using {@link FieldBatchFormatVersion.v2}.
 * @remarks See {@link schemaCompressedEncode} for more details.
 * Incremental encoding is supported from this version onwards.
 */
function schemaCompressedEncodeV2(schema, policy, fieldBatch, idCompressor, incrementalEncoder) {
    return schemaCompressedEncode(schema, policy, fieldBatch, idCompressor, incrementalEncoder, (0, index_js_2.brand)(index_js_3.FieldBatchFormatVersion.v2));
}
exports.schemaCompressedEncodeV2 = schemaCompressedEncodeV2;
/**
 * Encode data from `fieldBatch` in into an `EncodedChunk`.
 * @remarks
 * If `incrementalEncoder` is provided,
 * fields that support incremental encoding will encode their chunks separately via the `incrementalEncoder`.
 * See {@link IncrementalEncoder} for more details.
 *
 * Optimized for encoded size and encoding performance.
 * TODO: This function should eventually also take in the root FieldSchema to more efficiently compress the nodes.
 */
function schemaCompressedEncode(schema, policy, fieldBatch, idCompressor, incrementalEncoder, version) {
    return (0, compressedEncode_js_1.compressedEncode)(fieldBatch, buildContext(schema, policy, idCompressor, incrementalEncoder, version));
}
function buildContext(storedSchema, policy, idCompressor, incrementalEncoder, version) {
    const context = new compressedEncode_js_1.EncoderContext((fieldBuilder, schemaName) => getNodeEncoder(fieldBuilder, storedSchema, schemaName, incrementalEncoder), (nodeBuilder, fieldSchema) => getFieldEncoder(nodeBuilder, fieldSchema, context, storedSchema), policy.fieldKinds, idCompressor, incrementalEncoder, version);
    return context;
}
exports.buildContext = buildContext;
/**
 * Selects an encoder to use to encode fields.
 */
function getFieldEncoder(nodeBuilder, field, context, storedSchema) {
    const kind = context.fieldShapes.get(field.kind) ?? (0, internal_1.fail)(0xb52 /* missing FieldKind */);
    const type = (0, index_js_2.oneFromIterable)(field.types);
    const nodeEncoder = type === undefined ? compressedEncode_js_1.anyNodeEncoder : nodeBuilder.nodeEncoderFromSchema(type);
    if (kind.multiplicity === index_js_1.Multiplicity.Single) {
        if (field.kind === index_js_1.identifierFieldKindIdentifier) {
            (0, internal_1.assert)(type !== undefined, 0x999 /* field type must be defined in identifier field */);
            const nodeSchema = storedSchema.nodeSchema.get(type);
            (0, internal_1.assert)(nodeSchema !== undefined, 0x99a /* nodeSchema must be defined */);
            (0, internal_1.assert)(nodeSchema instanceof index_js_1.LeafNodeStoredSchema, 0x99b /* nodeSchema must be LeafNodeStoredSchema */);
            (0, internal_1.assert)(nodeSchema.leafValue === index_js_1.ValueSchema.String, 0x99c /* identifier field can only be type string */);
            const identifierNodeEncoder = new nodeEncoder_js_1.NodeShapeBasedEncoder(type, index_js_3.SpecialField.Identifier, [], undefined);
            return (0, compressedEncode_js_1.asFieldEncoder)(identifierNodeEncoder);
        }
        return (0, compressedEncode_js_1.asFieldEncoder)(nodeEncoder);
    }
    else {
        return context.nestedArrayEncoder(nodeEncoder);
    }
}
exports.getFieldEncoder = getFieldEncoder;
/**
 * Selects an encoder to use to encode nodes.
 */
function getNodeEncoder(fieldBuilder, storedSchema, schemaName, incrementalEncoder) {
    const shouldEncodeIncrementally = incrementalEncoder?.shouldEncodeIncrementally ?? incrementalEncodingPolicy_js_1.defaultIncrementalEncodingPolicy;
    const schema = storedSchema.nodeSchema.get(schemaName) ?? (0, internal_1.fail)(0xb53 /* missing node schema */);
    // This handles both object and array nodes.
    if (schema instanceof index_js_1.ObjectNodeStoredSchema) {
        // TODO:Performance:
        // consider moving some optional and sequence fields to extra fields if they are commonly empty
        // to reduce encoded size.
        const objectNodeFields = [];
        for (const [key, field] of schema.objectNodeFields ?? []) {
            const fieldEncoder = shouldEncodeIncrementally(schemaName, key)
                ? compressedEncode_js_1.incrementalFieldEncoder
                : fieldBuilder.fieldEncoderFromSchema(field);
            objectNodeFields.push({
                key,
                encoder: fieldEncoder,
            });
        }
        const shape = new nodeEncoder_js_1.NodeShapeBasedEncoder(schemaName, false, objectNodeFields, undefined);
        return shape;
    }
    if (schema instanceof index_js_1.LeafNodeStoredSchema) {
        const shape = new nodeEncoder_js_1.NodeShapeBasedEncoder(schemaName, valueShapeFromSchema(schema.leafValue), [], undefined);
        return shape;
    }
    // This handles both maps and record nodes.
    if (schema instanceof index_js_1.MapNodeStoredSchema) {
        const fieldEncoder = shouldEncodeIncrementally(schemaName)
            ? compressedEncode_js_1.incrementalFieldEncoder
            : fieldBuilder.fieldEncoderFromSchema(schema.mapFields);
        const shape = new nodeEncoder_js_1.NodeShapeBasedEncoder(schemaName, false, [], fieldEncoder);
        return shape;
    }
    (0, internal_1.fail)(0xb54 /* unsupported node kind */);
}
exports.getNodeEncoder = getNodeEncoder;
function valueShapeFromSchema(schema) {
    switch (schema) {
        case undefined: {
            return false;
        }
        case index_js_1.ValueSchema.Number:
        case index_js_1.ValueSchema.String:
        case index_js_1.ValueSchema.Boolean:
        case index_js_1.ValueSchema.FluidHandle: {
            return true;
        }
        case index_js_1.ValueSchema.Null: {
            return [null];
        }
        default: {
            (0, internal_1.unreachableCase)(schema);
        }
    }
}
//# sourceMappingURL=schemaBasedEncode.js.map