import { observable, computed, action, makeObservable } from "mobx";

import { BoundingRectBuilder } from "eez-studio-shared/geometry";

import { TreeObjectAdapter } from "project-editor/core/objectAdapter";

import { getClassInfo, getProjectStore } from "project-editor/store";

import type {
    IViewState,
    IFlowContext,
    IEditorOptions,
    IResizeHandler,
    IDataContext,
    ObjectIdUnderPointer
} from "project-editor/flow/flow-interfaces";

import { Component, getWidgetParent } from "project-editor/flow/component";
import type { FlowTabState } from "project-editor/flow/flow-tab-state";
import type { ConnectionLine } from "project-editor/flow/connection-line";

import { FlowDocument } from "project-editor/flow/editor/flow-document";
import { Transform } from "project-editor/flow/editor/transform";

////////////////////////////////////////////////////////////////////////////////

class ViewState implements IViewState {
    get transform() {
        return this.flowContext.tabState.transform;
    }

    set transform(transform: Transform) {
        this.flowContext.tabState.transform = transform;
    }

    dxMouseDrag: number | undefined;
    dyMouseDrag: number | undefined;

    hoveredConnectionLines: ObjectIdUnderPointer | undefined;

    constructor(public flowContext: EditorFlowContext) {
        makeObservable(this, {
            dxMouseDrag: observable,
            dyMouseDrag: observable,
            hoveredConnectionLines: observable,
            resetTransform: action,
            selectedObjects: computed,
            selectObjects: action,
            deselectAllObjects: action,
            hoveredConnectionLinesComponent: computed
        });
    }

    get projectStore() {
        return this.flowContext.projectStore;
    }

    get document() {
        return this.flowContext.document;
    }

    get containerId() {
        return this.flowContext.containerId;
    }

    resetTransform() {
        this.flowContext.tabState.resetTransform();
    }

    centerView() {
        this.flowContext.tabState.centerView();
    }

    getResizeHandlers(): IResizeHandler[] | undefined {
        const isEditor = this.document && !this.document.projectStore.runtime;

        if (
            !isEditor ||
            this.selectedObjects.length !== 1 ||
            !this.selectedObjects[0].getResizeHandlers
        ) {
            return undefined;
        }

        const resizingHandlers = this.selectedObjects[0].getResizeHandlers();
        if (resizingHandlers !== false) {
            return resizingHandlers;
        }

        return [
            {
                x: 0,
                y: 0,
                type: "nw-resize"
            },
            {
                x: 50,
                y: 0,
                type: "n-resize"
            },
            {
                x: 100,
                y: 0,
                type: "ne-resize"
            },
            {
                x: 0,
                y: 50,
                type: "w-resize"
            },
            {
                x: 100,
                y: 50,
                type: "e-resize"
            },
            {
                x: 0,
                y: 100,
                type: "sw-resize"
            },
            {
                x: 50,
                y: 100,
                type: "s-resize"
            },
            {
                x: 100,
                y: 100,
                type: "se-resize"
            }
        ];
    }

    get selectedObjects(): TreeObjectAdapter[] {
        return this.flowContext.dragComponent
            ? [new TreeObjectAdapter(this.flowContext.dragComponent)]
            : this.document?.flow.selectedItems ?? [];
    }

    get connectionLine() {
        return undefined;
    }

    get sourceComponent() {
        return undefined;
    }

    get targetComponent() {
        return undefined;
    }

    isObjectSelected(object: TreeObjectAdapter): boolean {
        return this.selectedObjects.indexOf(object) !== -1;
    }

    isObjectIdSelected(id: string): boolean {
        return (
            this.selectedObjects
                .map(selectedObject => selectedObject.id)
                .indexOf(id) !== -1
        );
    }

    selectObject(object: TreeObjectAdapter) {
        if (object.isSelectable && this.document) {
            this.document.flow.selectItem(object);
        }
    }

    deselectObject(object: TreeObjectAdapter) {
        if (this.document) {
            this.document.flow.deselectItem(object);
        }
    }

    selectObjects(objects: TreeObjectAdapter[]) {
        if (this.document) {
            this.document.flow.selectItems(
                objects.filter(object => object.isSelectable)
            );
        }
    }

    deselectAllObjects(): void {
        if (this.document) {
            this.document.flow.selectItems([]);
        }
    }

