// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import { assert } from './assert.js';
function safeDecodeURIComponent(s) {
    try {
        return window.decodeURIComponent(s);
    }
    catch (_e) {
        // If the string can't be decoded, return it verbatim.
        return s;
    }
}
function getCurrentPathname() {
    return safeDecodeURIComponent(window.location.pathname);
}
function getCurrentHash() {
    return safeDecodeURIComponent(window.location.hash.slice(1));
}
let instance = null;
export class CrRouter extends EventTarget {
    path_ = getCurrentPathname();
    query_ = window.location.search.slice(1);
    hash_ = getCurrentHash();
    /**
     * If the user was on a URL for less than `dwellTime_` milliseconds, it
     * won't be added to the browser's history, but instead will be replaced
     * by the next entry.
     *
     * This is to prevent large numbers of entries from clogging up the user's
     * browser history. Disable by setting to a negative number.
     */
    dwellTime_ = 2000;
    lastChangedAt_;
    constructor() {
        super();
        this.lastChangedAt_ = window.performance.now() - (this.dwellTime_ - 200);
        window.addEventListener('hashchange', () => this.hashChanged_());
        window.addEventListener('popstate', () => this.urlChanged_());
    }
    setDwellTime(dwellTime) {
        this.dwellTime_ = dwellTime;
        this.lastChangedAt_ = window.performance.now() - this.dwellTime_;
    }
    getPath() {
        return this.path_;
    }
    getQueryParams() {
        return new URLSearchParams(this.query_);
    }
    getHash() {
        return this.hash_;
    }
    setHash(hash) {
        this.hash_ = safeDecodeURIComponent(hash);
        if (this.hash_ !== getCurrentHash()) {
            this.updateState_();
        }
    }
    setQueryParams(params) {
        this.query_ = params.toString();
        if (this.query_ !== window.location.search.substring(1)) {
            this.updateState_();
        }
    }
    setPath(path) {
        assert(path.startsWith('/'));
        this.path_ = safeDecodeURIComponent(path);
        if (this.path_ !== getCurrentPathname()) {
            this.updateState_();
        }
    }
    hashChanged_() {
        const oldHash = this.hash_;
        this.hash_ = getCurrentHash();
        if (this.hash_ !== oldHash) {
            this.dispatchEvent(new CustomEvent('cr-router-hash-changed', { bubbles: true, composed: true, detail: this.hash_ }));
        }
    }
    // Dispatches cr-router-*-changed events if portions of the URL change from
    // window events.
    urlChanged_() {
        this.hashChanged_();
        const oldPath = this.path_;
        this.path_ = getCurrentPathname();
        if (oldPath !== this.path_) {
            this.dispatchEvent(new CustomEvent('cr-router-path-changed', { bubbles: true, composed: true, detail: this.path_ }));
        }
        const oldQuery = this.query_;
        this.query_ = window.location.search.substring(1);
        if (oldQuery !== this.query_) {
            this.dispatchEvent(new CustomEvent('cr-router-query-params-changed', { bubbles: true, composed: true, detail: this.getQueryParams() }));
        }
    }
    // Updates the window history state if the URL is updated from setters.
    updateState_() {
        const url = new URL(window.location.origin);
        const pathPieces = this.path_.split('/');
        url.pathname =
            pathPieces.map(piece => window.encodeURIComponent(piece)).join('/');
        if (this.query_) {
            url.search = this.query_;
        }
        if (this.hash_) {
            url.hash = window.encodeURIComponent(this.hash_);
        }
        const now = window.performance.now();
        const shouldReplace = this.lastChangedAt_ + this.dwellTime_ > now;
        this.lastChangedAt_ = now;
        if (shouldReplace) {
            window.history.replaceState({}, '', url.href);
        }
        else {
            window.history.pushState({}, '', url.href);
        }
    }
    static getInstance() {
        return instance || (instance = new CrRouter());
    }
    static resetForTesting() {
        instance = null;
    }
}
