"use strict";

const router = require("../router.js");
const views = require("../util/views.js");

const holderTemplate = views.getTemplate("endless-pager");
const pageTemplate = views.getTemplate("endless-pager-page");

function isScrolledIntoView(element) {
    let top = 0;
    do {
        top += element.offsetTop || 0;
        element = element.offsetParent;
    } while (element);
    return top >= window.scrollY && top <= window.scrollY + window.innerHeight;
}

class EndlessPageView {
    constructor(ctx) {
        this._hostNode = document.getElementById("content-holder");
        views.replaceContent(this._hostNode, holderTemplate());
    }

    run(ctx) {
        this._destroy();

        this._active = true;
        this._runningRequests = 0;
        this._initialPageLoad = true;

        this.clearMessages();
        views.emptyContent(this._pagesHolderNode);

        this.minOffsetShown = null;
        this.maxOffsetShown = null;
        this.totalRecords = null;
        this.currentOffset = 0;
        this.defaultLimit = parseInt(ctx.parameters.limit || ctx.defaultLimit);

        const initialOffset = parseInt(ctx.parameters.offset || 0);
        if (this._isCacheValid(ctx)) {
            this._loadCachedPages(ctx);
        } else {
            this._clearCache(ctx);
            this._loadPage(ctx, initialOffset, this.defaultLimit, true).then(
                (pageNode) => {
                    if (initialOffset !== 0) {
                        pageNode.scrollIntoView();
                    }
                }
            );
        }

        this._timeout = window.setInterval(() => {
            window.requestAnimationFrame(() => {
                this._probePageLoad(ctx);
                this._syncUrl(ctx);
                if (this._shouldUseCache(ctx)) {
                    ctx.browserState.scrollY = window.scrollY;
                }
            });
        }, 250);

        views.monitorNodeRemoval(this._pagesHolderNode, () => this._destroy());
    }

    get pageHeaderHolderNode() {
        return this._hostNode.querySelector(".page-header-holder");
    }

    get topPageGuardNode() {
        return this._hostNode.querySelector(".page-guard.top");
    }

    get bottomPageGuardNode() {
        return this._hostNode.querySelector(".page-guard.bottom");
    }

    get _pagesHolderNode() {
        return this._hostNode.querySelector(".pages-holder");
    }

    get _topPageNode() {
        let topPageNode = null;
        let element = document.elementFromPoint(
            window.innerWidth / 2,
            window.innerHeight / 2
        );
        while (element.parentNode !== null) {
            if (element.classList.contains("page")) {
                topPageNode = element;
                break;
            }
            element = element.parentNode;
        }
        return topPageNode;
    }

    _destroy() {
        window.clearInterval(this._timeout);
        this._active = false;
    }

    _syncUrl(ctx) {
        const topPageNode = this._topPageNode;
        if (!topPageNode) {
            return;
        }
        let topOffset = parseInt(topPageNode.getAttribute("data-offset"));
        let topLimit = parseInt(topPageNode.getAttribute("data-limit"));
        if (topOffset !== this.currentOffset) {
            const path = ctx.getClientUrlForPage(
                topOffset,
                topLimit === ctx.defaultLimit ? null : topLimit
            );
            if (this._shouldUseCache(ctx)) {
                // We only scrolled, so we should continue using the same cache entry;
                // Update the cache path so it's not invalidated:
                ctx.browserState.pageCache.path = "/" + path;
            }
            router.replace(
                path,
                // ctx here is not "real" context, it's the object from _syncPageController()
                ctx.browserState,
                false
            );
            this.currentOffset = topOffset;
        }
    }

    _probePageLoad(ctx) {
        if (!this._active || this._runningRequests) {
            return;
        }

        if (this.totalRecords === null) {
            return;
        }
        const scrollThreshold = this._topPageNode.scrollHeight * 0.2;

        if (this.minOffsetShown > 0 && window.scrollY < scrollThreshold) {
            this._loadPage(
                ctx,
                this.minOffsetShown - this.defaultLimit,
                this.defaultLimit,
                false
            );
        }

        const pageBottom = this._pagesHolderNode.getBoundingClientRect().bottom;
        if (
            this.maxOffsetShown < this.totalRecords &&
            pageBottom < window.innerHeight + scrollThreshold
        ) {
            this._loadPage(ctx, this.maxOffsetShown, this.defaultLimit, true);
        }
    }

