"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Tiles
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.IModelTileTree = exports.iModelTileTreeParamsFromJSON = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const IModelApp_1 = require("../IModelApp");
const GraphicalEditingScope_1 = require("../GraphicalEditingScope");
const GraphicBranch_1 = require("../render/GraphicBranch");
const internal_1 = require("./internal");
// Overrides nothing.
const viewFlagOverrides = {};
/** @internal */
function iModelTileTreeParamsFromJSON(props, iModel, modelId, options) {
    const location = core_geometry_1.Transform.fromJSON(props.location);
    const { formatVersion, id, rootTile, contentIdQualifier, maxInitialTilesToSkip, geometryGuid } = props;
    const tileScreenSize = props.tileScreenSize ?? 512;
    let contentRange;
    if (undefined !== props.contentRange)
        contentRange = core_geometry_1.Range3d.fromJSON(props.contentRange);
    let transformNodeRanges;
    if (props.transformNodeRanges) {
        transformNodeRanges = new Map();
        for (const entry of props.transformNodeRanges)
            transformNodeRanges.set(entry.id, core_geometry_1.Range3d.fromJSON(entry));
    }
    const priority = core_common_1.BatchType.Primary === options.batchType ? internal_1.TileLoadPriority.Primary : internal_1.TileLoadPriority.Classifier;
    return {
        formatVersion,
        id,
        rootTile,
        iModel,
        location,
        modelId,
        contentRange,
        geometryGuid,
        contentIdQualifier,
        maxInitialTilesToSkip,
        priority,
        options,
        tileScreenSize,
        transformNodeRanges,
    };
}
exports.iModelTileTreeParamsFromJSON = iModelTileTreeParamsFromJSON;
function findElementChangesForModel(changes, modelId) {
    for (const change of changes)
        if (change.id === modelId)
            return change.elements;
    return undefined;
}
/** No graphical editing scope is currently active. */
class StaticState {
    constructor(root) {
        this.type = "static";
        this.dispose = GraphicalEditingScope_1.GraphicalEditingScope.onEnter.addOnce((scope) => {
            root.transition(new InteractiveState(scope, root));
        });
    }
}
/** A graphical editing scope is currently active, but no elements in the tile tree's model have been modified. */
class InteractiveState {
    constructor(scope, root) {
        this.type = "interactive";
        const removeEndingListener = scope.onExiting.addOnce((_) => {
            root.transition(new StaticState(root));
        });
        const removeGeomListener = scope.onGeometryChanges.addListener((changes, _scope) => {
            (0, core_bentley_1.assert)(scope === _scope);
            const elemChanges = findElementChangesForModel(changes, root.tree.modelId);
            if (elemChanges)
                root.transition(new DynamicState(root, elemChanges, scope));
        });
        this.dispose = () => {
            removeEndingListener();
            removeGeomListener();
        };
    }
}
/** Elements in the tile tree's model have been modified during the current editing scope. */
class DynamicState {
    dispose() {
        this._dispose();
        this.rootTile.dispose();
    }
    constructor(root, elemChanges, scope) {
        this.type = "dynamic";
        this.rootTile = internal_1.DynamicIModelTile.create(root, elemChanges);
        const removeEndingListener = scope.onExiting.addOnce((_) => {
            root.transition(new StaticState(root));
        });
        const removeGeomListener = scope.onGeometryChanges.addListener((changes, _scope) => {
            (0, core_bentley_1.assert)(scope === _scope);
            const elems = findElementChangesForModel(changes, root.tree.modelId);
            if (elems)
                this.rootTile.handleGeometryChanges(elems);
        });
        this._dispose = () => {
            removeEndingListener();
            removeGeomListener();
        };
    }
}
/** The tile tree has been disposed. */
class DisposedState {
    constructor() {
        this.type = "disposed";
    }
    dispose() { }
}
const disposedState = new DisposedState();
/** Represents the root [[Tile]] of an [[IModelTileTree]]. The root tile has one or two direct child tiles which represent different branches of the tree:
 *  - The static branch, containing tiles that represent the state of the model's geometry as of the beginning of the current [[GraphicalEditingScope]].
 *  - The dynamic branch, containing tiles representing the geometry of elements that have been modified during the current [[GraphicalEditingScope]].
 * If no editing scope is currently active, the dynamic branch does not exist, and the static branch represents the current state of all elements in the model.
 */
