import * as React from "react";
import { connect, shallowEqual } from "react-redux";
import * as autoscroll from "./helpers/AutoScroll";
import type { VScroll } from "./helpers/VirtualScroll";
import { calcVScroll } from "./helpers/VirtualScroll";
import FlowTableHead from "./FlowTable/FlowTableHead";
import FlowRow from "./FlowTable/FlowRow";
import type { Flow } from "../flow";
import type { RootState } from "../ducks";

type FlowTableProps = {
    flowView: Flow[];
    rowHeight: number;
    highlightedIds: Set<string>;
    selectedIds: Set<string>;
    onlySelectedId: string | false;
    firstSelectedIndex: number | undefined;
    displayColumnNames: string[];
    listIndex: Map<string, number>;
};

type FlowTableState = {
    vScroll: VScroll;
    viewportTop: number;
};

export class PureFlowTable extends React.Component<
    FlowTableProps,
    FlowTableState
> {
    static defaultProps = {
        rowHeight: 32,
    };
    private viewport = React.createRef<HTMLDivElement>();
    private head = React.createRef<HTMLTableSectionElement>();

    constructor(props, context) {
        super(props, context);

        this.state = {
            vScroll: calcVScroll(),
            viewportTop: 0,
        };
        this.onViewportUpdate = this.onViewportUpdate.bind(this);
    }

    componentDidMount() {
        window.addEventListener("resize", this.onViewportUpdate);
        this.onViewportUpdate();
    }

    componentWillUnmount() {
        window.removeEventListener("resize", this.onViewportUpdate);
    }

    getSnapshotBeforeUpdate() {
        return autoscroll.isAtBottom(this.viewport);
    }

    componentDidUpdate(
        prevProps: FlowTableProps,
        prevState: FlowTableState,
        snapshot,
    ) {
        if (snapshot) {
            autoscroll.adjustScrollTop(this.viewport);
        }
        this.onViewportUpdate();

        const { onlySelectedId } = this.props;

        const selectedPotentiallyOffscreenFlow =
            onlySelectedId && onlySelectedId !== prevProps.onlySelectedId;

        if (selectedPotentiallyOffscreenFlow) {
            const { rowHeight, firstSelectedIndex } = this.props;
            const viewport = this.viewport.current!;
            const head = this.head.current;

            const headHeight = head ? head.offsetHeight : 0;

            const rowTop = firstSelectedIndex! * rowHeight + headHeight;
            const rowBottom = rowTop + rowHeight;

            const viewportTop = viewport.scrollTop;
            const viewportHeight = viewport.offsetHeight;

            // Account for pinned thead
            if (rowTop - headHeight < viewportTop) {
                viewport.scrollTop = rowTop - headHeight;
            } else if (rowBottom > viewportTop + viewportHeight) {
                viewport.scrollTop = rowBottom - viewportHeight;
            }
            this.onViewportUpdate();
        }
    }

    onViewportUpdate() {
        const viewport = this.viewport.current!;
        const viewportTop = viewport.scrollTop || 0;

        const vScroll = calcVScroll({
            viewportTop,
            viewportHeight: viewport.offsetHeight || 0,
            itemCount: this.props.flowView.length,
            rowHeight: this.props.rowHeight,
        });

        if (
            this.state.viewportTop !== viewportTop ||
            !shallowEqual(this.state.vScroll, vScroll)
        ) {
            // the next rendered state may only have much lower number of rows compared to what the current
            // viewportHeight anticipates. To make sure that we update (almost) at once, we already constrain
            // the maximum viewportTop value. See https://github.com/mitmproxy/mitmproxy/pull/5658 for details.
            const newViewportTop = Math.min(
                viewportTop,
                vScroll.end * this.props.rowHeight,
            );
            this.setState({
                vScroll,
                viewportTop: newViewportTop,
            });
        }
    }

    render() {
        const { vScroll, viewportTop } = this.state;
        const {
            flowView,
            selectedIds,
            highlightedIds,
            displayColumnNames,
            listIndex,
        } = this.props;

        return (
            <div
                className="flow-table"
                onScroll={this.onViewportUpdate}
                ref={this.viewport}
            >
                <table>
                    <thead
                        ref={this.head}
                        style={{ transform: `translateY(${viewportTop}px)` }}
                    >
                        <FlowTableHead />
                    </thead>
                    <tbody>
                        <tr style={{ height: vScroll.paddingTop }} />
                        {flowView
                            .slice(vScroll.start, vScroll.end)
                            .map((flow) => (
                                <FlowRow
                                    key={flow.id}
                                    flow={flow}
                                    selected={selectedIds.has(flow.id)}
                                    highlighted={highlightedIds.has(flow.id)}
                                    displayColumnNames={displayColumnNames}
                                    rowNumber={listIndex.get(flow.id)!}
                                />
                            ))}
                        <tr style={{ height: vScroll.paddingBottom }} />
                    </tbody>
                </table>
            </div>
        );
    }
}

export default connect((state: RootState) => ({
    flowView: state.flows.view,
    highlightedIds: state.flows.highlightedIds,
    selectedIds: state.flows.selectedIds,
    onlySelectedId:
        state.flows.selected.length === 1 && state.flows.selected[0].id,
    firstSelectedIndex: state.flows._viewIndex.get(state.flows.selected[0]?.id),
    // Fetch column names once at the table level; avoids N identical
    // useAppSelector subscriptions inside each FlowRow.
    displayColumnNames: state.options.web_columns,
    // Pass list index so the # column shows the original flow number
    // (list position), not the view position after sort/filter.
    listIndex: state.flows._listIndex,
}))(PureFlowTable);
