// (C) 2021-2026 GoodData Corporation
import { uniqBy } from "lodash-es";
import { batchActions } from "redux-batched-actions";
import { all, call, put, spawn } from "redux-saga/effects";
import { walkLayout } from "@gooddata/sdk-backend-spi";
import { areObjRefsEqual, isDrillToInsight, isInsightWidget, isVisualizationSwitcherWidget, serializeObjRef, } from "@gooddata/sdk-model";
import { createDisplayFormMap, createDisplayFormMapFromCatalog, } from "../../../../_staging/catalog/displayFormMap.js";
import { dashboardInitialized } from "../../../events/dashboard.js";
import { getPrivateContext } from "../../../store/_infra/contexts.js";
import { accessibleDashboardsActions } from "../../../store/accessibleDashboards/index.js";
import { automationsActions } from "../../../store/automations/index.js";
import { backendCapabilitiesActions } from "../../../store/backendCapabilities/index.js";
import { catalogActions } from "../../../store/catalog/index.js";
import { configActions } from "../../../store/config/index.js";
import { dashboardPermissionsActions } from "../../../store/dashboardPermissions/index.js";
import { entitlementsActions } from "../../../store/entitlements/index.js";
import { executionResultsActions } from "../../../store/executionResults/index.js";
import { filterViewsActions } from "../../../store/filterViews/index.js";
import { listedDashboardsActions } from "../../../store/listedDashboards/index.js";
import { loadingActions } from "../../../store/loading/index.js";
import { notificationChannelsActions } from "../../../store/notificationChannels/index.js";
import { permissionsActions } from "../../../store/permissions/index.js";
import { renderModeActions } from "../../../store/renderMode/index.js";
import { tabsActions } from "../../../store/tabs/index.js";
import { DEFAULT_TAB_ID } from "../../../store/tabs/tabsState.js";
import { uiActions } from "../../../store/ui/index.js";
import { userActions } from "../../../store/user/index.js";
import { applyDefaultFilterView } from "../common/filterViews.js";
import { actionsToInitializeExistingDashboard, actionsToInitializeNewDashboard, } from "../common/stateInitializers.js";
import { loadCatalog } from "./loadCatalog.js";
import { loadDashboardList } from "./loadDashboardList.js";
import { loadDashboardParameters } from "./loadDashboardParameters.js";
import { loadDashboardPermissions } from "./loadDashboardPermissions.js";
import { loadDateHierarchyTemplates } from "./loadDateHierarchyTemplates.js";
import { loadFilterViews } from "./loadFilterViews.js";
import { loadMeasureParameterDependencies } from "./loadMeasureParameterDependencies.js";
import { loadUser } from "./loadUser.js";
import { mergeDateFilterConfigWithOverrides } from "./mergeDateFilterConfigs.js";
import { preloadAttributeFiltersData as preloadAttributeFiltersDataFromBackend } from "./preloadAttributeFiltersData.js";
import { resolveDashboardConfigAndFeatureFlagDependentCalls } from "./resolveDashboardConfig.js";
import { resolveEntitlements } from "./resolveEntitlements.js";
import { resolvePermissions } from "./resolvePermissions.js";
async function loadDashboardFromBackend(ctx, privateCtx, dashboardRef, hasPersistedDashboard) {
    const { backend, workspace, filterContextRef } = ctx;
    const { preloadedDashboard } = privateCtx;
    if (preloadedDashboard && areObjRefsEqual(preloadedDashboard.ref, dashboardRef)) {
        // with persisted dashboard we cannot use the backend for resolution of the referenced insights
        // as our version of dashboard differs from what is on the backend
        // hence we must do the resolution on the client
        if (hasPersistedDashboard) {
            if (ctx.config?.references) {
                return {
                    dashboard: preloadedDashboard,
                    references: ctx.config.references,
                };
            }
            const insights = await loadInsightsForPersistedDashboard(ctx, preloadedDashboard);
            return {
                dashboard: preloadedDashboard,
                references: {
                    insights,
                    plugins: [],
                },
            };
        }
        return backend
            .workspace(workspace)
            .dashboards()
            .getDashboardReferencedObjects(preloadedDashboard, ["insight", "dataSet"])
            .then((references) => {
            return {
                dashboard: preloadedDashboard,
                references,
            };
        });
    }
    return backend
        .workspace(workspace)
        .dashboards()
        .getDashboardWithReferences(dashboardRef, filterContextRef, { loadUserData: true }, [
        "insight",
        "dataSet",
    ]);
}
async function loadInsightsForPersistedDashboard(ctx, dashboard) {
    if (!dashboard) {
        return [];
    }
    const { backend, workspace } = ctx;
    const referencedInsights = [];
    const collectInsightsFromWidget = (widget) => {
        if (isInsightWidget(widget)) {
            referencedInsights.push(
            // insight itself
            widget.insight, 
            // insights in drills to insight
            ...widget.drills.filter(isDrillToInsight).map((drill) => drill.target));
        }
        if (isVisualizationSwitcherWidget(widget)) {
            widget.visualizations.forEach((vis) => {
                if (isInsightWidget(vis)) {
                    referencedInsights.push(
                    // insight itself
                    vis.insight, 
                    // insights in drills to insight
                    ...vis.drills.filter(isDrillToInsight).map((drill) => drill.target));
                }
            });
        }
    };
    // Walk through tabs if they exist
    if (dashboard.tabs && dashboard.tabs.length > 0) {
        dashboard.tabs.forEach((tab) => {
            if (tab.layout) {
                walkLayout(tab.layout, {
                    widgetCallback: collectInsightsFromWidget,
                });
            }
        });
    }
    // Also walk through root layout for backwards compatibility or when tabs are not used
    if (dashboard.layout) {
        walkLayout(dashboard.layout, {
            widgetCallback: collectInsightsFromWidget,
        });
    }
    const uniqueRefs = uniqBy(referencedInsights, serializeObjRef);
    return Promise.all(uniqueRefs.map((ref) => backend.workspace(workspace).insights().getInsight(ref)));
}
function resolveActiveTabId(tabs, initialTabId, defaultActiveTabId) {
    if (!tabs || tabs.length === 0) {
        return defaultActiveTabId;
    }
    if (initialTabId && tabs.some((tab) => tab.localIdentifier === initialTabId)) {
        return initialTabId;
    }
    if (defaultActiveTabId && tabs.some((tab) => tab.localIdentifier === defaultActiveTabId)) {
        return defaultActiveTabId;
    }
    return tabs[0]?.localIdentifier ?? DEFAULT_TAB_ID;
}
function* getTabsFilterConfigs(dashboard, config, ctx, cmd) {
    const tabsAttributeFilterConfigs = {};
    const tabsDateFilterConfig = {};
    const tabsDateFilterConfigSource = {};
    const defaultTab = {
        localIdentifier: DEFAULT_TAB_ID,
        title: "",
        filterContext: dashboard.filterContext,
        dateFilterConfig: dashboard.dateFilterConfig,
        dateFilterConfigs: dashboard.dateFilterConfigs,
        attributeFilterConfigs: dashboard.attributeFilterConfigs,
    };
    // include default tab for case of old dashboard or turned off FeatureFlag for DashboardTabs
    const tabs = dashboard.tabs ? [...dashboard.tabs, defaultTab] : [defaultTab];
    for (const tab of tabs) {
        const effectiveDateFilterConfig = yield call(mergeDateFilterConfigWithOverrides, ctx, cmd, config.dateFilterConfig, tab.dateFilterConfig);
        tabsDateFilterConfig[tab.localIdentifier] = effectiveDateFilterConfig.config;
        tabsDateFilterConfigSource[tab.localIdentifier] = effectiveDateFilterConfig.source;
        tabsAttributeFilterConfigs[tab.localIdentifier] = tab.attributeFilterConfigs ?? [];
    }
    return { tabsAttributeFilterConfigs, tabsDateFilterConfig, tabsDateFilterConfigSource };
}
function* loadExistingDashboard(ctx, cmd, dashboardRef) {
    const { backend } = ctx;
    const privateCtx = yield call(getPrivateContext);
    const calls = [
        call(loadDashboardFromBackend, ctx, privateCtx, dashboardRef, !!cmd.payload.persistedDashboard),
        call(resolveDashboardConfigAndFeatureFlagDependentCalls, ctx, cmd),
        call(resolvePermissions, ctx, cmd),
        call(resolveEntitlements, ctx, cmd),
        call(loadUser, ctx),
        call(loadDashboardPermissions, ctx),
        call(loadDateHierarchyTemplates, ctx),
        call(loadFilterViews, ctx),
    ];
    const [dashboardWithReferences, { resolvedConfig: config, additionalData: { workspaceAutomationsCount, notificationChannelsCount }, }, permissions, entitlements, user, dashboardPermissions, dateHierarchyTemplates, filterViews,] = yield all(calls);
    const { dashboard: loadedDashboard, references: { insights }, } = dashboardWithReferences;
    const dashboardWithFilterView = applyDefaultFilterView(loadedDashboard, filterViews);
    // Resolve active tab: URL's initialTabId takes precedence, otherwise default to first tab
    // Note: activeTabLocalIdentifier is app state only, NOT persisted to/read from dashboard MD
    const resolvedActiveTabId = resolveActiveTabId(dashboardWithFilterView.tabs, cmd.payload.initialTabId, undefined);
    const dashboard = dashboardWithFilterView;
    const [{ tabsAttributeFilterConfigs, tabsDateFilterConfig, tabsDateFilterConfigSource }, workspaceParameters, measureParameterDependencies,] = yield all([
        call(getTabsFilterConfigs, dashboard, config, ctx, cmd),
        call(loadWorkspaceParametersWithStatus, ctx, config.settings?.enableParameters ?? false),
        call(loadMeasureParameterDependencies, ctx, insights, config.settings?.enableParameters ?? false),
    ]);
    const workspaceParametersList = Array.isArray(workspaceParameters) ? workspaceParameters : [];
    const initActions = yield call(actionsToInitializeExistingDashboard, ctx, dashboard, insights, config.settings, config.settings.enableImmediateAttributeFilterDisplayAsLabelMigration ?? false, undefined, undefined, tabsAttributeFilterConfigs, tabsDateFilterConfig, tabsDateFilterConfigSource, createDisplayFormMap([], []), cmd.payload.persistedDashboard, resolvedActiveTabId, workspaceParametersList);
    const catalogPayload = {
        dateHierarchyTemplates,
    };
    const batch = batchActions([
        backendCapabilitiesActions.setBackendCapabilities(backend.capabilities),
        configActions.setConfig(config),
        entitlementsActions.setEntitlements(entitlements),
        userActions.setUser(user),
        permissionsActions.setPermissions(permissions),
        catalogActions.setCatalogItems(catalogPayload),
        catalogActions.setCatalogParameters(makeCatalogParametersPayload(workspaceParameters)),
        catalogActions.setCatalogMeasureParameters(measureParameterDependencies),
        ...initActions,
        // NOTE: Tab configs (dateFilterConfig, dateFilterConfigs, attributeFilterConfigs, filterContext)
        // are now initialized as part of the tabs state in initActions via setTabs action
        uiActions.setMenuButtonItemsVisibility(config.menuButtonItemsVisibility),
        renderModeActions.setRenderMode(config.initialRenderMode),
        dashboardPermissionsActions.setDashboardPermissions(dashboardPermissions),
        automationsActions.setAllAutomationsCount(workspaceAutomationsCount),
        notificationChannelsActions.setNotificationChannelsCount(notificationChannelsCount),
        filterViewsActions.setFilterViews({
            dashboard: ctx.dashboardRef, // should be defined as we are in existing dashboard load fn
            filterViews,
        }),
    ], "@@GDC.DASH/BATCH.INIT.EXISTING");
    const event = dashboardInitialized(ctx, dashboard, insights, config, permissions, cmd.correlationId);
    return {
        batch,
        event,
    };
}
function* initializeNewDashboard(ctx, cmd) {
    const { backend } = ctx;
    const [{ resolvedConfig: config, additionalData: { notificationChannelsCount, workspaceAutomationsCount }, }, permissions, entitlements, catalog, user, listedDashboards, dateHierarchyTemplates,] = yield all([
        call(resolveDashboardConfigAndFeatureFlagDependentCalls, ctx, cmd),
        call(resolvePermissions, ctx, cmd),
        call(resolveEntitlements, ctx, cmd),
        call(loadCatalog, ctx, cmd),
        call(loadUser, ctx),
        call(loadDashboardList, ctx),
        call(loadDateHierarchyTemplates, ctx),
        call(loadFilterViews, ctx),
    ]);
    const workspaceParameters = yield call(loadWorkspaceParametersWithStatus, ctx, config.settings?.enableParameters ?? false);
    const workspaceParametersList = Array.isArray(workspaceParameters) ? workspaceParameters : [];
    const { initActions, dashboard, insights } = yield call(actionsToInitializeNewDashboard, ctx, config.settings, config.dateFilterConfig, catalog ? createDisplayFormMapFromCatalog(catalog) : createDisplayFormMap([], []), cmd.payload.initialTabId, workspaceParametersList);
    const measureParameterDependencies = yield call(loadMeasureParameterDependencies, ctx, insights, config.settings?.enableParameters ?? false);
    const batch = batchActions([
        backendCapabilitiesActions.setBackendCapabilities(backend.capabilities),
        configActions.setConfig(config),
        entitlementsActions.setEntitlements(entitlements),
        userActions.setUser(user),
        permissionsActions.setPermissions(permissions),
        catalogActions.setCatalogItems({
            attributes: catalog.attributes(),
            dateDatasets: catalog.dateDatasets(),
            facts: catalog.facts(),
            measures: catalog.measures(),
            attributeHierarchies: catalog.attributeHierarchies(),
            dateHierarchyTemplates: dateHierarchyTemplates,
        }),
        catalogActions.setCatalogParameters(makeCatalogParametersPayload(workspaceParameters)),
        catalogActions.setCatalogMeasureParameters(measureParameterDependencies),
        listedDashboardsActions.setListedDashboards(listedDashboards),
        accessibleDashboardsActions.setAccessibleDashboards(listedDashboards),
        executionResultsActions.clearAllExecutionResults(),
        ...initActions,
        tabsActions.setDateFilterConfig({
            dateFilterConfig: undefined,
            effectiveDateFilterConfig: config.dateFilterConfig,
            isUsingDashboardOverrides: false,
        }),
        uiActions.setMenuButtonItemsVisibility(config.menuButtonItemsVisibility),
        renderModeActions.setRenderMode(config.initialRenderMode),
        dashboardPermissionsActions.setDashboardPermissions({
            canViewDashboard: true,
            canShareDashboard: true,
            canShareLockedDashboard: true,
            canEditDashboard: true,
            canEditLockedDashboard: true,
        }),
        automationsActions.setAllAutomationsCount(workspaceAutomationsCount),
        notificationChannelsActions.setNotificationChannelsCount(notificationChannelsCount),
    ], "@@GDC.DASH/BATCH.INIT.NEW");
    const event = dashboardInitialized(ctx, dashboard, insights, config, permissions, cmd.correlationId);
    return {
        batch,
        event,
    };
}
export function* requestCatalog(ctx, cmd) {
    const [catalog, dateHierarchyTemplates] = yield all([
        call(loadCatalog, ctx, cmd),
        call(loadDateHierarchyTemplates, ctx),
    ]);
    yield put(catalogActions.setCatalogItems({
        attributes: catalog.attributes(),
        dateDatasets: catalog.dateDatasets(),
        facts: catalog.facts(),
        measures: catalog.measures(),
        attributeHierarchies: catalog.attributeHierarchies(),
        dateHierarchyTemplates: dateHierarchyTemplates,
    }));
}
function* loadWorkspaceParametersWithStatus(ctx, enableParameters) {
    if (!enableParameters) {
        return "gated-off";
    }
    try {
        const result = yield call(loadDashboardParameters, ctx);
        return result;
    }
    catch {
        return "failed";
    }
}
function makeCatalogParametersPayload(result) {
    if (result === "gated-off") {
        return { status: "gated-off", parameters: [] };
    }
    if (result === "failed") {
        return { status: "failed", parameters: [] };
    }
    return { status: "loaded", parameters: result };
}
export function* preloadAttributeFiltersData(ctx, dashboard) {
    const attributesWithReferences = yield call(preloadAttributeFiltersDataFromBackend, ctx, dashboard);
    yield put(tabsActions.setPreloadedAttributesWithReferences(attributesWithReferences));
}
export function* requestDashboardsList(ctx) {
    const listedDashboards = yield call(loadDashboardList, ctx);
    const dashboardsListActions = batchActions([
        listedDashboardsActions.setListedDashboards(listedDashboards),
        accessibleDashboardsActions.setAccessibleDashboards(listedDashboards),
    ]);
    yield put(dashboardsListActions);
}
function* advancedLoader(ctx, cmd, dashboard) {
    yield all([
        call(requestCatalog, ctx, cmd),
        call(preloadAttributeFiltersData, ctx, dashboard),
        call(requestDashboardsList, ctx),
    ]);
}
export function* initializeDashboardHandler(ctx, cmd) {
    const { dashboardRef } = ctx;
    try {
        yield put(loadingActions.setLoadingStart());
        let result;
        if (dashboardRef) {
            result = (yield call(loadExistingDashboard, ctx, cmd, dashboardRef));
        }
        else {
            result = (yield call(initializeNewDashboard, ctx, cmd));
        }
        yield put(result.batch);
        yield put(loadingActions.setLoadingSuccess());
        yield put(result.event);
        if (dashboardRef) {
            // let's run effects which are not essential for the existing
            // dashboard to be rendered, such as catalog load
            yield spawn(advancedLoader, ctx, cmd, result.event.payload.dashboard);
        }
    }
    catch (e) {
        yield put(loadingActions.setLoadingError(e));
        throw e;
    }
}
