/* eslint-disable react/no-array-index-key */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable default-param-last */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { DivHeight, DivWidth } from '../../ClientUtils';
import { SnappingManager } from '../util/SnappingManager';
import './ContextMenu.scss';
import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
import { ObservableReactComponent } from './ObservableReactComponent';

@observer
export class ContextMenu extends ObservableReactComponent<{}> {
    // eslint-disable-next-line no-use-before-define
    static Instance: ContextMenu;

    private _ignoreUp = false;
    private _reactionDisposer?: IReactionDisposer;
    private _defaultPrefix: string = '';
    private _defaultItem: ((name: string) => void) | undefined;
    private _onDisplay?: () => void = undefined;

    @observable.shallow _items: ContextMenuProps[] = [];
    @observable _pageX: number = 0;
    @observable _pageY: number = 0;
    @observable _display: boolean = false;
    @observable _searchString: string = '';
    @observable _showSearch: boolean = false;
    // afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
    @observable _yRelativeToTop: boolean = true;
    @observable _selectedIndex = -1;

    @observable _width: number = 0;
    @observable _height: number = 0;

    @observable _mouseX: number = -1;
    @observable _mouseY: number = -1;
    @observable _shouldDisplay: boolean = false;

    constructor(props: any) {
        super(props);
        makeObservable(this);
        ContextMenu.Instance = this;
    }

    public setIgnoreEvents(ignore: boolean) {
        this._ignoreUp = ignore;
    }

    @action
    onPointerDown = (e: PointerEvent) => {
        this._mouseX = e.clientX;
        this._mouseY = e.clientY;
    };
    @action
    onPointerUp = (e: PointerEvent) => {
        if (e.button !== 2 && !e.ctrlKey) return;
        const curX = e.clientX;
        const curY = e.clientY;
        if (this._ignoreUp) {
            this._ignoreUp = false;
            return;
        }
        if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) {
            this._shouldDisplay = false;
        }

