import { AxiosError, AxiosInstance, AxiosResponse } from "axios";
import { MainStateManager } from "../../MainStateManager";
import { ElementsOfFormFactory } from "../../Page/ElementsOfFormFactory";
import { SelfCheckedIDs } from "../../SelfModels/SelfCheckedIDs";
import { ISelfCheckTree } from "../../SelfModels/SelfCheckTree";
import { IMainStateFactory } from "../../Types";
import { ISpecifierCheckTreeView, IStructrulCodeTreeViewJson, StructrulCodeTreeViewCode } from "./StructrulCodeTreeViewCode";
import { TREECODE } from "amisa-paths";
import { StructureCoding } from "../../StructureCoding";


export interface IStructuralCodeFactoryForTreeView {
    urls: TREECODE;
    apiAxios: AxiosInstance;
    onlyLastLevel: boolean
    required: boolean;
    coding: StructureCoding;
    code: string;
    focusToElement: () => void;
    onChangeHandlerCode: (code: string) => void;
    getCodeData: () => void;
}
export class StructrulCodeTreeView {
    public forceUpdate = () => { }
    public mainStateManager: MainStateManager;
    public elementsOfForm;
    public get any() {
        return this as any;
    }

    public refForm?: React.RefObject<HTMLFormElement>;

    constructor(
        public mainStateFactory: IMainStateFactory,
        public selfStructuralCode: IStructuralCodeFactoryForTreeView,
        public onlyLastLevel: boolean,
        public closeModal: () => void,
    ) {
        this.mainStateManager = this.mainStateFactory.mainStateManager;
        this.elementsOfForm = new ElementsOfFormFactory(this.mainStateFactory);
        this.code = selfStructuralCode.code;
    }

    public code: string = '';
    private _nodes: StructrulCodeTreeViewCode[] = [];
    private _flatten?: StructrulCodeTreeViewCode[];
    public checkStates?: ISpecifierCheckTreeView[];

    public waitForLoad: boolean = true;
    public currentNodeWaitForLoading: boolean = false;

    public waitForEndLoading = (): Promise<void> => {
        return new Promise((resolve) => {
            if (this.waitForLoad || this.currentNodeWaitForLoading) {
                this.endingWaitForEndLoading = () => {
                    resolve();
                };
            } else {
                resolve();
            }
        });
    };
    public endingWaitForEndLoading = () => { }
    public selectedRow: StructrulCodeTreeViewCode | null = null;
    get nodes(): StructrulCodeTreeViewCode[] {
        return this._nodes;
    }
    get flatten(): StructrulCodeTreeViewCode[] {
        const flatten = this._flatten;
        if (flatten) {
            return flatten;
        } else {
            return this.getFlatten(this.nodes);
        }
    }

    private getFlatten = (nodes: StructrulCodeTreeViewCode[] | null): StructrulCodeTreeViewCode[] => {
        if (nodes === null) {
            return [];
        }
        return nodes.reduce((result: StructrulCodeTreeViewCode[], item: StructrulCodeTreeViewCode) => {
            return [...result, item, ...this.getFlatten(item.children)];
        }, []);
    }

    loadData = () => {
        this.waitForLoad = true;
        this.selfStructuralCode.apiAxios.get(this.selfStructuralCode.urls.tree)
            .then(response => {
                const result = response.data as IStructrulCodeTreeViewJson[];
                this.checkStates = SelfCheckedIDs.deserialize(result).checkStates;
                this.loadTreeView();
            })
            .catch(e => {
                console.error(e);
                this.waitForLoad = false;
                this.endingWaitForEndLoading();
                //this.events.trigger('form.wasErrored', e);
            });
    }