    moveSelection(
        where:
            | "left"
            | "up"
            | "right"
            | "down"
            | "home-x"
            | "end-x"
            | "home-y"
            | "end-y"
    ) {
        const components =
            this.document &&
            (this.document.flow.selectedObjects.filter(
                object => object instanceof Component
            ) as Component[]);

        if (!components || components.length === 0) {
            return;
        }

        const builder = new BoundingRectBuilder();
        components.forEach(widget => {
            builder.addRect(widget.rect);
        });
        const boundingRect = builder.getRect();

        const allWidgetsAreFromTheSameParent = !components.find(
            widget => getWidgetParent(widget) !== getWidgetParent(components[0])
        );

        const projectStore = getProjectStore(components[0]);

        projectStore.undoManager.setCombineCommands(true);

        components.forEach(component => {
            const classInfo = getClassInfo(component);
            const setRect = classInfo.setRect!;

            if (where === "left") {
                setRect(component, {
                    left: component.rect.left - 1
                });
            } else if (where === "up") {
                setRect(component, {
                    top: component.rect.top - 1
                });
            } else if (where === "right") {
                setRect(component, {
                    left: component.rect.left + 1
                });
            } else if (where === "down") {
                setRect(component, {
                    top: component.rect.top + 1
                });
            } else if (allWidgetsAreFromTheSameParent) {
                if (where === "home-x") {
                    setRect(component, {
                        left: 0 + component.rect.left - boundingRect.left
                    });
                } else if (where === "end-x") {
                    setRect(component, {
                        left:
                            getWidgetParent(component).width -
                            boundingRect.width +
                            component.rect.left -
                            boundingRect.left
                    });
                } else if (where === "home-y") {
                    setRect(component, {
                        top: 0 + component.rect.top - boundingRect.top
                    });
                } else if (where === "end-y") {
                    setRect(component, {
                        top:
                            getWidgetParent(component).height -
                            boundingRect.height +
                            component.rect.top -
                            boundingRect.top
                    });
                }
            }
        });

        projectStore.undoManager.setCombineCommands(false);
    }

    get hoveredConnectionLinesComponent() {
        if (!this.hoveredConnectionLines) {
            return undefined;
        }
        return this.flowContext.projectStore.getObjectFromObjectId(
            this.hoveredConnectionLines.id
        ) as Component;
    }

    isConnectionLineHovered(connectionLine: ConnectionLine) {
        if (!this.hoveredConnectionLines) {
            return false;
        }
        return (
            (connectionLine.sourceComponent ==
                this.hoveredConnectionLinesComponent &&
                connectionLine.output ==
                    this.hoveredConnectionLines.connectionOutput) ||
            (connectionLine.targetComponent ==
                this.hoveredConnectionLinesComponent &&
                connectionLine.input ==
                    this.hoveredConnectionLines.connectionInput)
        );
    }
}

////////////////////////////////////////////////////////////////////////////////

export class EditorFlowContext implements IFlowContext {
    tabState: FlowTabState;
    document: FlowDocument;

    viewState: ViewState = new ViewState(this);
    editorOptions: IEditorOptions = {};
    dragComponent: Component | undefined;
    dataContext: IDataContext;

    constructor() {
        makeObservable(this, {
            dragComponent: observable
        });
    }

    get projectStore() {
        return this.document.projectStore;
    }

    get containerId() {
        return this.tabState.containerId;
    }

    get flow() {
        return this.tabState.flow;
    }

    get flowState() {
        return this.tabState.flowState;
    }

    get frontFace() {
        return this.tabState.frontFace;
    }

    overrideDataContext(dataContextOverridesObject: any): IFlowContext {
        if (!dataContextOverridesObject) {
            return this;
        }
        return Object.assign(new EditorFlowContext(), this, {
            dataContext: this.dataContext.createWithDefaultValueOverrides(
                dataContextOverridesObject
            )
        });
    }

    overrideFlowState(component: Component): IFlowContext {
        return this;
    }

    set(tabState: FlowTabState, options?: IEditorOptions) {
        this.tabState = tabState;

        this.document = new FlowDocument(tabState.widgetContainer, this);

        this.editorOptions = options || {
            center: {
                x: 0,
                y: 0
            }
        };

        this.dataContext =
            this.document.projectStore.dataContext.createWithLocalVariables(
                this.flow.userPropertiesAndLocalVariables
            );
    }
}
