import {
    SLIDE_NUMBER_FORMAT_CURRENT,
    SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL,
} from "../utils/constants";

/**
 * Makes it possible to jump to a slide by entering its
 * slide number or id.
 */
export default class JumpToSlide {
    constructor(Reveal) {
        this.Reveal = Reveal;

        this.onInput = this.onInput.bind(this);
        this.onBlur = this.onBlur.bind(this);
        this.onKeyDown = this.onKeyDown.bind(this);
    }

    render() {
        this.element = document.createElement("div");
        this.element.className = "jump-to-slide";

        this.jumpInput = document.createElement("input");
        this.jumpInput.type = "text";
        this.jumpInput.className = "jump-to-slide-input";
        this.jumpInput.placeholder = "Jump to slide";
        this.jumpInput.addEventListener("input", this.onInput);
        this.jumpInput.addEventListener("keydown", this.onKeyDown);
        this.jumpInput.addEventListener("blur", this.onBlur);

        this.element.appendChild(this.jumpInput);
    }

    show() {
        this.indicesOnShow = this.Reveal.getIndices();

        this.Reveal.getRevealElement().appendChild(this.element);
        this.jumpInput.focus();
    }

    hide() {
        if (this.isVisible()) {
            this.element.remove();
            this.jumpInput.value = "";

            clearTimeout(this.jumpTimeout);
            delete this.jumpTimeout;
        }
    }

    isVisible() {
        return !!this.element.parentNode;
    }

    /**
     * Parses the current input and jumps to the given slide.
     */
    jump() {
        clearTimeout(this.jumpTimeout);
        delete this.jumpTimeout;

        let query = this.jumpInput.value.trim("");
        let indices;

        // When slide numbers are formatted to be a single linear mumber
        // (instead of showing a separate horizontal/vertical index) we
        // use the same format for slide jumps
        if (/^\d+$/.test(query)) {
            const slideNumberFormat = this.Reveal.getConfig().slideNumber;
            if (
                slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT ||
                slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL
            ) {
                const slide = this.Reveal.getSlides()[parseInt(query, 10) - 1];
                if (slide) {
                    indices = this.Reveal.getIndices(slide);
                }
            }
        }

        if (!indices) {
            // If the query uses "horizontal.vertical" format, convert to
            // "horizontal/vertical" so that our URL parser can understand
            if (/^\d+\.\d+$/.test(query)) {
                query = query.replace(".", "/");
            }

            indices = this.Reveal.location.getIndicesFromHash(query, {
                oneBasedIndex: true,
            });
        }

        // Still no valid index? Fall back on a text search
        if (!indices && /\S+/i.test(query) && query.length > 1) {
            indices = this.search(query);
        }

        if (indices && query !== "") {
            this.Reveal.slide(indices.h, indices.v, indices.f);
            return true;
        } else {
            this.Reveal.slide(
                this.indicesOnShow.h,
                this.indicesOnShow.v,
                this.indicesOnShow.f,
            );
            return false;
        }
    }

    jumpAfter(delay) {
        clearTimeout(this.jumpTimeout);
        this.jumpTimeout = setTimeout(() => this.jump(), delay);
    }

    /**
     * A lofi search that looks for the given query in all
     * of our slides and returns the first match.
     */
    search(query) {
        const regex = new RegExp("\\b" + query.trim() + "\\b", "i");

        const slide = this.Reveal.getSlides().find((slide) => {
            return regex.test(slide.innerText);
        });

        if (slide) {
            return this.Reveal.getIndices(slide);
        } else {
            return null;
        }
    }

    /**
     * Reverts back to the slide we were on when jump to slide was
     * invoked.
     */
    cancel() {
        this.Reveal.slide(
            this.indicesOnShow.h,
            this.indicesOnShow.v,
            this.indicesOnShow.f,
        );
        this.hide();
    }

    confirm() {
        this.jump();
        this.hide();
    }

    destroy() {
        this.jumpInput.removeEventListener("input", this.onInput);
        this.jumpInput.removeEventListener("keydown", this.onKeyDown);
        this.jumpInput.removeEventListener("blur", this.onBlur);

        this.element.remove();
    }

    onKeyDown(event) {
        if (event.keyCode === 13) {
            this.confirm();
        } else if (event.keyCode === 27) {
            this.cancel();

            event.stopImmediatePropagation();
        }
    }

    onInput(event) {
        this.jumpAfter(200);
    }

    onBlur() {
        setTimeout(() => this.hide(), 1);
    }
}
