import {DashKit} from '@gravity-ui/dashkit';
import {generateUniqId} from '@gravity-ui/dashkit/helpers';
import update from 'immutability-helper';
import pick from 'lodash/pick';
import {DashTabItemTitleSizes, DashTabItemType} from 'shared';
import {getDefaultDashWidgetBgColorByType} from 'shared/constants/widgets';
import {getAllTabItems} from 'shared/utils/dash';
import {migrateConnectionsForGroupControl} from 'ui/store/utils/controlDialog';
import {
    getUpdatedBackgroundData,
    getUpdatedConnections,
    getUpdatedTextData,
} from 'ui/utils/copyItems';

import {EMBEDDED_MODE} from '../../../../constants/embedded';
import {Mode} from '../../modules/constants';
import {getUniqIdsFromDashData} from '../../modules/helpers';
import {isItemGlobal} from '../../utils/selectors';
import * as actionTypes from '../constants/dashActionTypes';

import {
    TAB_PROPERTIES,
    addGlobalItemToTab,
    getCreatedItem,
    getGlobalItemsToCopy,
    getStateForControlWithGlobalLogic,
} from './dashHelpers';
import {dashTypedReducer} from './dashTypedReducer';

const initialState = {
    mode: Mode.Loading,

    entry: null,
    data: null,

    permissions: null,
    navigationPath: null,

    // for the case when the config was updated, and in editing mode, you need to immediately show the active Save,
    // and when Canceling, switch to the converted data, and not to the one in entry
    convertedEntryData: null,

    tabId: null,
    lastModifiedItemId: null,

    hashStates: null,

    openedDialog: null,
    openedItemId: null,

    showTableOfContent: false,

    dashKitRef: null,

    lockToken: null,

    // TODO: not yet MODE.ERROR is not used, but it is worth resetting to null
    error: null,

    isFullscreenMode: new URLSearchParams(window.location.search).get('mode') === EMBEDDED_MODE.TV,

    skipReload: false,

    widgetsCurrentTab: {},
};

