"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 Views
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DrawingViewState = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const core_geometry_1 = require("@itwin/core-geometry");
const CategorySelectorState_1 = require("./CategorySelectorState");
const CoordSystem_1 = require("./CoordSystem");
const DisplayStyleState_1 = require("./DisplayStyleState");
const Frustum2d_1 = require("./Frustum2d");
const IModelApp_1 = require("./IModelApp");
const FeatureSymbology_1 = require("./render/FeatureSymbology");
const GraphicBranch_1 = require("./render/GraphicBranch");
const MockRender_1 = require("./internal/render/MockRender");
const internal_1 = require("./tile/internal");
const Viewport_1 = require("./Viewport");
const ViewRect_1 = require("./common/ViewRect");
const ViewState_1 = require("./ViewState");
/** The information required to instantiate a [[SectionAttachment]]. This information is supplied to DrawingViewState constructor via ViewStateProps.
 * The spatial view is obtained asynchronously in DrawingViewState.load(). The SectionAttachment is created in DrawingViewState.attachToViewport and
 * disposed of in DrawingViewState.detachFromViewport.
 */
class SectionAttachmentInfo {
    _spatialView;
    _drawingToSpatialTransform;
    _displaySpatialView;
    get spatialView() { return this._spatialView; }
    get wantDisplayed() {
        return this._displaySpatialView || DrawingViewState.alwaysDisplaySpatialView;
    }
    constructor(spatialView, drawingToSpatialTransform, displaySpatialView) {
        this._spatialView = spatialView;
        this._drawingToSpatialTransform = drawingToSpatialTransform;
        this._displaySpatialView = displaySpatialView;
    }
    static fromJSON(props) {
        if (!props)
            return new SectionAttachmentInfo(core_bentley_1.Id64.invalid, core_geometry_1.Transform.createIdentity(), false);
        return new SectionAttachmentInfo(props.spatialView, core_geometry_1.Transform.fromJSON(props.drawingToSpatialTransform), true === props.displaySpatialView);
    }
    toJSON() {
        if ("string" === typeof this._spatialView && !core_bentley_1.Id64.isValidId64(this._spatialView))
            return undefined;
        return {
            spatialView: (this._spatialView instanceof ViewState_1.ViewState3d) ? this._spatialView.id : this._spatialView,
            drawingToSpatialTransform: this._drawingToSpatialTransform.isIdentity ? undefined : this._drawingToSpatialTransform.toJSON(),
            displaySpatialView: this._displaySpatialView,
        };
    }
    clone(iModel) {
        let spatialView = this._spatialView;
        if (spatialView instanceof ViewState_1.ViewState3d)
            spatialView = spatialView.clone(iModel);
        return new SectionAttachmentInfo(spatialView, this._drawingToSpatialTransform, this._displaySpatialView);
    }
    preload(options) {
        if (!this.wantDisplayed)
            return;
        if (this._spatialView instanceof ViewState_1.ViewState3d)
            return;
        if (!core_bentley_1.Id64.isValidId64(this._spatialView))
            return;
        options.spatialViewId = this._spatialView;
        options.viewStateLoadProps = {
            displayStyle: {
                omitScheduleScriptElementIds: !IModelApp_1.IModelApp.tileAdmin.enableFrontendScheduleScripts,
                compressExcludedElementIds: true,
            },
        };
    }
    async load(iModel) {
        if (!this.wantDisplayed)
            return;
        if (this._spatialView instanceof ViewState_1.ViewState3d)
            return;
        if (!core_bentley_1.Id64.isValidId64(this._spatialView))
            return;
        const spatialView = await iModel.views.load(this._spatialView);
        if (spatialView instanceof ViewState_1.ViewState3d)
            this._spatialView = spatialView;
    }
    async postload(options, iModel) {
        let spatialView;
        if (options.spatialViewProps) {
            spatialView = await iModel.views.convertViewStatePropsToViewState(options.spatialViewProps);
        }
        if (spatialView instanceof ViewState_1.ViewState3d)
            this._spatialView = spatialView;
    }
    createAttachment(toSheet) {
        if (!this.wantDisplayed || !(this._spatialView instanceof ViewState_1.ViewState3d))
            return undefined;
        const spatialToDrawing = this._drawingToSpatialTransform.inverse();
        return spatialToDrawing ? new SectionAttachment(this._spatialView, spatialToDrawing, this._drawingToSpatialTransform, toSheet) : undefined;
    }
    get sectionDrawingInfo() {
        return {
            drawingToSpatialTransform: this._drawingToSpatialTransform,
            spatialView: this._spatialView instanceof ViewState_1.ViewState3d ? this._spatialView.id : this._spatialView,
        };
    }
}
/** A mostly no-op [[RenderTarget]] for a [[SectionAttachment]]. It allocates no webgl resources. */
class SectionTarget extends MockRender_1.MockRender.OffScreenTarget {
    _attachment;
    constructor(attachment) {
        super(IModelApp_1.IModelApp.renderSystem, new ViewRect_1.ViewRect(0, 0, 1, 1));
        this._attachment = attachment;
    }
    changeScene(scene) {
        this._attachment.scene = scene;
    }
    overrideFeatureSymbology(ovrs) {
        this._attachment.symbologyOverrides = ovrs;
    }
}
/** Draws the contents of an orthographic [[ViewState3d]] directly into a [[DrawingViewState]], if the associated [SectionDrawing]($backend)
 * specifies it should be. We select tiles for the view in the context of a lightweight offscreen viewport with a no-op [[RenderTarget]], then
 * add the resultant graphics to the drawing view's scene. The attachment is created in DrawingViewState.attachToViewport and disposed of in
 * DrawingViewState.detachFromViewport.
 */
