/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
/* eslint-disable jsx-a11y/no-static-element-interactions */
import { Button, Colors, Type } from 'browndash-components';
import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils';
import { emptyFunction } from '../../../../../Utils';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
import { DocData } from '../../../../../fields/DocSymbols';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast } from '../../../../../fields/Types';
import { DragManager } from '../../../../util/DragManager';
import { undoable } from '../../../../util/UndoManager';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
import './Chart.scss';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const { DATA_VIZ_TABLE_ROW_HEIGHT } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore

interface TableBoxProps {
    Document: Doc;
    layoutDoc: Doc;
    records: { [key: string]: unknown }[];
    selectAxes: (axes: string[]) => void;
    selectTitleCol: (titleCol: string) => void;
    axes: string[];
    titleCol: string;
    width: number;
    height: number;
    margin: {
        top: number;
        right: number;
        bottom: number;
        left: number;
    };
    docView?: () => DocumentView | undefined;
    specHighlightedRow: number | undefined;
}

@observer
export class TableBox extends ObservableReactComponent<TableBoxProps> {
    _inputChangedDisposer?: IReactionDisposer;
    _containerRef: HTMLDivElement | null = null;

    @observable settingTitle: boolean = false; // true when setting a title column
    @observable hasRowsToFilter: boolean = false; // true when any rows are selected
    @observable filtering: boolean = false; // true when the filtering menu is open
    @observable filteringColumn = ''; // column to filter
    @observable filteringType: string = 'Value'; // "Value" or "Range"
    filteringVal = ['', '']; // value or range to filter the column with

    @observable _scrollTop = -1;
    @observable _tableHeight = 0;
    @observable _tableContainerHeight = 0;
    constructor(props: TableBoxProps) {
        super(props);
        makeObservable(this);
    }

    componentDidMount() {
        // if the tableData changes (ie., when records are selected by the parent (input) visulization),
        // then we need to remove any selected rows that are no longer part of the visualized dataset.
        this._inputChangedDisposer = reaction(() => this._tableData.slice(), this.filterSelectedRowsDown, { fireImmediately: true });
        const selected = NumListCast(this._props.layoutDoc.dataViz_selectedRows);
        if (selected.length > 0)
            runInAction(() => {
                this.hasRowsToFilter = true;
            });
        this.handleScroll();
    }
    componentWillUnmount() {
        this._inputChangedDisposer?.();
    }
    @computed get _tableDataIds() {
        return !this.parentViz ? this._props.records.map((rec, i) => i) : NumListCast(this.parentViz.dataViz_selectedRows);
    }
    // returns all the data records that will be rendered by only returning those records that have been selected by the parent visualization (or all records if there is no parent)
    @computed get _tableData() {
        return !this.parentViz ? this._props.records : this._tableDataIds.map(rowId => this._props.records[rowId]);
    }

    @computed get parentViz() {
        return DocCast(this._props.Document.dataViz_parentViz);
    }

    @computed get columns() {
        return this._tableData.length ? Array.from(Object.keys(this._tableData[0])).filter(header => header !== '' && header !== undefined) : [];
    }

    // updates the 'dataViz_selectedRows' and 'dataViz_highlightedRows' fields to no longer include rows that aren't in the table
    filterSelectedRowsDown = () => {
        const selected = NumListCast(this._props.layoutDoc.dataViz_selectedRows);
        this._props.layoutDoc.dataViz_selectedRows = new List<number>(selected.filter(rowId => this._tableDataIds.includes(rowId))); // filters through selected to remove guids that were removed in the incoming data
        const highlighted = NumListCast(this._props.layoutDoc.dataViz_highlitedRows);
        this._props.layoutDoc.dataViz_highlitedRows = new List<number>(highlighted.filter(rowId => this._tableDataIds.includes(rowId))); // filters through highlighted to remove guids that were removed in the incoming data
    };

