import { serializable } from "../../engine/engine_serialization.js";
import { getParam } from "../../engine/engine_utils.js";
import { Behaviour, GameObject } from "../Component.js";
import { Canvas } from "./Canvas.js";
import { type ILayoutGroup, type IRectTransform } from "./Interfaces.js";
import { RectTransform } from "./RectTransform.js";

const debug = getParam("debuguilayout");

export class Padding {
    @serializable()
    left: number = 0;
    @serializable()
    right: number = 0;
    @serializable()
    top: number = 0;
    @serializable()
    bottom: number = 0;

    get vertical() {
        return this.top + this.bottom;
    }
    get horizontal() {
        return this.left + this.right;
    }
}

export enum TextAnchor {
    UpperLeft = 0,
    UpperCenter = 1,
    UpperRight = 2,
    MiddleLeft = 3,
    MiddleCenter = 4,
    MiddleRight = 5,
    LowerLeft = 6,
    LowerCenter = 7,
    LowerRight = 8,
    Custom = 9
}

enum Axis {
    Horizontal = "x",
    Vertical = "y"
}

export abstract class LayoutGroup extends Behaviour implements ILayoutGroup {

    private _rectTransform: RectTransform | null = null;
    private get rectTransform() {
        return this._rectTransform;
    }

    onParentRectTransformChanged(_comp: IRectTransform): void {
        this._needsUpdate = true;
    }

    private _needsUpdate: boolean = false;
    get isDirty(): boolean {
        return this._needsUpdate;
    }

    get isLayoutGroup(): boolean {
        return true;
    }

    updateLayout() {
        if (!this._rectTransform) return;
        if (debug)
            console.warn("Layout Update", this.context.time.frame, this.name);
        this._needsUpdate = false;
        this.onCalculateLayout(this._rectTransform);
    }

    // onBeforeRender(): void {
    //     this.updateLayout();
    // }

    @serializable()
    childAlignment: TextAnchor = TextAnchor.UpperLeft;

    @serializable()
    reverseArrangement: boolean = false;

    @serializable()
    spacing: number = 0;
    @serializable(Padding)
    padding!: Padding;

    @serializable()
    minWidth: number = 0;
    @serializable()
    minHeight: number = 0;

    @serializable()
    flexibleHeight: number = 0;
    @serializable()
    flexibleWidth: number = 0;

    @serializable()
    preferredHeight: number = 0;
    @serializable()
    preferredWidth: number = 0;

    start() {
        this._needsUpdate = true;
    }

    onEnable(): void {
        if(debug) console.log(this.name, this);
        this._rectTransform = this.gameObject.getComponent(RectTransform);
        const canvas = this.gameObject.getComponentInParent(Canvas);
        if (canvas) {
            canvas.registerLayoutGroup(this);
        }
        this._needsUpdate = true;
    }

    onDisable(): void {
        const canvas = this.gameObject.getComponentInParent(Canvas);
        if (canvas) {
            canvas.unregisterLayoutGroup(this);
        }
    }

    protected abstract onCalculateLayout(rt: RectTransform);



    // for animation:
    private set m_Spacing(val) {
        if (val === this.spacing) return;
        this._needsUpdate = true;
        this.spacing = val;
    }
    get m_Spacing() {
        return this.spacing;
    }
}

export abstract class HorizontalOrVerticalLayoutGroup extends LayoutGroup {

    @serializable()
    childControlHeight: boolean = true;
    @serializable()
    childControlWidth: boolean = true;
    @serializable()
    childForceExpandHeight: boolean = false;
    @serializable()
    childForceExpandWidth: boolean = false;
    @serializable()
    childScaleHeight: boolean = false;
    @serializable()
    childScaleWidth: boolean = false;

    protected abstract get primaryAxis(): Axis;

