import { joinPath } from "./path";
import { PathWithParams } from "./PathWithParams";
const BASE_PATH = Symbol('BASE_PATH');
export { BASE_PATH };
export class LocationChangeEvent {
    name;
    type;
    location;
    state;
    _canceled = false;
    constructor(name, type, location, state) {
        this.name = name;
        this.type = type;
        this.location = location;
        this.state = state;
    }
    get isPageLoad() {
        return this.type === 'initial';
    }
    get isBackForward() {
        return this.type === 'popState';
    }
    get isLinkClick() {
        return this.type === 'linkClick';
    }
    get isNavigation() {
        return this.type === 'navigate';
    }
    get isCancelable() {
        return this.name === 'beforeChange';
    }
    cancel() {
        if (this.name === 'afterChange') {
            throw new Error('Cannot cancel afterChange event');
        }
        this._canceled = true;
    }
}
export class BeforeLocationChangeEvent extends LocationChangeEvent {
    constructor(type, location, state) {
        super('beforeChange', type, location, state);
    }
}
export class AfterLocationChangeEvent extends LocationChangeEvent {
    constructor(type, location, state) {
        super('afterChange', type, location, state);
    }
}
function getElementHrefAsUrl(elem) {
    if (elem && elem.tagName.toLowerCase() === 'a') {
        const href = elem?.href.trim();
        if (typeof href === 'string' && href.length > 0) {
            return new URL(href);
        }
    }
    return null;
}
export class HistoryNavigator {
    // params to preserve in the query string when navigating
    stickyParams;
    _popStateListener;
    _linkNavListener;
    _listeners = [];
    constructor() {
    }
    addListener(listener) {
        this._listeners.push(listener);
    }
    fireLocationChange(event) {
        for (const listener of this._listeners) {
            listener(event);
        }
    }
    /**
     * Should be called when the page is first loaded.
     * It will fire a location change event with type `initial` and the current window.location as the location
     */
    firePageLoad() {
        this.fireLocationChange(new AfterLocationChangeEvent('initial', new URL(window.location.href)));
    }
    addStickyParams(path) {
        if (this.stickyParams) {
            return new PathWithParams(path).add(this.stickyParams).toString();
        }
        return path;
    }
    navigate(to, options = {}) {
        if (options.basePath) {
            let basePath = options.basePath;
            if (!basePath.startsWith('/')) {
                basePath = '/' + basePath;
            }
            to = joinPath(basePath, to);
        }
        to = this.addStickyParams(to);
        this._navigate(new URL(to, window.location.href), 'navigate', options);
    }
    _navigate(to, type, options) {
        const beforeEvent = new BeforeLocationChangeEvent(type, to, options.state);
        this.fireLocationChange(beforeEvent);
        if (beforeEvent._canceled) {
            return;
        }
        // Build navigation chain by preserving previous history
        const currentState = window.history.state;
        const currentTitle = document.title;
        // Create new history chain entry
        const newChainEntry = {
            title: currentTitle,
            href: window.location.pathname + window.location.search + window.location.hash
        };
        // Build the history chain - clear if using replace
        let historyChain = [];
        if (!options.replace && currentState?.historyChain) {
            historyChain = [...currentState.historyChain];
        }
        // Only add to chain if not replacing
        if (!options.replace) {
            historyChain.push(newChainEntry);
            // Limit chain length to prevent memory issues (keep last 10 entries)
            if (historyChain.length > 10) {
                historyChain = historyChain.slice(-10);
            }
        }
        const stateToStore = {
            from: window.location.href,
            historyChain: historyChain,
            data: options.state || undefined
        };
        window.history[options.replace ? 'replaceState' : 'pushState'](stateToStore, '', to.href);
        this.fireLocationChange(new AfterLocationChangeEvent(type, to, options.state));
    }
    start() {
        if (typeof window === "undefined") {
            return;
        }
        const _popStateListener = (ev) => {
            let type = ev.state ? 'popState' : 'linkClick';
            const to = new URL(window.location.href);
            let state = undefined;
            if (ev.state) {
                type = 'popState';
                state = ev.state.data;
            }
            else {
                type = 'linkClick';
            }
            this.fireLocationChange(new AfterLocationChangeEvent(type, to, state));
        };
        const _linkNavListener = (ev) => {
            const url = getElementHrefAsUrl(ev.target);
            if (url && url.origin === window.location.origin) {
                ev.preventDefault();
                const to = new URL(this.addStickyParams(url.href));
                const basePath = ev[BASE_PATH] || ev.target[BASE_PATH];
                if (basePath) {
                    to.pathname = joinPath(basePath, to.pathname);
                }
                this._navigate(to, 'linkClick', {});
            }
        };
        this._popStateListener = _popStateListener;
        this._linkNavListener = _linkNavListener;
        window.addEventListener('popstate', _popStateListener);
        document.body.addEventListener('click', this._linkNavListener);
    }
    stop() {
        this._popStateListener && window.removeEventListener('popstate', this._popStateListener);
        this._linkNavListener && document.body.removeEventListener('click', this._linkNavListener);
    }
}
//# sourceMappingURL=HistoryNavigator.js.map