/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { bufferToString, IsoBuffer } from "@fluid-internal/client-utils";
import { assert } from "@fluidframework/core-utils";
import { createSingleBlobSummary } from "@fluidframework/shared-object-base";
import { cachedValue, recordDependency, schemaDataIsEmpty, } from "../core";
import { isJsonObject } from "../util";
import { makeSchemaCodec, Format, encodeRepo } from "./schemaIndexFormat";
/**
 * The storage key for the blob in the summary containing schema data
 */
const schemaBlobKey = "SchemaBlob";
const schemaStringKey = "SchemaString";
/**
 * Provides methods for summarizing and loading a schema repository.
 */
export class SchemaSummarizer {
    constructor(runtime, schema, options) {
        this.runtime = runtime;
        this.schema = schema;
        this.key = "Schema";
        this.codec = makeSchemaCodec(options);
        this.schemaBlob = cachedValue(async (observer) => {
            recordDependency(observer, this.schema);
            // Currently no Fluid handles are used, so just use JSON.stringify.
            const schemaText = JSON.stringify(this.codec.encode(this.schema));
            // For now we are not chunking the the schema, but still put it in a reusable blob:
            return this.runtime.uploadBlob(IsoBuffer.from(schemaText));
        });
    }
    getAttachSummary(stringify, fullTree, trackState, telemetryContext) {
        // Currently no Fluid handles are used, so just use JSON.stringify.
        const dataString = JSON.stringify(this.codec.encode(this.schema));
        return createSingleBlobSummary(schemaStringKey, dataString);
    }
    async summarize(stringify, fullTree, trackState, telemetryContext) {
        const schemaBlobHandle = await this.schemaBlob.get();
        return createSingleBlobSummary(schemaBlobKey, stringify(schemaBlobHandle));
    }
    getGCData(fullGC) {
        // TODO: Properly implement garbage collection. Right now, garbage collection is performed automatically
        // by the code in SharedObject (from which SharedTreeCore extends). The `runtime.uploadBlob` API delegates
        // to the `BlobManager`, which automatically populates the summary with ISummaryAttachment entries for each
        // blob.
        return {
            gcNodes: {},
        };
    }
    async load(services, parse) {
        const [hasString, hasBlob] = await Promise.all([
            services.contains(schemaStringKey),
            services.contains(schemaBlobKey),
        ]);
        assert(hasString || hasBlob, 0x3d8 /* Schema is required in summary */);
        let schemaBuffer;
        if (hasBlob) {
            const handleBuffer = await services.readBlob(schemaBlobKey);
            const handleString = bufferToString(handleBuffer, "utf-8");
            const handle = parse(handleString);
            schemaBuffer = await handle.get();
        }
        else {
            schemaBuffer = await services.readBlob(schemaStringKey);
        }
        // After the awaits, validate that the schema is in a clean state.
        // This detects any schema that could have been accidentally added through
        // invalid means and are about to be overwritten.
        assert(schemaDataIsEmpty(this.schema), 0x3da /* there should not already be stored schema when loading stored schema */);
        const schemaString = bufferToString(schemaBuffer, "utf-8");
        // Currently no Fluid handles are used, so just use JSON.parse.
        const decoded = this.codec.decode(JSON.parse(schemaString));
        this.schema.update(decoded);
    }
}
/**
 * Wraps a StoredSchemaRepository, adjusting its "update" function to hook into Fluid Ops.
 *
 * TODO: this should be more integrated with transactions.
 */
export class SchemaEditor {
    constructor(inner, submit, options) {
        this.inner = inner;
        this.submit = submit;
        this.codec = makeSchemaCodec(options);
        this.formatValidator = options.jsonValidator.compile(Format);
    }
    on(eventName, listener) {
        return this.inner.on(eventName, listener);
    }
    /**
     * @returns true if this is a schema op and was handled.
     *
     * TODO: Shared tree needs a pattern for handling non-changeset operations.
     * See TODO on `SharedTree.processCore`.
     */
    tryHandleOp(encodedOp) {
        const op = this.tryDecodeOp(encodedOp);
        if (op !== undefined) {
            // TODO: This does not correctly handle concurrency of schema edits.
            this.inner.update(op);
            return true;
        }
        return false;
    }
    tryApplyStashedOp(encodedOp) {
        return this.tryHandleOp(encodedOp);
    }
    /**
     * @returns true iff this is a schema op and was submitted.
     *
     * TODO: Shared tree needs a pattern for handling non-changeset operations.
     * See TODO on `SharedTree.processCore`.
     */
    tryResubmitOp(content) {
        const op = content;
        if (isJsonObject(op) && op.type === "SchemaOp") {
            assert(this.formatValidator.check(op.data), 0x79b /* unexpected format for resubmitted schema op */);
            const schemaOp = {
                type: op.type,
                data: op.data,
            };
            this.submit(schemaOp);
            return true;
        }
        return false;
    }
    update(newSchema) {
        const op = { type: "SchemaOp", data: this.codec.encode(newSchema) };
        this.submit(op);
        this.inner.update(newSchema);
    }
    registerDependent(dependent) {
        return this.inner.registerDependent(dependent);
    }
    removeDependent(dependent) {
        return this.inner.removeDependent(dependent);
    }
    get computationName() {
        return this.inner.computationName;
    }
    get listDependees() {
        return this.inner.listDependees?.bind(this.inner);
    }
    get rootFieldSchema() {
        return this.inner.rootFieldSchema;
    }
    get nodeSchema() {
        return this.inner.nodeSchema;
    }
    tryDecodeOp(encodedOp) {
        if (isJsonObject(encodedOp) && encodedOp.type === "SchemaOp") {
            return this.codec.decode(encodedOp.data);
        }
        return undefined;
    }
}
/**
 * Dumps schema into a deterministic JSON compatible semi-human readable but unspecified format.
 *
 * @remarks
 * This can be used to help inspect schema for debugging, and to save a snapshot of schema to help detect and review changes to an applications schema.
 *
 * This format may change across major versions of this package: such changes are considered breaking.
 * Beyond that, no compatibility guarantee is provided for this format: it should never be relied upon to load data, it should only be used for comparing outputs from this function.
 * @privateRemarks
 * This currently uses the schema summary format, but that could be changed to something more human readable (particularly if the encoded format becomes less human readable).
 * This intentionally does not leak the format types in the API.
 * @alpha
 */
export function encodeTreeSchema(schema) {
    return encodeRepo(schema);
}
//# sourceMappingURL=schemaSummarizer.js.map