/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Tile
 */
import { assert, compareBooleans, compareBooleansOrUndefined, compareNumbers, compareStrings, compareStringsOrUndefined, Id64, } from "@itwin/core-bentley";
import { Range3d, Vector3d } from "@itwin/core-geometry";
import { BatchType } from "../FeatureTable";
import { CurrentImdlVersion, FeatureTableHeader, ImdlFlags, ImdlHeader } from "./IModelTileIO";
import { TileReadError, TileReadStatus } from "./TileIO";
// cspell:ignore imdl mult bitfield
// NB: These constants correspond to those defined in Tile.cpp.
var Constants;
(function (Constants) {
    Constants.minToleranceRatioMultiplier = 2;
})(Constants || (Constants = {}));
function compareEdgeOptions(a, b) {
    if (typeof a !== typeof b)
        return a ? 1 : -1;
    if (typeof a === "boolean") {
        assert(typeof b === "boolean");
        return compareBooleans(a, b);
    }
    assert(typeof b === "object");
    let cmp = compareStrings(a.type, b.type);
    if (0 === cmp)
        cmp = compareBooleans(a.smooth, b.smooth);
    return cmp;
}
/** @internal */
export var TileOptions;
(function (TileOptions) {
    /** Given the string representation of an [[IModelTileTreeId]] and the contentId of a [Tile]($frontend) belonging to that [TileTree]($frontend),
     * compute the [[TileOptions]] that were used to generate the Ids.
     * @throws Error if `treeId` or `contentId` are not valid Ids.
     * @note `treeId` and `contentId` are assumed to be valid Ids. They are not fully parsed and validated - only the information required by this function is extracted.
     * @note `treeId` and `contentId` are assumed to have been produced for version 4 or later of the iMdl tile format.
     */
    function fromTreeIdAndContentId(treeId, contentId) {
        const tree = treeFlagsAndFormatVersionFromId(treeId);
        const contentFlags = contentFlagsFromId(contentId);
        const edgeOptions = edgeOptionsFromTreeId(treeId);
        return {
            maximumMajorTileFormatVersion: tree.version,
            enableInstancing: 0 !== (contentFlags & ContentFlags.AllowInstancing),
            enableImprovedElision: 0 !== (contentFlags & ContentFlags.ImprovedElision),
            ignoreAreaPatterns: 0 !== (contentFlags & ContentFlags.IgnoreAreaPatterns),
            enableExternalTextures: 0 !== (contentFlags & ContentFlags.ExternalTextures),
            useProjectExtents: 0 !== (tree.flags & TreeFlags.UseProjectExtents),
            expandProjectExtents: 0 !== (tree.flags & TreeFlags.ExpandProjectExtents),
            optimizeBRepProcessing: 0 !== (tree.flags & TreeFlags.OptimizeBRepProcessing),
            useLargerTiles: 0 !== (tree.flags & TreeFlags.UseLargerTiles),
            disableMagnification: false,
            alwaysSubdivideIncompleteTiles: false,
            edgeOptions,
            disablePolyfaceDecimation: 0 !== (tree.flags & TreeFlags.DisablePolyfaceDecimation),
        };
    }
    TileOptions.fromTreeIdAndContentId = fromTreeIdAndContentId;
})(TileOptions || (TileOptions = {}));
class Parser {
    input;
    curPos = 0;
    constructor(input) {
        this.input = input;
    }
    parse(contentId) {
        this.require(this.input.length > 0);
        // Skip version and flags, they're handled by TileOptions.fromTreeIdAndContentId
        while (this.curPos < this.input.length && this.cur() !== "-")
            this.advance();
        this.eat("-");
        this.require(this.curPos < this.input.length);
        const classifier = this.cur() === "C" ? this.parseClassifier() : undefined;
        const animationId = this.parseAnimation();
        const primary = classifier ? undefined : this.parsePrimary();
        this.require((undefined === classifier) !== (undefined === primary));
        const modelId = this.input.substring(this.curPos);
        this.require(Id64.isId64(modelId));
        const { flags: treeFlags } = treeFlagsAndFormatVersionFromId(this.input);
        const options = TileOptions.fromTreeIdAndContentId(this.input, contentId);
        let parsedContentId;
        try {
            parsedContentId = ContentIdProvider.create(true, options).specFromId(contentId);
        }
        catch {
            this.reject("Invalid content Id");
        }
        if (Object.keys(parsedContentId).some((key) => parsedContentId.hasOwnProperty(key) && typeof parsedContentId[key] === "number" && !Number.isFinite(parsedContentId[key])))
            throw new Error("Invalid content Id");
        const disablePolyfaceDecimation = options.disablePolyfaceDecimation;
        let treeId;
        if (classifier) {
            treeId = { ...classifier, animationId, disablePolyfaceDecimation };
        }
        else {
            assert(undefined !== primary);
            const enforceDisplayPriority = (treeFlags & TreeFlags.EnforceDisplayPriority) !== 0 ? true : undefined;
            treeId = { ...primary, animationId, type: BatchType.Primary, enforceDisplayPriority, disablePolyfaceDecimation };
        }
        return {
            contentId: parsedContentId,
            modelId,
            options,
            treeId,
        };
    }
    cur() {
        this.require(this.curPos < this.input.length);
        return this.input[this.curPos];
    }
    advance() {
        this.require(this.curPos < this.input.length);
        ++this.curPos;
    }
    eat(expectedChar) {
        this.require(this.cur() === expectedChar);
        this.advance();
    }
    reject(message = "Invalid tree Id") {
        throw new Error(message);
    }
    require(condition, message = "Invalid tree Id") {
        if (!condition)
            this.reject(message);
    }
    parseClassifier() {
        this.eat("C");
        let type = BatchType.VolumeClassifier;
        if (this.cur() === "P") {
            type = BatchType.PlanarClassifier;
            this.advance();
        }
        this.eat(":");
        // C: or CP: is always folowed by expansion then an underscore.
        let expansionStr = "";
        while (this.curPos < this.input.length && (this.cur() >= "0" && this.cur() <= "9" || this.cur() === ".")) {
            expansionStr += this.cur();
            this.advance();
        }
        this.eat("_");
        const expansion = Number.parseFloat(expansionStr);
        this.require(!Number.isNaN(expansion));
        return { type, expansion };
    }
    parseAnimation() {
        if (this.cur() !== "A")
            return undefined;
        this.eat("A");
        this.eat(":");
        const termPos = this.input.indexOf("_", this.curPos);
        this.require(termPos > this.curPos);
        const animationId = this.input.substring(this.curPos, termPos);
        this.require(Id64.isId64(animationId));
        this.curPos = termPos + 1; // Skip "_"
        return animationId;
    }
    parsePrimary() {
        const edges = this.parseEdges();
        const sectionCut = this.parseSectionCut();
        return { edges, sectionCut };
    }
    parseEdges() {
        if ("E" !== this.cur())
            return { type: "non-indexed", smooth: false };
        this.eat("E");
        this.eat(":");
        const flag = this.cur();
        this.eat(flag);
        this.eat("_");
        return "0" === flag ? false : edgeOptionsFromFlag(flag);
    }
    parseSectionCut() {
        if ("S" !== this.cur())
            return undefined;
        this.eat("S");
        const termPos = this.input.indexOf("s", this.curPos);
        this.require(termPos > this.curPos);
        const sectionCut = this.input.substring(this.curPos, termPos);
        this.curPos = termPos + 1; // Skip "_";
        return sectionCut;
    }
}
/** @internal */
export function parseTileTreeIdAndContentId(treeId, contentId) {
    const parser = new Parser(treeId);
    return parser.parse(contentId);
}
/** @internal */
export const defaultTileOptions = Object.freeze({
    maximumMajorTileFormatVersion: CurrentImdlVersion.Major,
    enableInstancing: true,
    enableImprovedElision: true,
    ignoreAreaPatterns: false,
    enableExternalTextures: true,
    useProjectExtents: true,
    expandProjectExtents: true,
    optimizeBRepProcessing: true,
    useLargerTiles: true,
    disableMagnification: false,
    alwaysSubdivideIncompleteTiles: false,
    disablePolyfaceDecimation: false,
    edgeOptions: {
        type: "compact",
        smooth: true,
    },
});
function contentFlagsFromId(id) {
    if (0 === id.length || "-" !== id[0])
        throw new Error("Invalid content Id");
    // V4: -flags-d-i-j-k-m - version in tree Id
    const end = id.indexOf("-", 1);
    if (-1 !== end) {
        const flags = Number.parseInt(id.substring(1, end), 16);
        if (!Number.isNaN(flags))
            return flags;
    }
    throw new Error("Invalid content Id");
}
function treeFlagsAndFormatVersionFromId(id) {
    if (0 === id.length)
        throw new Error("Invalid tree Id");
    let parts = id.split("-");
    if (parts.length > 0) {
        parts = parts[0].split("_");
        if (parts.length === 2) {
            const version = Number.parseInt(parts[0], 16);
            const flags = Number.parseInt(parts[1], 16);
            if (!Number.isNaN(version) || !Number.isNaN(flags))
                return { version, flags };
        }
    }
    throw new Error("Invalid tree Id");
}
function edgeOptionsFromTreeId(id) {
    const pos = id.indexOf("E:");
    if (pos <= 0)
        return { type: "non-indexed", smooth: false };
    return edgeOptionsFromFlag(id[pos + 2]);
}
function edgeOptionsFromFlag(flag) {
    if ("0" === flag)
        return defaultTileOptions.edgeOptions;
    const smooth = flag !== "2" && flag !== "5";
    let type;
    switch (flag) {
        case "2":
        case "4":
            type = "indexed";
            break;
        case "3":
            type = "non-indexed";
            break;
        case "5":
        case "6":
            type = "compact";
            break;
        default:
            throw new Error("Invalid tree Id");
    }
    return { type, smooth };
}
function edgeOptionsToString(options) {
    if (!options)
        return "E:0_";
    switch (options.type) {
        case "non-indexed": return options.smooth ? "E:3_" : "";
        case "indexed": return options.smooth ? "E:4_" : "E:2_";
        case "compact": return options.smooth ? "E:6_" : "E:5_";
        default: throw new Error("Invalid tree Id");
    }
}
/** @internal */
export function getMaximumMajorTileFormatVersion(maxMajorVersion, formatVersion) {
    // The `formatVersion` input is from the backend, telling us precisely the maximum major+minor version it can produce.
    // Ensure we do not request tiles of a newer major version than backend can supply or it can read; and also limit major version
    // to that optionally configured by the app.
    let majorVersion = maxMajorVersion;
    if (undefined !== formatVersion)
        majorVersion = Math.min((formatVersion >>> 0x10), majorVersion);
    // Version number less than 1 is invalid - ignore
    majorVersion = Math.max(majorVersion, 1);
    // Version number greater than current known version ignored
    majorVersion = Math.min(majorVersion, CurrentImdlVersion.Major);
    // Version numbers are integers - round down
    return Math.max(Math.floor(majorVersion), 1);
}
/** Flags controlling the structure of a tile tree. The flags are part of the tile tree's Id.
 * @alpha
 */