    loadTreeView = (): void => {
        const _findPropriateNodeByThisCodeForSelect = (code: string): void => {
            if (!code && this.flatten && this.flatten.length) {
                this.selectedRow = this.flatten[0];
                return;
            }

            let lastFind: StructrulCodeTreeViewCode | null = null;

            const _set = (item: StructrulCodeTreeViewCode) => {
                this.selectedRow = item;
                if (item.childCount && item.children && item.children.length) {
                    item.isExpanded = true;
                }
            };

            this.flatten.forEach((item: StructrulCodeTreeViewCode) => {
                if (code === item.code) {
                    _set(item);
                    return;
                } else if (
                    code.length > item.code.length &&
                    code.substring(0, item.code.length) === item.code
                ) {
                    _set(item);
                    lastFind = item;
                }
            });
            if (lastFind) {
                this._expandParent(lastFind);
            }
        };

        this.waitForLoad = true;
        this.selfStructuralCode.apiAxios.get(this.selfStructuralCode.urls.search, { params: { code: this.code } })
            .then((response: AxiosResponse): void => {
                const nodes: StructrulCodeTreeViewCode[] = response.data.map((item: StructrulCodeTreeViewCode) =>
                    this.deserializeRow(item)
                );

                this._nodes = nodes;
                this.checkStates = [];
                this._updateFlatten();
                _findPropriateNodeByThisCodeForSelect(this.code);
                this.waitForLoad = false;
                this.endingWaitForEndLoading();
                this.forceUpdate()
            })
            .catch((error: AxiosError): void => {
                this.waitForLoad = false;
                this.endingWaitForEndLoading();
                this.forceUpdate();
                console.error(error);
            });

    }


    selectPreviousNode(): void {
        const currentNode = this.currentNode();
        if (currentNode) {
            const siblingNodes = this.flatten.filter(
                (i) => i.parentId === currentNode.parentId
            );
            if (siblingNodes && siblingNodes.length > 1) {
                const currentNodeIndex = siblingNodes.indexOf(currentNode);
                if (currentNodeIndex > 0) {
                    this.selectThisNode(siblingNodes[currentNodeIndex - 1]);
                } else if (currentNodeIndex === 0) {
                    this.selectThisNode(siblingNodes[siblingNodes.length - 1]);
                }
            }
        } else {
            this.selectThisNode(this.flatten[0]);
        }
    }

    selectNextNode(): void {
        const currentNode = this.currentNode();
        if (currentNode) {
            const siblingNodes = this.flatten.filter(
                (i) => i.parentId === currentNode.parentId
            );
            if (siblingNodes && siblingNodes.length > 1) {
                const currentNodeIndex = siblingNodes.indexOf(currentNode);
                if (
                    currentNodeIndex > -1 &&
                    currentNodeIndex < siblingNodes.length - 1
                ) {
                    this.selectThisNode(siblingNodes[currentNodeIndex + 1]);
                } else if (currentNodeIndex === siblingNodes.length - 1) {
                    this.selectThisNode(siblingNodes[0]);
                }
            }
        } else {
            this.selectThisNode(this.flatten[0]);
        }
    }

    nodeCheckChange = (node: IStructrulCodeTreeViewJson) => {
        if (!this.checkStates) {
            this.checkStates = [];
        }

        if (this.flatten.find((i) => i.id === node.id && node.check === 1)) {
            this.removeCheckToNode(node);
        } else {
            this.addCheckToNode(node);
        }

        this.forceUpdate();
    };

    private removeCheckToNode(node: IStructrulCodeTreeViewJson) {
        if (node.parentId) {
            this.removeCheckToParent(node);
        }
        this.removeCheckToChild(node);
        this.checkStates = this.checkStates?.filter((i) => i.id !== node.id);
        node.check = 0;
    }

    private removeCheckToParent(node: IStructrulCodeTreeViewJson) {
        if (node.parentId) {
            const child = this.flatten.filter(
                (i) => i.parentId === node.parentId && i.check > 0
            );
            if (child.length === 1) {
                const parent = this.flatten.find((i) => i.id === node.parentId)!;
                this.removeCheckToParent(parent);
                this.checkStates = this.checkStates?.filter((i) => i.id !== parent.id);
                parent.check = 0;
            }
        }
    }

