import { Vector4, DWeakRef, Vector3 } from '@zephyr3d/base';
import { GraphNode } from './graph_node.js';
import { BoundingBox } from '../utility/bounding_volume.js';
import { ShadowMapper } from '../shadow/shadowmapper.js';
import { LIGHT_TYPE_DIRECTIONAL, LIGHT_TYPE_POINT, LIGHT_TYPE_SPOT, LIGHT_TYPE_RECT } from '../values.js';

/**
 * Base class for any kind of light node
 * @public
 */ class BaseLight extends GraphNode {
    /** @internal */ _type;
    /** @internal */ _intensity;
    /** @internal */ _positionRange;
    /** @internal */ _directionCutoff;
    /** @internal */ _diffuseIntensity;
    /** @internal */ _extraParams;
    /**
   * Creates a light node
   * @param scene - The scene to which the light node belongs
   * @param type - Type of the light node
   */ constructor(scene, type){
        super(scene);
        this._intensity = 1;
        this._type = type;
        this._positionRange = null;
        this._directionCutoff = null;
        this._diffuseIntensity = null;
        this._extraParams = null;
    }
    /** Gets the light type */ get lightType() {
        return this._type;
    }
    /** Intensity of the light */ get intensity() {
        return this._intensity;
    }
    set intensity(val) {
        this.setIntensity(val);
    }
    /**
   * Position and range of the light
   *
   * @remarks
   * Gets the position in world space of the light.
   * Light range is encoded into the W component.
   */ get positionAndRange() {
        if (!this._positionRange) {
            this.computeUniforms();
        }
        return this._positionRange;
    }
    /**
   * Direction and cutoff of the light
   *
   * @remarks
   * Gets the direction in world space of the light
   * Light cutoff (for spot light only) is encoded into the W component.
   */ get directionAndCutoff() {
        if (!this._directionCutoff) {
            this.computeUniforms();
        }
        return this._directionCutoff;
    }
    /**
   * Color and intensity of the light
   *
   * @remarks
   * Gets the color of the light.
   * Light intensity is encoded into the W component.
   */ get diffuseAndIntensity() {
        if (!this._diffuseIntensity) {
            this.computeUniforms();
        }
        return this._diffuseIntensity;
    }
    /**
   * Extra parameters of the light
   *
   * @remarks
   * Custom data for advanced light types.
   */ get extraParams() {
        if (!this._extraParams) {
            this.computeUniforms();
        }
        return this._extraParams;
    }
    /**
   * View matrix of the light
   *
   * @remarks
   * The view matrix of the light is used to transform a point
   * from the world space to the light space.
   */ get viewMatrix() {
        return this.invWorldMatrix;
    }
    /**
   * Sets the intensity of the light
   * @param val - Intensity of the light
   * @returns self
   */ setIntensity(val) {
        if (this._intensity !== val) {
            this._intensity = val;
            this.invalidateUniforms();
        }
        return this;
    }
    /** @internal */ invalidateUniforms() {
        this._positionRange = null;
        this._directionCutoff = null;
        this._diffuseIntensity = null;
        this._extraParams = null;
    }
    /** {@inheritDoc SceneNode.isLight} */ isLight() {
        return true;
    }
    /** {@inheritDoc SceneNode.isPunctualLight} */ isPunctualLight() {
        return false;
    }
    /** returns true if this is a directional light */ isDirectionLight() {
        return false;
    }
    /** returns true if this is a point light */ isPointLight() {
        return false;
    }
    /** returns true if this is a spot light */ isSpotLight() {
        return false;
    }
    /** returns true if this is a rect light */ isRectLight() {
        return false;
    }
}
/*
export abstract class AmbientLight extends BaseLight {
  constructor(scene: Scene, type: number) {
    super(scene, type);
  }
  isAmbientLight(): this is AmbientLight {
    return true;
  }
}
*/ /**
 * Base class for any kind of puncual light
 * @public
 */ class PunctualLight extends BaseLight {
    /** @internal */ _color;
    /** @internal */ _castShadow;
    /** @internal */ _shadowMapper;
    /**
   * Creates an instance of punctual light
   * @param scene - The scene to which the punctual light belongs
   * @param type - The light type
   */ constructor(scene, type){
        super(scene, type);
        this._color = Vector4.one();
        this._castShadow = false;
        this._shadowMapper = new ShadowMapper(this);
    }
    /** Color of the light */ get color() {
        return this._color.clone();
    }
    set color(color) {
        this.setColor(color);
    }
    /**
   * Sets color of the light
   * @param color - The color to set
   * @returns self
   */ setColor(color) {
        this._color.set(color);
        this.invalidateUniforms();
        return this;
    }
    /** Whether this light casts shadows */ get castShadow() {
        return this._castShadow;
    }
    set castShadow(b) {
        this.setCastShadow(b);
    }
    /**
   * Sets whether this light casts shadows
   * @param b - true if the light casts shadows
   * @returns self
   */ setCastShadow(b) {
        if (this._castShadow !== !!b) {
            this._castShadow = !!b;
            if (this._castShadow && this.isDirectionLight() && !this.shadow.shadowRegion) {
                this.shadow.updateDirectionalShadowRegion(true);
            }
        }
        return this;
    }
    /** The shadow mapper for this light */ get shadow() {
        return this._shadowMapper;
    }
    /**
   * {@inheritDoc BaseLight.isPunctualLight}
   * @override
   */ isPunctualLight() {
        return true;
    }
    /** @internal */ _onTransformChanged(invalidateLocal) {
        super._onTransformChanged(invalidateLocal);
        this.invalidateUniforms();
    // this._transformCallback(true, false);
    }
    /** @internal */ computeUniforms() {}
}
/**
 * Directional light
 * @public
 */ class DirectionalLight extends PunctualLight {
    static _currentSunLight = new WeakMap();
    /**
   * Creates an instance of directional light
   * @param scene - The scene to which the light belongs
   */ constructor(scene){
        super(scene, LIGHT_TYPE_DIRECTIONAL);
        this.intensity = 10;
        if (!DirectionalLight.getSunLight(scene)) {
            DirectionalLight.setSunLight(scene, this);
        }
    }
    static getSunLight(scene) {
        return this._currentSunLight.get(scene)?.get() ?? null;
    }
    static setSunLight(scene, light) {
        if (light && scene !== light.scene) {
            throw new Error('setSunLight(): Light does not belongs to scene');
        }
        const ref = this._currentSunLight.get(scene);
        if (!ref) {
            this._currentSunLight.set(scene, new DWeakRef(light));
        } else {
            ref.set(light);
        }
    }
    /**
   * true if the light was defined as sun light
   *
   * @remarks
   * Only one directional light will be marked as sun light.
   **/ get sunLight() {
        return DirectionalLight.getSunLight(this.scene) === this;
    }
    set sunLight(val) {
        if (val) {
            DirectionalLight.setSunLight(this.scene, this);
        } else if (DirectionalLight.getSunLight(this.scene) === this) {
            DirectionalLight.setSunLight(this.scene, null);
        }
    }
    /**
   * {@inheritDoc BaseLight.isDirectionLight}
   * @override
   */ isDirectionLight() {
        return true;
    }
    /** @internal */ computeBoundingVolume() {
        return null;
    }
    /** @internal */ computeUniforms() {
        const a = this.worldMatrix.getRow(3);
        const b = this.worldMatrix.getRow(2).scaleBy(-1);
        this._positionRange = new Vector4(a.x, a.y, a.z, -1);
        this._directionCutoff = new Vector4(b.x, b.y, b.z, 0);
        this._diffuseIntensity = new Vector4(this.color.x, this.color.y, this.color.z, this.intensity);
        this._extraParams = new Vector4(0, 0, 0, this.lightType);
    }
}
/**
 * Point light
 * @public
 */ class PointLight extends PunctualLight {
    /** @internal */ _range;
    /** @internal */ _diffuseScale;
    /** @internal */ _specularScale;
    /** @internal */ _sourceRadius;
    /**
   * Creates an instance of point light
   * @param scene - The scene to which the light belongs
   */ constructor(scene){
        super(scene, LIGHT_TYPE_POINT);
        this._range = 10;
        this._diffuseScale = 1;
        this._specularScale = 1;
        this._sourceRadius = 0;
        this.invalidateBoundingVolume();
    }
    /** The range of the light */ get range() {
        return this._range;
    }
    set range(val) {
        this.setRange(val);
    }
    /** Diffuse scale of the point light */ get diffuseScale() {
        return this._diffuseScale;
    }
    set diffuseScale(val) {
        this.setDiffuseScale(val);
    }
    /** Specular scale of the point light */ get specularScale() {
        return this._specularScale;
    }
    set specularScale(val) {
        this.setSpecularScale(val);
    }
    /** Source radius of the point light */ get sourceRadius() {
        return this._sourceRadius;
    }
    set sourceRadius(val) {
        this.setSourceRadius(val);
    }
    /**
   * Sets the range of the light
   * @param val - The value to set
   * @returns self
   */ setRange(val) {
        val = val < 0 ? 0 : val;
        if (this._range !== val) {
            this._range = val;
            this.invalidateUniforms();
            this.invalidateBoundingVolume();
        }
        return this;
    }
    /**
   * Sets the diffuse scale of the light
   * @param val - The value to set
   * @returns self
   */ setDiffuseScale(val) {
        val = val < 0 ? 0 : val;
        if (this._diffuseScale !== val) {
            this._diffuseScale = val;
            this.invalidateUniforms();
        }
        return this;
    }
    /**
   * Sets the specular scale of the light
   * @param val - The value to set
   * @returns self
   */ setSpecularScale(val) {
        val = val < 0 ? 0 : val;
        if (this._specularScale !== val) {
            this._specularScale = val;
            this.invalidateUniforms();
        }
        return this;
    }
    /**
   * Sets the source radius of the light
   * @param val - The value to set
   * @returns self
   */ setSourceRadius(val) {
        val = val < 0 ? 0 : val;
        if (this._sourceRadius !== val) {
            this._sourceRadius = val;
            this.invalidateUniforms();
        }
        return this;
    }
    /**
   * {@inheritDoc BaseLight.isPointLight}
   * @override
   */ isPointLight() {
        return true;
    }
    /** @internal */ computeBoundingVolume() {
        const bbox = new BoundingBox();
        bbox.minPoint = new Vector3(-this._range, -this._range, -this._range);
        bbox.maxPoint = new Vector3(this._range, this._range, this._range);
        return bbox;
    }
    /** @internal */ computeUniforms() {
        const a = this.worldMatrix.getRow(3);
        const b = this.worldMatrix.getRow(2);
        this._positionRange = new Vector4(a.x, a.y, a.z, this.range);
        this._directionCutoff = new Vector4(b.x, b.y, b.z, -1);
        this._diffuseIntensity = new Vector4(this.color.x, this.color.y, this.color.z, this.intensity);
        this._extraParams = new Vector4(this._diffuseScale, this._specularScale, this._sourceRadius, this.lightType);
    }
}
/**
 * Spot light
 * @public
 */ class SpotLight extends PunctualLight {
    /** @internal */ _range;
    /** @internal */ _cutoff;
    /**
   * Creates an instance of spot light
   * @param scene - The scene to which the light belongs
   */ constructor(scene){
        super(scene, LIGHT_TYPE_SPOT);
        this._range = 10;
        this._cutoff = Math.cos(Math.PI / 4);
        this.invalidateBoundingVolume();
    }
    /** The range of the light */ get range() {
        return this._range;
    }
    set range(val) {
        this.setRange(val);
    }
    /**
   * Sets the range of the light
   * @param val - The value to set
   * @returns self
   */ setRange(val) {
        val = val < 0 ? 0 : val;
        if (this._range !== val) {
            this._range = val;
            this.invalidateUniforms();
            this.invalidateBoundingVolume();
        }
        return this;
    }
    /** The cutoff of the light */ get cutoff() {
        return this._cutoff;
    }
    set cutoff(val) {
        this.setCutoff(val);
    }
    /**
   * Sets the cutoff of the light
   * @param val - The value to set
   * @returns self
   */ setCutoff(val) {
        val = val < 0 ? 0 : val;
        if (this._cutoff !== val) {
            this._cutoff = val;
            this.invalidateUniforms();
            this.invalidateBoundingVolume();
        }
        return this;
    }
    /**
   * {@inheritDoc BaseLight.isSpotLight}
   * @override
   */ isSpotLight() {
        return true;
    }
    /** @internal */ computeBoundingVolume() {
        const bbox = new BoundingBox();
        const cosCutoff = Math.cos(this._cutoff);
        const r = this._range / cosCutoff * Math.sqrt(1 - cosCutoff * cosCutoff);
        bbox.minPoint = new Vector3(-r, -r, 0);
        bbox.maxPoint = new Vector3(r, r, this._range);
        return bbox;
    }
    /** @internal */ computeUniforms() {
        const a = this.worldMatrix.getRow(3);
        const b = this.worldMatrix.getRow(2).scaleBy(-1);
        this._positionRange = new Vector4(a.x, a.y, a.z, this.range);
        this._directionCutoff = new Vector4(b.x, b.y, b.z, this.cutoff);
        this._diffuseIntensity = new Vector4(this.color.x, this.color.y, this.color.z, this.intensity);
        this._extraParams = new Vector4(0, 0, 0, this.lightType);
    }
}
/**
 * Rectangular area light
 * @public
 */ class RectLight extends PunctualLight {
    /** @internal */ _range;
    /** @internal */ _width;
    /** @internal */ _height;
    /**
   * Creates an instance of rect light
   * @param scene - The scene to which the light belongs
   */ constructor(scene){
        super(scene, LIGHT_TYPE_RECT);
        this._range = 10;
        this._width = 1;
        this._height = 1;
        this.invalidateBoundingVolume();
    }
    /** The range of the light */ get range() {
        return this._range;
    }
    set range(val) {
        this.setRange(val);
    }
    /**
   * Sets the range of the light
   * @param val - The value to set
   * @returns self
   */ setRange(val) {
        val = val < 0 ? 0 : val;
        if (this._range !== val) {
            this._range = val;
            this.invalidateUniforms();
            this.invalidateBoundingVolume();
        }
        return this;
    }
    /** The width of the light */ get width() {
        return this._width;
    }
    set width(val) {
        this.setWidth(val);
    }
    /**
   * Sets the width of the light
   * @param val - The value to set
   * @returns self
   */ setWidth(val) {
        val = val < 0 ? 0 : val;
        if (this._width !== val) {
            this._width = val;
            this.invalidateUniforms();
            this.invalidateBoundingVolume();
        }
        return this;
    }
    /** The height of the light */ get height() {
        return this._height;
    }
    set height(val) {
        this.setHeight(val);
    }
    /**
   * Sets the height of the light
   * @param val - The value to set
   * @returns self
   */ setHeight(val) {
        val = val < 0 ? 0 : val;
        if (this._height !== val) {
            this._height = val;
            this.invalidateUniforms();
            this.invalidateBoundingVolume();
        }
        return this;
    }
    /**
   * {@inheritDoc BaseLight.isRectLight}
   * @override
   */ isRectLight() {
        return true;
    }
    /**
   * Rect light supports shadow in current pipeline
   */ /** @internal */ computeBoundingVolume() {
        const bbox = new BoundingBox();
        const r = this._range;
        bbox.minPoint = new Vector3(-r, -r, -r);
        bbox.maxPoint = new Vector3(r, r, r);
        return bbox;
    }
    /** @internal */ computeUniforms() {
        const pos = this.worldMatrix.getRow(3);
        const axisX = this.worldMatrix.getRow(0).inplaceNormalize().scaleBy(this._width * 0.5);
        const axisY = this.worldMatrix.getRow(1).inplaceNormalize().scaleBy(this._height * 0.5);
        this._positionRange = new Vector4(pos.x, pos.y, pos.z, this.range);
        this._directionCutoff = new Vector4(axisX.x, axisX.y, axisX.z, 0);
        this._diffuseIntensity = new Vector4(this.color.x, this.color.y, this.color.z, this.intensity);
        this._extraParams = new Vector4(axisY.x, axisY.y, axisY.z, this.lightType);
    }
}

export { BaseLight, DirectionalLight, PointLight, PunctualLight, RectLight, SpotLight };
//# sourceMappingURL=light.js.map