    _shouldUseCache(ctx) {
        return ctx.browserState !== undefined && ctx.browserState != null &&
            ctx.readPageFromCache !== undefined;
    }

    _isCacheValid(ctx) {
        if (!this._shouldUseCache(ctx)) return false;
        const cache = ctx.browserState.pageCache;
        return cache !== null && cache !== undefined &&
            cache.path == history.state.path &&
            cache.pages !== undefined && cache.pages !== null;
    }

    _clearCache(ctx) {
        if (!this._shouldUseCache(ctx)) return;
        ctx.browserState.pageCache = {path: history.state.path, pages: {}};
    }

    _loadCachedPages(ctx) {
        if (!this._shouldUseCache(ctx)) return;
        // k-v map of page offset to raw response
        const pages = ctx.browserState.pageCache.pages || {};
        window.requestAnimationFrame(() => {
            for (const [offset, data] of Object.entries(pages)) {
                const response = {
                    offset: data.offset,
                    limit: data.limit,
                    total: data.total,
                    results: ctx.readPageFromCache(data.raw_data)
                };
                this._renderPage(ctx, true, response);
            }
            window.scroll(0, ctx.browserState.scrollY || 0);
        });
    }

    _loadPage(ctx, offset, limit, append) {
        this._runningRequests++;
        return new Promise((resolve, reject) => {
            ctx.requestPage(offset, limit).then(
                (response) => {
                    if (!this._active) {
                        this._runningRequests--;
                        return Promise.reject();
                    }
                    if (this._shouldUseCache(ctx)) {
                        // Need to extract raw_data, otherwise it can't be stored in history
                        const pages = ctx.browserState.pageCache.pages || {};
                        pages[offset] = {
                            offset: response.offset,
                            limit: response.limit,
                            total: response.total,
                            raw_data: response.results.raw_data,
                        };
                    }
                    window.requestAnimationFrame(() => {
                        let pageNode = this._renderPage(ctx, append, response);
                        this._runningRequests--;
                        resolve(pageNode);
                    });
                },
                (error) => {
                    this.showError(error.message);
                    this._runningRequests--;
                    reject();
                }
            );
        });
    }

    _renderPage(ctx, append, response) {
        let pageNode = null;

        if (response.total) {
            pageNode = pageTemplate({
                totalPages: Math.ceil(response.total / response.limit),
                page: Math.ceil(
                    (response.offset + response.limit) / response.limit
                ),
            });
            pageNode.setAttribute("data-offset", response.offset);
            pageNode.setAttribute("data-limit", response.limit);

            ctx.pageRenderer({
                parameters: ctx.parameters,
                response: response,
                hostNode: pageNode.querySelector(".page-content-holder"),
            });

            this.totalRecords = response.total;

            if (
                response.offset < this.minOffsetShown ||
                this.minOffsetShown === null
            ) {
                this.minOffsetShown = response.offset;
            }
            if (
                response.offset + response.results.length >
                this.maxOffsetShown ||
                this.maxOffsetShown === null
            ) {
                this.maxOffsetShown =
                    response.offset + response.results.length;
            }
            response.results.addEventListener("remove", (e) => {
                this.maxOffsetShown--;
                this.totalRecords--;
            });

            if (append) {
                this._pagesHolderNode.appendChild(pageNode);
                if (this._initialPageLoad && response.offset > 0) {
                    window.scroll(0, pageNode.getBoundingClientRect().top);
                }
            } else {
                this._pagesHolderNode.prependChild(pageNode);

                window.scroll(
                    window.scrollX,
                    window.scrollY + pageNode.offsetHeight
                );
            }
        } else if (!response.results.length) {
            this.showInfo("No data to show");
        }

        this._initialPageLoad = false;
        return pageNode;
    }

    clearMessages() {
        views.clearMessages(this._hostNode);
    }

    showSuccess(message) {
        views.showSuccess(this._hostNode, message);
    }

    showError(message) {
        views.showError(this._hostNode, message);
    }

    showInfo(message) {
        views.showInfo(this._hostNode, message);
    }
}

module.exports = EndlessPageView;