        if (this._shouldDisplay) {
            if (this._onDisplay) {
                this._onDisplay();
            } else {
                this._display = true;
            }
        }
    };
    componentWillUnmount() {
        document.removeEventListener('pointerdown', this.onPointerDown, true);
        document.removeEventListener('pointerup', this.onPointerUp);
        this._reactionDisposer?.();
    }

    componentDidMount() {
        document.addEventListener('pointerdown', this.onPointerDown, true);
        document.addEventListener('pointerup', this.onPointerUp);
    }

    @action
    clearItems() {
        this._items.length = 0;
        this._defaultPrefix = '';
        this._defaultItem = undefined;
    }

    findByDescription = (target: string, toLowerCase = false) =>
        this._items.find(menuItem => 
            (toLowerCase ?  menuItem.description.toLowerCase() : menuItem.description) === target); // prettier-ignore

    @action
    addItem(item: ContextMenuProps) {
        !this._items.includes(item) && this._items.push(item);
    }

    @action
    moveAfter(item: ContextMenuProps, after?: ContextMenuProps) {
        const curInd = this._items.findIndex(i => i.description === item.description);
        this._items.splice(curInd, 1);
        const afterInd = after && this.findByDescription(after.description) ? this._items.findIndex(i => i.description === after.description) : this._items.length;
        this._items.splice(afterInd, 0, item);
    }

    @action
    setDefaultItem(prefix: string, item: (name: string) => void) {
        this._defaultPrefix = prefix;
        this._defaultItem = item;
    }

    static readonly buffer = 20;
    get pageX() {
        return this._pageX + this._width > window.innerWidth - ContextMenu.buffer ? window.innerWidth - ContextMenu.buffer - this._width : Math.max(0, this._pageX);
    }

    get pageY() {
        return this._pageY + this._height > window.innerHeight - ContextMenu.buffer ? window.innerHeight - ContextMenu.buffer - this._height : Math.max(0, this._pageY);
    }

    @action
    displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
        // maxX and maxY will change if the UI/font size changes, but will work for any amount
        // of items added to the menu

        this._showSearch = showSearch;
        this._pageX = x;
        this._pageY = y;
        this._searchString = initSearch;
        this._shouldDisplay = true;
        this._onDisplay = onDisplay;
        this._display = !onDisplay;
    };

    @action
    closeMenu = () => {
        const wasOpen = this._display;
        this.clearItems();
        this._display = false;
        this._shouldDisplay = false;
        return wasOpen;
    };

    @computed get filteredItems(): (OriginalMenuProps | string[])[] {
        const searchString = this._searchString.toLowerCase().split(' ');
        const matches = (descriptions: string[]) => searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
        const flattenItems = (items: ContextMenuProps[], groupFunc: (groupName: any) => string[]) => {
            let eles: (OriginalMenuProps | string[])[] = [];

            const leaves: OriginalMenuProps[] = [];
            items.forEach(item => {
                const { description } = item;
                const path = groupFunc(description);
                if ('subitems' in item) {
                    const children = flattenItems(item.subitems, name => [...groupFunc(description), name]);
                    if (children.length || matches(path)) {
                        eles.push(path);
                        eles = eles.concat(children);
                    }
                } else if (matches(path)) {
                    leaves.push(item);
                }
            });

            eles = [...leaves, ...eles];

            return eles;
        };
        return flattenItems(this._items.slice(), name => [name]);
    }

    @computed get flatItems(): OriginalMenuProps[] {
        return this.filteredItems.filter(item => !Array.isArray(item)) as OriginalMenuProps[];
    }

    @computed get menuItems() {
        if (!this._searchString) {
            console.log('HERESDF');
            return this._items.map((item, ind) => <ContextMenuItem key={item.description + ind} {...item} selected={ind === this._selectedIndex} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} closeMenu={this.closeMenu} />);
        }
        return this.filteredItems.map((value, index) =>
            Array.isArray(value) ? (
                <div
                    key={index + value.join(' -> ')}
                    className="contextMenu-group"
                    style={{
                        background: SnappingManager.userVariantColor,
                    }}>
                    <div className="contextMenu-description">{value.join(' -> ')}</div>
                </div>
            ) : (
                <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this._selectedIndex} />
            )
        );
    }

    @computed get itemsNeedSearch() {
        return this._showSearch ? 1 : this._items.reduce((p, mi) => p + ((mi as any).noexpand ? 1 : (mi as any).subitems?.length || 1), 0) > 15;
    }

    _searchRef = React.createRef<HTMLInputElement>(); // bcz: we shouldn't need this, since we set autoFocus on the <input> tag, but for some reason we do...

    render() {
        this.itemsNeedSearch && setTimeout(() => this._searchRef.current?.focus());
        return (
            <div
                className="contextMenu-cont"
                ref={action((r: any) => {
                    if (r) {
                        this._width = DivWidth(r);
                        this._height = DivHeight(r);
                    }
                    this._searchRef.current?.focus();
                })}
                style={{
                    display: this._display ? '' : 'none',
                    left: this.pageX,
                    ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
                    background: SnappingManager.userBackgroundColor,
                    color: SnappingManager.userColor,
                }}>
                {!this.itemsNeedSearch ? null : (
                    <span className="search-icon">
                        <span className="icon-background">
                            <FontAwesomeIcon icon="search" size="lg" />
                        </span>
                        <input
                            ref={this._searchRef}
                            style={{ color: 'black' }}
                            className="contextMenu-item contextMenu-description search"
                            type="text"
                            placeholder="Filter Menu..."
                            value={this._searchString}
                            onKeyDown={this.onKeyDown}
                            onChange={this.onChange}
                            // eslint-disable-next-line jsx-a11y/no-autofocus
                            autoFocus
                        />
                    </span>
                )}
                {this.menuItems}
            </div>
        );
    }

    @action
    setLangIndex = (ind: number) => {
        this._selectedIndex = ind;
    };

    @action
    onKeyDown = (e: React.KeyboardEvent) => {
        if (e.key === 'ArrowDown') {
            if (this._selectedIndex < this.flatItems.length - 1) {
                this._selectedIndex++;
            }
            e.preventDefault();
        } else if (e.key === 'ArrowUp') {
            if (this._selectedIndex > 0) {
                this._selectedIndex--;
            }
            e.preventDefault();
        } else if (e.key === 'Enter' || e.key === 'Tab') {
            const item = this.flatItems[this._selectedIndex];
            if (item) {
                item.event({ x: this.pageX, y: this.pageY });
            } else {
                // if (this._searchString.startsWith(this._defaultPrefix)) {
                this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
            }
            this.closeMenu();
            e.preventDefault();
            e.stopPropagation();
        }
    };

    @action
    onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        this._searchString = e.target.value;
        if (!this._searchString) {
            this._selectedIndex = -1;
        } else if (this._selectedIndex === -1) {
            this._selectedIndex = 0;
        } else {
            this._selectedIndex = Math.min(this.flatItems.length - 1, this._selectedIndex);
        }
    };
}