    protected onCalculateLayout(rect: RectTransform) {
        const axis = this.primaryAxis;

        const totalWidth = rect.width;
        let actualWidth = totalWidth;
        const totalHeight = rect.height;
        let actualHeight = totalHeight;
        actualWidth -= this.padding.horizontal;
        actualHeight -= this.padding.vertical;

        // if (rect.name === "Title")
        //     console.log(rect.name, "width=" + totalWidth + ", height=" + totalHeight, rect.anchoredPosition.x)

        const paddingAxis = axis === Axis.Horizontal ? this.padding.horizontal : this.padding.vertical;
        const isHorizontal = axis === Axis.Horizontal;
        const isVertical = !isHorizontal;
        const otherAxis = isHorizontal ? "y" : "x";
        const controlSize = isHorizontal ? this.childControlWidth : this.childControlHeight;
        const controlSizeOtherAxis = isHorizontal ? this.childControlHeight : this.childControlWidth;
        const forceExpandSize = isHorizontal ? this.childForceExpandWidth : this.childForceExpandHeight;
        const forceExpandSizeOtherAxis = isHorizontal ? this.childForceExpandHeight : this.childForceExpandWidth;
        const actualExpandSize = isHorizontal ? actualHeight : actualWidth;
        const totalSpace = isHorizontal ? totalWidth : totalHeight;
        // 0 is left/top, 0.5 is middle, 1 is right/bottom
        const alignmentOnAxis = 0.5 * (isHorizontal ? this.childAlignment % 3 : Math.floor(this.childAlignment / 3));

        let start = 0;
        if (isHorizontal) {
            start += this.padding.left;
        }
        else
            start += this.padding.top;


        // Calculate total size of the elements
        let totalChildSize = 0;
        let actualRectTransformChildCount = 0;
        for (let i = 0; i < this.gameObject.children.length; i++) {
            const ch = this.gameObject.children[i];
            const rt = GameObject.getComponent(ch, RectTransform);
            if (rt?.activeAndEnabled) {
                actualRectTransformChildCount += 1;
                if (isHorizontal) {
                    totalChildSize += rt.width;
                }
                else {
                    totalChildSize += rt.height;
                }
            }
        }

        let sizePerChild = 0;
        const totalSpacing = this.spacing * (actualRectTransformChildCount - 1)
        if (forceExpandSize || controlSize) {
            let size = 0;
            if (isHorizontal) {
                size = actualWidth -= totalSpacing;
            }
            else {
                size = actualHeight -= totalSpacing;
            }
            if (actualRectTransformChildCount > 0)
                sizePerChild = size / actualRectTransformChildCount;
        }

        let leftOffset = 0;
        leftOffset += this.padding.left;
        leftOffset -= this.padding.right;

        if (alignmentOnAxis !== 0) {
            start = totalSpace - totalChildSize;
            start *= alignmentOnAxis;
            start -= totalSpacing * alignmentOnAxis;
            if (isHorizontal) {
                start -= this.padding.right * alignmentOnAxis;
                start += this.padding.left * (1 - alignmentOnAxis);
                if (start < this.padding.left) {
                    start = this.padding.left;
                }
            }
            else {
                start -= this.padding.bottom * alignmentOnAxis;
                start += this.padding.top * (1 - alignmentOnAxis);
                if (start < this.padding.top) {
                    start = this.padding.top;
                }
            }
        }

        // Apply layout
        let k = 1;
        for (let i = 0; i < this.gameObject.children.length; i++) {
            const ch = this.gameObject.children[i];
            const rt = GameObject.getComponent(ch, RectTransform);
            if (rt?.activeAndEnabled) {
                rt.pivot?.set(.5, .5);
                rt.anchorMin.set(0, 1);
                rt.anchorMax.set(0, 1);
                // Horizontal padding
                const x = totalWidth * .5 + leftOffset * .5;
                if (rt.anchoredPosition.x !== x)
                    rt.anchoredPosition.x = x;
                const y = totalHeight * -.5
                if (rt.anchoredPosition.y !== y)
                    rt.anchoredPosition.y = y;
                // Set the size for the secondary axis (e.g. height for a horizontal layout group)
                if (forceExpandSizeOtherAxis && controlSizeOtherAxis && rt.sizeDelta[otherAxis] !== actualExpandSize) {
                    rt.sizeDelta[otherAxis] = actualExpandSize;
                }
                // Set the size for the primary axis (e.g. width for a horizontal layout group)
                if (forceExpandSize && controlSize && rt.sizeDelta[axis] !== sizePerChild) {
                    rt.sizeDelta[axis] = sizePerChild
                }

                const size = isHorizontal ? rt.width : rt.height;
                const halfSize = size * .5;
                start += halfSize;

                // TODO: this isnt correct yet!
                if (forceExpandSize) {
                    // this is the center of the cell
                    const preferredStart = sizePerChild * k - sizePerChild * .5;
                    if (preferredStart > start) {
                        start = preferredStart - sizePerChild * .5 + size + this.padding.left;
                        start -= halfSize;
                    }
                }

                let value = start;
                if (axis === Axis.Vertical)
                    value = -value;
                // Only set the position if it's not already the correct one to avoid triggering the rectTransform dirty event
                if (rt.anchoredPosition[axis] !== value) {
                    rt.anchoredPosition[axis] = value
                }

                start += halfSize;
                start += this.spacing;
                k += 1;
            }
        }
    }


}

/**
 * [VerticalLayoutGroup](https://engine.needle.tools/docs/api/VerticalLayoutGroup) arranges child UI elements vertically with spacing, padding, and alignment options.
 * @category User Interface
 * @group Components
 */
export class VerticalLayoutGroup extends HorizontalOrVerticalLayoutGroup {

    protected get primaryAxis() {
        return Axis.Vertical;
    }

}

/**
 * [HorizontalLayoutGroup](https://engine.needle.tools/docs/api/HorizontalLayoutGroup) arranges child UI elements horizontally with spacing, padding, and alignment options.
 * @category User Interface
 * @group Components
 */
export class HorizontalLayoutGroup extends HorizontalOrVerticalLayoutGroup {

    protected get primaryAxis() {
        return Axis.Horizontal;
    }

}

/**
 * [GridLayoutGroup](https://engine.needle.tools/docs/api/GridLayoutGroup) arranges child UI elements in a grid pattern.
 * @category User Interface
 * @group Components
 */
export class GridLayoutGroup extends LayoutGroup {
    protected onCalculateLayout() {
    }

}