/**
 * SPA Navigation utilities
 * Provides navigation functions that work with the history API
 * and automatically update the location store.
 */

import { previousPath } from "./store"

export type SpaNavigateOptions = {
    /** Use replaceState instead of pushState (default: false) */
    replace?: boolean
    /** State object to pass to pushState/replaceState */
    state?: Record<string, unknown> | null
    /** Skip scrolling to top (default: false) */
    noScroll?: boolean
}

/**
 * Initialize scroll restoration for SPA navigation.
 * Call this once at app startup to enable automatic scroll position restoration
 * when using browser back/forward buttons.
 */
export const initScrollRestoration = (): void => {
    if (typeof window === "undefined") {
        return // SSR guard
    }

    // Disable browser's automatic scroll restoration - we handle it manually
    if ("scrollRestoration" in history) {
        history.scrollRestoration = "manual"
    }

    // Restore scroll position on popstate (back/forward navigation)
    window.addEventListener("popstate", (event) => {
        const state = event.state
        if (state?.scrollY !== undefined) {
            // Use requestAnimationFrame to ensure DOM has updated
            requestAnimationFrame(() => {
                window.scrollTo(0, state.scrollY)
            })
        }
    })
}

/**
 * Navigate to a new URL within the SPA.
 * Uses pushState by default (creates history entry).
 * Set replace: true to use replaceState (no history entry).
 * Automatically scrolls to top and saves scroll position for back navigation.
 *
 * @param url - The URL to navigate to (can be relative or absolute path)
 * @param options - Navigation options
 *
 * @example
 * // Navigate to /about (creates history entry)
 * spaNavigate('/about')
 *
 * @example
 * // Navigate with hash, replacing current entry
 * spaNavigate('/search#q=test', { replace: true })
 *
 * @example
 * // Navigate with state data
 * spaNavigate('/product/123', { state: { from: 'search' } })
 */
export const spaNavigate = (url: string, options: SpaNavigateOptions = {}): void => {
    if (typeof window === "undefined") {
        return // SSR guard
    }

    const { replace = false, state = null, noScroll = false } = options

    // Save current path to previousPath store before navigating
    previousPath.set(window.location.pathname)

    // Save current scroll position in current history entry before navigating
    const currentScrollY = window.scrollY
    const currentState = history.state || {}
    history.replaceState({ ...currentState, scrollY: currentScrollY }, "")

    // Merge user state with scroll position (for new page, start at top)
    const newState = { ...state, scrollY: 0 }

    // Normalize relative URLs to absolute paths
    let finalUrl = url
    if (
        !url.startsWith("/") &&
        !url.startsWith("http") &&
        !url.startsWith("mailto:") &&
        !url.startsWith("tel:") &&
        !url.startsWith("#")
    ) {
        finalUrl = "/" + url
    }

    if (replace) {
        window.history.replaceState(newState, "", finalUrl)
    } else {
        window.history.pushState(newState, "", finalUrl)
    }

    // Scroll to top for new navigation (unless noScroll is set)
    if (!noScroll) {
        window.scrollTo(0, 0)
    }
}

/**
 * Parse hash parameters from a URL hash string.
 * @param hash - The hash string (with or without leading #)
 * @returns URLSearchParams-like object for easy access
 */
export const parseHashParams = (hash: string): URLSearchParams => {
    const cleanHash = hash.startsWith("#") ? hash.slice(1) : hash
    return new URLSearchParams(cleanHash)
}

/**
 * Build a hash string from key-value pairs.
 * @param params - Object with parameter key-value pairs
 * @returns Hash string with leading #, or empty string if no params
 */
export const buildHashString = (params: Record<string, string | string[] | null | undefined>): string => {
    const parts: string[] = []

    for (const [key, value] of Object.entries(params)) {
        if (value === null || value === undefined || value === "") {
            continue
        }

        if (Array.isArray(value)) {
            if (value.length > 0) {
                parts.push(`${encodeURIComponent(key)}=${value.map(encodeURIComponent).join(",")}`)
            }
        } else {
            parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
        }
    }

    return parts.length > 0 ? `#${parts.join("&")}` : ""
}

/**
 * Svelte action for SPA link navigation.
 * Intercepts clicks on anchor elements and uses spaNavigate instead of full page reload.
 *
 * @param node - The anchor element to enhance
 * @param options - Navigation options passed to spaNavigate
 *
 * @example
 * <a href="/products" use:spaLink>Products</a>
 *
 * @example
 * // With options
 * <a href="/search" use:spaLink={{ replace: true }}>Search</a>
 */
export const spaLink = (node: HTMLAnchorElement, options: SpaNavigateOptions = {}) => {
    const handleClick = (event: MouseEvent) => {
        // Allow normal behavior for modifier keys (open in new tab, etc.)
        if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
            return
        }

        // Only handle left clicks
        if (event.button !== 0) {
            return
        }

        const href = node.getAttribute("href")

        // Skip external links or special protocols
        if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("tel:")) {
            return
        }

        // Handle anchor links: scroll to element instead of SPA navigation
        if (href.startsWith("#")) {
            event.preventDefault()
            const target = document.getElementById(href.slice(1))
            if (target) {
                target.scrollIntoView({ behavior: "smooth" })
            }
            return
        }

        // Skip if target is set (e.g., _blank)
        if (node.target && node.target !== "_self") {
            return
        }

        event.preventDefault()
        spaNavigate(href, options)
    }

    node.addEventListener("click", handleClick)

    return {
        update(newOptions: SpaNavigateOptions) {
            options = newOptions
        },
        destroy() {
            node.removeEventListener("click", handleClick)
        },
    }
}