class SectionAttachment {
    _viewFlagOverrides;
    toDrawing;
    _fromDrawing;
    _viewRect = new ViewRect_1.ViewRect(0, 0, 1, 1);
    _originalFrustum = new core_common_1.Frustum();
    _drawingExtents;
    viewport;
    _branchOptions;
    scene;
    symbologyOverrides;
    get view() {
        (0, core_bentley_1.assert)(this.viewport.view instanceof ViewState_1.ViewState3d);
        return this.viewport.view;
    }
    get zDepth() {
        return this._drawingExtents.z;
    }
    get drawingRange() {
        const frustum3d = this._originalFrustum.transformBy(this.toDrawing);
        return frustum3d.toRange();
    }
    constructor(view, toDrawing, fromDrawing, toSheet) {
        // Save the input for clone(). Attach a copy to the viewport.
        this.toDrawing = toDrawing;
        this._fromDrawing = fromDrawing;
        this.viewport = Viewport_1.OffScreenViewport.createViewport(view, new SectionTarget(this), true);
        this.symbologyOverrides = new FeatureSymbology_1.FeatureSymbology.Overrides(view);
        let clipVolume;
        let clip = this.view.getViewClip();
        if (clip) {
            clip = clip.clone();
            const clipTransform = toSheet ? toSheet.multiplyTransformTransform(this.toDrawing) : this.toDrawing;
            clip.transformInPlace(clipTransform);
            clipVolume = IModelApp_1.IModelApp.renderSystem.createClipVolume(clip);
        }
        this._branchOptions = {
            clipVolume,
            hline: view.getDisplayStyle3d().settings.hiddenLineSettings,
            inSectionDrawingAttachment: true,
            frustum: {
                is3d: true,
                scale: { x: 1, y: 1 },
            },
            contours: view.getDisplayStyle3d().settings.contours
        };
        this._viewFlagOverrides = { ...view.viewFlags, lighting: false, shadows: false };
        // Save off the original frustum (potentially adjusted by viewport).
        this.viewport.setupFromView();
        this.viewport.viewingSpace.getFrustum(CoordSystem_1.CoordSystem.World, true, this._originalFrustum);
        const drawingFrustum = this._originalFrustum.transformBy(this.toDrawing);
        const drawingRange = drawingFrustum.toRange();
        this._drawingExtents = drawingRange.diagonal();
        this._drawingExtents.z = Math.abs(this._drawingExtents.z);
    }
    [Symbol.dispose]() {
        this.viewport[Symbol.dispose]();
    }
    addToScene(context) {
        if (context.viewport.freezeScene)
            return;
        const pixelSize = context.viewport.getPixelSizeAtPoint();
        if (0 === pixelSize)
            return;
        // Adjust offscreen viewport's frustum based on intersection with drawing view frustum.
        const frustum3d = this._originalFrustum.transformBy(this.toDrawing);
        const frustumRange3d = frustum3d.toRange();
        const frustum2d = context.viewport.getWorldFrustum();
        const frustumRange2d = frustum2d.toRange();
        const intersect = frustumRange3d.intersect(frustumRange2d);
        if (intersect.isNull)
            return;
        frustum3d.initFromRange(intersect);
        frustum3d.transformBy(this._fromDrawing, frustum3d);
        this.viewport.setupViewFromFrustum(frustum3d);
        // Adjust view rect based on size of attachment on screen so tiles of appropriate LOD are selected.
        const width = this._drawingExtents.x * intersect.xLength() / frustumRange3d.xLength();
        const height = this._drawingExtents.y * intersect.yLength() / frustumRange3d.yLength();
        this._viewRect.width = Math.max(1, Math.round(width / pixelSize));
        this._viewRect.height = Math.max(1, Math.round(height / pixelSize));
        this.viewport.setRect(this._viewRect);
        // Propagate settings from drawing viewport.
        this.viewport.debugBoundingBoxes = context.viewport.debugBoundingBoxes;
        this.viewport.setTileSizeModifier(context.viewport.tileSizeModifier);
        // Create the scene.
        this.viewport.renderFrame();
        const scene = this.scene;
        if (!scene)
            return;
        // Extract graphics and insert into drawing's scene context.
        const outputGraphics = (source) => {
            if (0 === source.length)
                return;
            const graphics = new GraphicBranch_1.GraphicBranch();
            graphics.setViewFlagOverrides(this._viewFlagOverrides);
            graphics.symbologyOverrides = this.symbologyOverrides;
            for (const graphic of source)
                graphics.entries.push(graphic);
            const branch = context.createGraphicBranch(graphics, this.toDrawing, this._branchOptions);
            context.outputGraphic(branch);
        };
        outputGraphics(scene.foreground);
        context.withGraphicType(internal_1.TileGraphicType.BackgroundMap, () => outputGraphics(scene.background));
        context.withGraphicType(internal_1.TileGraphicType.Overlay, () => outputGraphics(scene.overlay));
        // Report tile statistics to drawing viewport.
        const tileAdmin = IModelApp_1.IModelApp.tileAdmin;
        const selectedAndReady = tileAdmin.getTilesForUser(this.viewport);
        const requested = tileAdmin.getRequestsForUser(this.viewport);
        tileAdmin.addExternalTilesForUser(context.viewport, {
            requested: requested?.size ?? 0,
            selected: selectedAndReady?.selected.size ?? 0,
            ready: selectedAndReady?.ready.size ?? 0,
        });
    }
}
/** A view of a [DrawingModel]($backend)
 * @public
 * @extensions
 */