    @computed get viewScale() {
        return this._props.docView?.()?.screenToViewTransform().Scale || 1;
    }
    @computed get rowHeight() {
        return (this.viewScale * this._tableHeight) / this._tableDataIds.length;
    }
    @computed get startID() {
        return this.rowHeight ? Math.max(Math.floor(this._scrollTop / this.rowHeight) - 1, 0) : 0;
    }
    @computed get endID() {
        return Math.ceil(this.startID + (this._tableContainerHeight * this.viewScale) / (this.rowHeight || 1));
    }
    @action handleScroll = () => {
        if (!this._props.docView?.()?.ContentDiv?.hidden) {
            this._scrollTop = this._containerRef?.scrollTop ?? 0;
        }
    };
    @action
    tableRowClick = (e: React.MouseEvent, rowId: number) => {
        const highlited = Cast(this._props.layoutDoc.dataViz_highlitedRows, listSpec('number'), null);
        const selected = Cast(this._props.layoutDoc.dataViz_selectedRows, listSpec('number'), null);
        if (e.metaKey) {
            // highlighting a row
            if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
            else highlited?.push(rowId);
            if (!selected?.includes(rowId)) selected?.push(rowId);
        } else if (selected?.includes(rowId)) {
            // selecting a row
            if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
            selected.splice(selected.indexOf(rowId), 1);
        } else selected?.push(rowId);
        e.stopPropagation();
        this.hasRowsToFilter = selected.length > 0;
    };

