import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, makeObservable, observable, ObservableMap } from 'mobx';
import { observer, useLocalObservable } from 'mobx-react';
import * as React from 'react';
import { useEffect, useRef } from 'react';
import { Handles, Rail, Slider, Ticks, Tracks } from 'react-compound-slider';
import { AiOutlineMinusSquare, AiOutlinePlusSquare } from 'react-icons/ai';
import { CiCircleRemove } from 'react-icons/ci';
import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { StrCast } from '../../fields/Types';
import { SearchUtil } from '../util/SearchUtil';
import { SnappingManager } from '../util/SnappingManager';
import { undoable } from '../util/UndoManager';
import { FieldsDropdown } from './FieldsDropdown';
import './FilterPanel.scss';
import { DocumentView } from './nodes/DocumentView';
import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components';
import { ObservableReactComponent } from './ObservableReactComponent';

interface HotKeyButtonProps {
    hotKey: Doc;
    selected?: Doc;
}

/**
 * Renders the buttons that correspond to each icon tag in the properties view. Allows users to change the icon,
 * title, and delete.
 */
const HotKeyIconButton: React.FC<HotKeyButtonProps> = observer(({ hotKey /*, selected */ }) => {
    const state = useLocalObservable(() => ({
        isActive: false,
        isEditing: false,
        myHotKey: hotKey,

        toggleActive() { this.isActive = !this.isActive; },
        deactivate()   { this.isActive = false; },
        startEditing() { this.isEditing = true; },
        stopEditing()  { this.isEditing = false; },
        setHotKey(newHotKey: string) { this.myHotKey.title = newHotKey; },
    })); // prettier-ignore

    const panelRef = useRef<HTMLDivElement>(null);
    const inputRef = useRef<HTMLInputElement>(null);

    const handleClick = () => state.toggleActive();

    /**
     * Updates the list of hotkeys based on the users input. replaces the old title with the new one and then assigns this new
     * hotkey with the current icon
     */
    const updateFromInput = undoable(() => {
        hotKey.title = StrCast(state.myHotKey.title);
        hotKey.toolTip = `Click to toggle the ${StrCast(hotKey.title)}'s group's visibility`;
    }, '');

    /**
     * Deselects if the user clicks outside the button
     * @param event
     */
    const handleClickOutside = (event: MouseEvent) => {
        if (panelRef.current && !panelRef.current.contains(event.target as Node)) {
            state.deactivate();
            if (state.isEditing) {
                state.stopEditing();

                updateFromInput();
            }
        }
    };

    useEffect(() => {
        document.addEventListener('mousedown', handleClickOutside);
        return () => document.removeEventListener('mousedown', handleClickOutside);
    }, []);

    const iconOpts = ['star', 'heart', 'bolt', 'satellite', 'palette', 'robot', 'lightbulb', 'highlighter', 'book', 'chalkboard'] as IconProp[];

    /**
     * Panel of icons the user can choose from to represent their tag
     */
    const iconPanel = iconOpts.map(icon => (
        <button
            key={icon.toString()}
            onClick={undoable(e => {
                e.stopPropagation;
                hotKey.$icon = icon.toString();
            }, '')}
            className="icon-panel-button">
            <FontAwesomeIcon icon={icon} color={SnappingManager.userColor} />
        </button>
    ));

    /**
     * Actually renders the buttons
     */

    return (
        <div
            className="filterHotKey-button"
            onClick={e => {
                e.stopPropagation();
                state.startEditing();
                setTimeout(() => inputRef.current?.focus(), 0);
            }}>
            <div className={`hotKey-icon-button ${state.isActive ? 'active' : ''}`} ref={panelRef}>
                <Tooltip title={<div className="dash-tooltip">Click to customize this hotkey&apos;s icon</div>}>
                    <button
                        type="button"
                        className="hotKey-icon"
                        onClick={(e: React.MouseEvent) => {
                            e.stopPropagation();
                            handleClick();
                        }}>
                        <FontAwesomeIcon icon={hotKey.icon as IconProp} size="2xl" color={SnappingManager.userColor} />
                    </button>
                </Tooltip>
                {state.isActive && <div className="icon-panel">{iconPanel}</div>}
            </div>
            {state.isEditing ? (
                <input
                    ref={inputRef}
                    type="text"
                    value={StrCast(state.myHotKey.title).toUpperCase()}
                    onChange={e => state.setHotKey(e.target.value)}
                    onBlur={() => {
                        state.stopEditing();
                        updateFromInput();
                    }}
                    onKeyDown={e => {
                        if (e.key === 'Enter') {
                            state.stopEditing();
                            updateFromInput();
                        }
                    }}
                    className="hotkey-title-input"
                />
            ) : (
                <p className="hotkey-title">{StrCast(hotKey.title).toUpperCase()}</p>
            )}
            <button
                className="hotKey-close"
                onClick={(e: React.MouseEvent) => {
                    e.stopPropagation();
                    Doc.RemFromFilterHotKeys(hotKey);
                }}>
                <FontAwesomeIcon icon={'x' as IconProp} color={SnappingManager.userColor} />
            </button>
        </div>
    );
});

