import * as FloatingUI from "@floating-ui/dom";
import { cancelPropagation, dom, svg, text, type Class, type NodeChildren } from "./dom.util";
import { button } from "./components.util";

export interface FloatingProperties
{
    placement?: FloatingUI.Placement;
    offset?: FloatingUI.OffsetOptions;
    arrow?: boolean;
    class?: Class;
    style?: Record<string, string | undefined | boolean | number> | string;
    viewport?: HTMLElement;
    cover?: 'width' | 'height' | 'all' | 'none';
}
export interface FollowerProperties extends FloatingProperties
{
    blur?: () => void;
    priority?: boolean;
}
export type FloatState = 'shown' | 'showing' | 'hidden' | 'hiding' | 'pinned';
export interface PopperProperties extends FloatingProperties
{
    content?: NodeChildren | (() => NodeChildren);
    delay?: number;
    events?: {
        show: Array<keyof HTMLElementEventMap>; 
        hide: Array<keyof HTMLElementEventMap>; 
        onshow?: (state: FloatState) => boolean; 
        onhide?: (state: FloatState) => boolean;
    };
}

export interface ModalProperties
{
    priority?: boolean;
    closeWhenOutside?: boolean;
    onClose?: () => boolean | void;
}

let teleport: HTMLDivElement;
export function init()
{
    teleport = dom('div', { attributes: { id: 'popper-container' }, class: 'absolute top-0 left-0 z-40' });
    document.body.appendChild(teleport);
}

