/**
 * Requests tools, and various panel views
 */

import axios, { type AxiosResponse } from "axios";
import { defineStore } from "pinia";
import Vue, { computed, type Ref, ref, shallowRef } from "vue";

import {
    MY_PANEL_VIEW_DESCRIPTION,
    MY_PANEL_VIEW_ID,
    MY_PANEL_VIEW_NAME,
    MY_PANEL_VIEW_TYPE,
} from "@/components/Panels/panelViews";
import { FAVORITES_KEYS, filterTools, type types_to_icons } from "@/components/Panels/utilities";
import { parseHelpForSummary } from "@/components/ToolsList/utilities";
import { useUserLocalStorage } from "@/composables/userLocalStorage";
import { getAppRoot } from "@/onload/loadConfig";
import { rethrowSimple } from "@/utils/simple-error";

export interface FilterSettings {
    [key: string]: string | undefined;
    name?: string;
    section?: string;
    ontology?: string;
    id?: string;
    owner?: string;
    help?: string;
}

export interface Panel {
    id: string;
    model_class: string;
    name: string;
    description: string;
    view_type: keyof typeof types_to_icons;
    searchable: boolean;
}

// TODO: Once the backend models are typed, we will replace these with the generated types from the schema.
export interface Tool {
    model_class: string;
    id: string;
    name: string;
    version: string;
    description: string;
    labels: string[];
    edam_operations: string[];
    edam_topics: string[];
    hidden: "" | boolean;
    is_workflow_compatible: boolean;
    xrefs: string[];
    config_file: string;
    link: string;
    min_width: number;
    target: string;
    panel_section_id: string;
    panel_section_name: string | null;
    form_style: string;
    disabled?: boolean;
    icon?: string;
    tool_shed_repository?: {
        name: string;
        owner: string;
        changeset_revision: string;
        tool_shed: string;
    };
    help?: string;
}

export interface ToolSection {
    model_class: "ToolSection";
    id: string;
    name: string;
    title?: string;
    version?: string;
    description?: string;
    links?: Record<string, string>;
    tools?: (string | ToolSectionLabel)[];
    elems?: (Tool | ToolSection)[]; // TODO: Are we sure that a `ToolSection` can have `ToolSection` children?
}

export interface ToolSectionLabel {
    model_class: "ToolSectionLabel";
    id: string;
    text: string;
    version?: string;
    description?: string | null;
    links?: Record<string, string> | null;
}

export type ToolPanelItem = Tool | ToolSection | ToolSectionLabel;

export type ToolHelpData = {
    help?: string;
    summary?: string;
};

const MY_PANEL_VIEW_SECTION_ID = "favorites";

const MY_PANEL_VIEW: Panel = {
    id: MY_PANEL_VIEW_ID,
    model_class: "StaticToolPanelView",
    name: MY_PANEL_VIEW_NAME,
    description: MY_PANEL_VIEW_DESCRIPTION,
    view_type: MY_PANEL_VIEW_TYPE,
    searchable: true,
};

