import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
import { addCSS, css, h as djh } from "@/util.ts";

type TooltipContext =  {
    show: (newText: string, x: number, y: number) => void,
    hide: () => void,
};

const tooltipContext = Symbol('tooltip') as InjectionKey<TooltipContext>;

const tooltipStyles = css`
.tooltip-container {
    display: inline-block;
}

.tooltip-carrier {
    opacity: 0;
    display: block;
    pointer-events: none;
    background-color: black;
    border: white solid 1px;
    color: white;
    padding: 10px;
    position: absolute;
    z-index: 1;
    overflow: hidden;
    height: 0;
    width: 0;
    position: absolute;
    transition: opacity 200ms, height 200ms, width 200ms;

    .text-carrier {
        position: absolute;
        width: 350px;
        font-size: 16px;
        font-family: "Roboto", serif;
        display: block;
        overflow: hidden;
    }
}`;

export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
    const { carrier } = options;

    addCSS('tooltip-carrier', tooltipStyles);

    watchEffect(() => {
        if (carrier.value) {
            carrier.value.classList.add('tooltip-carrier');
            carrier.value.appendChild(djh('div', { className: 'text-carrier' }));
        }
    });

    const listener = (pos: { x: number, y: number }) => {
        if (carrier.value && getComputedStyle(carrier.value).opacity !== "0") {
            if (pos.x + 15 + carrier.value.clientWidth <= document.body.scrollWidth) {
                carrier.value.style.left = (pos.x + 15) + "px";
            } else {
                carrier.value.style.left = (document.body.scrollWidth - carrier.value.clientWidth - 5) + "px";
            }
            if (pos.y + carrier.value.clientHeight <= document.body.scrollHeight) {
                carrier.value.style.top = pos.y + "px";
            } else {
                carrier.value.style.top = (document.body.scrollHeight - carrier.value.clientHeight - 5) + "px";
            }
        }
    };

    const active = ref(false);

    watch(active, async () => {
        const tooltipCarrier = carrier.value;
        if (tooltipCarrier) {
            tooltipCarrier.style.opacity = active.value ? '1' : '0';
            tooltipCarrier.style.width = active.value ? '350px' : '0';
            await nextTick();
            if (active.value) {
                if (tooltipCarrier.firstChild?.nodeType === Node.ELEMENT_NODE) {
                    const computedHeight = getComputedStyle(tooltipCarrier.firstChild as Element).height;
                    tooltipCarrier.style.height = computedHeight;
                }
            } else {
                tooltipCarrier.style.height = '0';
            }
        }
    });

    onMounted(() => document.addEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));
    onBeforeUnmount(() => document.removeEventListener("mousemove", (e) => listener({ x: e.pageX, y: e.pageY })));

    const ctx: TooltipContext = { 
        show(tooltip, x, y) {
            if (carrier.value) {
                (carrier.value.firstChild as HTMLElement)!.innerHTML = tooltip;
            }
            active.value = true;
            listener({ x, y });
        },
        hide() {
            active.value = false;
        },
    };

    provide(tooltipContext, ctx);
}

export default defineComponent({
    name: "dj-tooltip",
    props: {
        tooltip: {
            type: String,
            required: true,
        },
    },
    setup(props, { slots, attrs }) {
        const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true);

        onBeforeUnmount(() => tooltip.hide());

        return () => <>
            <div class="tooltip-container" 
                {...attrs} 
                onMouseenter={(e) => tooltip.show(props.tooltip, e.pageX, e.pageY)}
                onMouseleave={() => tooltip.hide()}>
                {slots.default && <slots.default />}
            </div>
        </>;
    },
});