class DrawingViewState extends ViewState_1.ViewState2d {
    static get className() { return "DrawingViewDefinition"; }
    /** Exposed strictly for testing and debugging. Indicates that when loading the view, the spatial view should be displayed even
     * if `SectionDrawing.displaySpatialView` is not `true`.
     * @internal
     */
    static alwaysDisplaySpatialView = false;
    /** Exposed strictly for testing and debugging. Indicates that the 2d graphics should not be displayed.
     * @internal
     */
    static hideDrawingGraphics = false;
    _viewedExtents;
    _attachmentInfo;
    _attachment;
    /** Strictly for testing. @internal */
    get sectionDrawingProps() {
        return this._attachmentInfo.toJSON();
    }
    /** Strictly for testing. @internal */
    get sectionDrawingInfo() {
        return this._attachmentInfo.sectionDrawingInfo;
    }
    /** Strictly for testing. @internal */
    get attachment() {
        return this._attachment;
    }
    /** Strictly for testing. @internal */
    get attachmentInfo() {
        return this._attachmentInfo;
    }
    constructor(props, iModel, categories, displayStyle, extents, sectionDrawing) {
        super(props, iModel, categories, displayStyle);
        if (categories instanceof DrawingViewState) {
            this._viewedExtents = categories._viewedExtents.clone();
            this._attachmentInfo = categories._attachmentInfo.clone(iModel);
        }
        else {
            this._viewedExtents = extents;
            this._attachmentInfo = SectionAttachmentInfo.fromJSON(sectionDrawing);
        }
    }
    /** See [[ViewState.attachToViewport]]. */
    attachToViewport(args) {
        super.attachToViewport(args);
        (0, core_bentley_1.assert)(undefined === this._attachment);
        this._attachment = this._attachmentInfo.createAttachment(args.drawingToSheetTransform);
    }
    /** See [[ViewState.detachFromViewport]]. */
    detachFromViewport() {
        super.detachFromViewport();
        this._attachment = (0, core_bentley_1.dispose)(this._attachment);
    }
    async changeViewedModel(modelId) {
        await super.changeViewedModel(modelId);
        const props = await this.querySectionDrawingProps();
        this._attachmentInfo = SectionAttachmentInfo.fromJSON(props);
        // super.changeViewedModel() throws if attached to viewport, and attachment only allocated while attached to viewport
        (0, core_bentley_1.assert)(undefined === this._attachment);
    }
    async querySectionDrawingProps() {
        let spatialView = core_bentley_1.Id64.invalid;
        let drawingToSpatialTransform;
        let displaySpatialView = false;
        try {
            const ecsql = `
        SELECT spatialView,
          json_extract(jsonProperties, '$.drawingToSpatialTransform') as drawingToSpatialTransform,
          CAST(json_extract(jsonProperties, '$.displaySpatialView') as BOOLEAN) as displaySpatialView
        FROM bis.SectionDrawing
        WHERE ECInstanceId=${this.baseModelId}`;
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            for await (const row of this.iModel.createQueryReader(ecsql, undefined, { rowFormat: core_common_1.QueryRowFormat.UseJsPropertyNames })) {
                spatialView = core_bentley_1.Id64.fromJSON(row.spatialView?.id);
                displaySpatialView = !!row.displaySpatialView;
                try {
                    drawingToSpatialTransform = JSON.parse(row.drawingToSpatialTransform);
                }
                catch {
                    // We'll use identity transform.
                }
                break;
            }
        }
        catch {
            // The version of BisCore ECSchema in the iModel is probably too old to contain the SectionDrawing ECClass.
        }
        return { spatialView, displaySpatialView, drawingToSpatialTransform };
    }
    /** @internal */
    preload(hydrateRequest) {
        (0, core_bentley_1.assert)(!this.isAttachedToViewport);
        super.preload(hydrateRequest);
        this._attachmentInfo.preload(hydrateRequest);
    }
    /** @internal */
    async postload(hydrateResponse) {
        const promises = [];
        promises.push(super.postload(hydrateResponse));
        promises.push(this._attachmentInfo.postload(hydrateResponse, this.iModel));
        await Promise.all(promises);
    }
    static createFromProps(props, iModel) {
        const cat = new CategorySelectorState_1.CategorySelectorState(props.categorySelectorProps, iModel);
        const displayStyleState = new DisplayStyleState_1.DisplayStyle2dState(props.displayStyleProps, iModel);
        const extents = props.modelExtents ? core_geometry_1.Range3d.fromJSON(props.modelExtents) : new core_geometry_1.Range3d();
        // use "new this" so subclasses are correct
        return new this(props.viewDefinitionProps, iModel, cat, displayStyleState, extents, props.sectionDrawing);
    }
    toProps() {
        const props = super.toProps();
        props.modelExtents = this._viewedExtents.toJSON();
        props.sectionDrawing = this._attachmentInfo.toJSON();
        return props;
    }
    getViewedExtents() {
        const extents = this._viewedExtents.clone();
        if (this._attachment) {
            extents.extendRange(this._attachment.drawingRange);
        }
        return extents;
    }
    get defaultExtentLimits() {
        return {
            min: core_geometry_1.Constant.oneMillimeter,
            max: 3 * core_geometry_1.Constant.diameterOfEarth,
        };
    }
    isDrawingView() { return true; }
    /** See [[ViewState.getOrigin]]. */
    getOrigin() {
        const origin = super.getOrigin();
        if (this._attachment)
            origin.z = -this._attachment.zDepth;
        return origin;
    }
    /** See [[ViewState.getExtents]]. */
    getExtents() {
        const extents = super.getExtents();
        if (this._attachment)
            extents.z = this._attachment.zDepth + Frustum2d_1.Frustum2d.minimumZDistance;
        return extents;
    }
    /** @internal */
    discloseTileTrees(trees) {
        super.discloseTileTrees(trees);
        if (this._attachment)
            trees.disclose(this._attachment.viewport);
    }
    /** @internal */
    createScene(context) {
        if (!DrawingViewState.hideDrawingGraphics)
            super.createScene(context);
        if (this._attachment)
            this._attachment.addToScene(context);
    }
    get areAllTileTreesLoaded() {
        return super.areAllTileTreesLoaded && (!this._attachment || this._attachment.view.areAllTileTreesLoaded);
    }
    /** @internal */
    get secondaryViewports() {
        return this._attachment ? [this._attachment.viewport] : super.secondaryViewports;
    }
    /** @internal */
    getAttachmentViewport(args) {
        const attach = args.inSectionDrawingAttachment ? this._attachment : undefined;
        return attach?.viewport;
    }
    /** @beta */
    computeDisplayTransform(args) {
        // ###TODO we're currently ignoring model and element Id in args, assuming irrelevant for drawings.
        // Should probably call super or have super call us.
        const attach = args.inSectionDrawingAttachment ? this._attachment : undefined;
        return attach?.toDrawing.clone(args.output);
    }
}
exports.DrawingViewState = DrawingViewState;
//# sourceMappingURL=DrawingViewState.js.map