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

import { getSidebarCustomize } from "@devowl-wp/customize";
import type { Customize } from "@devowl-wp/react-cookie-banner";

import { BannerPreset } from "../models/bannerPreset.js";
import { request } from "../utils/request.js";
import { locationRestPresetsBannerGet } from "../wp-api/presetsBanner.get.js";

import type { RootStore } from "./stores.js";
import type {
    ParamsRoutePresetsBannerGet,
    RequestRoutePresetsBannerGet,
    ResponseRoutePresetsBannerGet,
} from "../wp-api/presetsBanner.get.js";

class CustomizeBannerStore {
    @observable
    public visible = false;

    @observable
    public individualPrivacyOpen = false;

    @observable
    public previewCheckboxActiveState = false;

    @observable
    public previewStickyMenuOpenState = false;

    @observable
    public busyPresets = false;

    @observable
    public presets = new Map<string, BannerPreset>();

    public readonly rootStore: RootStore;

    public readonly presetConstants = new Map<string, string>();

    public readonly presetDefaults = new Map<string, any>();

    private debounceFromCustomize: { [key: string]: ReturnType<typeof setTimeout> } = {};

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

    public fetchPresets: () => void = flow(function* (this: CustomizeBannerStore) {
        this.busyPresets = true;
        try {
            const { defaults, constants, items }: ResponseRoutePresetsBannerGet = yield request<
                RequestRoutePresetsBannerGet,
                ParamsRoutePresetsBannerGet,
                ResponseRoutePresetsBannerGet
            >({
                location: locationRestPresetsBannerGet,
            });

            // Save defaults
            for (const did of Object.keys(defaults)) {
                this.presetDefaults.set(did, defaults[did]);
            }

            // Save constants
            for (const cid of Object.keys(constants)) {
                this.presetConstants.set(cid, constants[cid]);
            }

            // Save presets as object
            for (const pid of Object.keys(items)) {
                this.presets.set(pid, new BannerPreset({ id: pid, ...items[pid] }, this));
            }
        } catch (e) {
            console.log(e);
            throw e;
        } finally {
            this.busyPresets = false;
        }
    });

    @action
    public setBannerFromCustomize<T extends keyof Customize, P extends keyof Customize[T]>(
        section: T,
        setting: P,
        value: Customize[T][P],
        maybeDebounce = true,
    ) {
        const { customizeValuesBanner } = this.rootStore.optionStore.others;

        // Apply debounce for specific settings
        const debounceSetting = setting.toString();
        if (maybeDebounce && ["css", "animationInDuration", "animationOutDuration"].indexOf(debounceSetting) > -1) {
            clearTimeout(this.debounceFromCustomize[debounceSetting]);
            this.debounceFromCustomize[debounceSetting] = setTimeout(
                () => this.setBannerFromCustomize(section, setting, value, false),
                500,
            );
        } else {
            const currentValue = customizeValuesBanner[section][setting];
            customizeValuesBanner[section][setting] = value;

            // When we want to customize the animation out, let's simulate a "hide"
            if (debounceSetting.startsWith("animationOut") && currentValue !== value) {
                this.forceAnimationOutSimulation();
            }
        }
    }

    @action
    public setBannerFromPreset<T extends keyof Customize, P extends keyof Customize[T]>(
        batchUpdates: Array<[T, P, Customize[T][P]]>,
    ) {
        for (const row of batchUpdates) {
            const [section, setting, value] = row;
            this.rootStore.optionStore.others.customizeValuesBanner[section][setting] = value;
        }
    }

    @action
    public forceAnimationOutSimulation() {
        const { customizeValuesBanner } = this.rootStore.optionStore.others;
        if (customizeValuesBanner["layout"]["animationOut"] === "none") {
            return;
        }

        this.visible = false;
        setTimeout(
            () =>
                runInAction(() => {
                    this.visible = true;
                }),
            +customizeValuesBanner["layout"]["animationOutDuration"] + 1000,
        );
    }

    @action
    public setVisible(state: boolean) {
        this.visible = state;
    }

    @action
    public setIndividualPrivacyOpen(state: boolean) {
        this.individualPrivacyOpen = state;
    }

    @action
    public setPreviewCheckboxActiveState(state: boolean) {
        this.previewCheckboxActiveState = state;
    }

    @action
    public setPreviewStickyMenuOpenState(state: boolean) {
        this.previewStickyMenuOpenState = state;
    }

    /**
     * Allow developers to export the current customize settings via PHP.
     *
     * ```ts
     * realCookieBanner_customize.RootStore.get.customizeBannerStore.exportPhp()
     * ```
     */
    public exportPhp() {
        const result: { [key: string]: any } = {};
        const customize = getSidebarCustomize();

        this.presetDefaults.forEach((defaultValue, key) => {
            let currentValue = customize(key).get();

            // Fix booleans
            if (typeof defaultValue === "boolean") {
                currentValue = !!+currentValue;
            } else if (!isNaN(currentValue) && currentValue !== "") {
                // Fix integers
                currentValue = +currentValue;
            }

            if (JSON.stringify(defaultValue) !== JSON.stringify(currentValue)) {
                result[this.presetConstants.get(key)] = currentValue;
            }
        });

        // Prepare output
        return this.jsonToPHPArray(result);
    }

    protected jsonToPHPArray(result: { [key: string]: any }) {
        const output = JSON.stringify(result, null, 4).split("\n");
        output.shift();
        output.pop();

        return output
            .join("\n")
            .replace(/^(\s+)"([A-Za-z\\]+::[A-Z_]+)"(:)/gm, `$1$2 =>`)
            .replace(/^(\s+)([A-Za-z\\]+)::/gm, (full, spaces: string, namespaces: string) => {
                return `${spaces}${namespaces.replace(/\\\\/gm, "\\")}::`;
            });
    }
}

export { CustomizeBannerStore };