// eslint-disable-next-line complexity
function dash(state = initialState, action) {
    const {data, tabId} = state;

    const tabIndex = data ? data.tabs.findIndex(({id}) => id === tabId) : -1;
    const tab = tabIndex === -1 ? null : data.tabs[tabIndex];

    switch (action.type) {
        case actionTypes.SET_TABS: {
            let counter = data.counter;

            const salt = data.salt;
            const dashDataUniqIds = getUniqIdsFromDashData(data);

            // Get global items with 'allTabs' impact to copy from the first existing tab
            const firstExistingTab = data.tabs.length > 0 ? data.tabs[0] : null;
            const {globalItems: globalItemsToCopy, layout} = getGlobalItemsToCopy(firstExistingTab);

            const newTabs = action.payload.map((tab) => {
                let tabItem = null;
                const idsMapper = {};

                // tab is exist
                if (tab.id) {
                    tabItem = tab;
                    // tab is duplicated
                } else if (tab.duplicatedFrom) {
                    const tabForDuplication = state.data.tabs.find(
                        ({id}) => id === tab.duplicatedFrom,
                    );

                    const uniqTabIdData = generateUniqId({salt, counter, ids: dashDataUniqIds});
                    counter = uniqTabIdData.counter;

                    const newTabId = uniqTabIdData.id;

                    const items = tabForDuplication.items.map((item) => {
                        let widgetItem = null;
                        const uniqItemIdData = generateUniqId({
                            salt,
                            counter,
                            ids: dashDataUniqIds,
                        });
                        counter = uniqItemIdData.counter;

                        const itemId = uniqItemIdData.id;

                        idsMapper[item.id] = itemId;

                        if (item.type === DashTabItemType.GroupControl) {
                            widgetItem = {
                                ...item,
                                id: itemId,
                                data: {
                                    ...item.data,
                                    group: item.data.group.map((groupItem) => {
                                        const uniqEntityIdData = generateUniqId({
                                            salt,
                                            counter,
                                            ids: dashDataUniqIds,
                                        });
                                        counter = uniqEntityIdData.counter;

                                        const entityId = uniqEntityIdData.id;

                                        idsMapper[groupItem.id] = entityId;

                                        return {
                                            ...groupItem,
                                            id: entityId,
                                        };
                                    }),
                                },
                            };
                        } else if (item.type === DashTabItemType.Widget) {
                            widgetItem = {
                                ...item,
                                id: itemId,
                                data: {
                                    ...item.data,
                                    tabs: item.data.tabs.map((entity) => {
                                        const uniqEntityIdData = generateUniqId({
                                            salt,
                                            counter,
                                            ids: dashDataUniqIds,
                                        });
                                        counter = uniqEntityIdData.counter;

                                        const entityId = uniqEntityIdData.id;

                                        idsMapper[entity.id] = entityId;

                                        return {
                                            ...entity,
                                            id: entityId,
                                        };
                                    }),
                                },
                            };
                        } else {
                            widgetItem = {...item, id: itemId};
                        }

                        return widgetItem;
                    });

                    tabItem = {
                        id: newTabId,
                        title: tab.title,
                        aliases: {...tabForDuplication.aliases},
                        layout: tabForDuplication.layout.map((layoutItem) => ({
                            ...layoutItem,
                            i: idsMapper[layoutItem.i],
                        })),
                        connections: tabForDuplication.connections
                            // there may be elements in connections that are no longer in items
                            .filter(({from, to}) => idsMapper[from] && idsMapper[to])
                            .map(({from, to, kind}) => ({
                                from: idsMapper[from],
                                to: idsMapper[to],
                                kind,
                            })),
                        items,
                    };
                    // new tab
                } else {
                    const uniqTabIdData = generateUniqId({salt, counter, ids: dashDataUniqIds});
                    counter = uniqTabIdData.counter;

                    tabItem = {
                        id: uniqTabIdData.id,
                        ...tab,
                    };

                    // Copy global items to new tab if they exist
                    if (globalItemsToCopy.length > 0) {
                        globalItemsToCopy.forEach((globalItem) => {
                            tabItem = addGlobalItemToTab({
                                tab: tabItem,
                                item: globalItem,
                                layoutItem: layout[globalItem.id],
                            });
                        });
                    }
                }

                return tabItem;
            });

            let newTabId = tabId;

            if (tabIndex > newTabs.length - 1) {
                newTabId = newTabs[newTabs.length - 1].id;
            } else if (!newTabs.some(({id}) => id === tabId)) {
                newTabId = newTabs[tabIndex].id;
            }

            const updateData = {
                tabs: {$set: newTabs},
                counter: {$set: counter},
            };

            return {
                ...state,
                data: update(data, updateData),
                lastModifiedItemId: null,
                tabId: newTabId,
            };
        }
        case actionTypes.SET_CURRENT_TAB_DATA:
            return {
                ...state,
                data: update(data, {
                    tabs: {
                        [tabIndex]: {
                            $set: pick(action.payload, TAB_PROPERTIES),
                        },
                    },
                }),
            };
        case actionTypes.UPDATE_CURRENT_TAB_DATA:
            return {
                ...state,
                data: update(data, {
                    tabs: {
                        [tabIndex]: {
                            $merge: pick(action.payload, TAB_PROPERTIES),
                        },
                    },
                }),
            };
        case actionTypes.SET_COPIED_ITEM_DATA: {
            const itemData = action.payload.item.data;

            const textData =
                action.payload.item.type === DashTabItemType.Title
                    ? getUpdatedTextData({
                          textColor: itemData.textColor,
                          textSettings: itemData.textSettings,
                          allowCustomValues: false,
                          enableSeparateThemeColorSelector: true,
                          themeType: action.payload.dashVisualSettings?.themeType,
                      })
                    : {};

            const defaultBgColorValue = getDefaultDashWidgetBgColorByType(itemData.type);
            const backgroundData =
                action.payload.item.type !== DashTabItemType.Control &&
                action.payload.item.type !== DashTabItemType.GroupControl
                    ? getUpdatedBackgroundData({
                          background: itemData.background,
                          backgroundSettings: itemData.backgroundSettings,
                          allowCustomValues: false,
                          enableSeparateThemeColorSelector: true,
                          defaultOldColor: defaultBgColorValue,
                          themeType: action.payload.dashVisualSettings?.themeType,
                      })
                    : {};
            const newItem = {
                ...action.payload.item,
                data: {
                    ...itemData,
                    size:
                        action.payload.item.type === DashTabItemType.Title &&
                        typeof itemData.size === 'object'
                            ? DashTabItemTitleSizes.XL
                            : itemData.size,
                    ...backgroundData,
                    ...textData,
                },
            };

            const itemType = action.payload.item.type;

            const isGlobal =
                itemType === DashTabItemType.GroupControl || itemType === DashTabItemType.Control
                    ? isItemGlobal(action.payload.item)
                    : false;

            const tabData = DashKit.setItem({
                item: newItem,
                config: {...tab, salt: data.salt, counter: data.counter},
                options: {
                    ...action.payload.options,
                    excludeIds: getUniqIdsFromDashData(data),
                    ...(isGlobal ? {useGlobalItems: true} : {}),
                },
            });

            const {targetDashTabId, targetEntryId, targetIds} = action.payload.context;

            // Duplicate connections only if it's the same tab as targetItem
            if (
                tabId === targetDashTabId &&
                state.entry.entryId === targetEntryId &&
                targetIds?.length
            ) {
                const copiedItem = getCreatedItem({
                    isGlobal,
                    items: tabData.items,
                    globalItems: tabData.globalItems,
                });

                const updatedConnections = getUpdatedConnections({
                    connections: tabData.connections,
                    targetIds,
                    item: copiedItem,
                });

                tabData.connections = updatedConnections;
            }

            // Handle global control items (GroupControl and Control types)
            if (itemType === DashTabItemType.GroupControl || itemType === DashTabItemType.Control) {
                const updatedState = getStateForControlWithGlobalLogic({
                    state,
                    data,
                    tabData,
                    tabIndex,
                    itemType,
                    itemData,
                    isGlobal,
                });

                // If the function returned a state, return it
                if (updatedState) {
                    return updatedState;
                }
            }

            return {
                ...state,
                data: update(data, {
                    tabs: {
                        [tabIndex]: {$set: pick(tabData, TAB_PROPERTIES)},
                    },
                    counter: {$set: tabData.counter},
                }),
            };
        }
        case actionTypes.SET_ITEM_DATA: {
            const {
                type: itemType,
                data: itemData,
                namespace,
                defaults,
                contextList,
                id: itemId,
            } = action.payload.item;

            let targetTab = tab;
            let targetTabIndex = tabIndex;

            // if we have to change item not on the current tab
            if (action.payload.tabId) {
                targetTabIndex = state.data.tabs.findIndex(
                    (tabItem) => tabItem.id === action.payload.tabId,
                );
                targetTab = state.data.tabs[targetTabIndex];
            }

            if (!targetTab) {
                return state;
            }

            const targetTabId = action.payload.tabId ?? tabId;

            // TODO: remove after up platform
            const itemPayload = 'item' in action.payload ? action.payload.item : action.payload;

            const isGlobal =
                itemType === DashTabItemType.GroupControl || itemType === DashTabItemType.Control
                    ? isItemGlobal(itemPayload)
                    : false;

            const tabData = DashKit.setItem({
                item: {
                    id: state.openedItemId ?? itemId,
                    type: itemType ?? state.openedDialog,
                    data: itemData,
                    namespace,
                    layout: state.dragOperationProps?.itemLayout,
                    ...(defaults ? {defaults} : null),
                },
                config: {...targetTab, salt: data.salt, counter: data.counter},
                options: {
                    excludeIds: getUniqIdsFromDashData(data),
                    updateLayout: state.dragOperationProps?.newLayout,
                    ...(isGlobal ? {useGlobalItems: true} : {}),
                },
            });

            const allTabItems = getAllTabItems(tabData);

            // migration of connections if old selector becomes a group selector
            // 1. state.openedItemId existance means that widget already exist
            // 2. !action.payload.data.group[0].id - first selector doesn't have an id because it was just converted
            if (
                state.openedItemId &&
                itemType === DashTabItemType.GroupControl &&
                !itemData.group[0].id
            ) {
                tabData.connections = migrateConnectionsForGroupControl({
                    openedItemId: state.openedItemId,
                    currentTab: targetTab,
                    tabDataItems: allTabItems,
                });
            }

            // copy connections if the pasted selector was on the same dashboard tab
            if (contextList?.length > 0) {
                const indexTargetIdMap = {};
                contextList.forEach((context) => {
                    if (
                        context.targetEntryId === state.entry.entryId &&
                        context.targetDashTabId === targetTabId
                    ) {
                        indexTargetIdMap[context.index] = context.targetId;
                    }
                });

                const item = state.openedItemId
                    ? allTabItems.find((tabItem) => tabItem.id === state.openedItemId)
                    : getCreatedItem({
                          isGlobal,
                          items: tabData.items,
                          globalItems: tabData.globalItems,
                      });

                const updatedConnections = getUpdatedConnections({
                    connections: tabData.connections,
                    indexTargetIdMap,
                    item,
                });

                tabData.connections = updatedConnections;
            }

            // Handle global control items (GroupControl and Control types)
            if (itemType === DashTabItemType.GroupControl || itemType === DashTabItemType.Control) {
                const connectionsUpdaters = state.connectionsUpdaters;

                const updatedState = getStateForControlWithGlobalLogic({
                    state,
                    data,
                    tabData,
                    tabIndex: targetTabIndex,
                    itemType,
                    itemData,
                    isGlobal,
                    connectionsUpdaters,
                    itemId,
                    addingGlobalItemArgs: action.payload.addingGlobalItemArgs,
                });

                // If the function returned a state, return it
                if (updatedState) {
                    return updatedState;
                }
            }

            const modifiedItem = tabData.layout[tabData.layout.length - 1];

            return {
                ...state,
                lastModifiedItemId: modifiedItem.i,
                data: update(data, {
                    tabs: {
                        [tabIndex]: {$set: pick(tabData, TAB_PROPERTIES)},
                    },
                    counter: {$set: tabData.counter},
                }),
            };
        }

        default:
            return dashTypedReducer(state, action);
    }
}

export default dash;
