import { Base } from "../base";
import { ICarousel, ICarouselProps } from "./types";
import { CarouselItem } from "./item";
import { HTML } from "./templates";

/**
 * Carousel
 * @param props - The carousel properties.
 */
class _Carousel extends Base<ICarouselProps> implements ICarousel {
    private _eventId = null;
    private _indicators: HTMLElement[] = null;
    private _pauseFlag = false;
    private _slides: CarouselItem[] = null;
    private _toggle: boolean = false;

    // Constructor
    constructor(props: ICarouselProps, template: string = HTML, slideTemplate?: string) {
        super(template, props);

        // Configure the carousel
        this.configure(slideTemplate);

        // Configure the events
        this.configureEvents();

        // Configure the parent
        this.configureParent();
    }

    // Configure the card group
    private configure(slideTemplate: string) {
        // Set the attributes
        this.el.id = this.props.id == null ? "carousel" : this.props.id;
        this.props.enableCrossfade ? this.el.classList.add("carousel-fade") : null;

        // Render the indicators
        this.renderIndicators();

        // Render the controls
        this.renderControls();

        // Render the slides
        this.renderSlides(slideTemplate);

        // Set the dark theme
        this.props.isDark ? this.setTheme(true) : null;

        // Get the options
        let options = this.props.options;
        if (options) {
            // See if the interval is set
            if (options.interval) {
                this.start(options.interval);
            }

            // See if the starting slide is set
            if (options.slide) {
                this.nextWhenVisible(options.slide);
            }
        }

        // Call the event if it exists
        this.props.onRendered ? this.props.onRendered(this.el, this.props) : null;
    }

    // Configures the events
    private configureEvents() {
        let el: HTMLElement = this.el;

        // Get the options
        let options = this.props.options;
        if (options) {
            // See if the keyboard option is set
            if (options.keyboard) {
                // Add a keydown event
                el.addEventListener("keydown", (ev) => {
                    // See if the left arrow was pressed
                    if (ev.key == "ArrowLeft" || ev.keyCode == 37) {
                        // Move to the previous slide
                        this.previous();
                    }
                    // Else, see if the right arrow was pressed
                    else if (ev.key == "ArrowRight" || ev.keyCode == 39) {
                        // Move tot he next slide
                        this.next();
                    }
                });
            }

            // See if the pause option is set
            if (options.pause) {
                // Set the mouse enter event
                el.addEventListener("mouseenter", () => {
                    // See if automation exists
                    if (this._eventId) {
                        // Pause the automation
                        this.pause();
                    }
                });

                // Set the mouse exit event
                el.addEventListener("mouseenter", () => {
                    // See if automation exists
                    if (this._eventId) {
                        // Unpause the automation
                        this.unpause();
                    }
                });
            }
        }
    }

    // Moves to the another slides
    private moveToSlide(current: CarouselItem, next: CarouselItem, slideRight: boolean = true) {
        // Do nothing if the toggle flag is set
        if (this._toggle) { return; }

        // Set the flag
        this._toggle = true;

        // Ensure the slides exist
        if (current && next) {
            // Animate the current slide out
            next.el.classList.add(slideRight ? "carousel-item-next" : "carousel-item-prev");
            current.el.classList.add(slideRight ? "carousel-item-start" : "carousel-item-end");

            // Wait for the animation to complete
            setTimeout(() => {
                // Animate the next slide in
                next.el.classList.add(slideRight ? "carousel-item-start" : "carousel-item-end");

                // Wait for the animation to complete
                setTimeout(() => {
                    // Update the classes
                    next.el.classList.add("active");
                    current.el.classList.remove("active", "carousel-item-start", "carousel-item-end");
                    next.el.classList.remove("carousel-item-next", "carousel-item-prev", "carousel-item-start", "carousel-item-end");

                    // Set the flag
                    this._toggle = false;
                }, 600);
            }, 10);
        }
    }

    // Renders the controls
    private renderControls() {
        // Get the controls
        let nextControl = this.el.querySelector(".carousel-control-next") as HTMLAnchorElement;
        let prevControl = this.el.querySelector(".carousel-control-prev") as HTMLAnchorElement;

        // See if we are rendering controls
        if (this.props.enableControls) {
            // Configure the controls
            nextControl ? nextControl.setAttribute("data-bs-target", "#" + this.el.id) : null;
            prevControl ? prevControl.setAttribute("data-bs-target", "#" + this.el.id) : null;

            // Set the click event
            nextControl.addEventListener("click", ev => { ev.preventDefault(); this.next(); })
            prevControl.addEventListener("click", ev => { ev.preventDefault(); this.previous(); })
        } else {
            // Remove the controls
            nextControl ? this.el.removeChild(nextControl) : null;
            prevControl ? this.el.removeChild(prevControl) : null;
        }
    }

    // Renders the indicators
    private renderIndicators() {
        // Clear the indicators
        this._indicators = [];

        // Get the indicators
        let indicators = this.el.querySelector(".carousel-indicators");
        if (indicators) {
            // See if we are enabling indicators
            if (this.props.enableIndicators) {
                // Parse the items
                let items = this.props.items || [];
                for (let i = 0; i < items.length; i++) {
                    let item = items[i];

                    // Create the item
                    let elItem = document.createElement("button");
                    elItem.setAttribute("data-bs-target", "#" + this.el.id);
                    elItem.setAttribute("aria-label", "Slide " + (i + 1));
                    elItem.setAttribute("data-bs-slide-to", i.toString());
                    item.isActive ? elItem.classList.add("active") : null;
                    elItem.addEventListener("click", ev => {
                        let elSlide = ev.currentTarget as HTMLElement;

                        // Prevent the page from moving to the top
                        ev.preventDefault();

                        // Go to the slide
                        this.nextWhenVisible(elSlide.getAttribute("data-bs-slide-to"));
                    });

                    // Add the item
                    indicators.appendChild(elItem);
                    this._indicators.push(elItem);
                }
            } else {
                // Remove the indicators
                this.el.removeChild(indicators);
            }
        }
    }

