var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Color, LinearSRGBColorSpace, Texture } from 'three';
import SimpleStateBehavior from "three-mesh-ui/examples/behaviors/states/SimpleStateBehavior.js";
import { serializable } from '../../engine/engine_serialization_decorator.js';
import { NEEDLE_progressive } from '../../engine/extensions/NEEDLE_progressive.js';
import { RGBAColor } from "../../engine/js-extensions/index.js";
import { GameObject } from '../Component.js';
import { BaseUIComponent } from "./BaseUIComponent.js";
import { Outline } from './Outline.js';
import { RectTransform } from './RectTransform.js';
import { onChange, scheduleAction } from "./Utils.js";
const _colorStateObject = {
    backgroundColor: new Color(1, 1, 1),
    backgroundOpacity: 1,
    borderColor: new Color(1, 1, 1),
    borderOpacity: 1,
};
/**
 * [Graphic](https://engine.needle.tools/docs/api/Graphic) provides basic rendering for UI elements with color, opacity, and texture support.
 * @category User Interface
 * @group Components
 */
export class Graphic extends BaseUIComponent {
    get isGraphic() { return true; }
    get color() {
        if (!this._color)
            this._color = new RGBAColor(1, 1, 1, 1);
        return this._color;
    }
    set color(col) {
        const changed = !this._color || this._color.r !== col.r || this._color.g !== col.g || this._color.b !== col.b || this._color.alpha !== col.alpha;
        if (!changed)
            return;
        if (!this._color) {
            this._color = new RGBAColor(1, 1, 1, 1);
        }
        this._color.copy(col);
        this.onColorChanged();
    }
    _alphaFactor = 1;
    setAlphaFactor(factor) {
        this._alphaFactor = factor;
        this.onColorChanged();
    }
    get alphaFactor() {
        return this._alphaFactor;
    }
    sRGBColor = new Color(1, 0, 1);
    onColorChanged() {
        if (this.uiObject) {
            this.sRGBColor.copy(this._color);
            this.sRGBColor.convertLinearToSRGB();
            _colorStateObject.backgroundColor = this.sRGBColor;
            _colorStateObject.backgroundOpacity = this._color.alpha;
            // E.g. when a button is setting states, we need to merge the state color with the base color
            const activeStateName = this.uiObject["_simpleState__activeStates"]?.[0];
            if (activeStateName) {
                const active = this.uiObject["_simpleState__states"]?.[activeStateName];
                if (active) {
                    if ("backgroundColor" in active)
                        _colorStateObject.backgroundColor = active["backgroundColor"];
                    if ("backgroundOpacity" in active)
                        _colorStateObject.backgroundOpacity = active["backgroundOpacity"];
                }
            }
            _colorStateObject.backgroundOpacity *= this._alphaFactor;
            this.applyEffects(_colorStateObject, this._alphaFactor);
            this.uiObject.set(_colorStateObject);
            this.markDirty();
        }
    }
    // used via animations
    get m_Color() {
        return this._color;
    }
    raycastTarget = true;
    uiObject = null;
    _color = null;
    _rect = null;
    _stateManager = null;
    get rectTransform() {
        if (!this._rect) {
            this._rect = GameObject.getComponent(this.gameObject, RectTransform);
        }
        if (!this._rect)
            throw new Error("Not Supported: Make sure to add a RectTransform component before adding a UI Graphic component.");
        return this._rect;
    }
    onParentRectTransformChanged() {
        this.uiObject?.set({ width: this.rectTransform.width, height: this.rectTransform.height });
        this.markDirty();
    }
    __internalNewInstanceCreated(init) {
        super.__internalNewInstanceCreated(init);
        this._rect = null;
        this.uiObject = null;
        this._stateManager = null;
        if (this._color)
            this._color = this._color.clone();
        return this;
    }
    setState(state) {
        this.makePanel();
        if (this.uiObject) {
            //@ts-ignore
            this.uiObject.setState(state);
            this?.markDirty();
        }
    }
    setupState(state) {
        this.makePanel();
        if (this.uiObject) {
            // @marwie : v7.x now have a concurrent state management in core mimicking html/css
            // ie : (::firstChild::hover::disabled) where firstchild, hover and disabled are all on different channels
            // In order to keep needle Raycaster and EventSystem intact, I added in v7 a SimpleStateBehavior, which acts as previously
            if (!this._stateManager)
                this._stateManager = new SimpleStateBehavior(this.uiObject);
            //@ts-ignore
            this.uiObject.setupState(state.state, state.attributes);
        }
    }
    setOptions(opts) {
        this.makePanel();
        if (this.uiObject) {
            //@ts-ignore
            this.uiObject.set(opts);
            // if (opts["backgroundColor"] !== undefined || opts["backgroundOpacity"] !== undefined)
            //     this.uiObject["updateBackgroundMaterial"]?.call(this.uiObject);
        }
    }
    awake() {
        super.awake();
        this.makePanel();
        // when _color is written to
        onChange(this, "_color", () => scheduleAction(this, this.onColorChanged));
    }
    onEnable() {
        super.onEnable();
        if (this.uiObject) {
            this.rectTransform.shadowComponent?.add(this.uiObject);
            this.addShadowComponent(this.uiObject, this.rectTransform);
        }
    }
    onDisable() {
        super.onDisable();
        if (this.uiObject)
            this.removeShadowComponent();
    }
    _currentlyCreatingPanel = false;
    makePanel() {
        if (this.uiObject)
            return;
        if (this._currentlyCreatingPanel)
            return;
        this._currentlyCreatingPanel = true;
        const offset = .015;
        // if (this.Root) offset = .02 * (1 / this.Root.gameObject.scale.z);
        const opts = {
            backgroundColor: this.color,
            backgroundOpacity: this.color.alpha,
            offset: offset, // without a tiny offset we get z fighting
        };
        this.onBeforeCreate(opts);
        this.applyEffects(opts);
        this.onCreate(opts);
        this.controlsChildLayout = false;
        this._currentlyCreatingPanel = false;
        this.onAfterCreated();
        this.onColorChanged();
    }
    onBeforeCreate(_opts) { }
    onCreate(opts) {
        this.uiObject = this.rectTransform.createNewBlock(opts);
        this.uiObject.name = this.name;
    }
    onAfterCreated() { }
    applyEffects(opts, alpha = 1) {
        const outline = this.gameObject?.getComponent(Outline);
        if (outline) {
            if (outline.effectDistance)
                opts.borderWidth = Math.max(Math.abs(outline.effectDistance.x), Math.abs(outline.effectDistance.y));
            if (outline.effectColor) {
                opts.borderColor = outline.effectColor;
                opts.borderOpacity = outline.effectColor.alpha * alpha;
            }
        }
    }
    /** used internally to ensure textures assigned to UI use linear encoding */
    static textureCache = new Map();
    async setTexture(tex) {
        this.setOptions({ backgroundOpacity: 0 });
        if (tex) {
            // workaround for https://github.com/needle-tools/needle-engine-support/issues/109
            // if (tex.colorSpace === SRGBColorSpace || !tex.colorSpace || true) {
            if (Graphic.textureCache.has(tex)) {
                tex = Graphic.textureCache.get(tex);
            }
            else {
                if (tex.isRenderTargetTexture) {
                    // we can not clone the texture if it's a render target
                    // otherwise it won't be updated anymore in the UI
                    // TODO: below maskable graphic is flipped but settings a rendertexture results in the texture being upside down. 
                    // we should remove the flip below (scale.y *= -1) but this needs to be tested with all UI components
                }
                else {
                    const clone = tex.clone();
                    clone.colorSpace = LinearSRGBColorSpace;
                    Graphic.textureCache.set(tex, clone);
                    tex = clone;
                }
            }
            // }
            this.setOptions({ backgroundImage: tex, borderRadius: 0, backgroundOpacity: this.color.alpha, backgroundSize: "stretch" });
            NEEDLE_progressive.assignTextureLOD(tex, 0).then(res => {
                if (res instanceof Texture) {
                    if (tex)
                        Graphic.textureCache.set(tex, res);
                    this.setOptions({ backgroundImage: res });
                    this.markDirty();
                }
            });
        }
        else {
            this.setOptions({ backgroundImage: undefined, borderRadius: 0, backgroundOpacity: this.color.alpha });
        }
        this.markDirty();
    }
    onAfterAddedToScene() {
        super.onAfterAddedToScene();
        if (this.shadowComponent) {
            // @TODO: I think we dont even need this anymore and this leads to the offset being applied twice
            //@ts-ignore
            this.shadowComponent.offset = this.shadowComponent.position.z;
            // console.log(this.shadowComponent);
            // setTimeout(()=>{
            //     this.shadowComponent?.traverse(c => {
            //         console.log(c);
            //         if(c.material) c.material.depthTest = false;
            //     });
            // },1000);
        }
    }
}
__decorate([
    serializable(RGBAColor)
], Graphic.prototype, "color", null);
__decorate([
    serializable()
], Graphic.prototype, "raycastTarget", void 0);
/**
 * @category User Interface
 * @group Components
 */
export class MaskableGraphic extends Graphic {
    _flippedObject = false;
    onAfterCreated() {
        // flip image
        if (this.uiObject && !this._flippedObject) {
            this._flippedObject = true;
            this.uiObject.scale.y *= -1;
        }
    }
}
//# sourceMappingURL=Graphic.js.map