Source: configurator/material_assignment.js

import { BuildableComponent } from "../package/component/buildable_component.js";
import { WrappedMaterial } from "../package/material/wrapped_material.js";
import { Reporter } from "../reporter/reporter.js";
import { BlockInstance } from "./block_instance.js";
import { MaterialSet } from "../package/material/material_set.js";
import { Package } from "../package/package.js";
import { PositionedMesh } from "../package/mesh/positioned_mesh.js";

import { checkPropTypes, getAssigningComponent } from '../lib.js';
import { PositionedComponent } from '../package/component/positioned_component.js';
import { PositionedMeshGroup } from '../package/mesh/positioned_mesh_group.js';
import { Component } from '../component/component.js';
import { Project } from '../project.js';


class MaterialAssignment extends BuildableComponent {

    /**
     * @param {Reporter} reporter
     * @param {Object} settings
     * @param {UUID} [settings.id]
     * @param {string} [settings.name]
     * @param {BlockInstance} settings.blockInstance
     * @param {PositionedMeshGroup} [settings.positionedMeshGroup]
     * @param {PositionedMesh} [settings.positionedMesh]
     * @param {WrappedMaterial} settings.material
     */

    constructor(reporter, settings) {

        if ( settings.blockInstances ) {
            delete settings.blockInstances;
        }


        super(
            reporter,
            settings
        );

        checkPropTypes(
            settings,
            {
                blockInstance: BlockInstance,
                material: [
                    WrappedMaterial
                ]
            },
            {
                positionedMesh: [
                    PositionedComponent,
                    val => {
                        if (settings.blockInstance.dependsOn(val) !== true) {
                            return `${val.label} is not a dependency of ${settings.blockInstance.label}`;
                        }

                        return true;
                    }
                ]
            }
        );


        // check assignment

        this.assigningComponent = getAssigningComponent(settings);

        if (this.assigningComponent.assignable !== true) {
            throw new Error(`${this.assigningComponent.label} is not assignable`);
        }
        else if (!this.assigningComponent.applicableMaterialVariantGroups.find(mvg => mvg.has(settings.material))) {
            // throw new Error(`${assigningComponent.label} applicable material variant groups are incompatible with ${settings.material.label}`);
            console.warn(`${this.label} will affect no components.`);
        }


        // check dependencies

        if (settings.positionedMesh && !settings.blockInstance.dependsOn(settings.positionedMesh)) {
            throw new Error(`${settings.positionedMesh.label} is not a dependency of ${settings.blockInstance.label}`);
        }
        else if (settings.positionedMeshGroup) {
            if ( !settings.blockInstance.dependsOn(settings.positionedMeshGroup)) {
                throw new Error(`${settings.positionedMeshGroup.label} is not a dependency of ${settings.blockInstance.label}`);
            }
            else if (settings.positionedMesh && !settings.positionedMeshGroup.dependsOn(settings.positionedMesh)) {
                throw new Error(`${settings.positionedMesh.label} is not a dependency of ${settings.positionedMeshGroup.label}`);
            }
        }

        this._relevantMaterialSets = [];

        this.materializableComponents = this.assigningComponent.materializable
            ? [ this.assigningComponent, ...this.assigningComponent.materializableDependencies ]
            : this.assigningComponent.materializableDependencies;


        this.affectedComponents = this.materializableComponents
            .filter( dep =>
                dep instanceof PositionedComponent && dep.component.materialVariantGroup.has( settings.material )
            );
        


        // set specificity

        // this.specificity = settings.positionedMesh && settings.positionedMeshGroup
        //     ? 4
        //     : settings.positionedMesh
        //         ? 3
        //         : settings.positionedMeshGroup
        //             ? 2
        //             : 1
        //             ;


        // fucking typescript

        if (!this.blockInstance) {
            /** @type {BlockInstance} */
            this.blockInstance = settings.blockInstance;
        }
        if (!this.material) {
            /** @type {WrappedMaterial} */
            this.material = settings.material;
        }



        // this.positionedMeshes = settings.blockInstance.block.allDependencies.filter(dep => dep.constructor.name === 'PositionedMesh');

        // const applicablePositionedMeshes = this.positionedMeshes.filter(pm => pm.component.materialVariantGroup.has(settings.material));

        // if (applicablePositionedMeshes.length === 0) {
        //     console.warn(`${this.label} found no applicable meshes in ${settings.blockInstance.label} for ${settings.material.label} - wrong material variant groups`);
        // }

        // if (settings.mesh) {
        //     if (!applicablePositionedMeshes.includes(settings.mesh)) {
        //         throw new Error(`${settings.mesh.label} does not have ${settings.material.label} in its material variant group`);
        //     }
        //     this.applicablePositionedMeshes = [settings.mesh];
        // }
        // else {
        //     this.applicablePositionedMeshes = applicablePositionedMeshes;
        // }
    }


