/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
import { assert, bufferToString, IsoBuffer } from "@fluidframework/common-utils";
import { SummaryTreeBuilder } from "@fluidframework/runtime-utils";
import { cachedValue, recordDependency, schemaDataIsEmpty, } from "../core";
import { isJsonObject } from "../util";
import { getSchemaString, parseSchemaString } 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) {
        this.runtime = runtime;
        this.schema = schema;
        this.key = "Schema";
        this.schemaBlob = cachedValue(async (observer) => {
            recordDependency(observer, this.schema);
            const schemaText = getSchemaString(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) {
        const builder = new SummaryTreeBuilder();
        const dataString = getSchemaString(this.schema);
        builder.addBlob(schemaStringKey, dataString);
        return builder.getSummaryTree();
    }
    async summarize(stringify, fullTree, trackState, telemetryContext) {
        const schemaBlobHandle = await this.schemaBlob.get();
        const builder = new SummaryTreeBuilder();
        builder.addBlob(schemaBlobKey, stringify(schemaBlobHandle));
        return builder.getSummaryTree();
    }
    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");
        const decoded = parseSchemaString(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) {
        this.inner = inner;
        this.submit = submit;
    }
    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(message) {
        const op = message.contents;
        if (isJsonObject(op) && op.type === "SchemaOp") {
            const data = parseSchemaString(op.data);
            // TODO: This does not correctly handle concurrency of schema edits.
            this.inner.update(data);
            return true;
        }
        return false;
    }
    /**
     * @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(typeof op.data === "string", 0x5e3 /* expected string data 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: getSchemaString(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() {
        var _a;
        return (_a = this.inner.listDependees) === null || _a === void 0 ? void 0 : _a.bind(this.inner);
    }
    get policy() {
        return this.inner.policy;
    }
    get globalFieldSchema() {
        return this.inner.globalFieldSchema;
    }
    get treeSchema() {
        return this.inner.treeSchema;
    }
}
//# sourceMappingURL=schemaSummarizer.js.map