    // Renders the slides
    private renderSlides(slideTemplate: string) {
        // Clear the slides
        this._slides = [];

        // Get the indicators
        let slides = this.el.querySelector(".carousel-inner");
        if (slides) {
            let hasActiveItem = false;

            // Parse the items
            let items = this.props.items || [];
            for (let i = 0; i < items.length; i++) {
                let slide = new CarouselItem(items[i], slideTemplate);
                this._slides.push(slide);

                // See if this is active
                slide.isActive ? hasActiveItem = true : null;

                // Create the item element
                slides.appendChild(slide.el);

                // Call the event
                this.props.onSlideRendered ? this.props.onSlideRendered(slide.el, items[i]) : null;
            }

            // See if it doesn't have an active item
            if (!hasActiveItem) {
                // Set the first as active
                let firstSlide = this._slides[0];
                firstSlide ? firstSlide.el.classList.add("active") : null;
            }
        }
    }

    // Starts to move automatically
    private start(timeToWait = 5000) {
        // Do nothing if the event already exists
        if (this._eventId) { return; }

        // Validate the time
        if (timeToWait < 1000) { timeToWait = 1000; }

        // Start the event
        this._eventId = setInterval(() => {
            // Do nothing if we have paused it
            if (this._pauseFlag) { return; }

            // Move to the next slide
            this.next();
        }, timeToWait);
    }

    /**
     * Public Interface
     */


    // Cycle the carousel
    cycle() {
        // Start the event
        this.start(this.props.options && this.props.options.interval as any)
    }

    // Goes to the next slide
    next() {
        let currentSlide: CarouselItem = null;
        let nextSlide: CarouselItem = null;
        let options = this.props.options || {};

        // Ensure there are multiple slides
        if (this._slides.length < 2) { return; }

        // Parse the slides
        for (let i = 0; i < this._slides.length; i++) {
            let slide = this._slides[i];

            if (slide.isActive) {
                // See if we are at the end and wrapping is disabled
                if (i + 1 == this._slides.length && options.wrap == false) {
                    // Do nothing
                    return;
                }

                // Set the slides
                currentSlide = slide;
                nextSlide = this._slides[i + 1] || this._slides[0];

                // Update the indicators
                let indicator = this._indicators[i];
                indicator ? indicator.classList.remove("active") : null;
                let nextIndicator = this._indicators[i + 1] || this._indicators[0];
                nextIndicator ? nextIndicator.classList.add("active") : null;
                break;
            }
        }

        // Move to the next slide
        this.moveToSlide(currentSlide, nextSlide);
    }

    // Cycles the carousel to a particular frame
    nextWhenVisible(idx) {
        let currentSlide: CarouselItem = null;
        let nextSlide: CarouselItem = this._slides[idx];
        let slideRight = true;

        // Ensure there are multiple slides
        if (this._slides.length < 2) { return; }

        // Parse the slides
        for (let i = 0; i < this._slides.length; i++) {
            let slide = this._slides[i];

            // See if this slide is active
            if (slide.isActive) {
                // Do nothing if we selected the same slide
                if (idx == i) { return; }

                // Set the flag
                slideRight = idx > i;

                // Set the current slide
                currentSlide = slide;

                // Update the indicators
                let indicator = this._indicators[i];
                indicator ? indicator.classList.remove("active") : null;
                let nextIndicator = this._indicators[idx];
                nextIndicator ? nextIndicator.classList.add("active") : null;
                break;
            }
        }

        // Move to the next slide
        this.moveToSlide(currentSlide, nextSlide, slideRight);
    }

    // Pauses the slide
    pause() {
        // Set the flag
        this._pauseFlag = true;
    }

    // Goes to the previous slide
    previous() {
        let currentSlide: CarouselItem = null;
        let options = this.props.options || {};
        let prevSlide: CarouselItem = null;

        // Ensure there are multiple slides
        if (this._slides.length < 2) { return; }

        // Parse the slides
        for (let i = 0; i < this._slides.length; i++) {
            let slide = this._slides[i];

            if (slide.isActive) {
                // See if we are at the end and wrapping is disabled
                if (i - 1 < 0 && options.wrap == false) {
                    // Do nothing
                    return;
                }

                // Set the slides
                currentSlide = slide;
                prevSlide = this._slides[i - 1] || this._slides[this._slides.length - 1];

                // Update the indicators
                this._indicators[i].classList.remove("active");
                (this._indicators[i - 1] || this._indicators[this._indicators.length - 1]).classList.add("active");
                break;
            }
        }

        // Move to the next slide
        this.moveToSlide(currentSlide, prevSlide, false);
    }

    // Enables/Disables the dark theme
    setTheme(isDark: boolean) {
        // See if we are setting the dark theme
        if (isDark) {
            // Set the theme
            this.el.classList.add("carousel-dark");
        } else {
            // Set the theme
            this.el.classList.remove("carousel-dark");
        }
    }

    // Unpauses the carousel
    unpause() {
        // Set the flag
        this._pauseFlag = false;
    }
}
export const Carousel = (props: ICarouselProps, template?: string, slideTemplate?: string): ICarousel => { return new _Carousel(props, template, slideTemplate); }