import { IgniteElement } from "../ignite-html/ignite-element.js";
import { IgniteTemplate, slot, div, html } from "../ignite-html/ignite-template.js";

class RouterLink extends IgniteElement {
    constructor() {
        super();

        this.pushStateListener = () => this.update();
        this.popStateListener = () => this.update();

        window.addEventListener("popstate", this.popStateListener);
        window.addEventListener("pushstate", this.pushStateListener);
    }

    get properties() {
        return {
            active: false,
            routes: [],
            target: null
        };
    }

    get styles() {
        return /*css*/`
            router-link {
                display: flex;
                flex-direction: row;
                align-items: center;
                justify-content: center;
            }
        `;
    }

    render() {
        return this.template
            .onClick((event) => this.onClick(event))
            .class(this.active, value => value ? "active" : null)
            .child(new slot(this).class(this.active, value => value ? "active" : null));
    }

    ready() {
        this.update();
    }

    update() {
        var routeMatches = false;

        //Check the target first.
        routeMatches = Router.matches(this.target);

        //Check optional routes next.
        for (var i = 0; i < this.routes.length && !routeMatches; i++) {
            routeMatches = Router.matches(this.routes[i]);
        }

        if (routeMatches && !this.active) {
            this.active = true;
        } else if (!routeMatches && this.active) {
            this.active = false;
        }
    }

    onClick(event) {
        event.preventDefault();
        Router.navigate(this.target, false);
    }

    cleanup() {
        window.removeEventListener("popstate", this.popStateListener);
        window.removeEventListener("pushstate", this.pushStateListener);
    }
}

class RouterView extends IgniteElement {
    constructor() {
        super();

        this.pushStateListener = () => this.update();
        this.popStateListener = () => this.update();

        window.addEventListener("popstate", this.popStateListener);
        window.addEventListener("pushstate", this.pushStateListener);
    }

    get properties() {
        return {
            show: false,
            routes: [],
            strict: false
        };
    }

    render() {
        return this.template
            .child(new slot(this))
            .style("display", this.show, null, value => value ? null : "none");
    }

    ready() {
        this.update();
    }

    update() {
        var routeMatches = false;

        //Based on whether we are strict matching or not check if we have a match.
        if (!this.strict) {
            for (var i = 0; i < this.routes.length && !routeMatches; i++) {
                routeMatches = Router.matches(this.routes[i]);
            }
        } else {
            routeMatches = true;
            for (var i = 0; i < this.routes.length && routeMatches; i++) {
                routeMatches = Router.matches(this.routes[i]);
            }
        }

        //If we found a match show this router view if it's not already visible, otherwise hide it.
        if (routeMatches && !this.show) {
            this.show = true;
        } else if (!routeMatches && this.show) {
            this.show = false;
        }
    }

    cleanup() {
        window.removeEventListener("popstate", this.popStateListener);
        window.removeEventListener("pushstate", this.pushStateListener);
    }
}

class RouterLinkTemplate extends IgniteTemplate {
    /**
     * Initializes a new router link template.
     * @param {String} target The target route when the link is clicked.
     * @param {String|String[]} routes Optional routes that can be used to control the active state of the link.
     * @param  {...any} elements Elements to render within the link.
     */
    constructor(target, routes, ...elements) {
        super("router-link", elements);

        if (!routes) {
            routes = [];
        }

        if (!Array.isArray(routes)) {
            routes = [routes];
        }

        this.property("target", target);
        this.property("routes", routes);
    }
}

class RouterViewTemplate extends IgniteTemplate {
    /**
     * Initializes a new router view.
     * @param {String|String[]} routes Single or multiple routes to trigger this view to render. 
     * @param {any|any[]} elements Elements to render within the view.
     * @param {Boolean} strict If true all routes must match before this view becomes visible.
     */
    constructor(routes, elements, strict = false) {
        super("router-view", Array.isArray(elements) ? elements : [elements]);

        if (!Array.isArray(routes)) {
            routes = [routes];
        }

        this.property("routes", routes);
        this.property("strict", strict);
    }
}

