/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Elements
 */
import { Id64, JsonUtils } from "@itwin/core-bentley";
import { BisCodeSpec, Code, } from "@itwin/core-common";
import { DefinitionElement } from "./Element";
/* eslint-disable @typescript-eslint/naming-convention */
/** A PhysicalMaterial defines the matter that makes up physical elements.
 * @note See [[RenderMaterialElement]] for the DefinitionElement used to define rendering characteristics.
 * @public
 */
export class PhysicalMaterial extends DefinitionElement {
    static get className() { return "PhysicalMaterial"; }
    /** Create a Code for a PhysicalMaterial given a name that is meant to be unique within the scope of the specified DefinitionModel.
     * @param iModel  The IModelDb
     * @param definitionModelId The Id of the DefinitionModel that will contain the PhysicalMaterial and provide the scope for its name.
     * @param name The name (codeValue) of the PhysicalMaterial
     */
    static createCode(iModel, definitionModelId, name) {
        const codeSpec = iModel.codeSpecs.getByName(BisCodeSpec.physicalMaterial);
        return new Code({ spec: codeSpec.id, scope: definitionModelId, value: name });
    }
    /** Create a PhysicalMaterial
     * @param iModelDb The IModelDb
     * @param definitionModelId The Id of the DefinitionModel that will contain the PhysicalMaterial and provide the scope for its name.
     * @param name The name (codeValue) of the PhysicalMaterial
     * @returns The newly constructed PhysicalMaterial
     * @throws [[IModelError]] if there is a problem creating the PhysicalMaterial
     */
    static create(iModelDb, definitionModelId, name) {
        const elementProps = {
            classFullName: this.classFullName,
            model: definitionModelId,
            code: this.createCode(iModelDb, definitionModelId, name),
        };
        return iModelDb.elements.createElement(elementProps);
    }
}
/** Defines a rendering material.
 * @note See [[PhysicalMaterial]] for the DefinitionElement used to define the matter that makes up physical elements.
 * @public
 */
