"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ViewCreator2d = void 0;
/** @packageDocumentation
 * @module Views
 */
/*
API for creating a 2D view from a given modelId and modelType (classFullName).
Additional options (such as background color) can be passed during view creation.
*/
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 DrawingViewState_1 = require("./DrawingViewState");
const ModelState_1 = require("./ModelState");
const SheetViewState_1 = require("./SheetViewState");
/**
 * API for creating a [[ViewState2d]] for a 2D model ([[GeometricModel2dState]]). @see [[ViewCreator3d]] to create a view for a 3d model.
 * Example usage:
 * ```ts
 * const viewCreator = new ViewCreator2d(imodel);
 * const models = await imodel.models.queryProps({ from: "BisCore.GeometricModel2d" });
 * if (models.length > 0)
 *   const view = await viewCreator.createViewForModel(models[0].id!);
 * ```
 * @public
 * @extensions
 */
class ViewCreator2d {
    /**
     * Constructs a ViewCreator2d using an [[IModelConnection]].
     * @param _imodel [[IModelConnection]] to query for categories and/or models.
     */
    constructor(_imodel) {
        this._imodel = _imodel;
        /**
         * Creates ViewStateProps for the model. ViewStateProps are composed of the 4 sets of Props below.
         * @param modelId of target model.
         * @param options for view creation.
         */
        this._createViewStateProps = async (modelId, options) => {
            // Use dictionary model in all props
            const dictionaryId = core_common_1.IModel.dictionaryId;
            const categories = await this._getAllCategories();
            // Get bg color from options or default to white
            const bgColor = options?.bgColor ? options.bgColor : core_common_1.ColorDef.white;
            // model extents
            const modelProps = await this._imodel.models.queryExtents(modelId);
            const modelExtents = core_geometry_1.Range3d.fromJSON(modelProps[0]?.extents);
            let originX = modelExtents.low.x;
            let originY = modelExtents.low.y;
            let deltaX = modelExtents.xLength();
            let deltaY = modelExtents.yLength();
            // if vp aspect given, update model extents to fit view
            if (options?.vpAspect) {
                const modelAspect = deltaY / deltaX;
                if (modelAspect > options.vpAspect) {
                    const xFix = deltaY / options.vpAspect;
                    originX = originX - xFix / 2;
                    deltaX = deltaX + xFix;
                }
                else if (modelAspect < options.vpAspect) {
                    const yFix = deltaX * options.vpAspect;
                    originY = originY - yFix / 2;
                    deltaY = deltaY + yFix;
                }
            }
            const modelSelectorProps = {
                models: [modelId],
                code: core_common_1.Code.createEmpty(),
                model: dictionaryId,
                classFullName: "BisCore:ModelSelector",
            };
            const categorySelectorProps = {
                categories,
                code: core_common_1.Code.createEmpty(),
                model: dictionaryId,
                classFullName: "BisCore:CategorySelector",
            };
            const viewDefinitionProps = {
                baseModelId: modelId,
                categorySelectorId: "",
                displayStyleId: "",
                origin: { x: originX, y: originY },
                delta: { x: deltaX, y: deltaY },
                angle: { radians: 0 },
                code: core_common_1.Code.createEmpty(),
                model: dictionaryId,
                classFullName: "BisCore:ViewDefinition2d",
            };
            const displayStyleProps = {
                code: core_common_1.Code.createEmpty(),
                model: dictionaryId,
                classFullName: "BisCore:DisplayStyle2d",
                jsonProperties: {
                    styles: {
                        backgroundColor: bgColor.tbgr,
                    },
                },
            };
            const viewStateProps = {
                displayStyleProps,
                categorySelectorProps,
                modelSelectorProps,
                viewDefinitionProps,
                modelExtents,
            };
            // merge seed view props if needed
            return options?.useSeedView ? this._mergeSeedView(modelId, viewStateProps) : viewStateProps;
        };
        /**
         * Helper function to execute ECSql queries.
         * @param query statement to execute.
         */
        this._executeQuery = async (query) => {
            const rows = [];
            for await (const row of this._imodel.createQueryReader(query, undefined, { rowFormat: core_common_1.QueryRowFormat.UseJsPropertyNames }))
                rows.push(row.id);
            return rows;
        };
    }
    /**
     * Creates and returns view for the 2D model id passed in.
     * @param modelId Id of the 2D model for the view.
     * @param [options] Options for creating the view.
     * @throws [IModelError]($common) If modelType is not supported.
     */
    async createViewForModel(modelId, options) {
        const baseClassName = await this._getModelBaseClassName(modelId);
        const viewState = await this._createViewState2d(modelId, baseClassName.classFullName, options);
        try {
            await viewState.load();
        }
        catch { }
        return viewState;
    }
    /**
     * Gets model base class name from id.
     * @param modelId of target model.
     * @throws [IModelError]($common) if modelId is invalid.
     */
    async _getModelBaseClassName(modelId) {
        let baseClassName;
        const modelProps = await this._imodel.models.getProps(modelId);
        if (modelProps.length > 0) {
            const modelType = modelProps[0].classFullName;
            baseClassName = await this._imodel.findClassFor(modelType, undefined);
        }
        else
            throw new core_common_1.IModelError(core_bentley_1.IModelStatus.BadModel, "ViewCreator2d._getModelBaseClassName: modelId is invalid");
        if (baseClassName === undefined)
            throw new core_common_1.IModelError(core_bentley_1.IModelStatus.WrongClass, "ViewCreator2d.getViewForModel: modelType is invalid");
        return baseClassName;
    }
    /**
     * Creates view from any 2D model type (Drawing/SectionDrawing/Sheet)
     * @param modelId of target model.
     * @param modelType classFullName of target 2D model.
     * @param options for view creation.
     * @throws [IModelError]($common) if modelType is not supported.
     */
    async _createViewState2d(modelId, modelType, options) {
        let viewState;
        if (this._isDrawingModelClass(modelType)) {
            const props = await this._createViewStateProps(modelId, options);
            viewState = DrawingViewState_1.DrawingViewState.createFromProps(props, this._imodel);
        }
        else if (this._isSheetModelClass(modelType)) {
            let props = await this._createViewStateProps(modelId, options);
            props = await this._addSheetViewProps(modelId, props);
            viewState = SheetViewState_1.SheetViewState.createFromProps(props, this._imodel);
        }
        else
            throw new core_common_1.IModelError(core_bentley_1.IModelStatus.WrongClass, "ViewCreator2d._createViewState2d: modelType not supported");
        return viewState;
    }
    /**
     * Checks to see if given model is of [[DrawingModelState]].
     * @param modelType classFullName of model.
     */
    _isDrawingModelClass(modelType) {
        if (ViewCreator2d._drawingModelClasses.includes(modelType)) {
            return true;
        }
        return false;
    }
    /**
     * Checks to see if given model is of [[SheetModelState]].
     * @param modelType classFullName of model.
     */
    _isSheetModelClass(modelType) {
        if (ViewCreator2d._sheetModelClasses.includes(modelType)) {
            return true;
        }
        return false;
    }
    /**
     * Adds Sheet view props to given view props.
     * @param modelId of target model.
     * @param props input ViewStateProps.
     */
    async _addSheetViewProps(modelId, props) {
        let width = 0;
        let height = 0;
        for await (const row of this._imodel.createQueryReader(`SELECT Width, Height FROM bis.Sheet WHERE ECInstanceId = ?`, core_common_1.QueryBinder.from([modelId]), { rowFormat: core_common_1.QueryRowFormat.UseJsPropertyNames })) {
            width = row.width;
            height = row.height;
            break;
        }
        const sheetProps = {
            model: modelId,
            code: { spec: "", scope: "" },
            classFullName: "DrawingSheetModel",
            height,
            width,
        };
        props.sheetAttachments = await this._getSheetAttachments(modelId);
        props.sheetProps = sheetProps;
        return props;
    }
    /**
      * Merges a seed view in the iModel with the passed view state props. It will be a no-op if there are no 2D views for target model.
      * @param modelId of target model.
      * @param props Input view props to be merged
      */
    async _mergeSeedView(modelId, props) {
        const viewDefinitionId = await this._getViewDefinitionsIdForModel(modelId);
        // Return incase no viewDefinition found.
        if (viewDefinitionId === undefined)
            return props;
        const seedViewState = (await this._imodel.views.load(viewDefinitionId));
        const seedViewStateProps = {
            categorySelectorProps: seedViewState.categorySelector.toJSON(),
            viewDefinitionProps: seedViewState.toJSON(),
            displayStyleProps: seedViewState.displayStyle.toJSON(),
        };
        const mergedDisplayProps = seedViewStateProps.displayStyleProps;
        if (mergedDisplayProps.jsonProperties !== undefined) {
            mergedDisplayProps.jsonProperties.styles = {
                ...mergedDisplayProps.jsonProperties.styles,
                ...props.displayStyleProps.jsonProperties.styles,
            };
        }
        return { ...seedViewStateProps, ...props, displayStyleProps: mergedDisplayProps };
    }
    /**
     * Get all view definitions for a given model.
     * @param modelId of target model.
     */
    async _getViewDefinitionsIdForModel(modelId) {
        const query = `SELECT ECInstanceId from Bis.ViewDefinition2D WHERE BaseModel.Id = ${modelId} AND isPrivate = false LIMIT 1`;
        const viewDefinitionsId = await this._executeQuery(query);
        return (viewDefinitionsId.length) > 0 ? viewDefinitionsId[0] : undefined;
    }
    /**
     * Get all drawing categories
     */
    async _getAllCategories() {
        const query = "SELECT ECInstanceId from BisCore.DrawingCategory";
        const categories = await this._executeQuery(query);
        return categories;
    }
    /**
     * Get all sheet attachments
     * @param modelId of target model.
     */
    async _getSheetAttachments(modelId) {
        const query = `SELECT ECInstanceId FROM Bis.ViewAttachment WHERE Model.Id = ${modelId}`;
        const attachments = await this._executeQuery(query);
        return attachments;
    }
}
exports.ViewCreator2d = ViewCreator2d;
// Types of 2D models the API supports
ViewCreator2d._drawingModelClasses = [ModelState_1.DrawingModelState.classFullName, ModelState_1.SectionDrawingModelState.classFullName];
ViewCreator2d._sheetModelClasses = [ModelState_1.SheetModelState.classFullName];
//# sourceMappingURL=ViewCreator2d.js.map