class RootTile extends internal_1.Tile {
    get tileState() {
        return this._tileState;
    }
    constructor(params, tree) {
        const rootParams = {
            ...params,
            range: params.range.clone(),
            contentRange: params.contentRange?.clone(),
            isLeaf: false,
            contentId: "",
        };
        super(rootParams, tree);
        this.staticBranch = new internal_1.IModelTile(params, tree);
        this._staticTreeContentRange = tree.contentRange?.clone();
        if (!this._contentRange)
            this._contentRange = this.staticBranch.contentRange.clone();
        // Determine initial state.
        const scope = tree.iModel.isBriefcaseConnection() ? tree.iModel.editingScope : undefined;
        if (undefined === scope) {
            this._tileState = new StaticState(this);
        }
        else {
            const changes = scope.getGeometryChangesForModel(tree.modelId);
            this._tileState = changes ? new DynamicState(this, changes, scope) : new InteractiveState(scope, this);
        }
        // Load the children immediately.
        this.setIsReady();
        this.loadChildren();
    }
    dispose() {
        this.transition(disposedState);
        super.dispose();
    }
    _loadChildren(resolve, _reject) {
        const children = [this.staticBranch];
        if (this._tileState.type === "dynamic")
            children.push(this._tileState.rootTile);
        resolve(children);
    }
    get channel() {
        throw new Error("Root iModel tile has no content");
    }
    async requestContent(_isCanceled) {
        (0, core_bentley_1.assert)(false, "Root iModel tile has no content");
        return undefined;
    }
    async readContent(_data, _system, _isCanceled) {
        throw new Error("Root iModel tile has no content");
    }
    draw(args, tiles, numStaticTiles) {
        (0, core_bentley_1.assert)(numStaticTiles >= 0 && numStaticTiles <= tiles.length);
        // Draw the static tiles.
        for (let i = 0; i < numStaticTiles; i++)
            tiles[i].drawGraphics(args);
        if ("dynamic" !== this._tileState.type || numStaticTiles === tiles.length) {
            if ("dynamic" === this._tileState.type)
                args.addAppearanceProvider(this._tileState.rootTile.appearanceProvider);
            args.drawGraphics();
            return;
        }
        // We need to hide any modified elements in the static tiles. Pull their graphics into a separate branch.
        if (!args.graphics.isEmpty) {
            const staticBranch = new GraphicBranch_1.GraphicBranch();
            for (const staticGraphic of args.graphics.entries)
                staticBranch.add(staticGraphic);
            let appearanceProvider = this._tileState.rootTile.appearanceProvider;
            if (args.appearanceProvider)
                appearanceProvider = core_common_1.FeatureAppearanceProvider.chain(args.appearanceProvider, appearanceProvider);
            args.graphics.clear();
            args.graphics.add(args.context.createGraphicBranch(staticBranch, core_geometry_1.Transform.createIdentity(), { appearanceProvider }));
        }
        // Draw the dynamic tiles.
        for (let i = numStaticTiles; i < tiles.length; i++)
            tiles[i].drawGraphics(args);
        args.drawGraphics();
    }
    prune(olderThan) {
        this.staticBranch.pruneChildren(olderThan);
        if ("dynamic" === this._tileState.type)
            this._tileState.rootTile.pruneChildren(olderThan);
    }
    transition(newState) {
        (0, core_bentley_1.assert)(newState.type !== this._tileState.type);
        const resetRange = "dynamic" === this._tileState.type;
        (0, core_bentley_1.assert)(undefined !== this.children);
        if ("dynamic" === this._tileState.type) {
            (0, core_bentley_1.assert)(2 === this.children.length);
            this.children.pop();
        }
        else if ("dynamic" === newState.type) {
            (0, core_bentley_1.assert)(1 === this.children.length);
            this.children.push(newState.rootTile);
        }
        this._tileState.dispose();
        this._tileState = newState;
        if (resetRange)
            this.resetRange();
    }
    resetRange() {
        this.staticBranch.range.clone(this.range);
        this.staticBranch.contentRange.clone(this._contentRange);
        if (this._staticTreeContentRange && this.tree.contentRange)
            this._staticTreeContentRange.clone(this.tree.contentRange);
    }
    get tileScreenSize() {
        return this.staticBranch.iModelTree.tileScreenSize;
    }
    updateDynamicRange(tile) {
        this.resetRange();
        if (this._staticTreeContentRange && this.tree.contentRange && !tile.contentRange.isNull)
            this.tree.contentRange.extendRange(tile.contentRange);
        if (!tile.range.isNull)
            this.range.extendRange(tile.range);
        (0, core_bentley_1.assert)(undefined !== this._contentRange);
        if (!tile.contentRange.isNull)
            this._contentRange.extendRange(tile.contentRange);
    }
}
/** A TileTree whose contents are derived from geometry stored in a Model in an IModelDb.
 * @internal
 */