export class RenderMaterialElement extends DefinitionElement {
    static get className() { return "RenderMaterial"; }
    /** The name of a palette that can be used to categorize multiple materials. */
    paletteName;
    /** An optional description of the material. */
    description;
    constructor(props, iModel) {
        super(props, iModel);
        this.paletteName = props.paletteName;
        this.description = props.description;
    }
    toJSON() {
        const val = super.toJSON();
        val.paletteName = this.paletteName;
        val.description = this.description;
        return val;
    }
    /**
     * RenderMaterialElement custom HandledProps includes 'paletteName'.
     * @inheritdoc
     * @beta
     */
    static _customHandledProps = [
        { propertyName: "paletteName", source: "Class" },
    ];
    /**
     * RenderMaterialElement deserializes 'paletteName'.
     * @inheritdoc
     * @beta
     */
    static deserialize(props) {
        const elProps = super.deserialize(props);
        const instance = props.row;
        elProps.paletteName = JsonUtils.asString(instance.paletteName);
        return elProps;
    }
    /**
     * RenderMaterialElement serializes 'paletteName'.
     * @inheritdoc
     * @beta
     */
    static serialize(props, iModel) {
        const inst = super.serialize(props, iModel);
        inst.paletteName = props.paletteName;
        return inst;
    }
    /** Create a Code for a RenderMaterial given a name that is meant to be unique within the scope of the specified DefinitionModel.
     * @param iModel  The IModelDb
     * @param scopeModelId The Id of the DefinitionModel that contains the RenderMaterial and provides the scope for its name.
     * @param name The RenderMaterial name
     */
    static createCode(iModel, scopeModelId, name) {
        const codeSpec = iModel.codeSpecs.getByName(BisCodeSpec.renderMaterial);
        return 0 === name.length ? Code.createEmpty() : new Code({ spec: codeSpec.id, scope: scopeModelId, value: name });
    }
    /**
     * Create a RenderMaterial with given parameters.
     * @param iModelDb The iModel
     * @param definitionModelId The [[DefinitionModel]]
     * @param materialName The name/CodeValue of the RenderMaterial
     * @param params Parameters object which describes how to construct the RenderMaterial
     * @returns The newly constructed RenderMaterial element.
     * @throws [[IModelError]] if unable to create the element.
     */
    static create(iModelDb, definitionModelId, materialName, params) {
        let maps;
        const pbr_normal = params.normalMap?.scale;
        if (params.patternMap || params.normalMap) {
            function choose(obj, key) {
                const pat = params.patternMap;
                if (pat && undefined !== pat[key])
                    obj[key] = pat[key];
                else if (params.normalMap && undefined !== params.normalMap[key])
                    obj[key] = params.normalMap[key];
            }
            const baseProps = {};
            choose(baseProps, "pattern_angle");
            choose(baseProps, "pattern_u_flip");
            choose(baseProps, "pattern_flip");
            choose(baseProps, "pattern_scale");
            choose(baseProps, "pattern_offset");
            choose(baseProps, "pattern_scalemode");
            choose(baseProps, "pattern_mapping");
            choose(baseProps, "pattern_weight");
            choose(baseProps, "pattern_useconstantlod");
            choose(baseProps, "pattern_constantlod_repetitions");
            choose(baseProps, "pattern_constantlod_offset");
            choose(baseProps, "pattern_constantlod_mindistanceclamp");
            choose(baseProps, "pattern_constantlod_maxdistanceclamp");
            maps = {};
            if (params.patternMap)
                maps.Pattern = { ...params.patternMap, ...baseProps };
            if (params.normalMap) {
                maps.Normal = {
                    ...params.normalMap,
                    ...baseProps,
                };
                delete maps.Normal.scale;
            }
        }
        const renderMaterialProps = {
            classFullName: this.classFullName,
            code: this.createCode(iModelDb, definitionModelId, materialName),
            paletteName: params.paletteName,
            description: params.description,
            jsonProperties: {
                materialAssets: {
                    renderMaterial: {
                        HasBaseColor: params.color !== undefined,
                        color: params.color,
                        HasSpecularColor: params.specularColor !== undefined,
                        specular_color: params.specularColor,
                        HasFinish: params.finish !== undefined,
                        finish: params.finish,
                        HasTransmit: params.transmit !== undefined ? true : undefined,
                        transmit: params.transmit,
                        HasDiffuse: params.diffuse !== undefined,
                        diffuse: params.diffuse,
                        HasSpecular: params.specular !== undefined,
                        specular: params.specular,
                        HasReflect: params.reflect !== undefined,
                        reflect: params.reflect,
                        HasReflectColor: params.reflectColor !== undefined,
                        reflect_color: params.reflectColor,
                        Map: maps,
                        pbr_normal,
                    },
                },
            },
            model: definitionModelId,
            isPrivate: false,
        };
        return new RenderMaterialElement(renderMaterialProps, iModelDb);
    }
    /**
     * Insert a new RenderMaterial into a model.
     * @param iModelDb Insert into this iModel
     * @param definitionModelId Insert the new Texture into this DefinitionModel
     * @param materialName The name/CodeValue of the RenderMaterial
     * @param params Parameters object which describes how to construct the RenderMaterial
     * @returns The Id of the newly inserted RenderMaterial element.
     * @throws [[IModelError]] if unable to insert the element.
     */
    static insert(iModelDb, definitionModelId, materialName, params) {
        const renderMaterial = this.create(iModelDb, definitionModelId, materialName, params);
        return iModelDb.elements.insertElement(renderMaterial.toJSON());
    }
    /** @beta */
    static async onCloned(context, sourceProps, targetProps) {
        await super.onCloned(context, sourceProps, targetProps);
        for (const mapName in sourceProps.jsonProperties?.materialAssets?.renderMaterial?.Map ?? {}) {
            if (typeof mapName !== "string")
                continue;
            const sourceMap = sourceProps.jsonProperties.materialAssets.renderMaterial.Map[mapName];
            // sourceMap could be null/undefined, keep it the same in targetProps
            if (!sourceMap) {
                targetProps.jsonProperties.materialAssets.renderMaterial.Map[mapName] = sourceMap;
                continue;
            }
            if (!Id64.isValid(sourceMap.TextureId) || sourceMap.TextureId === undefined)
                continue;
            targetProps.jsonProperties.materialAssets.renderMaterial.Map[mapName].TextureId = context.findTargetElementId(sourceMap.TextureId ?? Id64.invalid);
        }
    }
}
/** @public */
(function (RenderMaterialElement) {
    /** Parameters used to construct a [[RenderMaterial]].
     * The persistent JSON representation - [RenderMaterialAssetProps]($common) - is quite verbose and unwieldy. This representation simplifies it somewhat.
     * @see [[RenderMaterialElement.create]] and [[RenderMaterialElement.insert]] to create a [[RenderMaterial]] from parameters of this type.
     * @deprecated in 3.6 - might be removed in next major version. Because it is not useful to use a `class` - just use [[RenderMaterialElementParams]] directly instead.
     */
    class Params {
        /** A required palette name that categorizes this RenderMaterial */
        paletteName;
        /** An optional description of this RenderMaterial */
        description;
        /** If defined, the color to use for surface fill or diffuse illumination, overriding the surface's own color. */
        color;
        /** The color to use for specular illumination. Default: black. */
        specularColor;
        /** The specular exponent describing the surface's shininess, in the range 0 through 128.
         * Default: 0.
         */
        finish;
        /** A transparency to be applied to the surface, ranging from 0 (fully opaque) to 1 (fully transparent).
         * If defined, then the material transparency overrides the transparency of whatever surface the material is applied to.
         * If undefined, the material has no effect on surface transparency.
         * Default: undefined.
         */
        transmit;
        /** The surface's diffuse reflectivity from 0.0 to 1.0. Default: 0.6. */
        diffuse;
        /** The surface's specular reflectivity from 0.0 to 1.0. Default: 0.0. */
        specular;
        /** Currently unused. */
        reflect;
        /** Currently unused. */
        reflectColor;
        /** Specifies a texture image to map onto the surface, replacing or mixing with the surface's own color and transparency.
         * @note With the exception of `TextureId`, the [TextureMapProps]($common) of [[patternMap]] and [[normalMap]] are expected to be identical. If a property is defined in both
         * [[patternMap]]] and [[normalMap]], the value in [[patternMap]] takes precedence.
         */
        patternMap;
        /** Specifies a [normal map](https://en.wikipedia.org/wiki/Normal_mapping) to apply to the surface to simulate more surface detail than is present in the
         * surface's geometry.
         * @note With the exception of `TextureId`, the [TextureMapProps]($common) of [[patternMap]] and [[normalMap]] are expected to be identical. If a property is defined in both
         * [[patternMap]]] and [[normalMap]], the value in [[patternMap]] takes precedence.
         */
        normalMap;
        /** Construct a new RenderMaterial.Params object with the specified paletteName. Alter the public members on that object to specify settings. */
        constructor(paletteName) {
            this.paletteName = paletteName;
        }
    }
    RenderMaterialElement.Params = Params;
})(RenderMaterialElement || (RenderMaterialElement = {}));
//# sourceMappingURL=Material.js.map