export function popper(container: HTMLElement, properties?: PopperProperties)
{
    let state: FloatState = 'hidden', manualStop = false, timeout: Timer;
    const arrow = svg('svg', { class: ' group-data-[pinned]:hidden absolute fill-light-35 dark:fill-dark-35', attributes: { width: "12", height: "8", viewBox: "0 0 20 10" } }, [svg('polygon', { attributes: { points: "0,0 20,0 10,10" } })]);
    const content = dom('div', { class: properties?.class, style: properties?.style });
    const floater = dom('div', { class: 'fixed hidden group', attributes: { 'data-state': 'closed' } }, [ content, properties?.arrow ? arrow : undefined ]);
    const rect = properties?.viewport?.getBoundingClientRect() ?? 'viewport';

    function update()
    {
        FloatingUI.computePosition(container, floater, {
            placement: properties?.placement,
            strategy: 'fixed',
            middleware: [
                properties?.offset ? FloatingUI.offset(properties?.offset) : undefined,
                FloatingUI.hide({ rootBoundary: rect }),
                FloatingUI.shift({ rootBoundary: rect }),
                FloatingUI.flip({ rootBoundary: rect }),
                properties?.cover && properties?.cover !== 'none' && FloatingUI.size({ rootBoundary: rect, apply: ({ availableWidth, availableHeight }) => {
                    if(properties?.cover === 'width' || properties?.cover === 'all') 
                        floater.style.maxWidth = `${availableWidth}px`;
                    if(properties?.cover === 'height' || properties?.cover === 'all') 
                        floater.style.maxHeight = `${availableHeight}px`;
                } }),
                properties?.offset && properties?.arrow ? FloatingUI.arrow({ element: arrow, padding: 8 }) : undefined,
            ]
        }).then(({ x, y, placement, middlewareData }) => {
            Object.assign(floater.style, {
                left: `${x}px`,
                top: `${y}px`,
                visibility: middlewareData.hide?.referenceHidden || middlewareData.hide?.escaped ? 'hidden' : 'visible',
            });
            
            const side = placement.split('-')[0] as FloatingUI.Side;
            
            floater.setAttribute('data-side', side);
            
            if(middlewareData.arrow)
            {
                const { x: arrowX, y: arrowY } = middlewareData.arrow;
        
                const staticSide = {
                    top: 'bottom',
                    right: 'left',
                    bottom: 'top',
                    left: 'right',
                }[side]!;

                const rotation = {
                    top: "0",
                    bottom: "180",
                    left: "270",
                    right: "90"
                }[side]!;
                 
                Object.assign(arrow.style, {
                    left: arrowX != null ? `${arrowX}px` : '',
                    top: arrowY != null ? `${arrowY}px` : '',
                    right: '',
                    bottom: '',
                    [staticSide]: `-7px`,
                    transform: `rotate(${rotation}deg)`,
                });
            }
        });
    }

    let _stop: () => void | undefined, empty = true;
    function show()
    {
        if(state !== 'shown' && state !== 'showing' && state !== 'pinned' && (!properties?.events?.onshow || properties?.events?.onshow(state) !== false))
        {
            if(typeof properties?.content === 'function')
                properties.content = properties.content();

            if(properties?.content && empty)
            {
                content.replaceChildren(...properties!.content.filter(e => !!e));
                empty = false;
            }

            clearTimeout(timeout);
            state = 'showing';

            timeout = setTimeout(() => {
                if(state !== 'shown')
                {
                    teleport!.appendChild(floater);
                    
                    floater.setAttribute('data-state', 'open');
                    floater.classList.toggle('hidden', false);

                    update();
                    _stop && _stop();
                    _stop = FloatingUI.autoUpdate(container, floater, update, {
                        animationFrame: true,
                        layoutShift: false,
                        elementResize: false,
                        ancestorScroll: false,
                        ancestorResize: false,
                    });
                }
                state = 'shown';
            }, properties?.delay ?? 0);
        }
    }
    
    function hide()
    {
        if(state !== 'hiding' && state !== 'pinned' && (!properties?.events?.onhide || properties?.events?.onhide(state) !== false))
        {
            clearTimeout(timeout);
            state = 'hiding';

            timeout = setTimeout(() => {
                floater.remove();
                _stop && _stop();

                floater.setAttribute('data-state', 'closed');
                floater.classList.toggle('hidden', true);

                state = 'hidden';
            }, properties?.delay ?? 0);
        }
    }

    function start()
    {
        state = 'hidden';
        floater.toggleAttribute('data-pinned', false);
        update();
        _stop && _stop();
        _stop = FloatingUI.autoUpdate(container, floater, update, {
            animationFrame: true,
            layoutShift: false,
            elementResize: false,
            ancestorScroll: false,
            ancestorResize: false,
        });
    }

    function stop()
    {
        state = 'pinned';
        floater.toggleAttribute('data-pinned', true);
        _stop && _stop();
        clearTimeout(timeout);
    }
    
    function link(element: HTMLElement) {
        (properties?.events?.show ?? ['mouseenter', 'mousemove', 'focus']).forEach((e: keyof HTMLElementEventMap) => element.addEventListener(e, show));
        (properties?.events?.hide ?? ['mouseleave', 'blur']).forEach((e: keyof HTMLElementEventMap) => element.addEventListener(e, hide));
    }

    link(container);
    link(floater);

    return { container, content: floater, stop, start, show: () => {
        if(typeof properties?.content === 'function')
            properties.content = properties.content();

        if(properties?.content && empty)
        {
            content.replaceChildren(...properties!.content.filter(e => !!e));
            empty = false;
        }
        
        if(state !== 'shown')
        {
            teleport!.appendChild(floater);
            
            floater.setAttribute('data-state', 'open');
            floater.classList.toggle('hidden', false);

            update();
        }
        state = 'shown';
    }, hide: () => {
        floater.remove();
        _stop && _stop();

        floater.setAttribute('data-state', 'closed');
        floater.classList.toggle('hidden', true);
        
        manualStop = false;
        floater.toggleAttribute('data-pinned', false);

        state = 'hidden';
    } };
}
export function followermenu(target: FloatingUI.ReferenceElement, content: NodeChildren, properties?: FollowerProperties)
{
    const rect = properties?.viewport?.getBoundingClientRect() ?? 'viewport';

    const arrow = svg('svg', { class: 'absolute fill-light-35 dark:fill-dark-35', attributes: { width: "12", height: "8", viewBox: "0 0 20 10" } }, [svg('polygon', { attributes: { points: "0,0 20,0 10,10" } })]);
    const container = dom('div', { class: ['fixed bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 z-50', properties?.class], style: properties?.style }, content);

    function update()
    {
        FloatingUI.computePosition(target, container, {
            placement: properties?.placement,
            strategy: 'fixed',
            middleware: [
                properties?.offset ? FloatingUI.offset(properties?.offset) : undefined,
                FloatingUI.hide({ rootBoundary: rect }),
                FloatingUI.shift({ rootBoundary: rect }),
                FloatingUI.flip({ rootBoundary: rect }),
                properties?.cover && properties?.cover !== 'none' && FloatingUI.size({ rootBoundary: rect, apply: ({ availableWidth, availableHeight }) => {
                    if(properties?.cover === 'width' || properties?.cover === 'all') 
                        container.style.width = `${availableWidth}px`;
                    if(properties?.cover === 'height' || properties?.cover === 'all') 
                        container.style.height = `${availableHeight}px`;
                } }),
                properties?.offset && properties?.arrow ? FloatingUI.arrow({ element: arrow, padding: 8 }) : undefined,
            ]
        }).then(({ x, y, placement, middlewareData }) => {
            Object.assign(container.style, {
                left: `${x}px`,
                top: `${y}px`,
                visibility: middlewareData.hide?.referenceHidden ? 'hidden' : 'visible',
            });

            const side = placement.split('-')[0] as FloatingUI.Side;
            
            container.setAttribute('data-side', side);
            
            if(middlewareData.arrow)
            {
                const { x: arrowX, y: arrowY } = middlewareData.arrow;
        
                const staticSide = {
                    top: 'bottom',
                    right: 'left',
                    bottom: 'top',
                    left: 'right',
                  }[side]!;

                  const rotation = {
                    top: "0",
                    bottom: "180",
                    left: "270",
                    right: "90"
                  }[side]!;
                 
                  Object.assign(arrow.style, {
                    left: arrowX != null ? `${arrowX}px` : '',
                    top: arrowY != null ? `${arrowY}px` : '',
                    right: '',
                    bottom: '',
                    [staticSide]: `-8px`,
                    transform: `rotate(${rotation}deg)`,
                  });
            }
        });
    }
    update();

    properties?.priority || document.addEventListener('mousedown', close);
    container.addEventListener('mousedown', cancelPropagation);

    const stop = FloatingUI.autoUpdate(target, container, update, {
        animationFrame: true,
        layoutShift: false,
        elementResize: false,
        ancestorScroll: false,
        ancestorResize: false,
    });
    teleport!.appendChild(container);

    function close()
    {
        properties?.priority || document.removeEventListener('mousedown', close);
        container.removeEventListener('mousedown', cancelPropagation);

        container.remove();
        stop();
        properties?.blur && properties.blur();
    }

    return { close, container, content };
}
export function contextmenu(x: number, y: number, content: NodeChildren, properties?: FollowerProperties)
{
    return followermenu({
        getBoundingClientRect() {
            return {
                x: x,
                y: y,
                top: y,
                left: x,
                bottom: y,
                right: x,
                width: 0,
                height: 0,
            };
        },
    }, content, properties);
}
export function tooltip(container: HTMLElement, txt: string | Text, placement: FloatingUI.Placement, delay?: number): HTMLElement
{
    return popper(container, { 
        arrow: true,
        offset: 8,
        delay: delay,
        content: () => [ typeof txt === 'string' ? text(txt) : txt ],
        placement: placement,
        class: "border border-light-30 dark:border-dark-30 px-2 py-1 bg-light-10 dark:bg-dark-10 text-light-70 dark:text-dark-70 z-50 max-w-96"
    }).container;
}

