import { computed, flow, observable } from "mobx";

import { SingleFile } from "../models/singleFile.js";
import { request } from "../utils/request.js";
import { locationRestAttachmentGet } from "../wp-api/attachment.get.js";
import { locationRestAttachmentsDelete } from "../wp-api/attachments.delete.js";
import { locationRestAttachmentsGet } from "../wp-api/attachments.get.js";
import { locationRestAttachmentsPost } from "../wp-api/attachments.post.js";

import type { RootStore } from "./stores.js";
import type {
    ParamsRestAttachmentGet,
    RequestRestAttachmentGet,
    ResponseRestAttachmentGet,
} from "../wp-api/attachment.get.js";
import type {
    ParamsRestAttachmentsDelete,
    RequestRestAttachmentsDelete,
    ResponseRestAttachmentsDelete,
} from "../wp-api/attachments.delete.js";
import type {
    ParamsRestAttachmentsGet,
    RequestRestAttachmentsGet,
    ResponseRestAttachmentsGet,
    ResponseRestAttachmentsGetEntity,
} from "../wp-api/attachments.get.js";
import type {
    ParamsRestAttachmentsPost,
    RequestRestAttachmentsPost,
    ResponseRestAttachmentsPost,
} from "../wp-api/attachments.post.js";

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

class AttachmentsStore {
    @observable
    public analyzeBusy = false;

    @observable
    public regenerateBusy = false;

    @observable
    public singleBusy = false;

    @observable
    public single: SingleFile;

    @observable
    public all = new Map<number, ResponseRestAttachmentsGetEntity>();

    @observable
    public regenerated: number[] = [];

    @observable
    public analyzed: number[] = [];

    public readonly rootStore: RootStore;

    @computed public get regenerateFailures() {
        return this.regeneratedObjects.filter(({ error }) => !!error?.length).length;
    }

    @computed public get regeneratedObjects() {
        return this.regenerated.map((id) => this.all.get(id));
    }

    @computed public get analyzedObjects() {
        return this.analyzed.map((id) => this.all.get(id));
    }

    public constructor(rootStore: RootStore) {
        this.rootStore = rootStore;
    }

    public analyze: () => CancellablePromise<void> = flow(function* (this: AttachmentsStore) {
        this.analyzeBusy = true;
        this.analyzed = [];

        let page = 0;
        try {
            while (++page) {
                const result: ResponseRestAttachmentsGet = yield request<
                    RequestRestAttachmentsGet,
                    ParamsRestAttachmentsGet,
                    ResponseRestAttachmentsGet
                >({
                    location: locationRestAttachmentsGet,
                    params: {
                        page,
                        posts_per_page: 40,
                    },
                });

                if (result?.length) {
                    for (const row of result) {
                        this.all.set(row.id, row);
                        this.analyzed.unshift(row.id);
                    }
                } else {
                    break;
                }
            }
        } catch (e) {
            console.log(e);
        } finally {
            this.analyzeBusy = false;
            this.rootStore.statsStore.fetch();
        }
    });

    public fetchSingle: (id: number, inDialog?: any) => CancellablePromise<SingleFile> = flow(function* (
        this: AttachmentsStore,
        id,
        inDialog = true,
    ) {
        this.singleBusy = true;
        try {
            const result: ResponseRestAttachmentGet = yield request<
                RequestRestAttachmentGet,
                ParamsRestAttachmentGet,
                ResponseRestAttachmentGet
            >({
                location: locationRestAttachmentGet,
                params: {
                    id,
                },
            });

            const obj = new SingleFile(result);
            if (inDialog) {
                this.single = obj;
            }
            return obj;
        } finally {
            this.singleBusy = false;
            this.rootStore.statsStore.fetch();
        }
    });

    public regenerate: (
        sizes: string[],
        forceNewSchema: boolean,
        skipExisting: boolean,
        startPage?: any,
    ) => CancellablePromise<void> = flow(function* (
        this: AttachmentsStore,
        sizes,
        forceNewSchema,
        skipExisting,
        startPage = 0,
    ) {
        this.regenerateBusy = true;
        this.regenerated = [];
        const { chunkSize } = this.rootStore.optionStore.others;

        let page = startPage;
        try {
            while (++page) {
                const result: ResponseRestAttachmentsPost = yield request<
                    RequestRestAttachmentsPost,
                    ParamsRestAttachmentsPost,
                    ResponseRestAttachmentsPost
                >({
                    location: locationRestAttachmentsPost,
                    params: {
                        page,
                        posts_per_page: chunkSize,
                    },
                    request: {
                        forceNewSchema,
                        sizes,
                        skipExisting,
                    },
                });

                if (result?.length) {
                    for (const row of result) {
                        this.all.set(row.id, row);
                        this.regenerated.unshift(row.id);
                    }
                } else {
                    break;
                }
            }
        } catch (e) {
            console.log(e);

            // Catch 503er errors when the server is overloaded
            const eXhr = e as JQuery.jqXHR<any>;
            if (eXhr.status === 503) {
                // Do not rely on Retry-After, simply wait 10 seconds and try again
                yield new Promise((resolve) => setTimeout(resolve, 10000));
                return yield this.regenerate(sizes, forceNewSchema, skipExisting, page - 1);
            }
        } finally {
            this.regenerateBusy = false;
            this.rootStore.statsStore.fetch();
        }
    });

    public clear: () => CancellablePromise<void> = flow(function* (this: AttachmentsStore) {
        if (process.env.PLUGIN_CTX === "pro") {
            /* onlypro:start */
            this.analyzeBusy = true;
            this.analyzed = [];

            let page = 0;
            try {
                while (++page) {
                    const result: ResponseRestAttachmentsDelete = yield request<
                        RequestRestAttachmentsDelete,
                        ParamsRestAttachmentsDelete,
                        ResponseRestAttachmentsDelete
                    >({
                        location: locationRestAttachmentsDelete,
                        params: {
                            page,
                            posts_per_page: 100,
                        },
                    });

                    if (result?.length) {
                        for (const row of result) {
                            this.all.set(row.id, row);
                            this.analyzed.unshift(row.id);
                        }
                    } else {
                        break;
                    }
                }
            } catch (e) {
                console.log(e);
            } finally {
                this.analyzeBusy = false;
                this.rootStore.statsStore.fetch();
            }
            /* onlypro:end */
        } else {
            throw new Error("This feature is not available in the free version.");
        }
    });
}

export { AttachmentsStore };
