import { applyMaterialMixins, MeshMaterial } from './meshmaterial.js';
import { ShaderHelper } from './shader/helper.js';
import { RENDER_PASS_TYPE_LIGHT, MaterialVaryingFlags } from '../values.js';
import { Vector3, Vector2, DRef, Vector4 } from '@zephyr3d/base';
import { mixinPBRBluePrint } from './mixins/lightmodel/pbrblueprintmixin.js';
import { MaterialBlueprintIR } from '../utility/blueprint/material/ir.js';
import { PBRBlockNode, VertexBlockNode } from '../utility/blueprint/material/pbr.js';

const PBR_REFLECTION_MODE = {
    none: 0,
    ggx: 1,
    anisotropic: 2,
    glint: 3
};
/**
 * Physically-based rendering material driven by blueprint graphs.
 *
 * @remarks
 * This material extends {@link MeshMaterial} with PBR behavior via
 * {@link mixinPBRBluePrint}, and uses {@link MaterialBlueprintIR}
 * graphs for both vertex and fragment stages.
 *
 * - The **vertex blueprint IR** (`vertexIR`) controls vertex
 *   transformations and per-vertex data.
 * - The **fragment blueprint IR** (`fragmentIR`) produces inputs
 *   for the PBR shading model (albedo, roughness, metalness, etc.).
 *
 * Uniform values and textures for the blueprints are provided via
 * {@link PBRBluePrintMaterial.uniformValues} and
 * {@link PBRBluePrintMaterial.uniformTextures}.
 *
 * @public
 */ class PBRBluePrintMaterial extends applyMaterialMixins(MeshMaterial, mixinPBRBluePrint) {
    /** @internal */ static FEATURE_VERTEX_COLOR = this.defineFeature();
    /** @internal */ static FEATURE_VERTEX_UV = this.defineFeature();
    /** @internal */ static FEATURE_SUBSURFACE_SCATTERING = this.defineFeature();
    /** @internal */ _irFrag;
    /** @internal */ _irVertex;
    /** @internal */ _uniformValues;
    /** @internal */ _uniformTextures;
    /** @internal */ _reflectionMode;
    /** @internal */ _subsurfaceColor;
    /** @internal */ _subsurfaceScale;
    /** @internal */ _subsurfacePower;
    /** @internal */ _subsurfaceIntensity;
    /** @internal */ _anisotropy;
    /** @internal */ _anisotropyDirection;
    /** @internal */ _anisotropyDirectionScaleBias;
    /**
   * Creates a new {@link PBRBluePrintMaterial} instance.
   *
   * @param irFrag - Optional fragment blueprint IR. If omitted, a default
   *   IR containing a single {@link PBRBlockNode} is created.
   * @param irVertex - Optional vertex blueprint IR. If omitted, a default
   *   IR containing a single {@link VertexBlockNode} is created.
   * @param uniformValues - Optional initial list of uniform value descriptors.
   * @param uniformTextures - Optional initial list of texture uniform descriptors.
   */ constructor(irFrag, irVertex, uniformValues, uniformTextures){
        super();
        this._irFrag = irFrag ?? new MaterialBlueprintIR({
            nodeMap: {
                '1': new PBRBlockNode()
            },
            roots: [
                1
            ],
            order: [
                1
            ],
            graph: {
                incoming: {},
                outgoing: {}
            }
        }, '', {
            nodes: [
                {
                    id: 1,
                    title: '',
                    locked: true,
                    node: {
                        ClassName: 'PBRBlockNode',
                        Object: ''
                    }
                }
            ],
            links: []
        });
        this._irVertex = irVertex ?? new MaterialBlueprintIR({
            nodeMap: {
                '1': new VertexBlockNode()
            },
            roots: [
                1
            ],
            order: [
                1
            ],
            graph: {
                incoming: {},
                outgoing: {}
            }
        }, '', {
            nodes: [
                {
                    id: 1,
                    title: '',
                    locked: true,
                    node: {
                        ClassName: 'VertexBlockNode',
                        Object: ''
                    }
                }
            ],
            links: []
        });
        this._uniformValues = uniformValues ?? [];
        this._uniformTextures = uniformTextures ?? [];
        this._reflectionMode = 'ggx';
        this._subsurfaceColor = new Vector3(1, 0.3, 0.2);
        this._subsurfaceScale = 0.5;
        this._subsurfacePower = 1.5;
        this._subsurfaceIntensity = 0.5;
        this._anisotropy = 0.75;
        this._anisotropyDirection = 0;
        this._anisotropyDirectionScaleBias = new Vector2(1, 0);
        this.useFeature(PBRBluePrintMaterial.FEATURE_SUBSURFACE_SCATTERING, false);
        this.useFeature(PBRBluePrintMaterial.FEATURE_VERTEX_COLOR, this._irVertex.behaviors.useVertexColor);
        this.useFeature(PBRBluePrintMaterial.FEATURE_VERTEX_UV, this._irVertex.behaviors.useVertexUV);
    }
    get reflectionMode() {
        return this._reflectionMode;
    }
    set reflectionMode(val) {
        if (val !== this._reflectionMode) {
            this._reflectionMode = val;
            this.uniformChanged();
        }
    }
    get subsurfaceScattering() {
        return this.featureUsed(PBRBluePrintMaterial.FEATURE_SUBSURFACE_SCATTERING);
    }
    set subsurfaceScattering(val) {
        this.useFeature(PBRBluePrintMaterial.FEATURE_SUBSURFACE_SCATTERING, !!val);
    }
    get subsurfaceColor() {
        return this._subsurfaceColor;
    }
    set subsurfaceColor(val) {
        if (!val.equalsTo(this._subsurfaceColor)) {
            this._subsurfaceColor.set(val);
            this.uniformChanged();
        }
    }
    get subsurfaceScale() {
        return this._subsurfaceScale;
    }
    set subsurfaceScale(val) {
        if (val !== this._subsurfaceScale) {
            this._subsurfaceScale = val;
            this.uniformChanged();
        }
    }
    get subsurfacePower() {
        return this._subsurfacePower;
    }
    set subsurfacePower(val) {
        if (val !== this._subsurfacePower) {
            this._subsurfacePower = val;
            this.uniformChanged();
        }
    }
    get subsurfaceIntensity() {
        return this._subsurfaceIntensity;
    }
    set subsurfaceIntensity(val) {
        if (val !== this._subsurfaceIntensity) {
            this._subsurfaceIntensity = val;
            this.uniformChanged();
        }
    }
    get anisotropy() {
        return this._anisotropy;
    }
    set anisotropy(val) {
        const clamped = Math.max(-0.95, Math.min(0.95, val));
        if (clamped !== this._anisotropy) {
            this._anisotropy = clamped;
            this.uniformChanged();
        }
    }
    get anisotropyDirection() {
        return this._anisotropyDirection;
    }
    set anisotropyDirection(val) {
        if (val !== this._anisotropyDirection) {
            this._anisotropyDirection = val;
            this.uniformChanged();
        }
    }
    get anisotropyDirectionScaleBias() {
        return this._anisotropyDirectionScaleBias;
    }
    set anisotropyDirectionScaleBias(val) {
        if (!val.equalsTo(this._anisotropyDirectionScaleBias)) {
            this._anisotropyDirectionScaleBias.set(val);
            this.uniformChanged();
        }
    }
    /**
   * Gets the fragment blueprint IR.
   */ get fragmentIR() {
        return this._irFrag;
    }
    set fragmentIR(ir) {
        if (ir && ir !== this._irFrag) {
            this._irFrag = ir;
            this.clearCache();
            this.optionChanged(true);
        }
    }
    /**
   * Gets the vertex blueprint IR.
   */ get vertexIR() {
        return this._irVertex;
    }
    set vertexIR(ir) {
        if (ir && ir !== this._irVertex) {
            this._irVertex = ir;
            this.useFeature(PBRBluePrintMaterial.FEATURE_VERTEX_COLOR, this._irVertex.behaviors.useVertexColor);
            this.useFeature(PBRBluePrintMaterial.FEATURE_VERTEX_UV, this._irVertex.behaviors.useVertexUV);
            this.clearCache();
            this.optionChanged(true);
        }
    }
    /**
   * Gets the list of uniform value descriptors used by the blueprints.
   */ get uniformValues() {
        return this._uniformValues;
    }
    set uniformValues(val) {
        this._uniformValues = (val ?? []).map((v)=>({
                ...v
            }));
        this.uniformChanged();
    }
    /**
   * Gets the list of texture uniform descriptors used by the blueprints.
   */ get uniformTextures() {
        return this._uniformTextures;
    }
    set uniformTextures(val) {
        if (val !== this._uniformTextures) {
            const newUniforms = val.map((v)=>({
                    finalTexture: new DRef(v.finalTexture.get()),
                    finalSampler: v.finalSampler,
                    name: v.name,
                    params: v.params?.clone() ?? Vector4.zero(),
                    texture: v.texture,
                    type: v.type,
                    sRGB: v.sRGB,
                    wrapS: v.wrapS,
                    wrapT: v.wrapT,
                    inFragmentShader: v.inFragmentShader,
                    inVertexShader: v.inVertexShader,
                    minFilter: v.minFilter,
                    magFilter: v.magFilter,
                    mipFilter: v.mipFilter
                }));
            for (const u of this._uniformTextures){
                u.finalTexture.dispose();
            }
            this._uniformTextures = newUniforms;
            this.uniformChanged();
        }
    }
    /**
   * Creates a deep copy of this material.
   *
   * @remarks
   * The clone shares the same vertex/fragment IR references and copies
   * the current blueprint uniform descriptors, then calls `copyFrom`
   * to copy base-class and mixin state.
   *
   * @returns A new {@link PBRBluePrintMaterial} instance.
   */ clone() {
        const other = new PBRBluePrintMaterial(this._irFrag, this._irVertex, this._uniformValues, this._uniformTextures);
        other.copyFrom(this);
        return other;
    }
    copyFrom(other) {
        super.copyFrom(other);
        this.reflectionMode = other.reflectionMode;
        this.subsurfaceScattering = other.subsurfaceScattering;
        this.subsurfaceColor = other.subsurfaceColor;
        this.subsurfaceScale = other.subsurfaceScale;
        this.subsurfacePower = other.subsurfacePower;
        this.subsurfaceIntensity = other.subsurfaceIntensity;
        this.anisotropy = other.anisotropy;
        this.anisotropyDirection = other.anisotropyDirection;
        this.anisotropyDirectionScaleBias = other.anisotropyDirectionScaleBias;
    }
    /**
   * Builds the vertex shader for this PBR blueprint material.
   *
   * @param scope - The current vertex shader function scope.
   */ vertexShader(scope) {
        super.vertexShader(scope);
        const pb = scope.$builder;
        scope.$inputs.zVertexPos = pb.vec3().attrib('position');
        scope.$inputs.zVertexNormal = pb.vec3().attrib('normal');
        scope.$inputs.zVertexTangent = pb.vec4().attrib('tangent');
        if (this.featureUsed(PBRBluePrintMaterial.FEATURE_VERTEX_COLOR)) {
            scope.$inputs.zVertexColor = pb.vec4().attrib('diffuse');
        }
        if (this.featureUsed(PBRBluePrintMaterial.FEATURE_VERTEX_UV)) {
            scope.$inputs.zVertexUV = pb.vec2().attrib('texCoord0');
        }
        for (const u of [
            ...this._uniformValues,
            ...this._uniformTextures
        ]){
            if (u.inVertexShader) {
                // @ts-ignore
                pb.getGlobalScope()[u.name] = pb[u.type]().uniform(2);
            }
        }
        const outputs = this._irVertex.create(pb);
        scope.$l.oPos = this.getOutput(outputs, 'Position') ?? ShaderHelper.resolveVertexPosition(scope);
        const worldMatrix = ShaderHelper.getWorldMatrix(scope);
        scope.$outputs.worldPos = pb.mul(worldMatrix, pb.vec4(scope.oPos, 1)).xyz;
        scope.$l.csPos = pb.mul(ShaderHelper.getViewProjectionMatrix(scope), pb.vec4(scope.$outputs.worldPos, 1));
        ShaderHelper.setClipSpacePosition(scope, scope.csPos);
        scope.$outputs.zVertexColor = this.getOutput(outputs, 'Color') ?? (this.featureUsed(PBRBluePrintMaterial.FEATURE_VERTEX_COLOR) ? scope.$inputs.zVertexColor : pb.vec4(1));
        scope.$outputs.zVertexUV = this.getOutput(outputs, 'UV') ?? (this.featureUsed(PBRBluePrintMaterial.FEATURE_VERTEX_UV) ? scope.$inputs.zVertexUV : pb.vec2(0));
        scope.$l.oNorm = this.getOutput(outputs, 'Normal') ?? ShaderHelper.resolveVertexNormal(scope);
        scope.$outputs.zVertexNormal = pb.mul(ShaderHelper.getNormalMatrix(scope), pb.vec4(scope.oNorm, 0)).xyz;
        scope.$l.oTangent = this.getOutput(outputs, 'Tangent') ?? ShaderHelper.resolveVertexTangent(scope);
        scope.$outputs.zVertexTangent = pb.mul(ShaderHelper.getNormalMatrix(scope), pb.vec4(scope.oTangent.xyz, 0)).xyz;
        scope.$outputs.zVertexBinormal = pb.mul(pb.cross(scope.$outputs.zVertexNormal, scope.$outputs.zVertexTangent), scope.oTangent.w);
    }
    /**
   * Builds the fragment shader for this PBR blueprint material.
   *
   * @param scope - The current fragment shader function scope.
   */ fragmentShader(scope) {
        super.fragmentShader(scope);
        const pb = scope.$builder;
        if (this.subsurfaceScattering && this.needFragmentColor() && this.drawContext.renderPass.type === RENDER_PASS_TYPE_LIGHT) {
            scope.zSubsurfaceColor = pb.vec3().uniform(2);
            scope.zSubsurfaceScale = pb.float().uniform(2);
            scope.zSubsurfacePower = pb.float().uniform(2);
            scope.zSubsurfaceIntensity = pb.float().uniform(2);
        }
        if (this.needFragmentColor()) {
            scope.zAnisotropy = pb.float().uniform(2);
            scope.zAnisotropyDirection = pb.float().uniform(2);
            scope.zAnisotropyDirectionScaleBias = pb.vec2().uniform(2);
            for (const u of [
                ...this._uniformValues,
                ...this._uniformTextures
            ]){
                if (u.inFragmentShader) {
                    // @ts-ignore
                    pb.getGlobalScope()[u.name] = pb[u.type]().uniform(2);
                }
            }
            scope.$l.viewVec = this.calculateViewVector(scope, scope.$inputs.worldPos);
            scope.$l.commonData = this.getCommonDatasStruct(scope)();
            this.getCommonData(scope, scope.commonData, scope.viewVec, scope.$inputs.worldPos, scope.$inputs.zVertexNormal, scope.$inputs.zVertexTangent, scope.$inputs.zVertexBinormal, scope.$inputs.zVertexColor, scope.$inputs.zVertexUV, this._irFrag);
            if (this.drawContext.renderPass.type === RENDER_PASS_TYPE_LIGHT) {
                if (this.drawContext.materialFlags & MaterialVaryingFlags.SSR_STORE_ROUGHNESS) {
                    scope.$l.outRoughness = pb.vec4();
                    scope.$l.litColor = this.PBRLight(scope, scope.$inputs.worldPos, scope.viewVec, scope.commonData, scope.outRoughness);
                    if (this.subsurfaceScattering) {
                        scope.litColor = this.applySubsurfaceScattering(scope, scope.litColor, scope.commonData.albedo.rgb, scope.commonData.normal, scope.viewVec);
                    }
                    this.outputFragmentColor(scope, scope.$inputs.worldPos, scope.litColor, scope.outRoughness, pb.vec4(pb.add(pb.mul(scope.commonData.normal, 0.5), pb.vec3(0.5)), 1));
                } else {
                    scope.$l.litColor = this.PBRLight(scope, scope.$inputs.worldPos, scope.viewVec, scope.commonData);
                    if (this.subsurfaceScattering) {
                        scope.litColor = this.applySubsurfaceScattering(scope, scope.litColor, scope.commonData.albedo.rgb, scope.commonData.normal, scope.viewVec);
                    }
                    this.outputFragmentColor(scope, scope.$inputs.worldPos, scope.litColor);
                }
            } else {
                this.outputFragmentColor(scope, scope.$inputs.worldPos, scope.commonData.albedo);
            }
        } else {
            this.outputFragmentColor(scope, scope.$inputs.worldPos, null);
        }
    }
    /**
   * Applies runtime uniform values and textures to the given bind group.
   *
   * @remarks
   * - Calls the base implementation first to bind standard mesh/PBR uniforms.
   * - If fragment color is needed for the current context, all blueprint
   *   scalar/vector uniform values and textures are then bound by name.
   *
   * @param bindGroup - The bind group to which material resources are bound.
   * @param ctx - The current draw context.
   * @param pass - Index of the active render pass.
   */ applyUniformValues(bindGroup, ctx, pass) {
        super.applyUniformValues(bindGroup, ctx, pass);
        if (this.needFragmentColor(ctx)) {
            bindGroup.setValue('zReflectionMode', PBR_REFLECTION_MODE[this._reflectionMode]);
            bindGroup.setValue('zAnisotropy', this._anisotropy);
            bindGroup.setValue('zAnisotropyDirection', this._anisotropyDirection);
            bindGroup.setValue('zAnisotropyDirectionScaleBias', this._anisotropyDirectionScaleBias);
            for (const u of this._uniformValues){
                bindGroup.setValue(u.name, u.finalValue);
            }
            for (const u of this._uniformTextures){
                bindGroup.setTexture(u.name, u.finalTexture.get(), u.finalSampler);
            }
            if (this.subsurfaceScattering && ctx.renderPass.type === RENDER_PASS_TYPE_LIGHT) {
                bindGroup.setValue('zSubsurfaceColor', this._subsurfaceColor);
                bindGroup.setValue('zSubsurfaceScale', this._subsurfaceScale);
                bindGroup.setValue('zSubsurfacePower', this._subsurfacePower);
                bindGroup.setValue('zSubsurfaceIntensity', this._subsurfaceIntensity);
            }
        }
    }
    /**
   * Creates a unique hash string used for program caching.
   *
   * @remarks
   * The hash includes:
   * - The base material hash (`super._createHash()`).
   * - The fragment IR hash.
   * - The vertex IR hash.
   *
   * Different blueprint graphs will therefore produce different programs.
   *
   * @returns A hash string that uniquely identifies this material configuration.
   */ _createHash() {
        return `${super._createHash()}:${this._irFrag.hash}:${this._irVertex.hash}`;
    }
    /**
   * Creates the GPU program for this blueprint PBR material.
   *
   * @remarks
   * This calls the base implementation and returns its result.
   * Commented-out `console.log` lines are provided for debugging the
   * generated vertex and fragment shader sources.
   *
   * @param ctx - The current draw context.
   * @param pass - Index of the active render pass.
   * @returns The created GPU program.
   */ createProgram(ctx, pass) {
        const program = super.createProgram(ctx, pass);
        //console.log(program.getShaderSource('vertex'));
        //console.log(program.getShaderSource('fragment'));
        return program;
    }
    /**
   * Disposes resources associated with this material.
   *
   * @remarks
   * - Calls the base `onDispose` to clean up inherited resources.
   * - Disposes all `finalTexture` references from the blueprint
   *   texture uniform descriptors.
   *
   * This method is intended to be called by the engine's resource
   * management system rather than directly by user code.
   */ onDispose() {
        super.onDispose();
        for (const u of this._uniformTextures){
            u.finalTexture.dispose();
        }
    }
    /**
   * Retrieves a named output expression from a blueprint output list.
   *
   * @param outputs - The list of outputs generated by a blueprint graph.
   * @param name - The desired output name (e.g. `"Position"`, `"Color"`, `"UV"`).
   * @returns The expression associated with the given name, or `undefined` if not found.
   */ getOutput(outputs, name) {
        return outputs.find((output)=>output.name === name)?.exp;
    }
    applySubsurfaceScattering(scope, litColor, albedoRGB, normal, viewVec) {
        const pb = scope.$builder;
        scope.$l.NoV = pb.clamp(pb.dot(normal, viewVec), 0, 1);
        scope.$l.wrapNdotV = pb.clamp(pb.div(pb.add(pb.sub(1, scope.NoV), scope.zSubsurfaceScale), pb.add(1, scope.zSubsurfaceScale)), 0, 1);
        scope.$l.sssFactor = pb.mul(pb.pow(scope.wrapNdotV, scope.zSubsurfacePower), scope.zSubsurfaceIntensity);
        return pb.vec4(pb.add(litColor.rgb, pb.mul(albedoRGB, scope.zSubsurfaceColor, scope.sssFactor)), litColor.a);
    }
}

export { PBRBluePrintMaterial };
//# sourceMappingURL=pbrblueprint.js.map