export function fullblocker(content: NodeChildren, properties?: ModalProperties)
{
    if(!content)
        return { close: () => {} };

    const close = () => (!properties?.onClose || properties.onClose() !== false) && _modal.remove();
    const _modalBlocker = dom('div', { class: [' absolute top-0 left-0 bottom-0 right-0 z-0', { 'bg-light-0 dark:bg-dark-0 opacity-70': properties?.priority ?? false }], listeners: { click: properties?.closeWhenOutside ? (close) : undefined } });
    const _modal = dom('div', { class: 'fixed flex justify-center items-center top-0 left-0 bottom-0 right-0 inset-0 z-40' }, [ _modalBlocker, ...content]);

    teleport.appendChild(_modal);

    return { close };
}
export function modal(content: NodeChildren, properties?: ModalProperties)
{
    return fullblocker([ dom('div', { class: 'max-h-[85vh] max-w-[450px] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 text-light-100 dark:text-dark-100 z-10 relative' }, content) ], properties);
}

export function confirm(title: string): Promise<boolean>
{
    return new Promise(res => {
        const mod = modal([ dom('div', { class: 'flex flex-col justify-start gap-4' }, [ dom('div', { class: 'text-xl' }, [ text(title) ]), dom('div', { class: 'flex flex-row gap-2' }, [ button(text("Non"), () => (mod.close(), res(false)), 'h-[35px] px-[15px]'), button(text("Oui"), () => (mod.close(), res(true)), 'h-[35px] px-[15px] !border-light-red dark:!border-dark-red text-light:red dark:text-dark-red') ]) ]) ], {
            priority: true,
            closeWhenOutside: false,
        });
    })
}