    private removeCheckToChild(node: IStructrulCodeTreeViewJson) {
        const children = this.flatten.filter((i) => i.parentId === node.id);
        if (children && children.length) {
            for (const child of children) {
                this.removeCheckToChild(child);
            }
        }
        this.checkStates = this.checkStates?.filter((i) => i.id !== node.id);
        node.check = 0;
    }

    private addCheckToNode(node: IStructrulCodeTreeViewJson) {
        if (this.onlyLastLevel && node.childCount > 0) {
            return;
        }
        if (node.parentId) {
            this.addCheckType4ToParent(this.flatten.find((i) => i.id === node.parentId)!);
        }
        this.addCheckType4ToChild(node);
        this.checkStates?.push({
            id: node.id,
            isFromChild: false,
            isFromParent: false,
            code: node as any,
        });
        node.check = 1;
    }

    private addCheckType4ToParent(node: IStructrulCodeTreeViewJson) {
        if (node.parentId) {
            this.addCheckType4ToParent(
                this.flatten.find((i) => i.id === node.parentId)!
            );
        }
        this.checkStates?.push({
            id: node.id,
            isFromChild: true,
            isFromParent: false,
            code: node as any,
        });
        node.check = 4;
    }
    private addCheckType4ToChild(node: IStructrulCodeTreeViewJson) {
        const children = this.flatten.filter((i) => i.parentId === node.id);
        if (children && children.length) {
            for (const child of children) {
                this.addCheckType4ToChild(child);
            }
        }
        this.checkStates?.push({
            id: node.id,
            isFromChild: true,
            isFromParent: false,
            code: node as any,
        });
        node.check = 4;
    }

    private _setUnCheckNodeCheckType(mainNode: IStructrulCodeTreeViewJson) {
        mainNode.check = 0;
        if (mainNode.children && mainNode.children.length) {
            mainNode.children.map((i) => this._setUnCheckNodeCheckType(i));
        }
    }
    private _setParentCheckNodeCheckType(mainNode: IStructrulCodeTreeViewJson, check: ISelfCheckTree) {
        mainNode.check = check;
        if (mainNode.children && mainNode.children.length) {
            mainNode.children.map((i) => this._setParentCheckNodeCheckType(i, 4));
        }
    }
    private _setDoubleCheckNodeCheckType(mainNode: IStructrulCodeTreeViewJson) {
        mainNode.check = 2;
        if (mainNode.children && mainNode.children.length) {
            mainNode.children.map((i) => this._setDoubleCheckNodeCheckType(i));
        }
    }
    private _setSingleCheckNodeCheckType(mainNode: IStructrulCodeTreeViewJson) {
        mainNode.check = 3;
        if (mainNode.children && mainNode.children.length) {
            mainNode.children.map((i) => this._setSingleCheckNodeCheckType(i));
        }
    }

    selectFirstNode(): void {
        if (this.nodes.length > 0) {
            this.selectThisNode(this.nodes[0]);
        }
    }
    selectLastNode(): void {
        if (this.nodes.length > 0) {
            this.selectThisNode(this.nodes[this.nodes.length - 1]);
        }
    }

