/**
 * Stores the Activity Bar state
 */
import { useDebounceFn, watchImmediate } from "@vueuse/core";
import { computed, type Ref, ref, set } from "vue";

import { useHashedUserId } from "@/composables/hashedUserIdFromUserStore";
import { useUserLocalStorage } from "@/composables/userLocalStorage";
import { ensureDefined } from "@/utils/assertions";

import { defaultActivities } from "./activitySetup";
import type { Activity } from "./activityStoreTypes";
import { defineScopedStore } from "./scopedStore";

export interface ActivityMeta {
    disabled: boolean;
}

function defaultActivityMeta(): ActivityMeta {
    return {
        disabled: false,
    };
}

export const useActivityStore = defineScopedStore("activityStore", (scope) => {
    const activities: Ref<Array<Activity>> = useUserLocalStorage(`activity-store-activities-${scope}`, []);
    const activityMeta: Ref<Record<string, ActivityMeta>> = ref({});

    const { hashedUserId } = useHashedUserId();

    const customDefaultActivities = ref<Activity[] | null>(null);
    const currentDefaultActivities = computed(() => customDefaultActivities.value ?? defaultActivities);
    const isSideBarOpen = computed(() => toggledSideBar.value !== "" && toggledSideBar.value !== "closed");

    const specialPanelActivityIds = ref<Set<string>>(new Set());

    function setSpecialPanelActivityIds(ids: string[]) {
        specialPanelActivityIds.value = new Set(ids);
    }

    const toggledSideBar = useUserLocalStorage(`activity-store-current-side-bar-${scope}`, "tools");
    const sidePanelWidth = useUserLocalStorage(`activity-store-side-panel-width-${scope}`, 300);

    function toggleSideBar(currentOpen = "") {
        toggledSideBar.value = toggledSideBar.value === currentOpen ? "" : currentOpen;
    }

    function closeSideBar() {
        toggledSideBar.value = "closed";
    }

    function overrideDefaultActivities(activities: Activity[]) {
        customDefaultActivities.value = activities;
        sync();
    }

    function resetDefaultActivities() {
        customDefaultActivities.value = null;
        sync();
    }

    /**
     * Restores the default activity bar items
     */
    function restore() {
        activities.value = currentDefaultActivities.value.slice();
    }

    /**
     * The set of built-in activities is defined in activitySetup.js.
     * This helper function applies changes of the built-in activities,
     * to the user stored activities which are persisted in local cache.
     */
    const sync = useDebounceFn(() => {
        // create a map of built-in activities
        const activitiesMap: Record<string, Activity> = {};

        currentDefaultActivities.value.forEach((a) => {
            activitiesMap[a.id] = a;
        });

        // create an updated array of activities
        const newActivities: Array<Activity> = [];
        const foundActivity = new Set();

        activities.value.forEach((a: Activity) => {
            if (a.mutable) {
                // existing custom activity
                newActivities.push({ ...a });
            } else {
                // update existing built-in activity attributes
                // skip legacy built-in activities
                const sourceActivity = activitiesMap[a.id];

                if (sourceActivity) {
                    foundActivity.add(a.id);
                    newActivities.push({
                        ...sourceActivity,
                        visible: a.visible,
                    });
                }
            }
        });

        // add new built-in activities
        currentDefaultActivities.value.forEach((a) => {
            if (!foundActivity.has(a.id)) {
                newActivities.push({ ...a });
            }
        });

        activities.value = newActivities;

        // if toggled side-bar does not exist, choose the first option
        if (isSideBarOpen.value) {
            const allSideBars = activities.value.flatMap((activity) => {
                if (activity.panel) {
                    return [activity.id];
                } else {
                    return [];
                }
            });

            const allSideBarsSet = new Set([...allSideBars, ...specialPanelActivityIds.value]);
            const firstSideBar = allSideBars[0];

            if (firstSideBar && !allSideBarsSet.has(toggledSideBar.value)) {
                toggledSideBar.value = firstSideBar;
            }
        }
    }, 10);

    function getAll() {
        return activities.value;
    }

    function setAll(newActivities: Array<Activity>) {
        activities.value = [...newActivities];
    }

    function remove(activityId: string) {
        const findIndex = activities.value.findIndex((a: Activity) => a.id === activityId);

        if (findIndex !== -1) {
            activities.value.splice(findIndex, 1);
        }
    }

    const metaForId = computed(() => (activityId: string) => {
        let meta = activityMeta.value[activityId];

        if (!meta) {
            set(activityMeta.value, activityId, defaultActivityMeta());
            meta = ensureDefined(activityMeta.value[activityId]);
        }

        return meta;
    });

    function setMeta<K extends keyof ActivityMeta>(activityId: string, metaKey: K, value: ActivityMeta[K]) {
        let meta = activityMeta.value[activityId];

        if (!meta) {
            set(activityMeta.value, activityId, defaultActivityMeta());
            meta = ensureDefined(activityMeta.value[activityId]);
        }

        set(meta, metaKey, value);
    }

    function findById(activityId: string): Activity | undefined {
        return activities.value.find((a: Activity) => a.id === activityId);
    }

    function setPosition(activityId: string, position: number) {
        const currentIndex = activities.value.findIndex((a: Activity) => a.id === activityId);
        if (currentIndex === -1) {
            return;
        }
        const boundedPosition = Math.max(0, Math.min(position, activities.value.length - 1));
        const spliced = activities.value.splice(currentIndex, 1);
        const activity = spliced[0];
        if (!activity) {
            return;
        }
        activities.value.splice(boundedPosition, 0, activity);
        activities.value = [...activities.value];
    }

    function ensureSideBarOpen(activityId: string) {
        const activity = findById(activityId);
        if (!activity || !activity.panel) {
            return;
        }
        if (toggledSideBar.value !== activityId) {
            toggledSideBar.value = activityId;
        }
    }

    function ensureVisible(activityId: string) {
        const activity = findById(activityId);
        if (!activity) {
            return;
        }
        activity.visible = true;
    }

    watchImmediate(
        () => hashedUserId.value,
        () => {
            sync();
        },
    );

    return {
        toggledSideBar,
        sidePanelWidth,
        toggleSideBar,
        closeSideBar,
        ensureSideBarOpen,
        ensureVisible,
        isSideBarOpen,
        activities,
        activityMeta,
        metaForId,
        setMeta,
        getAll,
        findById,
        remove,
        setPosition,
        setAll,
        restore,
        sync,
        customDefaultActivities,
        currentDefaultActivities,
        overrideDefaultActivities,
        resetDefaultActivities,
        setSpecialPanelActivityIds,
    };
});
