/**
 * Hash-based Client-Side Router
 * @module router
 */

/**
 * @typedef {Object} Route
 * @property {string} path - Current path
 * @property {Object<string, string>} params - Route and query params
 * @property {string} componentTag - Web component tag name
 */

/**
 * Hash-based router for SPA navigation
 * @class
 */
class Router {
    constructor() {
        /** @type {Map<string, string>} */
        this.routes = new Map()
        /** @type {Route|null} */
        this.currentRoute = null
        /** @type {HTMLElement|null} */
        this.outlet = null
        /** @type {Function|null} */
        this.beforeNavigate = null
        /** @type {Function|null} */
        this.afterNavigate = null
        
        window.addEventListener('hashchange', () => this.handleRouteChange())
    }

    /**
     * Set the outlet element where components will be rendered
     * @param {HTMLElement} element - Container element
     */
    setOutlet(element) {
        this.outlet = element
    }

    /**
     * Register a route
     * @param {string} path - Route path (e.g., '/listing/:id')
     * @param {string} componentTag - Web component tag name
     * @returns {Router} this for chaining
     */
    register(path, componentTag) {
        this.routes.set(path, componentTag)
        return this
    }

    /**
     * Parse current hash into path and query params
     * @private
     * @returns {{path: string, params: Object}}
     */
    parseHash() {
        const hash = window.location.hash.slice(1) || '/'
        const [path, queryString] = hash.split('?')
        const params = new URLSearchParams(queryString || '')
        
        return { path, params: Object.fromEntries(params) }
    }

    /**
     * Match a path to a registered route
     * @private
     * @param {string} path - Path to match
     * @returns {{componentTag: string, params: Object}|null}
     */
    matchRoute(path) {
        if (this.routes.has(path)) {
            return { componentTag: this.routes.get(path), params: {} }
        }
        
        for (const [routePath, componentTag] of this.routes) {
            const routeParts = routePath.split('/')
            const pathParts = path.split('/')
            
            if (routeParts.length !== pathParts.length) continue
            
            const params = {}
            let match = true
            
            for (let i = 0; i < routeParts.length; i++) {
                if (routeParts[i].startsWith(':')) {
                    params[routeParts[i].slice(1)] = pathParts[i]
                } else if (routeParts[i] !== pathParts[i]) {
                    match = false
                    break
                }
            }
            
            if (match) {
                return { componentTag, params }
            }
        }
        
        return null
    }

    /**
     * Handle hash change event
     * @private
     */
    async handleRouteChange() {
        const { path, params: queryParams } = this.parseHash()
        const match = this.matchRoute(path)
        
        if (this.beforeNavigate) {
            const shouldContinue = await this.beforeNavigate(path)
            if (!shouldContinue) return
        }
        
        if (!match) {
            this.renderNotFound()
            return
        }
        
        const { componentTag, params: routeParams } = match
        
        this.currentRoute = {
            path,
            params: { ...routeParams, ...queryParams },
            componentTag
        }
        
        this.render()
        
        if (this.afterNavigate) {
            this.afterNavigate(this.currentRoute)
        }
    }

    /**
     * Render current route's component into outlet
     * @private
     */
    render() {
        if (!this.outlet || !this.currentRoute) return
        
        const { componentTag, params } = this.currentRoute
        const oldComponent = this.outlet.firstElementChild
        const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
        
        const component = document.createElement(componentTag)
        
        Object.entries(params).forEach(([key, value]) => {
            component.setAttribute(`data-${key}`, value)
        })
        
        if (prefersReducedMotion || !oldComponent) {
            if (oldComponent) oldComponent.remove()
            this.outlet.appendChild(component)
            return
        }
        
        component.classList.add('animate__animated', 'animate__fadeIn', 'animate__faster')
        oldComponent.classList.add('animate__animated', 'animate__fadeOut', 'animate__faster')
        
        const fallbackTimer = setTimeout(() => {
            oldComponent.remove()
            this.outlet.appendChild(component)
        }, 300)
        
        oldComponent.addEventListener('animationend', () => {
            clearTimeout(fallbackTimer)
            oldComponent.remove()
            this.outlet.appendChild(component)
        }, { once: true })
    }

    /** @private */
    renderNotFound() {
        if (!this.outlet) return
        
        this.outlet.innerHTML = ''
        const notFound = document.createElement('page-not-found')
        this.outlet.appendChild(notFound)
    }

    /**
     * Navigate to a path
     * @param {string} path - Target path
     * @param {Object<string, string>} [params={}] - Query parameters
     */
    navigate(path, params = {}) {
        let url = `#${path}`
        
        if (Object.keys(params).length > 0) {
            const queryString = new URLSearchParams(params).toString()
            url += `?${queryString}`
        }
        
        window.location.hash = url.slice(1)
    }

    /** Navigate back in history */
    back() {
        window.history.back()
    }

    /** Navigate forward in history */
    forward() {
        window.history.forward()
    }

    /**
     * Get current route info
     * @returns {Route|null}
     */
    getCurrentRoute() {
        return this.currentRoute
    }
}

export const router = new Router()
