import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import { Doc, Opt } from '../../../fields/Doc';
import { List } from '../../../fields/List';
import { listSpec } from '../../../fields/Schema';
import { ComputedField } from '../../../fields/ScriptField';
import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { TraceMobx } from '../../../fields/util';
import { numberRange } from '../../../Utils';
import { DocumentManager } from '../../util/DocumentManager';
import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { DocComponent } from '../DocComponent';
import { StyleProp } from '../StyleProvider';
import './CollectionFreeFormDocumentView.scss';
import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView';
import React = require('react');

export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
    dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined;
    sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined;
    renderCutoffProvider: (doc: Doc) => boolean;
    zIndex?: number;
    dataTransition?: string;
    replica: string;
    CollectionFreeFormView: CollectionFreeFormView;
}

@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() {
    public static animFields: { key: string; val?: number }[] = [
        { key: '_height' },
        { key: '_width' },
        { key: 'x' },
        { key: 'y' },
        { key: '_rotation', val: 0 },
        { key: '_layout_scrollTop' },
        { key: 'opacity', val: 1 },
        { key: '_currentFrame' },
        { key: 'freeform_scale', val: 1 },
        { key: 'freeform_panX' },
        { key: 'freeform_panY' },
    ]; // fields that are configured to be animatable using animation frames
    public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames
    public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames
    @observable _animPos: number[] | undefined = undefined;
    @observable _contentView: DocumentView | undefined | null;
    get displayName() {
        // this makes mobx trace() statements more descriptive
        return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')';
    }

    get transform() {
        return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rot, this.Rot)}deg)`;
    }
    get X() {
        return this.dataProvider?.x ?? NumCast(this.Document.x);
    }
    get Y() {
        return this.dataProvider?.y ?? NumCast(this.Document.y);
    }
    get ZInd() {
        return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex);
    }
    get Rot() {
        return this.dataProvider?.rotation ?? NumCast(this.Document._rotation);
    }
    get Opacity() {
        return this.dataProvider?.opacity;
    }
    get BackgroundColor() {
        return this.dataProvider?.backgroundColor ?? Cast(this.Document._backgroundColor, 'string', null);
    }
    get Color() {
        return this.dataProvider?.color ?? Cast(this.Document._color, 'string', null);
    }
    @computed get dataProvider() {
        return this.props.dataProvider?.(this.props.Document, this.props.replica);
    }
    @computed get sizeProvider() {
        return this.props.sizeProvider?.(this.props.Document, this.props.replica);
    }

    styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => {
        if (doc === this.layoutDoc) {
            // prettier-ignore
            switch (property) {
                case StyleProp.Opacity:         return this.Opacity; // only change the opacity for this specific document, not its children
                case StyleProp.BackgroundColor: return this.BackgroundColor;
                case StyleProp.Color:           return this.Color;
            }
        }
        return this.props.styleProvider?.(doc, props, property);
    };

    public static getValues(doc: Doc, time: number, fillIn: boolean = true) {
        return CollectionFreeFormDocumentView.animFields.reduce((p, val) => {
            p[val.key] = Cast(doc[`${val.key}-indexed`], listSpec('number'), fillIn ? [NumCast(doc[val.key], val.val)] : []).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as number);
            return p;
        }, {} as { [val: string]: Opt<number> });
    }

    public static getStringValues(doc: Doc, time: number) {
        return CollectionFreeFormDocumentView.animStringFields.reduce((p, val) => {
            p[val] = Cast(doc[`${val}-indexed`], listSpec('string'), [StrCast(doc[val])]).reduce((p, v, i) => ((i <= Math.round(time) && v !== undefined) || p === undefined ? v : p), undefined as any as string);
            return p;
        }, {} as { [val: string]: Opt<string> });
    }

    public static setStringValues(time: number, d: Doc, vals: { [val: string]: Opt<string> }) {
        const timecode = Math.round(time);
        Object.keys(vals).forEach(val => {
            const findexed = Cast(d[`${val}-indexed`], listSpec('string'), []).slice();
            findexed[timecode] = vals[val] as any as string;
            d[`${val}-indexed`] = new List<string>(findexed);
        });
    }

    public static setValues(time: number, d: Doc, vals: { [val: string]: Opt<number> }) {
        const timecode = Math.round(time);
        Object.keys(vals).forEach(val => {
            const findexed = Cast(d[`${val}-indexed`], listSpec('number'), []).slice();
            findexed[timecode] = vals[val] as any as number;
            d[`${val}-indexed`] = new List<number>(findexed);
        });
    }

    public static setupZoom(doc: Doc, targDoc: Doc) {
        const width = new List<number>();
        const height = new List<number>();
        const top = new List<number>();
        const left = new List<number>();
        width.push(NumCast(targDoc._width));
        height.push(NumCast(targDoc._height));
        top.push(NumCast(targDoc._height) / -2);
        left.push(NumCast(targDoc._width) / -2);
        doc['viewfinder-width-indexed'] = width;
        doc['viewfinder-height-indexed'] = height;
        doc['viewfinder-top-indexed'] = top;
        doc['viewfinder-left-indexed'] = left;
    }

    public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) {
        docs.forEach(doc => {
            if (doc.appearFrame === undefined) doc.appearFrame = currTimecode;
            if (!doc['opacity-indexed']) {
                // opacity is unlike other fields because it's value should not be undefined before it appears to enable it to fade-in
                doc['opacity-indexed'] = new List<number>(numberRange(currTimecode + 1).map(t => (!doc.z && makeAppear && t < NumCast(doc.appearFrame) ? 0 : 1)));
            }
            CollectionFreeFormDocumentView.animFields.forEach(val => (doc[val.key] = ComputedField.MakeInterpolatedNumber(val.key, 'activeFrame', doc, currTimecode, val.val)));
            CollectionFreeFormDocumentView.animStringFields.forEach(val => (doc[val] = ComputedField.MakeInterpolatedString(val, 'activeFrame', doc, currTimecode)));
            CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => (doc[val] = ComputedField.MakeInterpolatedDataField(val, 'activeFrame', doc, currTimecode)));
            const targetDoc = doc; // data fields, like rtf 'text' exist on the data doc, so
            //doc !== targetDoc && (targetDoc.embedContainer = doc.embedContainer); // the computed fields don't see the layout doc -- need to copy the embedContainer to the data doc (HACK!!!) and set the activeFrame on the data doc (HACK!!!)
            targetDoc.activeFrame = ComputedField.MakeFunction('self.embedContainer?._currentFrame||0');
            targetDoc.dataTransition = 'inherit';
        });
    }

    @action public float = () => {
        const topDoc = this.rootDoc;
        const containerDocView = this.props.docViewPath().lastElement();
        const screenXf = containerDocView?.screenToLocalTransform();
        if (screenXf) {
            SelectionManager.DeselectAll();
            if (topDoc.z) {
                const spt = screenXf.inverse().transformPoint(NumCast(topDoc.x), NumCast(topDoc.y));
                topDoc.z = 0;
                topDoc.x = spt[0];
                topDoc.y = spt[1];
                this.props.removeDocument?.(topDoc);
                this.props.addDocTab(topDoc, OpenWhere.inParentFromScreen);
            } else {
                const spt = this.screenToLocalTransform().inverse().transformPoint(0, 0);
                const fpt = screenXf.transformPoint(spt[0], spt[1]);
                topDoc.z = 1;
                topDoc.x = fpt[0];
                topDoc.y = fpt[1];
            }
            setTimeout(() => SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0);
        }
    };

    dragEnding = () => this.props.CollectionFreeFormView?.dragEnding();
    dragStarting = () => this.props.CollectionFreeFormView?.dragStarting(false, true);

    nudge = (x: number, y: number) => {
        this.props.Document.x = NumCast(this.props.Document.x) + x;
        this.props.Document.y = NumCast(this.props.Document.y) + y;
    };
    panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.();
    panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.();
    screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y);
    returnThis = () => this;

    /// this indicates whether the doc view is activated because of its relationshop to a group
    //  'group' - this is a group that is activated because it's on an active canvas, but is not part of some other group
    //  'child' - this is a group child that is activated because its containing group is activated
    //  'inactive' - this is a group child but it is not active
    //  undefined - this is not activated by a group
    isGroupActive = () => {
        if (this.props.CollectionFreeFormView.isAnyChildContentActive()) return undefined;
        const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent');
        return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined;
    };
    render() {
        TraceMobx();
        const divProps: DocumentViewProps = {
            ...this.props,
            CollectionFreeFormDocumentView: this.returnThis,
            styleProvider: this.styleProvider,
            ScreenToLocalTransform: this.screenToLocalTransform,
            PanelWidth: this.panelWidth,
            PanelHeight: this.panelHeight,
            isGroupActive: this.isGroupActive,
        };
        return (
            <div
                className="collectionFreeFormDocumentView-container"
                style={{
                    width: this.panelWidth(),
                    height: this.panelHeight(),
                    transform: this.transform,
                    transformOrigin: '50% 50%',
                    transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)),
                    zIndex: this.ZInd,
                    display: this.sizeProvider?.width ? undefined : 'none',
                    pointerEvents: 'none',
                }}>
                {this.props.renderCutoffProvider(this.props.Document) ? (
                    <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} />
                ) : (
                    <DocumentView {...divProps} ref={action((r: DocumentView | null) => (this._contentView = r))} />
                )}
            </div>
        );
    }
}
