<!--
@component

Metro-like tile. Must be in a group to display correctly.
-->

<script lang="ts">
    import randomNumber from "$lib/utils/random-number";
    import { cva } from "class-variance-authority";
    import { onDestroy, onMount } from "svelte";
    import TileIconPage from "./pages/TileIconPage.svelte";

    /**
     * Max angle value.
     */
    const MAX_ANGLE = 10;

    /**
     * Base size of a icon in pixels.
     **/
    const ICON_BASE_SIZE = 45;

    /**
     * Icon sizes.
     */
    const ICON_SIZES = {
        small: ICON_BASE_SIZE,
        medium: ICON_BASE_SIZE * 2,
        wide: ICON_BASE_SIZE * 2,
        large: ICON_BASE_SIZE * 4,
    };

    /**
     * Base size of a page in pixels.
     */
    const TILE_BASE_SIZE = 75;

    /**
     * Page heights.
     */
    const PAGE_HEIGHTS = {
        small: TILE_BASE_SIZE,
        medium: TILE_BASE_SIZE * 2,
        wide: TILE_BASE_SIZE * 2,
        large: TILE_BASE_SIZE * 4,
    };

    /**
     * Max scroll interval delay in milliseconds.
     */
    const SCROLL_INTERVAL_MAX = 7.5 * 1000;

    /**
     * Min scroll interval delay in milliseconds.
     */
    const SCROLL_INTERVAL_MIN = SCROLL_INTERVAL_MAX / 2;

    /**
     * Tile pages.
     */
    let pages: HTMLElement;

    /**
     * Properties.
     */
    let {
        size,
        row,
        icon,
        column,
        children,
        link = "#",
        enabled = true,
    }: {
        /**
         * Size.
         */
        size: "small" | "medium" | "wide" | "large";

        /**
         * Row index. Must be in a small tiles.
         */
        row: number;

        /**
         * Column index. Must be in a small tiles.
         */
        column: number;

        /**
         * Icon. Must be a valid URL.
         */
        icon: string;

        /**
         * Link.
         */
        link?: string;

        /**
         * Is tile enabled?
         */
        enabled?: boolean;

        /**
         * Pages.
         */
        children: () => any;
    } = $props();

    /**
     * Tile style.
     **/
    const style = cva(["relative", "bg-(--tile-color)/80"], {
        variants: {
            size: {
                small: ["row-span-1", "col-span-1"],
                medium: ["row-span-2", "col-span-2"],
                wide: ["row-span-2", "col-span-4"],
                large: ["row-span-4", "col-span-4"],
            },
            enabled: {
                false: ["grayscale", "opacity-50"],
                true: [
                    "active:scale-95",
                    "perspective-distant",
                    "active:transform-gpu",
                    "active:transform-3d",
                    "active:rotate-x-(--tile-rotate-x)",
                    "active:rotate-y-(--tile-rotate-y)",
                    "duration-75",
                    "ease-in-out",
                ],
            },
        },
    });

    /**
     * Is tile active?
     */
    const active = link !== "#" && enabled && children !== null;

    /**
     * Icon size.
     */
    const iconSize = ICON_SIZES[size];

    /**
     * Page height.
     */
    const pageHeight = PAGE_HEIGHTS[size];

    /**
     * Mouse position. Used by border (on mouse over).
     */
    let mousePosition = $state({ x: 0, y: 0 });

    /**
     * Rotation values. Used by transform (on click).
     */
    let rotationValues = $state({ x: 0, y: 0 });

    /**
     * Updates the mouse position-based values.
     * @param event `mouseover` event.
     */
    function updateDynamicMouseValues(event: MouseEvent) {
        const element = event.target as HTMLElement;
        const bounds = element.getBoundingClientRect();

        mousePosition = {
            x: event.clientX - bounds.left,
            y: event.clientY - bounds.top,
        };

        const tileCenterX = bounds.width / 2;
        const tileCenterY = bounds.height / 2;

        rotationValues.x =
            -((mousePosition.y - tileCenterY) / tileCenterY) * MAX_ANGLE;
        rotationValues.y =
            -((tileCenterX - mousePosition.x) / tileCenterX) * MAX_ANGLE;
    }

    /**
     * Current page number.
     */
    let currentPage = 1;

    /**
     * Scroll orientation.
     */
    let scrollOrientation: "up" | "down" = "down";

    /**
     * Page count.
     */
    let pageCount: number;

    /**
     * Scroll between pages. Used by scroll interval.
     */
    function scrollPages() {
        // NOTE: Handle the current page and multipler.
        let scrollMultipler: number;
        if (scrollOrientation === "down") {
            scrollMultipler = 1;
            currentPage++;
        } else {
            scrollMultipler = -1;
            currentPage--;
        }

        // NOTE: Scroll the pages.
        pages.scrollBy({ top: pageHeight * scrollMultipler });

        // NOTE: Change the orientation after scroll.
        if (currentPage === pageCount) {
            scrollOrientation = "up";
        } else if (currentPage === 1) {
            scrollOrientation = "down";
        }
    }

    /**
     * Scroll interval.
     */
    let scrollInterval: NodeJS.Timeout;

    onMount(() => {
        pageCount = Math.floor(pages.scrollHeight / pageHeight);

        if (size === "small" || pageCount === 1) {
            return;
        }

        const scrollDelay = randomNumber(
            SCROLL_INTERVAL_MIN,
            SCROLL_INTERVAL_MAX,
        );

        scrollInterval = setInterval(scrollPages, scrollDelay);
    });

    onDestroy(() => {
        if (!scrollInterval) {
            return;
        }

        clearInterval(scrollInterval);
    });
</script>

<a
    class={style({ size, enabled })}
    style="
        --tile-icon: url('{icon}');
        --tile-icon-size: {iconSize}px;
        --tile-page-height: {pageHeight}px;
        --tile-name-display: {size !== 'small' ? 'inline' : 'none'};
        --tile-rotate-x: {rotationValues.x}deg;
        --tile-rotate-y: {rotationValues.y}deg;
        grid-row-start: {row};
        grid-column-start: {column};
    "
    onmousemove={active ? updateDynamicMouseValues : null}
    href="#h"
>
    <!-- Tile pages -->
    <div
        class="w-full h-full overflow-y-hidden scroll-smooth duration-150"
        bind:this={pages}
    >
        {#if active}
            {@render children()}
        {:else}
            <TileIconPage />
        {/if}
    </div>

    {#if active}
        <!-- Tile border -->
        <div
            class="
                absolute top-0
                w-full h-full
                border-2 border-white
                opacity-0 hover:opacity-50 hover:mask-(--mask) active:opacity-100
                duration-75 ease-in-out
            "
            style="
                --mask: radial-gradient(
                    {TILE_BASE_SIZE}px
                    at {mousePosition.x}px {mousePosition.y}px,
                    black 45%,
                    transparent
                );
            "
        ></div>
    {/if}
</a>
