import {} from "./RenderDevice.js";
import { TextureBinding } from "./TextureBinding.js";
export const DEFAULT_RENDER_STATE = {
    depthTest: true,
    depthWrite: true,
    cullMode: "back",
    blend: false,
    depthCompare: "less-equal",
    colorWrite: [true, true, true, true],
    scissor: null,
    polygonOffset: null,
    stencil: null
};
export class Material {
    disposed = false;
    name;
    shaderKey;
    shaderVariant;
    renderState;
    requiredAttributes;
    requiredUniforms;
    uniformSchema;
    parameters = new Map();
    dirty = true;
    revision = 0;
    constructor(descriptor) {
        if (!descriptor.shaderKey.trim()) {
            throw new Error("Material shaderKey is required");
        }
        this.name = descriptor.name ?? descriptor.shaderKey;
        this.shaderKey = descriptor.shaderKey;
        this.shaderVariant = validateShaderVariant(descriptor.shaderVariant);
        this.renderState = validateRenderState({ ...DEFAULT_RENDER_STATE, ...(descriptor.renderState ?? {}) });
        this.requiredAttributes = descriptor.requiredAttributes ?? [];
        this.uniformSchema = validateUniformSchema(descriptor.uniformSchema ?? deriveRequiredUniformSchema(descriptor.requiredUniforms ?? []));
        this.requiredUniforms = descriptor.requiredUniforms ?? this.uniformSchema.filter((uniform) => uniform.required !== false).map((uniform) => uniform.name);
        for (const [key, value] of Object.entries(descriptor.parameters ?? {})) {
            this.setParameter(key, value);
        }
    }
    setParameter(name, value) {
        this.assertAlive();
        if (!name.trim()) {
            throw new Error("Material parameter name is required");
        }
        this.parameters.set(name, cloneUniformValue(value));
        this.dirty = true;
        this.revision += 1;
    }
    getParameter(name) {
        this.assertAlive();
        const value = this.parameters.get(name);
        return value === undefined ? undefined : cloneUniformValue(value);
    }
    getParameters() {
        this.assertAlive();
        const output = new Map();
        for (const [key, value] of this.parameters) {
            output.set(key, cloneUniformValue(value));
        }
        return output;
    }
    isDirty() {
        return this.dirty;
    }
    getRevision() {
        return this.revision;
    }
    markClean() {
        this.assertAlive();
        this.dirty = false;
    }
    dispose() {
        if (this.disposed)
            return;
        this.parameters.clear();
        this.dirty = false;
        this.disposed = true;
    }
    assertAlive() {
        if (this.disposed) {
            throw new Error(`Material ${this.name} is disposed`);
        }
    }
}
function validateShaderVariant(variant) {
    if (variant === undefined)
        return undefined;
    const trimmed = variant.trim();
    if (!trimmed) {
        throw new Error("Material shaderVariant must be non-empty when provided");
    }
    return trimmed;
}
export function validateRenderState(state) {
    if (state.blend && state.depthWrite) {
        throw new Error("Transparent blended materials must disable depthWrite");
    }
    if (state.scissor) {
        if (state.scissor.width < 0 || state.scissor.height < 0) {
            throw new Error("RenderState scissor width and height must be non-negative");
        }
    }
    if (state.stencil) {
        const reference = state.stencil.reference ?? 0;
        if (!Number.isInteger(reference) || reference < 0) {
            throw new Error("RenderState stencil reference must be a non-negative integer");
        }
    }
    return state;
}
function deriveRequiredUniformSchema(requiredUniforms) {
    return requiredUniforms.map((name) => ({ name, kind: "any", required: true }));
}
function validateUniformSchema(schema) {
    const seen = new Set();
    return schema.map((uniform) => {
        if (!uniform.name.trim()) {
            throw new Error("Material uniform schema name is required");
        }
        if (seen.has(uniform.name)) {
            throw new Error(`Duplicate material uniform schema entry: ${uniform.name}`);
        }
        seen.add(uniform.name);
        return { ...uniform, required: uniform.required ?? true };
    });
}
function cloneUniformValue(value) {
    if (typeof value === "number") {
        return value;
    }
    if (value instanceof TextureBinding) {
        return value;
    }
    return ArrayBuffer.isView(value) ? new value.constructor(value) : [...value];
}
//# sourceMappingURL=Material.js.map