export var TreeFlags;
(function (TreeFlags) {
    TreeFlags[TreeFlags["None"] = 0] = "None";
    TreeFlags[TreeFlags["UseProjectExtents"] = 1] = "UseProjectExtents";
    TreeFlags[TreeFlags["EnforceDisplayPriority"] = 2] = "EnforceDisplayPriority";
    TreeFlags[TreeFlags["OptimizeBRepProcessing"] = 4] = "OptimizeBRepProcessing";
    TreeFlags[TreeFlags["UseLargerTiles"] = 8] = "UseLargerTiles";
    TreeFlags[TreeFlags["ExpandProjectExtents"] = 16] = "ExpandProjectExtents";
    TreeFlags[TreeFlags["DisablePolyfaceDecimation"] = 32] = "DisablePolyfaceDecimation";
})(TreeFlags || (TreeFlags = {}));
function animationIdToString(animationId) {
    return `A:${animationId}_`;
}
/** Convert a tile tree Id to its string representation.
 * @internal
 */
export function iModelTileTreeIdToString(modelId, treeId, options) {
    let idStr = "";
    let flags = options.useProjectExtents ? TreeFlags.UseProjectExtents : TreeFlags.None;
    if (options.optimizeBRepProcessing)
        flags |= TreeFlags.OptimizeBRepProcessing;
    if (options.disablePolyfaceDecimation)
        flags |= TreeFlags.DisablePolyfaceDecimation;
    if (options.useLargerTiles)
        flags |= TreeFlags.UseLargerTiles;
    if (options.expandProjectExtents)
        flags |= TreeFlags.ExpandProjectExtents;
    if (BatchType.Primary === treeId.type) {
        if (undefined !== treeId.animationId)
            idStr = `${idStr}${animationIdToString(treeId.animationId)}`;
        else if (treeId.enforceDisplayPriority) // animation and priority are currently mutually exclusive
            flags |= TreeFlags.EnforceDisplayPriority;
        const edges = edgeOptionsToString(treeId.edges);
        const sectionCut = treeId.sectionCut ? `S${treeId.sectionCut}s` : "";
        idStr = `${idStr}${edges}${sectionCut}`;
    }
    else {
        const typeStr = BatchType.PlanarClassifier === treeId.type ? "CP" : "C";
        idStr = `${idStr + typeStr}:${treeId.expansion.toFixed(6)}_`;
        if (BatchType.VolumeClassifier === treeId.type) {
            // Volume classifiers always use the exact project extents.
            flags |= TreeFlags.UseProjectExtents;
            flags &= ~TreeFlags.ExpandProjectExtents;
        }
        if (undefined !== treeId.animationId)
            idStr = `${idStr}${animationIdToString(treeId.animationId)}`;
    }
    const version = getMaximumMajorTileFormatVersion(options.maximumMajorTileFormatVersion);
    if (version >= 4) {
        const prefix = `${version.toString(16)}_${flags.toString(16)}-`;
        idStr = prefix + idStr;
    }
    return idStr + modelId;
}
/** Ordinal comparison of two tile tree Ids, e.g., for use in sorted containers.
 * @internal
 */