class IModelTileTree extends internal_1.TileTree {
    constructor(params, treeId) {
        super(params);
        /** A little hacky...we must not override selectTiles(), but draw() needs to distinguish between static and dynamic tiles.
         * So _selectTiles() puts the static tiles first in the Tile[] array, and records the number of static tiles selected, to be
         * used by draw().
         */
        this._numStaticTilesSelected = 0;
        this.iModelTileTreeId = treeId;
        this.contentIdQualifier = params.contentIdQualifier;
        this.geometryGuid = params.geometryGuid;
        this.tileScreenSize = params.tileScreenSize;
        if (core_common_1.BatchType.Primary === treeId.type)
            this.stringifiedSectionClip = treeId.sectionCut;
        this.maxInitialTilesToSkip = params.maxInitialTilesToSkip ?? 0;
        this.maxTilesToSkip = IModelApp_1.IModelApp.tileAdmin.maximumLevelsToSkip;
        this._options = params.options;
        this._transformNodeRanges = params.transformNodeRanges;
        this.contentIdProvider = core_common_1.ContentIdProvider.create(params.options.allowInstancing, IModelApp_1.IModelApp.tileAdmin, params.formatVersion);
        params.rootTile.contentId = this.contentIdProvider.rootContentId;
        this._rootTile = new RootTile((0, internal_1.iModelTileParamsFromJSON)(params.rootTile, undefined), this);
        this.decoder = (0, internal_1.acquireImdlDecoder)({
            type: this.batchType,
            omitEdges: false === this.edgeOptions,
            timeline: this.timeline,
            iModel: this.iModel,
            batchModelId: this.modelId,
            is3d: this.is3d,
            containsTransformNodes: this.containsTransformNodes,
            noWorker: !IModelApp_1.IModelApp.tileAdmin.decodeImdlInWorker,
        });
    }
    dispose() {
        this.decoder.release();
        super.dispose();
    }
    get maxDepth() { return 32; }
    get rootTile() { return this._rootTile; }
    /** Exposed chiefly for tests. */
    get staticBranch() { return this._rootTile.staticBranch; }
    get is3d() { return this._options.is3d; }
    get isContentUnbounded() { return false; }
    get viewFlagOverrides() { return viewFlagOverrides; }
    get batchType() { return this._options.batchType; }
    get edgeOptions() { return this._options.edges; }
    get timeline() { return this._options.timeline; }
    get loadPriority() {
        // If the model has been modified, we want to prioritize keeping its graphics up to date.
        return this.tileState === "dynamic" ? internal_1.TileLoadPriority.Dynamic : super.loadPriority;
    }
    _selectTiles(args) {
        args.markUsed(this._rootTile);
        const tiles = [];
        this._rootTile.staticBranch.selectTiles(tiles, args, 0);
        this._numStaticTilesSelected = tiles.length;
        if (this._rootTile.tileState.type === "dynamic")
            this._rootTile.tileState.rootTile.selectTiles(tiles, args);
        return tiles;
    }
    draw(args) {
        const tiles = this.selectTiles(args);
        this._rootTile.draw(args, tiles, this._numStaticTilesSelected);
    }
    prune() {
        const olderThan = core_bentley_1.BeTimePoint.now().minus(this.expirationTime);
        this._rootTile.prune(olderThan);
    }
    /** Exposed strictly for tests. */
    get tileState() {
        return this._rootTile.tileState.type;
    }
    /** Exposed strictly for tests. */
    get hiddenElements() {
        const state = this._rootTile.tileState;
        return "dynamic" === state.type ? state.rootTile.hiddenElements : [];
    }
    getTransformNodeRange(nodeId) {
        return this._transformNodeRanges?.get(nodeId);
    }
    get containsTransformNodes() {
        return undefined !== this._transformNodeRanges;
    }
}
exports.IModelTileTree = IModelTileTree;
//# sourceMappingURL=IModelTileTree.js.map