export const useToolStore = defineStore("toolStore", () => {
    const currentPanelView: Ref<string> = useUserLocalStorage("tool-store-view", "");
    const defaultPanelView: Ref<string> = ref("");
    const loading = ref(false);
    const panels = ref<Record<string, Panel>>({});
    const searchWorker = ref<Worker | undefined>(undefined);
    const toolsById = shallowRef<Record<string, Tool>>({});
    const toolResults = ref<Record<string, string[]>>({});
    const toolSections = ref<Record<string, Record<string, ToolPanelItem>>>({});
    const fetchedHelpIds = ref<Set<string>>(new Set());
    const helpDataCached = ref<Record<string, ToolHelpData>>({});

    const currentToolSections = computed(() => {
        const effectiveView = currentPanelView.value;
        return toolSections.value[effectiveView] || {};
    });

    const getLinkById = computed(() => {
        return (toolId: string) => {
            const tool = toolsById.value[toolId];
            const appRoot = getAppRoot();
            if (tool && tool.model_class === "DataSourceTool") {
                return `${appRoot}tool_runner/data_source_redirect?tool_id=${encodeURIComponent(toolId)}`;
            } else if (tool?.model_class) {
                return `${appRoot}?tool_id=${encodeURIComponent(toolId)}&version=latest`;
            } else {
                // accommodates hacky toolbox markdown directive overload
                return undefined;
            }
        };
    });

    const getTargetById = computed(() => {
        return (toolId: string) => {
            const tool = toolsById.value[toolId];
            return tool?.model_class === "DataSourceTool" ? "_top" : "galaxy_main";
        };
    });

    const getToolsById = computed(() => {
        return (q?: string) => {
            if (!q?.trim()) {
                return toolsById.value;
            } else {
                return filterTools(toolsById.value, toolResults.value[q] || []);
            }
        };
    });

    const getInteractiveTools = computed(() => {
        return () => {
            return Object.values(toolsById.value).filter((tool) => tool.model_class === "InteractiveTool");
        };
    });

    const getToolForId = computed(() => {
        return (toolId: string) => toolsById.value[toolId];
    });

    const getToolNameById = computed(() => {
        return (toolId: string) => {
            const details = toolsById.value[toolId];
            return details?.name || "...";
        };
    });

    const isPanelPopulated = computed(() => {
        return Object.keys(toolsById.value).length > 0 && Object.keys(currentToolSections.value).length > 0;
    });

    /** These are filtered tool sections (`ToolSection[]`) for the `currentPanel`;
     * Only sections that are typically referenced by `Tool.panel_section_id`
     * are included for the `default` view, while for ontologies, only ontologies are
     * included and `Uncategorized` section is skipped.
     */
    const panelSections = computed(() => {
        return (panelView: string) => {
            return Object.values(toolSections.value[panelView] || {}).filter((section) => {
                const sec = section as ToolSection;
                return (
                    sec.tools &&
                    sec.tools.length > 0 &&
                    sec.id !== "uncategorized" &&
                    sec.id !== "builtin_converters" &&
                    sec.name !== undefined
                );
            }) as ToolSection[];
        };
    });

    const sectionDatalist = computed(() => {
        return (panelView: string) => {
            return panelSections.value(panelView).map((section) => {
                return { value: section.id, text: section.name };
            });
        };
    });

    async function fetchToolSections(panelView: string) {
        if (!panelView || toolSections.value[panelView]) {
            return;
        }
        if (panelView === MY_PANEL_VIEW_ID) {
            saveToolSections(panelView, {
                [MY_PANEL_VIEW_SECTION_ID]: {
                    model_class: "ToolSection",
                    id: MY_PANEL_VIEW_SECTION_ID,
                    name: "Favorites",
                    tools: [],
                },
            });
            return;
        }
        try {
            loading.value = true;
            const { data } = await axios.get(`${getAppRoot()}api/tool_panels/${panelView}`);
            saveToolSections(panelView, data);
        } catch (e) {
            rethrowSimple(e);
        } finally {
            loading.value = false;
        }
    }

    async function fetchPanels() {
        try {
            if (!defaultPanelView.value || Object.keys(panels.value).length === 0) {
                const { data } = await axios.get(`${getAppRoot()}api/tool_panels`);
                defaultPanelView.value = data.default_panel_view;
                panels.value = {
                    ...data.views,
                    ...(data.views[MY_PANEL_VIEW_ID] ? {} : { [MY_PANEL_VIEW_ID]: MY_PANEL_VIEW }),
                };
            }
        } catch (e) {
            rethrowSimple(e);
        }
    }

    async function fetchToolForId(toolId: string) {
        try {
            const { data } = await axios.get(`${getAppRoot()}api/tools/${toolId}`);
            saveToolForId(toolId, data);
        } catch (e) {
            rethrowSimple(e);
        }
    }

    async function fetchTools(q?: string) {
        try {
            loading.value = true;
            // Backend search
            if (q?.trim()) {
                // We have either cached the backend search result,
                // or it is a favorites search (which we always repeat for changes)
                if (!toolResults.value[q] || FAVORITES_KEYS.includes(q.trim())) {
                    const { data } = await axios.get(`${getAppRoot()}api/tools`, { params: { q } });
                    saveToolResults(q, data);
                }
            }

            // Fetch all tools by IDs if not already fetched
            if (Object.keys(toolsById.value).length === 0) {
                const { data } = await axios.get(`${getAppRoot()}api/tools?in_panel=False`);
                saveAllTools(data as Tool[]);
            }
        } catch (e) {
            rethrowSimple(e);
        } finally {
            loading.value = false;
        }
    }

    async function fetchHelpForId(toolId: string) {
        try {
            if (!helpDataCached.value[toolId] && !fetchedHelpIds.value.has(toolId)) {
                fetchedHelpIds.value.add(toolId);

                const toolHelpData: ToolHelpData = {};

                const { data } = (await axios.get(
                    `${getAppRoot()}api/tools/${encodeURIComponent(toolId)}/build`,
                )) as AxiosResponse<ToolHelpData>;

                const help = data.help;
                if (help && help !== "\n") {
                    toolHelpData.help = help;
                    toolHelpData.summary = parseHelpForSummary(help);
                } else {
                    toolHelpData.help = ""; // for cases where helpText == '\n'
                }

                Vue.set(helpDataCached.value, toolId, toolHelpData);
            }
        } catch (error) {
            console.error("Error fetching help:", error);
            fetchedHelpIds.value.delete(toolId); // Allow retrying on next request
        }
    }

    async function initializePanel() {
        try {
            currentPanelView.value = currentPanelView.value || defaultPanelView.value;
            await setPanel(currentPanelView.value);
        } catch (e) {
            await setPanel(defaultPanelView.value);
        }
    }

    function saveAllTools(toolsData: Tool[]) {
        toolsById.value = toolsData.reduce(
            (acc, item) => {
                acc[item.id] = item;
                return acc;
            },
            {} as Record<string, Tool>,
        );
    }

    function saveToolSections(panelView: string, newPanel: { [id: string]: ToolPanelItem }) {
        Vue.set(toolSections.value, panelView, newPanel);
    }

    function saveToolForId(toolId: string, toolData: Tool) {
        Vue.set(toolsById.value, toolId, toolData);
    }

    function saveToolResults(whooshQuery: string, toolsData: Array<string>) {
        Vue.set(toolResults.value, whooshQuery, toolsData);
    }

    async function setPanel(panelView: string) {
        try {
            if (panelView === MY_PANEL_VIEW_ID && defaultPanelView.value && panelView !== defaultPanelView.value) {
                await fetchToolSections(defaultPanelView.value);
            }
            await fetchToolSections(panelView);
            currentPanelView.value = panelView;
        } catch (e) {
            rethrowSimple(e);
        }
    }

    return {
        currentPanelView,
        currentToolSections,
        defaultPanelView,
        fetchHelpForId,
        fetchedHelpIds,
        fetchToolSections,
        fetchPanels,
        fetchToolForId,
        fetchTools,
        getLinkById,
        getTargetById,
        helpDataCached,
        initializePanel,
        isPanelPopulated,
        loading,
        getToolForId,
        getToolNameById,
        getToolsById,
        getInteractiveTools,
        panels,
        panelSections,
        saveAllTools,
        saveToolForId,
        saveToolResults,
        searchWorker,
        sectionDatalist,
        setPanel,
        toolsById,
        toolSections,
    };
});