export function compareIModelTileTreeIds(lhs, rhs) {
    let cmp = compareNumbers(lhs.type, rhs.type);
    if (0 === cmp)
        cmp = compareStringsOrUndefined(lhs.animationId, rhs.animationId);
    if (0 !== cmp)
        return cmp;
    // NB: The redundant checks on BatchType below are to satisfy compiler.
    assert(lhs.type === rhs.type);
    if (BatchType.Primary === lhs.type && BatchType.Primary === rhs.type) {
        cmp = compareEdgeOptions(lhs.edges, rhs.edges);
        if (0 === cmp) {
            cmp = compareBooleansOrUndefined(lhs.enforceDisplayPriority, rhs.enforceDisplayPriority);
            if (0 === cmp)
                cmp = compareStringsOrUndefined(lhs.sectionCut, rhs.sectionCut);
        }
    }
    else if (BatchType.Primary !== lhs.type && BatchType.Primary !== rhs.type) {
        cmp = compareNumbers(lhs.expansion, rhs.expansion);
    }
    return cmp;
}
/** Flags controlling how tile content is produced. The flags are part of the ContentId.
 * @alpha
 */
export var ContentFlags;
(function (ContentFlags) {
    ContentFlags[ContentFlags["None"] = 0] = "None";
    ContentFlags[ContentFlags["AllowInstancing"] = 1] = "AllowInstancing";
    ContentFlags[ContentFlags["ImprovedElision"] = 2] = "ImprovedElision";
    ContentFlags[ContentFlags["IgnoreAreaPatterns"] = 4] = "IgnoreAreaPatterns";
    ContentFlags[ContentFlags["ExternalTextures"] = 8] = "ExternalTextures";
})(ContentFlags || (ContentFlags = {}));
/** Contains logic for working with tile content Ids according to a specific content Id scheme. Which scheme is used depends on
 * the major version of the tile format.
 * @internal
 */
