import { action, computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { returnFalse } from '../../../../Utils';
import { Doc, DocListCast } from '../../../../fields/Doc';
import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { DragManager, dropActionType } from '../../../util/DragManager';
import { Transform } from '../../../util/Transform';
import { undoBatch } from '../../../util/UndoManager';
import { DocumentView } from '../../nodes/DocumentView';
import { CollectionSubView } from '../CollectionSubView';
import './CollectionMultirowView.scss';
import HeightLabel from './MultirowHeightLabel';
import ResizeBar from './MultirowResizer';
interface HeightSpecifier {
    magnitude: number;
    unit: string;
}

interface LayoutData {
    heightSpecifiers: HeightSpecifier[];
    starSum: number;
}

export const DimUnit = {
    Pixel: 'px',
    Ratio: '*',
};

const resolvedUnits = Object.values(DimUnit);
const resizerHeight = 8;

@observer
export class CollectionMultirowView extends CollectionSubView() {
    constructor(props: any) {
        super(props);
        makeObservable(this);
    }

    /**
     * @returns the list of layout documents whose width unit is
     * *, denoting that it will be displayed with a ratio, not fixed pixel, value
     */
    @computed
    private get ratioDefinedDocs() {
        return this.childLayoutPairs.map(pair => pair.layout).filter(layout => StrCast(layout._dimUnit, '*') === DimUnit.Ratio);
    }

    @computed
    private get minimumDim() {
        const ratioDocs = this.ratioDefinedDocs.filter(layout => layout._dimMagnitude);
        return ratioDocs.length ? Math.min(...ratioDocs.map(layout => NumCast(layout._dimMagnitude))) : 1;
    }

    /**
     * This loops through all childLayoutPairs and extracts the values for _dimUnit
     * and _dimUnit, ignoring any that are malformed. Additionally, it then
     * normalizes the ratio values so that one * value is always 1, with the remaining
     * values proportionate to that easily readable metric.
     * @returns the list of the resolved width specifiers (unit and magnitude pairs)
     * as well as the sum of the * coefficients, i.e. the ratio magnitudes
     */
    @computed
    private get resolvedLayoutInformation(): LayoutData {
        let starSum = 0;
        const heightSpecifiers: HeightSpecifier[] = [];
        this.childLayoutPairs.map(pair => {
            const unit = StrCast(pair.layout._dimUnit, '*');
            const magnitude = NumCast(pair.layout._dimMagnitude, this.minimumDim);
            if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) {
                unit === DimUnit.Ratio && (starSum += magnitude);
                heightSpecifiers.push({ magnitude, unit });
            }
            /**
             * Otherwise, the child document is ignored and the remaining
             * space is allocated as if the document were absent from the child list
             */
        });

        /**
         * Here, since these values are all relative, adjustments during resizing or
         * manual updating can, though their ratios remain the same, cause the values
         * themselves to drift toward zero. Thus, whenever we change any of the values,
         * we normalize everything (dividing by the smallest magnitude).
         */
        // setTimeout(() => {
        //     const { ratioDefinedDocs } = this;
        //     if (this.childLayoutPairs.length) {
        //         const minimum = Math.min(...ratioDefinedDocs.map(layout => NumCast(layout._dimMagnitude, 1)));
        //         if (minimum !== 0) {
        //             ratioDefinedDocs.forEach(layout => layout._dimMagnitude = NumCast(layout._dimMagnitude, 1) / minimum);
        //         }
        //     }
        // });

        return { heightSpecifiers, starSum };
    }

    /**
     * This returns the total quantity, in pixels, that this
     * view needs to reserve for child documents that have
     * (with higher priority) requested a fixed pixel width.
     *
     * If the underlying resolvedLayoutInformation returns null
     * because we're waiting on promises to resolve, this value will be undefined as well.
     */
    @computed
    private get totalFixedAllocation(): number | undefined {
        return this.resolvedLayoutInformation?.heightSpecifiers.reduce((sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0);
    }

    /**
     * @returns the total quantity, in pixels, that this
     * view needs to reserve for child documents that have
     * (with lower priority) requested a certain relative proportion of the
     * remaining pixel width not allocated for fixed widths.
     *
     * If the underlying totalFixedAllocation returns undefined
     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
     */
    @computed
    private get totalRatioAllocation(): number | undefined {
        const layoutInfoLen = this.resolvedLayoutInformation.heightSpecifiers.length;
        if (layoutInfoLen > 0 && this.totalFixedAllocation !== undefined) {
            return this._props.PanelHeight() - (this.totalFixedAllocation + resizerHeight * (layoutInfoLen - 1)) - 2 * NumCast(this._props.Document._yMargin);
        }
    }

    /**
     * @returns the total quantity, in pixels, that
     * 1* (relative / star unit) is worth. For example,
     * if the configuration has three documents, with, respectively,
     * widths of 2*, 2* and 1*, and the panel width returns 1000px,
     * this accessor returns 1000 / (2 + 2 + 1), or 200px.
     * Elsewhere, this is then multiplied by each relative-width
     * document's (potentially decimal) * count to compute its actual width (400px, 400px and 200px).
     *
     * If the underlying totalRatioAllocation or this.resolveLayoutInformation return undefined
     * because we're waiting indirectly on promises to resolve, this value will be undefined as well.
     */
    @computed
    private get rowUnitLength(): number | undefined {
        if (this.resolvedLayoutInformation && this.totalRatioAllocation !== undefined) {
            return this.totalRatioAllocation / this.resolvedLayoutInformation.starSum;
        }
    }

    /**
     * This wrapper function exists to prevent mobx from
     * needlessly rerendering the internal ContentFittingDocumentViews
     */
    private getRowUnitLength = () => this.rowUnitLength;

    /**
     * @param layout the document whose transform we'd like to compute
     * Given a layout document, this function
     * returns the resolved width it has requested, in pixels.
     * @returns the stored row width if already in pixels,
     * or the ratio width evaluated to a pixel value
     */
    private lookupPixels = (layout: Doc): number => {
        const rowUnitLength = this.rowUnitLength;
        if (rowUnitLength === undefined) {
            return 0; // we're still waiting on promises to resolve
        }
        let height = NumCast(layout._dimMagnitude, this.minimumDim);
        if (StrCast(layout._dimUnit, '*') === DimUnit.Ratio) {
            height *= rowUnitLength;
        }
        return height;
    };

    /**
     * @returns the transform that will correctly place
     * the document decorations box, shifted to the right by
     * the sum of all the resolved row widths of the
     * documents before the target.
     */
    private lookupIndividualTransform = (layout: Doc) => {
        const rowUnitLength = this.rowUnitLength;
        if (rowUnitLength === undefined) {
            return Transform.Identity(); // we're still waiting on promises to resolve
        }
        let offset = 0;
        for (const { layout: candidate } of this.childLayoutPairs) {
            if (candidate === layout) {
                return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1));
            }
            offset += this.lookupPixels(candidate) + resizerHeight;
        }
        return Transform.Identity(); // type coersion, this case should never be hit
    };

    @undoBatch
    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
        let dropInd = -1;
        if (de.complete.docDragData && this._mainCont) {
            let curInd = -1;
            de.complete.docDragData?.droppedDocuments.forEach(d => (curInd = this.childDocs.indexOf(d)));
            Array.from(this._mainCont.children).forEach((child, index) => {
                const brect = child.getBoundingClientRect();
                if (brect.y < de.y && brect.y + brect.height > de.y) {
                    if (curInd !== -1 && curInd === Math.floor(index / 2)) {
                        dropInd = curInd;
                    } else if (child.className === 'multiColumnResizer') {
                        dropInd = Math.floor(index / 2);
                    } else {
                        dropInd = Math.ceil(index / 2 + (de.y - brect.y > brect.height / 2 ? 0 : -1));
                    }
                }
            });
            if (super.onInternalDrop(e, de)) {
                de.complete.docDragData?.droppedDocuments.forEach(
                    action((d: Doc) => {
                        d._dimUnit = '*';
                        d._dimMagnitude = 1;
                        if (dropInd !== curInd || dropInd === -1) {
                            if (this.childDocs.includes(d)) {
                                if (dropInd > this.childDocs.indexOf(d)) dropInd--;
                            }
                            Doc.RemoveDocFromList(this.dataDoc, this._props.fieldKey, d);
                            Doc.AddDocToList(this.dataDoc, this._props.fieldKey, d, DocListCast(this.dataDoc[this._props.fieldKey])[dropInd], undefined, dropInd === -1);
                        }
                    })
                );
                return true;
            }
        }
        return false;
    };

    onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
    onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);

    isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive();
    isChildContentActive = () => {
        const childDocsActive = this._props.childDocumentsActive?.() ?? this.Document.childDocumentsActive;
        return this._props.isContentActive?.() === false || childDocsActive === false
            ? false //
            : this._props.isDocumentActive?.() && childDocsActive
              ? true
              : undefined;
    };
    getDisplayDoc = (layout: Doc) => {
        const height = () => this.lookupPixels(layout);
        const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0);
        const dxf = () =>
            this.lookupIndividualTransform(layout)
                .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin))
                .scale(this._props.NativeDimScaling?.() || 1);
        const shouldNotScale = () => this._props.fitContentsToBox?.() || BoolCast(layout.freeform_fitContentsToBox);
        return (
            <DocumentView
                Document={layout}
                TemplateDataDocument={layout.resolvedDataDoc as Doc}
                styleProvider={this._props.styleProvider}
                docViewPath={this._props.docViewPath}
                LayoutTemplate={this._props.childLayoutTemplate}
                LayoutTemplateString={this._props.childLayoutString}
                renderDepth={this._props.renderDepth + 1}
                PanelWidth={width}
                PanelHeight={height}
                rootSelected={this.rootSelected}
                dropAction={StrCast(this.Document.childDragAction) as dropActionType}
                onClick={this.onChildClickHandler}
                onDoubleClick={this.onChildDoubleClickHandler}
                ScreenToLocalTransform={dxf}
                isContentActive={this.isChildContentActive}
                isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive}
                hideResizeHandles={layout.layout_fitWidth || this._props.childHideResizeHandles ? true : false}
                hideDecorationTitle={this._props.childHideDecorationTitle}
                fitContentsToBox={this._props.fitContentsToBox}
                dragAction={this._props.childDragAction}
                focus={this._props.focus}
                childFilters={this.childDocFilters}
                childFiltersByRanges={this.childDocRangeFilters}
                searchFilterDocs={this.searchFilterDocs}
                dontRegisterView={this._props.dontRegisterView}
                addDocument={this._props.addDocument}
                moveDocument={this._props.moveDocument}
                removeDocument={this._props.removeDocument}
                whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged}
                addDocTab={this._props.addDocTab}
                pinToPres={this._props.pinToPres}
                bringToFront={returnFalse}
                dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as any} // 'y', 'x', 'xy'
            />
        );
    };
    /**
     * @returns the resolved list of rendered child documents, displayed
     * at their resolved pixel widths, each separated by a resizer.
     */
    @computed
    private get contents(): JSX.Element[] | null {
        const { childLayoutPairs } = this;
        const collector: JSX.Element[] = [];
        for (let i = 0; i < childLayoutPairs.length; i++) {
            const { layout } = childLayoutPairs[i];
            collector.push(
                <div className="document-wrapper" style={{ flexDirection: 'row', height: this.lookupPixels(layout) }} key={'wrapper' + i}>
                    {this.getDisplayDoc(layout)}
                    <HeightLabel layout={layout} collectionDoc={this.Document} />
                </div>,
                <ResizeBar
                    height={resizerHeight}
                    styleProvider={this._props.styleProvider}
                    isContentActive={this._props.isContentActive}
                    key={'resizer' + i}
                    columnUnitLength={this.getRowUnitLength}
                    toTop={layout}
                    toBottom={childLayoutPairs[i + 1]?.layout}
                />
            );
        }
        collector.pop(); // removes the final extraneous resize bar
        return collector;
    }

    render() {
        return (
            <div
                className="collectionMultirowView_contents"
                style={{
                    width: `calc(100% - ${2 * NumCast(this.Document._xMargin)}px)`,
                    height: `calc(100% - ${2 * NumCast(this.Document._yMargin)}px)`,
                    marginLeft: NumCast(this.Document._xMargin),
                    marginRight: NumCast(this.Document._xMargin),
                    marginTop: NumCast(this.Document._yMargin),
                    marginBottom: NumCast(this.Document._yMargin),
                }}
                ref={this.createDashEventsTarget}>
                {this.contents}
            </div>
        );
    }
}
