export class ScrollSpy {
    constructor(selector, options = {}) {
        this.current = null;
        this.nodes = [];
        this.offset = 0;
        this.timeout = 0;
        this.handleScroll = () => {
            if (this.timeout)
                window.cancelAnimationFrame(this.timeout);
            this.timeout = window.requestAnimationFrame(() => this.detect());
        };
        this.selector = selector;
        this.options = Object.assign({ class: 'active', scroll: window }, options);
        this.reload();
        window.addEventListener('resize', this.handleScroll);
        this.options.scroll.addEventListener('scroll', this.handleScroll);
    }
    get active() {
        const last = this.nodes[this.nodes.length - 1];
        if (last && this.bottom && this.isVisible(last.section))
            return last;
        for (let i = this.nodes.length - 1; i >= 0; i--)
            if (this.isVisible(this.nodes[i].section))
                return this.nodes[i];
        return null;
    }
    get bottom() {
        const { root: r } = this;
        return r.scrollHeight - window.innerHeight <= r.scrollTop;
    }
    get root() {
        return (document.scrollingElement || document.documentElement);
    }
    destroy() {
        if (this.current)
            this.deactivate(this.current);
        window.removeEventListener('resize', this.handleScroll);
        this.options.scroll.removeEventListener('scroll', this.handleScroll);
    }
    detect() {
        const { active } = this;
        const current = this.current;
        if (current) {
            if (current === active)
                return;
            else
                this.deactivate(current);
        }
        if (active)
            this.activate(active);
        this.current = active;
    }
    reload() {
        const { root: r } = this;
        const elements = r.querySelectorAll(this.selector);
        this.nodes = Array.from(elements).reduce((acc, el) => {
            const hash = ('hash' in el && el.hash) || '';
            const id = decodeURIComponent(hash.slice(1));
            const section = document.getElementById(id);
            return section ? [...acc, { nav: el, section }] : acc;
        }, []);
        this.offset = Math.min(...this.nodes.map(node => node.section.offsetTop));
        this.detect();
    }
    activate(node) {
        node.nav.classList.add(this.options.class);
    }
    deactivate(node) {
        node.nav.classList.remove(this.options.class);
    }
    isVisible(el, bottom) {
        const rect = el.getBoundingClientRect();
        return bottom ? rect.bottom < window.innerHeight : rect.top <= this.offset;
    }
}