export class ContentIdProvider {
    majorFormatVersion;
    contentFlags;
    constructor(formatVersion, contentFlags) {
        this.majorFormatVersion = formatVersion;
        this.contentFlags = contentFlags;
    }
    get rootContentId() {
        return this.computeId(0, 0, 0, 0, 1);
    }
    idFromParentAndMultiplier(parentId, multiplier) {
        const lastSepPos = parentId.lastIndexOf(this._separator);
        assert(-1 !== lastSepPos);
        return parentId.substring(0, lastSepPos + 1) + multiplier.toString(16);
    }
    specFromId(id) {
        const parts = id.split(this._separator);
        const len = parts.length;
        assert(len >= 5);
        return {
            depth: parseInt(parts[len - 5], 16),
            i: parseInt(parts[len - 4], 16),
            j: parseInt(parts[len - 3], 16),
            k: parseInt(parts[len - 2], 16),
            multiplier: parseInt(parts[len - 1], 16),
        };
    }
    idFromSpec(spec) {
        return this.computeId(spec.depth, spec.i, spec.j, spec.k, spec.multiplier);
    }
    join(depth, i, j, k, mult) {
        const sep = this._separator;
        return depth.toString(16) + sep + i.toString(16) + sep + j.toString(16) + sep + k.toString(16) + sep + mult.toString(16);
    }
    /** formatVersion is the maximum major version supported by the back-end supplying the tile tree.
     * Must ensure front-end does not request tiles of a format the back-end cannot supply, and back-end does
     * not supply tiles of a format the front-end doesn't recognize.
     */
    static create(allowInstancing, options, formatVersion) {
        const majorVersion = getMaximumMajorTileFormatVersion(options.maximumMajorTileFormatVersion, formatVersion);
        assert(majorVersion > 0);
        assert(Math.floor(majorVersion) === majorVersion);
        switch (majorVersion) {
            case 0:
            case 1:
                return new ContentIdV1Provider(majorVersion);
            case 2:
            case 3:
                return new ContentIdV2Provider(majorVersion, allowInstancing, options);
            default:
                return new ContentIdV4Provider(allowInstancing, options, majorVersion);
        }
    }
}
/** The original (major version 1) tile format used a content Id scheme of the format
 * `depth/i/j/k/multiplier`.
 * @internal
 */
