import { Button, Type } from 'browndash-components';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, Field, NumListCast } from '../../../../../fields/Doc';
import { List } from '../../../../../fields/List';
import { listSpec } from '../../../../../fields/Schema';
import { Cast, DocCast } from '../../../../../fields/Types';
import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../../Utils';
import { DragManager } from '../../../../util/DragManager';
import { DocumentView } from '../../DocumentView';
import { DataVizView } from '../DataVizBox';
import './Chart.scss';
import { ObservableReactComponent } from '../../../ObservableReactComponent';
const {  default: { DATA_VIZ_TABLE_ROW_HEIGHT } } = require('../../../global/globalCssVariables.module.scss'); // prettier-ignore
interface TableBoxProps {
    Document: Doc;
    layoutDoc: Doc;
    records: { [key: string]: any }[];
    selectAxes: (axes: string[]) => void;
    axes: string[];
    width: number;
    height: number;
    margin: {
        top: number;
        right: number;
        bottom: number;
        left: number;
    };
    docView?: () => DocumentView | undefined;
}

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

    @observable _scrollTop = -1;
    @observable _tableHeight = 0;
    @observable _tableContainerHeight = 0;
    constructor(props: any) {
        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 });
        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);
        // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links
        //     .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link
        //     .map(link => DocCast(link.link_anchor_1)); // then return the source of the link
    }

    @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?.()?._props.ScreenToLocalTransform().Scale || 1;
    }
    @computed get rowHeight() {
        console.log('scale = ' + this.viewScale + '  table = ' + this._tableHeight + ' ids = ' + this._tableDataIds.length);
        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() {
        console.log('start = ' + this.startID + '  container = ' + this._tableContainerHeight + ' scale = ' + this.viewScale + ' row = ' + this.rowHeight);
        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 {
            // selecting a row
            if (selected?.includes(rowId)) {
                if (highlited?.includes(rowId)) highlited.splice(highlited.indexOf(rowId), 1);
                selected.splice(selected.indexOf(rowId), 1);
            } else selected?.push(rowId);
        }
        e.stopPropagation();
    };

    columnPointerDown = (e: React.PointerEvent, col: string) => {
        const downX = e.clientX;
        const downY = e.clientY;
        setupMoveUpEvents(
            {},
            e,
            e => {
                // dragging off a column to create a brushed DataVizBox
                const sourceAnchorCreator = () => this._props.docView?.()!.Document!;
                const targetCreator = (annotationOn: Doc | undefined) => {
                    const embedding = Doc.MakeEmbedding(this._props.docView?.()!.Document!);
                    embedding._dataViz = DataVizView.TABLE;
                    embedding._dataViz_axes = new List<string>([col, 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;
                };
                if (this._props.docView?.() && !Utils.isClick(e.clientX, e.clientY, downX, downY, Date.now())) {
                    DragManager.StartAnchorAnnoDrag(e.target instanceof HTMLElement ? [e.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, {
                        dragComplete: e => {
                            if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) {
                                e.linkDocument.link_displayLine = true;
                                e.linkDocument.link_matchEmbeddings = true;
                                e.linkDocument.link_displayArrow = true;
                                // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document;
                                // e.annoDragData.linkSourceDoc.followLinkZoom = false;
                            }
                        },
                    });
                    return true;
                }
                return false;
            },
            emptyFunction,
            action(e => {
                const newAxes = this._props.axes;
                if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
                else if (newAxes.length > 1) newAxes[1] = col;
                else newAxes.push(col);
                this._props.selectAxes(newAxes);
            })
        );
    };

    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="selectAll-buttons">
                        <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>(this._tableDataIds)))} text="Select All" type={Type.SEC} color={'black'} />
                        <Button onClick={action(() => (this._props.layoutDoc.dataViz_selectedRows = new List<number>()))} text="Deselect All" type={Type.SEC} color={'black'} />
                    </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.lastElement() === col ? 'darkred' : undefined,
                                                background: this._props.axes.slice().reverse().lastElement() === col ? '#E3fbdb' : this._props.axes.lastElement() === col ? '#Fbdbdb' : 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 <= i && i <= this.endID)
                                    ?.map(rowId => (
                                        <tr
                                            key={rowId}
                                            className={`tableBox-row ${this.columns[0]}`}
                                            onClick={e => this.tableRowClick(e, rowId)}
                                            style={{
                                                background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
                                            }}>
                                            {this.columns.map(col => {
                                                const colSelected = this._props.axes.length > 1 ? this._props.axes[0] == col || this._props.axes[1] == col : this._props.axes.length > 0 ? this._props.axes[0] == col : false;
                                                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]}</div>
                                                    </td>
                                                );
                                            })}
                                        </tr>
                                    ))}
                            </tbody>
                            <div style={{ height: (this._tableDataIds.length - this.endID) * Number(DATA_VIZ_TABLE_ROW_HEIGHT) }} />
                        </table>
                    </div>
                </div>
            );
        } else
            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>
            );
    }
}
