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

import { BaseOptions } from "@devowl-wp/utils";

import { TcfDataCategory } from "../models/tcfDataCategory.js";
import { TcfFeature } from "../models/tcfFeature.js";
import { TcfPurpose } from "../models/tcfPurpose.js";
import { TcfVendor } from "../models/tcfVendor.js";
import { TcfVendorConfigurationCollection } from "../models/tcfVendorConfigurationCollection.js";
import { request } from "../utils/request.js";
import { locationRestTcfDeclarationsGet } from "../wp-api/tcfDeclarations.get.js";
import { locationRestTcfGvlPut } from "../wp-api/tcfGvl.put.js";
import { locationRestTcfVendorsGet } from "../wp-api/tcfVendors.get.js";

import type { RootStore } from "./stores.js";
import type {
    ParamsRouteTcfDeclarationsGet,
    RequestRouteTcfDeclarationsGet,
    ResponseRouteTcfDeclarationsGet,
} from "../wp-api/tcfDeclarations.get.js";
import type { ParamsRouteTcfGvlPut, RequestRouteTcfGvlPut, ResponseRouteTcfGvlPut } from "../wp-api/tcfGvl.put.js";
import type {
    ParamsRouteTcfVendorsGet,
    RequestRouteTcfVendorsGet,
    ResponseRouteTcfVendorsGet,
} from "../wp-api/tcfVendors.get.js";
import type { Declarations } from "@iabtechlabtcf/core";

class TcfStore extends BaseOptions {
    @observable
    public busyGvl = false;

    @observable
    public busyVendors = false;

    @observable
    public busyDeclarations = false;

    @observable
    public vendorConfigurations: TcfVendorConfigurationCollection;

    @observable
    public fetchedAllVendorConfigurations = false;

    @observable
    public vendorListVersion: ResponseRouteTcfVendorsGet["vendorListVersion"];

    @observable
    public gvlSpecificationVersion: ResponseRouteTcfDeclarationsGet["gvlSpecificationVersion"];

    @observable
    public tcfPolicyVersion: ResponseRouteTcfDeclarationsGet["tcfPolicyVersion"];

    @observable
    public declarations: Omit<Declarations, "stacks">;

    @observable
    public vendors = new Map<string, TcfVendor>();

    @observable
    public purposes = new Map<string, TcfPurpose>();

    @observable
    public specialPurposes = new Map<string, TcfPurpose>();

    @observable
    public features = new Map<string, TcfFeature>();

    @observable
    public specialFeatures = new Map<string, TcfFeature>();

    @observable
    public dataCategories = new Map<string, TcfDataCategory>();

    public readonly rootStore: RootStore;

    @computed
    public get vendorConfigurationCount() {
        return this.fetchedAllVendorConfigurations
            ? this.vendorConfigurations.entries.size
            : this.rootStore.optionStore.allTcfVendorConfigurationCount;
    }

    public constructor(rootStore: RootStore) {
        super();
        this.rootStore = rootStore;
        runInAction(() => {
            this.vendorConfigurations = new TcfVendorConfigurationCollection(this);
        });
    }

    public fetchVendorConfigurations: () => Promise<void> = flow(function* (this: TcfStore) {
        // In our UI, we do not have pagination, yet, so load all
        const perPage = 100;
        const pages = Math.ceil(this.vendorConfigurationCount / perPage);
        for (let i = 0; i < pages; i++) {
            yield this.vendorConfigurations.get({
                request: {
                    status: ["draft", "publish", "private"],
                },
                params: {
                    offset: i * perPage,
                    per_page: 100, // Fetch all
                    context: "edit",
                },
            });
        }

        this.fetchedAllVendorConfigurations = true;
    });

    public fetchVendors: () => Promise<void> = flow(function* (this: TcfStore) {
        this.busyVendors = true;
        try {
            const { vendorListVersion, vendors }: ResponseRouteTcfVendorsGet = yield request<
                RequestRouteTcfVendorsGet,
                ParamsRouteTcfVendorsGet,
                ResponseRouteTcfVendorsGet
            >({
                location: locationRestTcfVendorsGet,
            });

            for (const vid of Object.keys(vendors)) {
                this.vendors.set(vid, new TcfVendor(vendors[vid], this));
            }
            this.vendorListVersion = vendorListVersion;
        } catch (e) {
            console.log(e);
            throw e;
        } finally {
            this.busyVendors = false;
        }
    });

    public fetchDeclarations: () => Promise<void> = flow(function* (this: TcfStore) {
        this.busyDeclarations = true;
        try {
            const {
                gvlSpecificationVersion,
                tcfPolicyVersion,
                purposes,
                specialPurposes,
                features,
                specialFeatures,
                dataCategories,
            }: ResponseRouteTcfDeclarationsGet = yield request<
                RequestRouteTcfDeclarationsGet,
                ParamsRouteTcfDeclarationsGet,
                ResponseRouteTcfDeclarationsGet
            >({
                location: locationRestTcfDeclarationsGet,
            });

            for (const pid of Object.keys(purposes)) {
                this.purposes.set(pid, new TcfPurpose(purposes[pid], false, this));
            }
            for (const pid of Object.keys(specialPurposes)) {
                this.specialPurposes.set(pid, new TcfPurpose(specialPurposes[pid], true, this));
            }
            for (const pid of Object.keys(features)) {
                this.features.set(pid, new TcfFeature(features[pid], false, this));
            }
            for (const pid of Object.keys(specialFeatures)) {
                this.specialFeatures.set(pid, new TcfFeature(specialFeatures[pid], true, this));
            }
            for (const pid of Object.keys(dataCategories)) {
                this.dataCategories.set(pid, new TcfDataCategory(dataCategories[pid], this));
            }

            this.declarations = {
                purposes,
                specialPurposes,
                features,
                specialFeatures,
                dataCategories,
            };

            this.gvlSpecificationVersion = gvlSpecificationVersion;
            this.tcfPolicyVersion = tcfPolicyVersion;
        } catch (e) {
            console.log(e);
            throw e;
        } finally {
            this.busyDeclarations = false;
        }
    });

    public updateGvl: () => Promise<void> = flow(function* (this: TcfStore) {
        this.busyGvl = true;
        try {
            const { gvlDownloadTime }: ResponseRouteTcfGvlPut = yield request<
                RequestRouteTcfGvlPut,
                ParamsRouteTcfGvlPut,
                ResponseRouteTcfGvlPut
            >({
                location: locationRestTcfGvlPut,
            });

            this.rootStore.optionStore.tcfGvlDownloadTime = gvlDownloadTime;
        } catch (e) {
            console.log(e);
            throw e;
        } finally {
            this.busyGvl = false;
        }
    });
}

export { TcfStore };
