/*
Copyright 2024 New Vector Ltd.
Copyright 2019, 2020 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import { type IConfig } from "./resizer";
import type Resizer from "./resizer";
import type Sizer from "./sizer";

export default class ResizeItem<C extends IConfig> {
    public readonly domNode: HTMLElement;
    protected readonly id: string | null;
    protected reverse: boolean;

    public constructor(
        handle: HTMLElement,
        public readonly resizer: Resizer<C, any>,
        public readonly sizer: Sizer,
        public readonly container?: HTMLElement,
    ) {
        this.reverse = resizer.isReverseResizeHandle(handle);
        if (container) {
            this.domNode = container;
        } else {
            this.domNode = <HTMLElement>(this.reverse ? handle.nextElementSibling : handle.previousElementSibling);
        }
        this.id = handle.getAttribute("data-id");
    }

    private copyWith(
        handle: HTMLElement,
        resizer: Resizer<C, any>,
        sizer: Sizer,
        container?: HTMLElement,
    ): ResizeItem<C> {
        const Ctor = this.constructor as typeof ResizeItem;
        return new Ctor(handle, resizer, sizer, container);
    }

    private advance(forwards: boolean): ResizeItem<C> | undefined {
        // opposite direction from fromResizeHandle to get back to handle
        let handle: Element | null | undefined = this.reverse
            ? this.domNode.previousElementSibling
            : this.domNode.nextElementSibling;
        const moveNext = forwards !== this.reverse; // xor
        // iterate at least once to avoid infinite loop
        do {
            if (moveNext) {
                handle = handle?.nextElementSibling;
            } else {
                handle = handle?.previousElementSibling;
            }
        } while (handle && !this.resizer.isResizeHandle(<HTMLElement>handle));

        if (handle) {
            const nextHandle = this.copyWith(<HTMLElement>handle, this.resizer, this.sizer);
            nextHandle.reverse = this.reverse;
            return nextHandle;
        }
    }

    public next(): ResizeItem<C> | undefined {
        return this.advance(true);
    }

    public previous(): ResizeItem<C> | undefined {
        return this.advance(false);
    }

    public size(): number {
        return this.sizer.getItemSize(this.domNode);
    }

    public offset(): number {
        return this.sizer.getItemOffset(this.domNode);
    }

    public start(): void {
        this.sizer.start(this.domNode);
    }

    public finish(): void {
        this.sizer.finish(this.domNode);
    }

    public getSize(): string {
        return this.sizer.getDesiredItemSize(this.domNode);
    }

    public setRawSize(size: string): void {
        this.sizer.setItemSize(this.domNode, size);
    }

    public setSize(size: number): void {
        this.setRawSize(`${Math.round(size)}px`);
        this.resizer.config?.onResized?.(size, this.id, this.domNode);
    }

    public clearSize(): void {
        this.sizer.clearItemSize(this.domNode);
        this.resizer.config?.onResized?.(null, this.id, this.domNode);
    }

    public first(): ResizeItem<C> | undefined {
        if (!this.domNode.parentElement?.children) {
            return;
        }
        const firstHandle = Array.from(this.domNode.parentElement.children).find((el) => {
            return this.resizer.isResizeHandle(<HTMLElement>el);
        });
        if (firstHandle) {
            return this.copyWith(<HTMLElement>firstHandle, this.resizer, this.sizer);
        }
    }

    public last(): ResizeItem<C> | undefined {
        if (!this.domNode.parentElement?.children) {
            return;
        }
        const lastHandle = Array.from(this.domNode.parentElement.children)
            .reverse()
            .find((el) => {
                return this.resizer.isResizeHandle(<HTMLElement>el);
            });
        if (lastHandle) {
            return this.copyWith(<HTMLElement>lastHandle, this.resizer, this.sizer);
        }
    }
}
