import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";
import IDUtil from "../../../util/IDUtil";
import RegexUtil from "../../../util/RegexUtil";

import MediaEvents from "../_MediaEvents";

export default class TimedList extends React.PureComponent {
    constructor(props) {
        super(props);

        this.state = {
            items: props.items,
            currentId: "",
        };

        this.userHasScrolled = false;
        this.scrollTimerId = null;

        // default scroll offset
        // corrects for filter results line
        // and keeps previous lines in view
        this.scrollOffset = 0;

        this.scrollContainer = React.createRef();
    }

    componentDidMount() {
        // bind to PLAYER_POS to update the position
        this.props.mediaEvents.bind(
            MediaEvents.PLAYER_POS,
            this.updatePosition
        );

        // load with last data
        const data = this.props.mediaEvents.getData(MediaEvents.PLAYER_POS);
        if (data) {
            this.updatePosition(data);
        }
    }

    componentDidUpdate() {
        this.scrollOffset =
            this.props.searchTerm && this.props.searchTerm.length > 2 ? -29 : 0;
    }

    componentWillUnmount() {
        // unbind to PLAYER_POS
        this.props.mediaEvents.unbind(
            MediaEvents.PLAYER_POS,
            this.updatePosition
        );
    }

    onSeek = (pos) => {
        // Update player by triggering SET_PLAYER_PS event
        this.props.mediaEvents.trigger(MediaEvents.SET_PLAYER_POS, pos);
    };

    // called via external ref
    updatePosition = (pos) => {
        const item = this.findClosestItem(Math.trunc(pos * 1000));

        if (!item) {
            this.setState({ currentId: "" });
            return;
        }

        // set current
        this.setState({ currentId: item.id });

        // Get elem by position
        const elem = document.getElementById(item.id);
        // Scroll to elem
        if (elem && !this.userHasScrolled) {
            this.scrollContainer.current.scrollTop =
                elem.offsetTop + this.scrollOffset;
        }
    };

    // makes sure the user can still scroll through the list
    onScrollInList = () => {
        this.userHasScrolled = true;
        clearTimeout(this.scrollTimerId);
        this.scrollTimerId = setTimeout(() => {
            this.userHasScrolled = false;
        }, 2400);
    };

    // make item with given id current
    setCurrentId(id) {
        this.setState({ currentId: id }, () => {
            // scroll to current
            this.userHasScrolled = false;
            this.props.items.some((element, i) => {
                if (element.id === id) {
                    this.onSeek(element.start / 1000);
                    return true;
                }
                return false;
            });
        });
    }

    // FIXME make this one faster
    findClosestItem = (currentTime) => {
        let index = this.props.items.findIndex((a) => a.start >= currentTime);

        // if no item was found it's either the very last item (if player time is bigger than 0) or the first item
        if (index === -1 && this.props.items.length > 0) {
            index = currentTime > 0 ? this.props.items.length - 1 : 0;
        }

        // adjust the index to the previous item when the start time is larger than the current time
        if (
            this.props.items[index] &&
            this.props.items[index].start > currentTime
        ) {
            index = index <= 0 ? 0 : index - 1;
        }

        // correct for end time: if time is after end time; skip
        if (
            this.props.items[index] &&
            this.props.items[index].end &&
            currentTime > this.props.items[index].end
        ) {
            return null;
        }

        return this.props.items[index];
    };

    filterList = (items, searchTerm) => {
        if (searchTerm.length <= 2) {
            return items;
        }

        searchTerm = searchTerm.toLowerCase();

        let regex = null;
        try {
            // add "*" around the searchterm to have more/better matches
            regex = RegexUtil.generateRegexForSearchTerm(
                "*" + searchTerm + "*"
            );
        } catch (err) {
            regex = null;
        }

        if (!regex) {
            return items;
        }

        // filter the list using the regex
        const filteredList = items
            .filter((item) => {
                return item.data.toLowerCase().search(regex) !== -1;
            })
            .map((item) => {
                // return item with highlighted text
                return Object.assign({}, item, {
                    data: item.data.replace(
                        regex,
                        (term) =>
                            "<span class='highLight-text'>" + term + "</span>"
                    ),
                });
            });

        return filteredList;
    };

    /* ---------------------------- RENDERING FUNCTIONS ---------------------------------- */

    renderList = (list, currentId, setCurrentId, onScrollInList) => {
        const items = list.map((item) => {
            return (
                <li
                    key={item.id}
                    id={item.id}
                    className={classNames("item", {
                        current: item.id === currentId,
                    })}
                    onClick={setCurrentId.bind(this, item.id)}
                >
                    <span className="start-time">
                        {item.startLabel}
                        {item.endLabel ? " - " + item.endLabel : null}
                    </span>
                    <span
                        dangerouslySetInnerHTML={{ __html: item.data }}
                    ></span>
                </li>
            );
        });

        return (
            <ul className="list-container" onScroll={onScrollInList}>
                {items}
            </ul>
        );
    };

    /* ----------------- Rendering --------------------- */
    render = () => {
        const items = this.filterList(this.props.items, this.props.searchTerm);
        const list = this.renderList(
            items,
            this.state.currentId,
            this.setCurrentId,
            this.onScrollInList
        );

        const filterResults =
            items.length != this.props.items.length ? (
                <div className="filter-results">
                    Showing {items.length} of {this.props.items.length} lines
                </div>
            ) : null;

        return (
            <div
                className={IDUtil.cssClassName("timed-list")}
                ref={this.scrollContainer}
            >
                <div className="timed-list-items">
                    {filterResults}
                    {list}
                </div>
            </div>
        );
    };
}

TimedList.propTypes = {
    items: PropTypes.arrayOf(
        PropTypes.shape({
            id: PropTypes.string.isRequired,
            start: PropTypes.number.isRequired,
            end: PropTypes.number,
            startLabel: PropTypes.string.isRequired,
            endLabel: PropTypes.string,
            data: PropTypes.string,
        }).isRequired
    ).isRequired,
    searchTerm: PropTypes.string.isRequired,
    mediaEvents: PropTypes.object,
};