class ContentIdV1Provider extends ContentIdProvider {
    constructor(majorVersion) {
        super(majorVersion, ContentFlags.None);
    }
    get _separator() { return "/"; }
    computeId(depth, i, j, k, mult) {
        return this.join(depth, i, j, k, mult);
    }
}
/** Tile formats 2 and 3 use a content Id scheme encoding styling flags and the major format version
 * into the content Id, of the format `_majorVersion_flags_depth_i_j_k_multiplier`.
 * @internal
 */
class ContentIdV2Provider extends ContentIdProvider {
    _prefix;
    constructor(majorVersion, allowInstancing, options) {
        const flags = (allowInstancing && options.enableInstancing) ? ContentFlags.AllowInstancing : ContentFlags.None;
        super(majorVersion, flags);
        this._prefix = this._separator + majorVersion.toString(16) + this._separator + flags.toString(16) + this._separator;
    }
    get _separator() { return "_"; }
    computeId(depth, i, j, k, mult) {
        return this._prefix + this.join(depth, i, j, k, mult);
    }
}
/** Tile formats 4+ encode styling flags but not major format version. (The version is specified by the tile tree's Id).
 * Format: `-flags-depth-i-j-k-multiplier`.
 * @internal
 */
class ContentIdV4Provider extends ContentIdProvider {
    _prefix;
    constructor(allowInstancing, options, majorVersion) {
        let flags = (allowInstancing && options.enableInstancing) ? ContentFlags.AllowInstancing : ContentFlags.None;
        if (options.enableImprovedElision)
            flags = flags | ContentFlags.ImprovedElision;
        if (options.ignoreAreaPatterns)
            flags = flags | ContentFlags.IgnoreAreaPatterns;
        if (options.enableExternalTextures)
            flags = flags | ContentFlags.ExternalTextures;
        super(majorVersion, flags);
        this._prefix = this._separator + flags.toString(16) + this._separator;
    }
    get _separator() { return "-"; }
    computeId(depth, i, j, k, mult) {
        return this._prefix + this.join(depth, i, j, k, mult);
    }
}
/** @internal */
export function bisectTileRange3d(range, takeUpper) {
    const diag = range.diagonal();
    const pt = takeUpper ? range.high : range.low;
    if (diag.x > diag.y && diag.x > diag.z)
        pt.x = (range.low.x + range.high.x) / 2.0;
    else if (diag.y > diag.z)
        pt.y = (range.low.y + range.high.y) / 2.0;
    else
        pt.z = (range.low.z + range.high.z) / 2.0;
}
/** @internal */
export function bisectTileRange2d(range, takeUpper) {
    const diag = range.diagonal();
    const pt = takeUpper ? range.high : range.low;
    if (diag.x > diag.y)
        pt.x = (range.low.x + range.high.x) / 2.0;
    else
        pt.y = (range.low.y + range.high.y) / 2.0;
}
/** Given a description of a tile, compute the ranges which would result from sub-dividing its range into 4 or 8 sub-volumes.
 * @internal
 */