interface filterProps {
    Document: Doc;
    addHotKey: (hotKey: string) => void;
}

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

    constructor(props: filterProps) {
        super(props);
        makeObservable(this);
        FilterPanel.Instance = this;
    }

    @observable _selectedFacetHeaders = new Set<string>();
    /**
     * @returns the relevant doc according to the value of FilterBox._filterScope i.e. either the Current Dashboard or the Current Collection
     */
    get Document() {
        return this._props.Document;
    }
    @computed get targetDocChildKey() {
        const targetView = DocumentView.getFirstDocumentView(this.Document);
        return targetView?.ComponentView?.annotationKey || (targetView?.ComponentView?.fieldKey ?? 'data');
    }
    @computed get targetDocChildren() {
        return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_sidebar'])];
    }

    @computed get rangeFilters() {
        return StrListCast(this.Document?._childFiltersByRanges).filter((filter, i) => !(i % 3));
    }

    /**
     * activeFilters( ) -- all filters that currently have a filter set on them in this document (ranges, and others)
     * ["#tags::bob::check", "tags::joe::check", "width", "height"]
     */
    @computed get activeFilters() {
        return StrListCast(this.Document?._childFilters).concat(this.rangeFilters);
    }

    @computed get mapActiveFiltersToFacets() {
        const filters = new Map<string, string>();
        // this.targetDoc.docFilters
        this.activeFilters.map(filter => filters.set(filter.split(Doc.FilterSep)[1], filter.split(Doc.FilterSep)[0]));
        return filters;
    }

    //
    // activeFacetHeaders() - just the facet names, not the rest of the filter
    //
    // this wants to return all the filter facets that have an existing filter set on them in order to show them in the rendered panel
    // this set may overlap the selectedFilters
    // if the components reloads, these will still exist and be shown
    //
    // ["#tags", "width", "height"]
    //
    @computed get activeFacetHeaders() {
        const activeHeaders = [] as string[];
        this.activeFilters.map(filter => activeHeaders.push(filter.split(Doc.FilterSep)[0]));

        return activeHeaders;
    }

    static gatherFieldValues(childDocs: Doc[], facetKey: string, childFilters: string[]) {
        const valueSet = new Set<string>(childFilters.map(filter => filter.split(Doc.FilterSep)[1]));
        let rtFields = 0;
        let subDocs = childDocs;
        const gatheredDocs = [] as Doc[];
        if (subDocs.length > 0) {
            let newarray: Doc[] = [];
            while (subDocs.length > 0) {
                newarray = [];
                // eslint-disable-next-line no-loop-func
                subDocs.forEach(t => {
                    gatheredDocs.push(t);
                    const facetVal = t[facetKey];
                    if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++;
                    facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType));
                    (facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal));
                    const fieldKey = Doc.LayoutFieldKey(t);
                    const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView');
                    DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc));
                    annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
                });
                subDocs = newarray.filter(d => !gatheredDocs.includes(d));
            }
        }
        //     }
        // });

        return { strings: Array.from(valueSet.keys()), rtFields };
    }

    public removeFilter = (filterName: string) => {
        Doc.setDocFilter(this.Document, filterName, undefined, 'remove');
        Doc.setDocRangeFilter(this.Document, filterName, undefined);
    };

    // @observable _chosenFacets = new ObservableMap<string, 'text' | 'checkbox' | 'slider' | 'range'>();
    @observable _chosenFacetsCollapse = new ObservableMap<string, boolean>();
    @observable _collapseReturnKeys = [] as string[];

    // this computed function gets the active filters and maps them to their headers
    //
    // activeRenderedFacetInfos()
    //    returns renderInfo for all user selected filters and for all existing filters set on the document
    // Map("tags" => {"checkbox"},
    //     "width" => {"range", domain:[1978,1992]})
    //
    @computed get activeRenderedFacetInfos() {
        return new Set(
            Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => {
                const facetValues = facetHeader.startsWith('#') ? { strings: [] } : FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.Document.childFilters));

                let nonNumbers = 0;
                let minVal = Number.MAX_VALUE;
                let maxVal = -Number.MAX_VALUE;
                facetValues.strings.forEach(val => {
                    const num = val ? Number(val) : Number.NaN;
                    if (isNaN(num)) {
                        val && nonNumbers++;
                    } else {
                        minVal = Math.min(num, minVal);
                        maxVal = Math.max(num, maxVal);
                    }
                });

                if (facetHeader === 'text') {
                    return { facetHeader, renderType: 'text' };
                }
                if (facetHeader.startsWith('#')) {
                    return { facetHeader, renderType: 'togglebox' };
                }
                if (facetHeader !== 'tags' && !facetHeader.startsWith('#') && nonNumbers / facetValues.strings.length < 0.1) {
                    const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * 0.1));
                    const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * 0.05)));
                    const ranged: number[] | undefined = Doc.readDocRangeFilter(this.Document, facetHeader); // not the filter range, but the zooomed in range on the filter
                    return { facetHeader, renderType: 'range', domain: [extendedMinVal, extendedMaxVal], range: ranged || [extendedMinVal, extendedMaxVal] };
                }
                return { facetHeader, renderType: 'checkbox' };
            })
        );
    }

    /**
     * user clicks on a filter facet because they want to see it.
     * this adds this chosen filter to a set of user selected filters called:  selectedFilters
     * if this component reloads, then these filters will go away since they haven't been written to any Doc anywhere
     *
     *   // this._selectedFacets.add(facetHeader);  .. add to Set() not array
     */

    @action
    facetClick = (facetHeader: string) => this._selectedFacetHeaders.add(facetHeader);

    @action
    sortingCurrentFacetValues = (facetHeader: string) => {
        this._collapseReturnKeys.splice(0);

        Array.from(this.activeRenderedFacetInfos.keys()).forEach(renderInfo => {
            if (renderInfo.renderType === 'range' && renderInfo.facetHeader === facetHeader && renderInfo.range) {
                this._collapseReturnKeys.push(...renderInfo.range.map(number => number.toFixed(2)));
            }
        });

        this.facetValues(facetHeader).forEach(key => {
            if (this.mapActiveFiltersToFacets.get(key)) {
                this._collapseReturnKeys.push(key);
            }
        });

        return <div className=" filterbox-collpasedAndActive">{this._collapseReturnKeys.join(',  ')}</div>;
    };

    facetValues = (facetHeader: string) => {
        const allCollectionDocs = new Set<Doc>();
        SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc));
        const set = new Set<string>([...StrListCast(this.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]);

        allCollectionDocs.forEach(child => {
            const fieldVal = child[facetHeader] as FieldType;
            const fieldStrList = StrListCast(child[facetHeader]).filter(h => h);
            if (fieldStrList.length) fieldStrList.forEach(key => set.add(key));
            else if (!(fieldVal instanceof List)) {
                // currently we have no good way of filtering based on a field that is a list
                set.add(Field.toString(fieldVal));
                (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString());
            }
        });
        const facetValues = Array.from(set).filter(v => v);

        let nonNumbers = 0;

        facetValues.map(val => isNaN(Number(val)) && nonNumbers++);
        return nonNumbers / facetValues.length > 0.1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2));
    };

    /**
     * Renders the newly formed hotkey icon buttons
     * @returns the buttons to be rendered
     */
    hotKeyButtons = () => {
        const selected = DocumentView.SelectedDocs().lastElement();
        const hotKeys = Doc.MyFilterHotKeys;

        // Selecting a button should make it so that the icon on the top filter panel becomes said icon
        const buttons = hotKeys.map(hotKey => (
            <Tooltip key={StrCast(hotKey.title)} title={<div className="dash-tooltip">Click to customize this hotkey&apos;s icon</div>}>
                <HotKeyIconButton hotKey={hotKey} selected={selected} />
            </Tooltip>
        ));

        return buttons;
    };

    // @observable iconPanelMap: Map<string, number> = new Map();

    render() {
        return (
            <div className="filterBox-treeView">
                <div className="filterBox-select">
                    <div style={{ width: '100%' }}>
                        <FieldsDropdown Doc={this.Document} selectFunc={this.facetClick} showPlaceholder placeholder="add a filter" addedFields={['acl_Guest', LinkedTo, 'Star', 'Heart', 'Bolt', 'Cloud']} />
                    </div>
                    {/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */}
                    {/* <div className="filterBox-select-bool"> 
                        <select className="filterBox-selection" onChange={action(e => this.targetDoc && (this.targetDoc._childFilters_boolean = (e.target as any).value))} defaultValue={StrCast(this.targetDoc?.childFilters_boolean)}>
                            {['AND', 'OR'].map(bool => (
                                <option value={bool} key={bool}>
                                    {bool}
                                </option>
                            ))}
                        </select>
                    </div>{' '} */}
                </div>

                <div className="filterBox-tree" key="tree">
                    {Array.from(this.activeRenderedFacetInfos.keys()).map(
                        // iterate over activeFacetRenderInfos  ==> renderInfo  which you can renderInfo.facetHeader
                        renderInfo => (
                            <div key={renderInfo.facetHeader}>
                                <div className="filterBox-facetHeader">
                                    <div className="filterBox-facetHeader-Header"> </div>
                                    {renderInfo.facetHeader.charAt(0).toUpperCase() + renderInfo.facetHeader.slice(1)}
                                    <div
                                        className="filterBox-facetHeader-collapse"
                                        onClick={action(() => {
                                            const collapseBoolValue = this._chosenFacetsCollapse.get(renderInfo.facetHeader);
                                            this._chosenFacetsCollapse.set(renderInfo.facetHeader, !collapseBoolValue);
                                        })}>
                                        {this._chosenFacetsCollapse.get(renderInfo.facetHeader) ? <AiOutlinePlusSquare /> : <AiOutlineMinusSquare />}
                                    </div>
                                    <div
                                        className="filterBox-facetHeader-remove"
                                        onClick={action(() => {
                                            if (renderInfo.facetHeader === 'text') {
                                                Doc.setDocFilter(this.Document, renderInfo.facetHeader, 'match', 'remove');
                                            } else {
                                                this.facetValues(renderInfo.facetHeader).forEach((key: string) => {
                                                    if (this.mapActiveFiltersToFacets.get(key)) {
                                                        Doc.setDocFilter(this.Document, renderInfo.facetHeader, key, 'remove');
                                                    }
                                                });
                                            }
                                            this._selectedFacetHeaders.delete(renderInfo.facetHeader);
                                            this._chosenFacetsCollapse.delete(renderInfo.facetHeader);

                                            if (renderInfo.domain) {
                                                Doc.setDocRangeFilter(this.Document, renderInfo.facetHeader, renderInfo.domain, 'remove');
                                            }
                                        })}>
                                        <CiCircleRemove />{' '}
                                    </div>
                                </div>

                                {this._chosenFacetsCollapse.get(renderInfo.facetHeader)
                                    ? this.sortingCurrentFacetValues(renderInfo.facetHeader)
                                    : this.displayFacetValueFilterUIs(renderInfo.renderType, renderInfo.facetHeader, renderInfo.domain, renderInfo.range)}
                                {/*  */}
                            </div>
                        )
                    )}
                </div>
                <div>
                    <div className="filterBox-select">
                        <div style={{ width: '100%' }}>
                            <FieldsDropdown Doc={this.Document} selectFunc={this._props.addHotKey} showPlaceholder placeholder="add a hotkey" addedFields={['acl_Guest', LinkedTo]} />
                        </div>
                    </div>
                </div>

                <div>{this.hotKeyButtons()}</div>
            </div>
        );
    }

    private displayFacetValueFilterUIs(type: string | undefined, facetHeader: string, renderInfoDomain?: number[] | undefined, renderInfoRange?: number[]): React.ReactNode {
        switch (type) {
            case 'text':
                return (
                    <input
                        key={this.Document[Id]}
                        placeholder="enter text to match"
                        defaultValue={
                            StrListCast(this.Document._childFilters)
                                .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader)
                                ?.split(Doc.FilterSep)[1]
                        }
                        style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                        onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')}
                        onKeyDown={e => e.key === 'Enter' && undoable(() => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')()}
                    />
                );
            case 'checkbox':
                return this.facetValues(facetHeader).map(fval => {
                    const facetValue = fval;
                    return (
                        <div key={facetValue}>
                            <input
                                style={{ width: 20, marginLeft: 20 }}
                                checked={['check', 'exists'].includes(
                                    StrListCast(this.Document._childFilters)
                                        .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] === facetValue)
                                        ?.split(Doc.FilterSep)[2] ?? ''
                                )}
                                type={type}
                                onChange={undoable(e => Doc.setDocFilter(this.Document, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')}
                            />
                            {facetValue}
                        </div>
                    );
                });
            case 'togglebox':
                return (
                    <div>
                        <input
                            style={{ width: 20, marginLeft: 20 }}
                            checked={['check', 'exists'].includes(
                                StrListCast(this.Document._childFilters)
                                    .find(filter => filter.split(Doc.FilterSep)[0] === 'tags' && filter.split(Doc.FilterSep)[1] === facetHeader)
                                    ?.split(Doc.FilterSep)[2] ?? ''
                            )}
                            type={'checkbox'}
                            onChange={undoable(e => Doc.setDocFilter(this.Document, 'tags', facetHeader, e.target.checked ? 'check' : 'remove'), 'set filter')}
                        />
                        -set-
                    </div>
                );

            case 'range':
                {
                    const domain = renderInfoDomain;
                    if (domain) {
                        return (
                            <div className="sliderBox-outerDiv" style={{ width: '95%', height: 45, float: 'right' }}>
                                <Slider
                                    mode={2}
                                    step={Math.min(1, 0.1 * (domain[1] - domain[0]))}
                                    domain={[domain[0], domain[1]]} // -1000, 1000
                                    rootStyle={{ position: 'relative', width: '100%' }}
                                    onChange={values => Doc.setDocRangeFilter(this.Document, facetHeader, values)}
                                    values={renderInfoRange!}>
                                    <Rail>{railProps => <TooltipRail {...railProps} />}</Rail>
                                    <Handles>
                                        {({ handles, activeHandleID, getHandleProps }) => (
                                            <div className="slider-handles">
                                                {handles.map(handle => (
                                                    // const value = i === 0 ? defaultValues[0] : defaultValues[1];
                                                    <div key={handle.id}>
                                                        <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} />
                                                    </div>
                                                ))}
                                            </div>
                                        )}
                                    </Handles>
                                    <Tracks left={false} right={false}>
                                        {({ tracks, getTrackProps }) => (
                                            <div className="slider-tracks">
                                                {tracks.map(({ id, source, target }) => (
                                                    <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} />
                                                ))}
                                            </div>
                                        )}
                                    </Tracks>
                                    <Ticks count={5}>
                                        {({ ticks }) => (
                                            <div className="slider-ticks">
                                                {ticks.map(tick => (
                                                    <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} />
                                                ))}
                                            </div>
                                        )}
                                    </Ticks>
                                </Slider>
                            </div>
                        );
                    }
                }
                break;
            default:

            // case 'range'
            // return <Slider ...
            //  return <slider domain={renderInfo.domain}> domain is number[] for min and max
            //         onChange = { ...     Doc.setDocRangeFilter(this.targetDoc, facetHeader, [extendedMinVal, extendedMaxVal] ) }
            //
            //  OR

            // return <div>
            //     <slider domain={renderInfo.domain}> // domain is number[] for min and max
            //     <dimain changing handles >
            //  <?div
        }
        return undefined;
    }
}
