import $ from "jquery";
import { action, flow, observable, runInAction, set } from "mobx";

import type { TreeNodeProps } from "@devowl-wp/react-folder-tree";

import { request } from "../utils/request.js";
import { locationRestTermsDelete } from "../wp-api/terms.delete.js";
import { locationRestTermsPut } from "../wp-api/terms.put.js";

import type { TreeStore } from "../store/tree.js";
import type { ClassProperties } from "../utils/types.js";
import type {
    ParamsRouteTermsDelete,
    RequestRouteTermsDelete,
    ResponseRouteTermsDelete,
} from "../wp-api/terms.delete.js";
import type { ParamsRouteTermsPut, RequestRouteTermsPut, ResponseRouteTermsPut } from "../wp-api/terms.put.js";
import type { ResponseRouteTreeGet, SingleTreeItem } from "../wp-api/tree.get.js";

type CancellablePromise<R> = ReturnType<ReturnType<typeof flow<R, any>>>;

class CategoryNode {
    @observable
    public id: ResponseRouteTreeGet["selectedId"];

    @observable
    public hash = "";

    @observable
    public className = "";

    @observable
    public icon = "";

    @observable
    public iconActive = "";

    @observable
    public childNodes: CategoryNode[] = [];

    @observable
    public title = "";

    @observable
    public count = 0;

    @observable
    public isTreeLinkDisabled = false;

    @observable
    public selected = false;

    @observable
    public $busy = false;

    @observable
    public $droppable = true;

    @observable
    public $visible = true;

    @observable
    public $rename = false;

    @observable
    public $create: TreeNodeProps;

    @observable
    public properties: any;

    @observable
    public isQueried = true;

    @observable
    public parent?: CategoryNode;

    public readonly treeStore: TreeStore;

    public constructor(categoryNode: Partial<ClassProperties<CategoryNode>>, treeStore: TreeStore) {
        this.treeStore = treeStore;
        runInAction(() => {
            set(this, categoryNode);
            if (this.id) {
                treeStore.refs.set(this.id, this);
            }
        });
    }

    public static mapFromRestEndpoint(
        this: TreeStore,
        { term_id, name, count, childNodes, ...rest }: SingleTreeItem,
    ): CategoryNode {
        return new CategoryNode(
            {
                id: term_id,
                title: name,
                count,
                icon: "folder",
                iconActive: "folder-open",
                childNodes: childNodes ? childNodes.map(CategoryNode.mapFromRestEndpoint.bind(this)) : [],
                properties: rest,
            },
            this,
        );
    }

    public setTitle: (name: string) => CancellablePromise<ResponseRouteTermsPut> = flow(function* (
        this: CategoryNode,
        name,
    ) {
        this.$busy = true;
        try {
            const result: ResponseRouteTermsPut = yield request<
                RequestRouteTermsPut,
                ParamsRouteTermsPut,
                ResponseRouteTermsPut
            >({
                location: locationRestTermsPut,
                params: {
                    id: +this.id,
                },
                request: {
                    name: name,
                    taxonomy: this.properties.taxonomy,
                },
            });

            this.title = name;
            this.properties = $.extend({}, this.properties, result);
            return result;
        } finally {
            this.$busy = false;
        }
    });

    public trash: () => CancellablePromise<void> = flow(function* (this: CategoryNode) {
        this.$busy = true;
        try {
            yield request<RequestRouteTermsDelete, ParamsRouteTermsDelete, ResponseRouteTermsDelete>({
                location: locationRestTermsDelete,
                params: {
                    id: +this.id,
                    taxonomy: this.properties.taxonomy,
                },
            });

            this.$visible = false;
        } finally {
            this.$busy = false;
        }
    });

    @action
    public overwriteCompletelyFromResponse(categoryNode: Partial<ClassProperties<CategoryNode>>) {
        // set(this, categoryNode); does not work as it removes not passed observables
        $.each(categoryNode, (key, value) => set(this, key, value));
    }

    /**
     * Do not use it directly, it is automatically used by TreeStore#persist.
     */
    @action
    public addChildNode(node: CategoryNode) {
        this.childNodes.push(node);
    }

    @action
    public setSelected(state: boolean) {
        if (this.selected === state) {
            return;
        }

        this.selected = state;
        if (state) {
            this.treeStore.setSelected(this);
        }
    }

    @action
    public setBusy(state: boolean) {
        this.$busy = state;
    }

    @action
    public setRename(state: boolean) {
        this.$rename = state;
    }

    @action
    public setCreate(data: TreeNodeProps) {
        this.$create = data;
    }
}

export { CategoryNode };