export function computeChildTileRanges(tile, root) {
    const emptyMask = tile.emptySubRangeMask;
    const is2d = root.is2d;
    const bisectRange = is2d ? bisectTileRange2d : bisectTileRange3d;
    const ranges = [];
    for (let i = 0; i < 2; i++) {
        for (let j = 0; j < 2; j++) {
            for (let k = 0; k < (is2d ? 1 : 2); k++) {
                const emptyBit = 1 << (i + j * 2 + k * 4);
                const isEmpty = 0 !== (emptyMask & emptyBit);
                const range = tile.range.clone();
                bisectRange(range, 0 === i);
                bisectRange(range, 0 === j);
                if (!is2d)
                    bisectRange(range, 0 === k);
                ranges.push({ range, isEmpty });
            }
        }
    }
    return ranges;
}
/** Given a description of the parent tile, obtain the properties of its child tiles, and the number of empty children.
 * @internal
 */
export function computeChildTileProps(parent, idProvider, root) {
    let numEmpty = 0;
    const children = [];
    // Leaf nodes have no children
    if (parent.isLeaf)
        return { children, numEmpty };
    // One child, same volume as parent, but higher-resolution.
    if (undefined !== parent.sizeMultiplier) {
        const sizeMultiplier = parent.sizeMultiplier * 2;
        const contentId = idProvider.idFromParentAndMultiplier(parent.contentId, sizeMultiplier);
        children.push({
            contentId,
            range: parent.range,
            contentRange: parent.contentRange,
            sizeMultiplier,
            isLeaf: false,
            maximumSize: root.tileScreenSize,
        });
        return { children, numEmpty };
    }
    // Sub-divide parent's range into 4 (for 2d trees) or 8 (for 3d trees) child tiles.
    const parentSpec = idProvider.specFromId(parent.contentId);
    const childSpec = { ...parentSpec };
    childSpec.depth = parentSpec.depth + 1;
    // This mask is a bitfield in which an 'on' bit indicates sub-volume containing no geometry.
    // Don't bother creating children or requesting content for such empty volumes.
    const emptyMask = parent.emptySubRangeMask;
    // Spatial tree range == project extents; content range == model range.
    // Trivially reject children whose ranges are entirely outside model range.
    let treeContentRange = root.contentRange;
    if (undefined !== treeContentRange && treeContentRange.containsRange(parent.range)) {
        // Parent is wholly within model range - don't bother testing child ranges against it.
        treeContentRange = undefined;
    }
    const is2d = root.is2d;
    const bisectRange = is2d ? bisectTileRange2d : bisectTileRange3d;
    for (let i = 0; i < 2; i++) {
        for (let j = 0; j < 2; j++) {
            for (let k = 0; k < (is2d ? 1 : 2); k++) {
                const emptyBit = 1 << (i + j * 2 + k * 4);
                if (0 !== (emptyMask & emptyBit)) {
                    // volume is known to contain no geometry.
                    ++numEmpty;
                    continue;
                }
                const range = parent.range.clone();
                bisectRange(range, 0 === i);
                bisectRange(range, 0 === j);
                if (!is2d)
                    bisectRange(range, 0 === k);
                if (undefined !== treeContentRange && !range.intersectsRange(treeContentRange)) {
                    // volume is within project extents but entirely outside model range
                    ++numEmpty;
                    continue;
                }
                childSpec.i = parentSpec.i * 2 + i;
                childSpec.j = parentSpec.j * 2 + j;
                childSpec.k = parentSpec.k * 2 + k;
                const childId = idProvider.idFromSpec(childSpec);
                children.push({ contentId: childId, range, maximumSize: root.tileScreenSize });
            }
        }
    }
    return { children, numEmpty };
}
/** Deserializes tile content metadata.
 * @throws [[TileReadError]]
 * @internal
 * @deprecated in 4.0 - will not be removed until after 2026-06-13. Use decodeTileContentDescription. I think tile agents (or their tests) are using this function.
 */