    columnPointerDown = (e: React.PointerEvent, col: string) => {
        const downX = e.clientX;
        const downY = e.clientY;
        setupMoveUpEvents(
            {},
            e,
            moveEv => {
                // dragging off a column to create a brushed DataVizBox
                const sourceAnchorCreator = () => this._props.docView?.()?.Document || this._props.Document;
                const targetCreator = (annotationOn: Doc | undefined) => {
                    const doc = this._props.docView?.()?.Document;
                    if (doc) {
                        const embedding = Doc.MakeEmbedding(doc);
                        embedding._dataViz = DataVizView.TABLE;
                        embedding._dataViz_axes = new List<string>([col]);
                        embedding._dataViz_parentViz = this._props.Document;
                        embedding.annotationOn = annotationOn;
                        embedding.histogramBarColors = Field.Copy(this._props.layoutDoc.histogramBarColors);
                        embedding.defaultHistogramColor = this._props.layoutDoc.defaultHistogramColor;
                        embedding.pieSliceColors = Field.Copy(this._props.layoutDoc.pieSliceColors);
                        return embedding;
                    }
                    return this._props.Document;
                };
                if (this._props.docView?.() && !ClientUtils.isClick(moveEv.clientX, moveEv.clientY, downX, downY, Date.now())) {
                    DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
                        dragComplete: completeEv => {
                            if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) {
                                completeEv.linkDocument[DocData].link_matchEmbeddings = true;
                                completeEv.linkDocument[DocData].stroke_startMarker = true;
                                this._props.docView?.()?._props.addDocument?.(completeEv.linkDocument);
                            }
                        },
                    });
                    return true;
                }
                return false;
            },
            emptyFunction,
            action(moveEv => {
                if (moveEv.shiftKey || this.settingTitle) {
                    if (this.settingTitle) this.settingTitle = false;
                    if (this._props.titleCol === col) this._props.titleCol = '';
                    else this._props.titleCol = col;
                    this._props.selectTitleCol(this._props.titleCol);
                } else {
                    const newAxes = this._props.axes;
                    if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
                    else newAxes.push(col);
                    this._props.selectAxes(newAxes);
                }
            })
        );
    };

    /**
     * These functions handle the filtering popup for when the "filter" button is pressed to select rows
     */
    filter = undoable((e: React.MouseEvent) => {
        let start: string | number;
        let end: string | number;
        if (this.filteringType === 'Range') {
            start = Number.isNaN(Number(this.filteringVal[0])) ? this.filteringVal[0] : Number(this.filteringVal[0]);
            end = Number.isNaN(Number(this.filteringVal[1])) ? this.filteringVal[1] : Number(this.filteringVal[1]);
        }

        this._tableDataIds.forEach(rowID => {
            if (this.filteringType === 'Value') {
                if (this._props.records[rowID][this.filteringColumn] === this.filteringVal[0]) {
                    if (!NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowID)) {
                        this.tableRowClick(e, rowID);
                    }
                }
            } else {
                let compare = this._props.records[rowID][this.filteringColumn] as string | number;
                if (Number(compare) == compare) compare = Number(compare);
                if (start <= compare && compare <= end) {
                    if (!NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowID)) {
                        this.tableRowClick(e, rowID);
                    }
                }
            }
        });
        this.filtering = false;
        this.filteringColumn = '';
        this.filteringVal = ['', ''];
    }, 'filter table');
    @action
    setFilterColumn = (e: React.ChangeEvent<HTMLSelectElement>) => {
        this.filteringColumn = e.currentTarget.value;
    };
    @action
    setFilterType = (e: React.ChangeEvent<HTMLSelectElement>) => {
        this.filteringType = e.currentTarget.value;
    };
    changeFilterValue = action((e: React.ChangeEvent<HTMLInputElement>) => {
        this.filteringVal[0] = e.target.value;
    });
    changeFilterRange0 = action((e: React.ChangeEvent<HTMLInputElement>) => {
        this.filteringVal[0] = e.target.value;
    });
    changeFilterRange1 = action((e: React.ChangeEvent<HTMLInputElement>) => {
        this.filteringVal[1] = e.target.value;
    });
    @computed get renderFiltering() {
        if (this.filteringColumn === '') [this.filteringColumn] = this.columns;
        return (
            <div className="tableBox-filterPopup" style={{ right: this._props.width * 0.05 }}>
                <div className="tableBox-filterPopup-selectColumn">
                    Column:
                    <select className="tableBox-filterPopup-selectColumn-each" value={this.filteringColumn !== '' ? this.filteringColumn : this.columns[0]} onChange={this.setFilterColumn}>
                        {this.columns.map(column => (
                            <option className="" key={column} value={column}>
                                {' '}
                                {column}{' '}
                            </option>
                        ))}
                    </select>
                </div>
                <div className="tableBox-filterPopup-setValue">
                    <select className="tableBox-filterPopup-setValue-each" value={this.filteringType} onChange={this.setFilterType}>
                        <option className="" key="Value" value="Value">
                            {' '}
                            {'Value'}{' '}
                        </option>
                        <option className="" key="Range" value="Range">
                            {' '}
                            {'Range'}{' '}
                        </option>
                    </select>
                    :
                    {this.filteringType === 'Value' ? (
                        <input
                            className="tableBox-filterPopup-setValue-input"
                            defaultValue=""
                            autoComplete="off"
                            onChange={this.changeFilterValue}
                            onKeyDown={e => {
                                e.stopPropagation();
                            }}
                            type="text"
                            placeholder=""
                            id="search-input"
                        />
                    ) : (
                        <div>
                            <input
                                className="tableBox-filterPopup-setValue-input"
                                defaultValue=""
                                autoComplete="off"
                                onChange={this.changeFilterRange0}
                                onKeyDown={e => {
                                    e.stopPropagation();
                                }}
                                type="text"
                                placeholder=""
                                id="search-input"
                                style={{ width: this._props.width * 0.15 }}
                            />
                            to
                            <input
                                className="tableBox-filterPopup-setValue-input"
                                defaultValue=""
                                autoComplete="off"
                                onChange={this.changeFilterRange1}
                                onKeyDown={e => {
                                    e.stopPropagation();
                                }}
                                type="text"
                                placeholder=""
                                id="search-input"
                                style={{ width: this._props.width * 0.15 }}
                            />
                        </div>
                    )}
                </div>
                <div className="tableBox-filterPopup-setFilter">
                    <Button onClick={this.filter} text="Set Filter" type={Type.SEC} color="black" />
                </div>
            </div>
        );
    }

    render() {
        if (this._tableData.length > 0) {
            return (
                <div
                    className="tableBox"
                    style={{ width: this._props.width + this._props.margin.right }}
                    tabIndex={0}
                    onKeyDown={e => {
                        if (this._props.layoutDoc && e.key === 'a' && (e.ctrlKey || e.metaKey)) {
                            e.stopPropagation();
                            this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
                        }
                    }}>
                    <div className="tableBox-selectButtons">
                        <div className="tableBox-selectTitle">
                            <Button
                                onClick={action(() => {
                                    this.settingTitle = !this.settingTitle;
                                })}
                                text="Select Title Column"
                                type={Type.SEC}
                                color="black"
                            />
                        </div>
                        <div className="tableBox-filtering">
                            {this.filtering ? this.renderFiltering : null}
                            <Button
                                onClick={action(() => {
                                    this.filtering = !this.filtering;
                                })}
                                text="Filter"
                                type={Type.SEC}
                                color="black"
                            />
                            <div className="tableBox-filterAll">
                                {this.hasRowsToFilter ? (
                                    <Button
                                        onClick={action(() => {
                                            this._props.layoutDoc.dataViz_selectedRows = new List<number>();
                                            this.hasRowsToFilter = false;
                                        })}
                                        text="Deselect All"
                                        type={Type.SEC}
                                        color="black"
                                        tooltip="Select rows to be displayed in any DataViz boxes dragged off of this one."
                                    />
                                ) : (
                                    <Button
                                        onClick={action(() => {
                                            this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds);
                                            this.hasRowsToFilter = true;
                                        })}
                                        text="Select All"
                                        type={Type.SEC}
                                        color="black"
                                        tooltip="Select rows to be displayed in any DataViz boxes dragged off of this one."
                                    />
                                )}
                            </div>
                        </div>
                    </div>
                    <div
                        className={`tableBox-container ${this.columns[0]}`}
                        style={{ height: '100%', overflow: 'auto' }}
                        onScroll={this.handleScroll}
                        ref={action((r: HTMLDivElement | null) => {
                            this._containerRef = r;
                            if (!this._props.docView?.()?.ContentDiv?.hidden && r) {
                                this._tableContainerHeight = r.getBoundingClientRect().height ?? 0;
                                r.addEventListener(
                                    'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this)
                                    (e: WheelEvent) => {
                                        if (!r.scrollTop && e.deltaY <= 0) e.preventDefault();
                                        e.stopPropagation();
                                    },
                                    { passive: false }
                                );
                            }
                        })}>
                        <table
                            className="tableBox-table"
                            ref={action((r: HTMLTableElement | null) => {
                                if (!this._props.docView?.()?.ContentDiv?.hidden && r) {
                                    this._tableHeight = r?.getBoundingClientRect().height ?? 0;
                                }
                            })}>
                            <div style={{ height: this.startID * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
                            <thead>
                                <tr>
                                    {this.columns.map(col => (
                                        <th
                                            key={this.columns.indexOf(col)}
                                            style={{
                                                color:
                                                    this._props.axes.slice().reverse().lastElement() === col
                                                        ? 'darkgreen'
                                                        : this._props.axes.length > 3 && this._props.axes.lastElement() === col
                                                          ? 'darkred'
                                                          : this._props.axes.length > 3 && this._props.axes[1] === col
                                                            ? 'darkblue'
                                                            : this._props.axes.lastElement() === col || (this._props.axes.length > 3 && this._props.axes[2] === col) 
                                                              ? 'darkcyan'
                                                              : undefined,
                                                background: this.settingTitle
                                                    ? 'lightgrey'
                                                    : this._props.axes.slice().reverse().lastElement() === col
                                                      ? '#E3fbdb'
                                                      : this._props.axes.length > 2 && this._props.axes.lastElement() === col
                                                        ? '#Fbdbdb'
                                                        : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
                                                          ? '#c6ebf7'
                                                          : this._props.axes.lastElement() === col || (this._props.axes.length > 3 && this._props.axes[2] === col) 
                                                            ? '#c2f0f4'
                                                            : undefined,
                                                fontWeight: 'bolder',
                                                border: '3px solid black',
                                            }}
                                            onPointerDown={e => this.columnPointerDown(e, col)}>
                                            {col}
                                        </th>
                                    ))}
                                </tr>
                            </thead>
                            <tbody>
                                {this._tableDataIds
                                    .filter((rowId, i) => this.startID - 2 <= i && i <= this.endID + 2)
                                    ?.map(rowId => (
                                        <tr
                                            key={rowId}
                                            className={`tableBox-row ${this.columns[0]}`}
                                            onClick={e => this.tableRowClick(e, rowId)}
                                            style={{
                                                background: rowId === this._props.specHighlightedRow ? 'lightblue' : NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
                                                border: rowId === this._props.specHighlightedRow ? `solid 3px ${Colors.MEDIUM_BLUE}` : ''
                                            }}>
                                            {this.columns.map(col => {
                                                let colSelected = false;
                                                if (this._props.axes.length > 2) colSelected = this._props.axes[0] === col || this._props.axes[1] === col || this._props.axes[2] === col;
                                                else if (this._props.axes.length > 1) colSelected = this._props.axes[0] === col || this._props.axes[1] === col;
                                                else if (this._props.axes.length > 0) colSelected = this._props.axes[0] === col;
                                                if (this._props.titleCol === col) colSelected = true;
                                                return (
                                                    <td key={this.columns.indexOf(col)} style={{ border: colSelected ? '3px solid black' : '1px solid black', fontWeight: colSelected ? 'bolder' : 'normal' }}>
                                                        <div className="tableBox-cell">{this._props.records[rowId][col] as string | number}</div>
                                                    </td>
                                                );
                                            })}
                                        </tr>
                                    ))}
                            </tbody>
                            <div style={{ height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
                        </table>
                    </div>
                </div>
            );
        }
        return (
            // when it is a brushed table and the incoming table doesn't have any rows selected
            <div className="chart-container">Selected rows of data from the incoming DataVizBox to display.</div>
        );
    }
}