    expandCurrentNode = (): void => {
        const currentNode = this.currentNode();
        if (currentNode) {
            this.expandThisNode(currentNode);
        }
    };
    expandThisNode = (node: StructrulCodeTreeViewCode): void => {
        if (node.childCount && node.children && node.children.length) {
            node.isExpanded = true;
            this.selectThisNodeFirstChild(node);
        } else if (node.childCount && !(node.children && node.children.length)) {
            node.isExpanded = true;
            node.isLoading = true;
            this.forceUpdate();
            this.getTreeViewChildren(node);
        }
    };
    getTreeViewChildren = (node: StructrulCodeTreeViewCode): void => {
        node.isLoading = true;
        node.isExpanded = true;
        this.currentNodeWaitForLoading = true;
        this.forceUpdate();
        this.selfStructuralCode.apiAxios
            .get(`${this.selfStructuralCode.urls.treeChild}/${node.id}`)
            .then((response: AxiosResponse): void => {
                node.children = response.data.map((item: IStructrulCodeTreeViewJson) =>
                    this.deserializeRow(item)
                );
                node.isLoading = false;
                this.currentNodeWaitForLoading = false;
                this._updateFlatten();
                this.selectThisNodeFirstChild(node);
                this.forceUpdate();
            })
            .catch((error: AxiosError): void => {
                this.currentNodeWaitForLoading = false;
                node.isLoading = false;
                console.error(error);
                this.forceUpdate();
            });
    };
    get canCloseItsModal(): boolean {
        const currentNode = this.currentNode();
        if (currentNode) {
            if (currentNode.parentId) {
                const parentNode = this.flatten.find(
                    (i) => i.id === currentNode.parentId
                );
                if (parentNode && parentNode.isExpanded) {
                    return false;
                }
            }
        }
        return true;
    }

    collapseCurrentNode = () => {
        const currentNode = this.currentNode();
        if (currentNode) {
            this.collapseThisNode(currentNode);
        }
    };
    collapseThisNode = (node: IStructrulCodeTreeViewJson) => {
        if (node.isExpanded) {
            node.isExpanded = false;
            this.forceUpdate();
        } else {
            if (node.parentId) {
                const parentNode = this.flatten.find((i) => i.id === node.parentId);
                if (parentNode) {
                    this.selectThisNode(parentNode);
                }
            }
        }
    };
    toggleThisNode = (node: StructrulCodeTreeViewCode): void => {
        if (node.isExpanded) {
            this.collapseThisNode(node);
        } else {
            this.expandThisNode(node);
        }
    };

    private _updateFlatten = (): void => {
        this._flatten = this.getFlatten(this.nodes);
    };

    currentNode = (): StructrulCodeTreeViewCode | undefined => {
        return this.flatten.find((i) => i === this.selectedRow);
    };

    selectThisNodeFirstChild(node: StructrulCodeTreeViewCode) {
        if (node.children && node.children.length) {
            const firstChild = (node.children)[0];
            this.selectThisNode(firstChild);
        }
    }
    selectThisNode = (node: StructrulCodeTreeViewCode): void => {
        this.selectedRow = node;
        this.forceUpdate();
    };

    private _expandParent = (node: IStructrulCodeTreeViewJson): void => {
        if (!node.parentId) {
            return;
        }
        const parentNode = this.flatten.find((i) => i.id === node.parentId);
        if (parentNode) {
            parentNode.isExpanded = true;
            this._expandParent(parentNode);
        }
    };


    deserializeRow = (json: IStructrulCodeTreeViewJson): StructrulCodeTreeViewCode => {
        if (this.checkStates) {
            if (this.checkStates.find((i) => i.id === json.id && !i.isFromChild)) {
                json.check = 1;
            } else if (this.checkStates.find((i) => i.id === json.id && i.isFromChild)) {
                json.check = 4;
            } else if (this.checkStates.find((i) => i.id === json.parentId && !i.isFromChild)
            ) {
                this.checkStates?.push({
                    id: json.id,
                    isFromChild: false,
                    isFromParent: true,
                    code: json as any
                });
                json.check = 4;
            }
        }

        const result = StructrulCodeTreeViewCode.deserialize(json, json.children || []);
        return result;
    };

    acceptCurrentRow = () => {
        if (this.selectedRow) {
            this.closeModal();
            this.selfStructuralCode.focusToElement();
            this.selfStructuralCode.onChangeHandlerCode(this.selectedRow.code);
            this.selfStructuralCode.getCodeData();
        }
    }

    cancelForm = () => {
        this.closeModal();
        this.selfStructuralCode.focusToElement();
    }
}