export function readTileContentDescription(stream, sizeMultiplier, is2d, options, isVolumeClassifier) {
    return decodeTileContentDescription({ stream, sizeMultiplier, is2d, options, isVolumeClassifier });
}
/** @internal */
export function decodeTileContentDescription(args) {
    const { stream, options } = args;
    const isVolumeClassifier = args.isVolumeClassifier ?? false;
    stream.reset();
    const header = new ImdlHeader(stream);
    if (!header.isValid)
        throw new TileReadError(TileReadStatus.InvalidHeader);
    else if (!header.isReadableVersion)
        throw new TileReadError(TileReadStatus.NewerMajorVersion);
    // Skip the feature table.
    const featureTableStartPos = stream.curPos;
    const ftHeader = FeatureTableHeader.readFrom(stream);
    if (undefined === ftHeader)
        throw new TileReadError(TileReadStatus.InvalidFeatureTable);
    stream.curPos = featureTableStartPos + ftHeader.length;
    let sizeMultiplier = args.sizeMultiplier;
    let isLeaf = args.isLeaf;
    if (undefined === isLeaf) {
        // Determine subdivision based on header data.
        const completeTile = 0 === (header.flags & ImdlFlags.Incomplete);
        const emptyTile = completeTile && 0 === header.numElementsIncluded && 0 === header.numElementsExcluded;
        isLeaf = (emptyTile || isVolumeClassifier); // Current classifier algorithm supports only a single tile.
        if (!isLeaf) {
            // Non-spatial (2d) models are of arbitrary scale and contain geometry like line work and especially text which
            // can be adversely affected by quantization issues when zooming in closely.
            const maxLeafTolerance = 1.0;
            // Must sub-divide if tile explicitly specifies...
            let canSkipSubdivision = 0 === (header.flags & ImdlFlags.DisallowMagnification);
            // ...or in 2d, or if app explicitly disabled magnification, or tolerance large enough to risk quantization error...
            canSkipSubdivision = canSkipSubdivision && !args.is2d && !options.disableMagnification && header.tolerance <= maxLeafTolerance;
            // ...or app specifies incomplete tiles must always be sub-divided.
            canSkipSubdivision = canSkipSubdivision && (completeTile || !options.alwaysSubdivideIncompleteTiles);
            if (canSkipSubdivision) {
                const minElementsPerTile = 100;
                if (completeTile && 0 === header.numElementsExcluded && header.numElementsIncluded <= minElementsPerTile) {
                    const containsCurves = 0 !== (header.flags & ImdlFlags.ContainsCurves);
                    if (!containsCurves)
                        isLeaf = true;
                    else if (undefined === sizeMultiplier)
                        sizeMultiplier = 1.0;
                }
                else if (undefined === sizeMultiplier && header.numElementsIncluded + header.numElementsExcluded <= minElementsPerTile) {
                    sizeMultiplier = 1.0;
                }
            }
        }
    }
    return {
        featureTableStartPos,
        contentRange: header.contentRange,
        isLeaf,
        sizeMultiplier,
        emptySubRangeMask: header.emptySubRanges,
    };
}
const scratchRangeDiagonal = new Vector3d();
/** Compute the chord tolerance for the specified tile of the given range with the specified size multiplier.
 * @internal
 */
export function computeTileChordTolerance(tile, is3d, tileScreenSize) {
    if (tile.range.isNull)
        return 0;
    const diagonal = tile.range.diagonal(scratchRangeDiagonal);
    const diagDist = is3d ? diagonal.magnitude() : diagonal.magnitudeXY();
    const mult = Math.max(tile.sizeMultiplier ?? 1, 1);
    return diagDist / (tileScreenSize * Constants.minToleranceRatioMultiplier * Math.max(1, mult));
}
/** Deserializes tile metadata.
 * @internal
 */
export class TileMetadataReader {
    _is2d;
    _isVolumeClassifier;
    _options;
    constructor(type, is2d, options) {
        this._is2d = is2d;
        this._isVolumeClassifier = BatchType.VolumeClassifier === type;
        this._options = options;
    }
    /** Produce metadata from the specified tile content.
     * @throws [[TileReadError]]
     */
    read(stream, props) {
        const content = decodeTileContentDescription({
            stream,
            sizeMultiplier: props.sizeMultiplier,
            is2d: this._is2d,
            options: this._options,
            isVolumeClassifier: this._isVolumeClassifier,
        });
        return {
            contentRange: content.contentRange,
            isLeaf: content.isLeaf,
            sizeMultiplier: content.sizeMultiplier,
            emptySubRangeMask: content.emptySubRangeMask,
            range: Range3d.fromJSON(props.range),
            contentId: props.contentId,
        };
    }
}
//# sourceMappingURL=TileMetadata.js.map