class Router {
    static navigate(route, refresh = false) {
        if (refresh) {
            if (Router.hashMode) {
                window.location.hash = route;
                window.location.reload();
            } else {
                window.location.href = route;
            }
        } else {
            if (Router.hashMode) {
                window.location.hash = route;
            } else {
                window.history.pushState(route, route, route);
            }

            window.dispatchEvent(new Event("pushstate"));
        }
    }

    static matches(route) {
        //Get the path parts from the window
        var pathParts = [];

        //If hash mode is set and we have a hash location, get it and split it.
        if (Router.hashMode && window.location.hash && window.location.hash.length > 0) {
            pathParts = window.location.hash.substr(1).split("/");
            if (pathParts.length > 0 && pathParts[0].length == 0) {
                pathParts.splice(1);
            }
        }
        else if (!Router.hashMode) {
            pathParts = window.location.pathname.split("/").splice(1);
        }

        //Get the route parts
        var fromRoot = (route.trim().startsWith("/"));
        var routeParts = (fromRoot ? route.trim().split("/").splice(1) : route.trim().split("/"));

        //Check to see if we have a trailing route part, if so, remove it.
        if (pathParts.length > 0 && pathParts[pathParts.length - 1] == "") {
            pathParts.pop();
        }

        if (routeParts.length > 0 && routeParts[routeParts.length - 1] == "") {
            routeParts.pop();
        }

        //If path parts is 0 and route parts is 0 we have a match.
        if (pathParts.length == 0 && routeParts.length == 0) {
            return true;
        }
        //If path parts is 0, and the route part is ** then this is a match.
        else if (pathParts.length == 0 && routeParts.length == 1 && (routeParts[0] == "**" || routeParts[0] == "*")) {
            return true;
        }
        //If path parts is 0 and the route starts with ! and is a length of 1 then this is a match.
        else if (pathParts.length == 0 && routeParts.length == 1 && routeParts[0].startsWith("!")) {
            return true;
        }
        //If the path parts is 0 and the route is !*/** then this is a match
        else if (pathParts.length == 0 && routeParts.length == 2 && routeParts[0].startsWith("!") && routeParts[1] == "**") {
            return true;
        }

        //Check the route parts against the path parts.
        var max = Math.min(pathParts.length, routeParts.length);
        if (fromRoot) {
            for (var i = 0; i < max; i++) {
                if (routeParts[i].startsWith("!") && pathParts[i] == routeParts[i].substring(1)) {
                    return false;
                } else if (routeParts[i] == "**") {
                    return true;
                } else if (routeParts[i] != pathParts[i] && routeParts[i] != "*" && !routeParts[i].startsWith("!")) {
                    return false;
                } else if (i + 2 == routeParts.length && i + 1 == pathParts.length && routeParts[i + 1] == "**") {
                    return true;
                }
            }

            if (routeParts.length > pathParts.length) {
                return false;
            } else if (pathParts.length > routeParts.length && routeParts[routeParts.length - 1] != "**") {
                return false;
            } else {
                return true;
            }
        } else {
            for (var offset = 0; offset < pathParts.length; offset++) {
                for (var i = 0; i < max; i++) {
                    if (i + offset >= pathParts.length) {
                        return false;
                    }

                    if (routeParts[i].startsWith("!") && pathParts[i + offset] == routeParts[i].substring(1)) {
                        break;
                    } else if (routeParts[i] == "**") {
                        return true;
                    }
                    else if (routeParts[i] != pathParts[i + offset] && routeParts[i] != "*" && !routeParts[i].startsWith("!")) {
                        break;
                    } else if (i + 1 == routeParts.length && offset + routeParts.length == pathParts.length) {
                        return true;
                    } else if (i + 2 == routeParts.length && offset + i + 1 == pathParts.length && routeParts[i + 1] == "**") {
                        return true;
                    }
                }
            }

            return false;
        }
    }
}

Router.hashMode = false;

customElements.define("router-link", RouterLink);
customElements.define("router-view", RouterView);

window.Router = Router;

export {
    RouterLinkTemplate as RouterLink,
    RouterViewTemplate as RouterView,
    Router
}