    static _exportName = {
        singular: 'materialAssignment',
        plural: 'materialAssignments',
    };

    /**
     * @static
     * @type {ExportLevel}
     */

    static _exportLevel = 'inline';

    // static getassigningComponent = obj => obj.positionedMesh || obj.positionedMeshGroup || obj.blockInstance;

    static treeLock = false;


    /** @type {Array<Component>} */

    affectedComponents;


    /** @type {Array<MaterialSet>} */

    _relevantMaterialSets;

    _updateRelevantMaterialSets() {
        if (this._tree) {
            this._relevantMaterialSets = this._tree.materialSetsByMaterial(this.material);
        }
    }


    /** @type {Package} */

    _tree;


    _onTreeSet() {
        if (!this._tree.findComponentById(this.blockInstance.block.id)) {
            throw new Error(`BlockInstance.block (the BlockInstance's "template") not in tree`);
        }

        if (!this._tree.findComponentById(this.material.id)) {
            throw new Error('Material not in tree');
        }

        this._tree.on('material-sets-by-material-update', this._updateRelevantMaterialSets.bind(this), this.id);


        // const relevantMaterialSets = this._tree.materialSetsByMaterial(this.material);

        // // which quality??
        // const applicableMeshes = this.blockInstance.content.medium.origin

        // this.material
        // mat => {

        //     // find all meshes with material sets with this material
        //     const block = blockInstance.block;

        //     // meshes need to be found by their userData.origin property

        //     return true;
        // }
    }

    _onTreeUnset() {
        this._tree.removeListener('material-sets-by-material-update', this.id);
    }


    _build(part, quality, dependencies) {
        switch (part) {

            case 'UI':

                if (this._settings.thumbnail) {
                    this._setContent('UI', quality, this._settings.thumbnail.content.main[quality]);
                }
                else {
                    this._setContent('UI', quality, Project.defaultImages.missing.cloneNode(true));
                }
                break;

            case 'main':


                // const applicablePositionedMeshIds = this.applicablePositionedMeshes.map(apm => apm.id);

                // /** @type {Group} */

                // const actualBlockGroup = this.blockInstance.content.main[quality];

                // console.assert(actualBlockGroup instanceof Group);

                // if (this.applicablePositionedMeshes.length > 0) {

                //     /** @type {Array<Mesh>} */

                //     const applicableActualMeshes = [];

                //     actualBlockGroup.traverse(child => {
                //         if (applicablePositionedMeshIds.includes(child.userData?.origin)) {
                //             applicableActualMeshes.push(child)
                //         }
                //     });

                //     // modifying the actual meshes (content) in the BlockInstance

                //     this.report({ msg: `Applying material to ${applicableActualMeshes.length} meshes in block` });

                //     applicableActualMeshes.forEach(aaMesh => {
                //         aaMesh.material = this.material.content.main[quality];
                //         aaMesh.needsUpdate = true;
                //     });

                // }

                this._setContent('main', quality, this._settings.material);

                break;
                
            default:

                this._setContent(part, quality, null);
                
                break;
        }

        return this;
    }
}

export { MaterialAssignment };