/* eslint-disable complexity */
import type {ConfigItemData} from '@gravity-ui/dashkit';
import type {AxiosResponse} from 'axios';
import type {History} from 'history';
import isEmpty from 'lodash/isEmpty';
import type {DashTabItemType, StringParams} from 'shared';
import {FOCUSED_WIDGET_PARAM_NAME, Feature, isTrueArg} from 'shared';
import {isExternalControl} from 'ui/components/DashKit/plugins/Control/utils';
import {DL, URL_OPTIONS} from 'ui/constants/common';
import type DatalensChartkitCustomError from 'ui/libs/DatalensChartkit/modules/datalens-chartkit-custom-error/datalens-chartkit-custom-error';
import {ERROR_CODE} from 'ui/libs/DatalensChartkit/modules/datalens-chartkit-custom-error/datalens-chartkit-custom-error';
import {DASH_WIDGET_TYPES} from 'ui/units/dash/modules/constants';
import {isEnabledFeature} from 'ui/utils/isEnabledFeature';

import type {ChartsStats} from '../../../../../shared/types/charts';
import type {ChartKitLoadSuccess} from '../../../../libs/DatalensChartkit/components/ChartKitBase/ChartKitBase';
import type {ChartKitWrapperOnLoadProps} from '../../../../libs/DatalensChartkit/components/ChartKitBase/types';
import {convertChartToTable} from '../../../../libs/DatalensChartkit/helpers/convert-data-to-table';
import type {
    ChartsData,
    ResponseError,
    ResponseSourcesSuccess,
} from '../../../../libs/DatalensChartkit/modules/data-provider/charts';
import type {
    CombinedError,
    LoadedWidgetData,
    Widget,
} from '../../../../libs/DatalensChartkit/types';
import Utils from '../../../../utils';
import {isWidgetTypeDoNotNeedOverlay} from '../../../DashKit/plugins/Widget/components/helpers';
import type {CurrentTab, WidgetPluginDataWithTabs} from '../../../DashKit/plugins/Widget/types';
import type {
    DashkitMetaDataItemBase,
    DatasetsData,
    DatasetsFieldsListData,
} from '../../../DashKit/plugins/types';
import type {AdjustWidgetLayoutProps} from '../../../DashKit/utils';
import {adjustWidgetLayout} from '../../../DashKit/utils';
import type {State as ChartState} from '../store/types';
import type {
    ChartContentProps,
    ChartWithProviderProps,
    ResolveWidgetControlDataRefArgs,
    WidgetDataRef,
} from '../types';

import {getMapSimpleConfig} from './yandex-map';

export const COMPONENT_CLASSNAME = 'dl-widget';

/**
 * copied from DL Dashkit
 */
export const X_CSRF_TOKEN_HEADER = 'X-CSRF-Token';
/**
 * copied from DL Dashkit
 */
export const DATALENS_DEBUG_MODE_KEY = 'datalens-debug-mode';
/**
 * copied from DL Dashkit
 */
export const DRAGGABLE_HANDLE_CLASS_NAME = 'dl-draggable-handle-element';

/**
 * copied from DL Chartkit use later in restyle dash relations
 */
export const CHARTKIT_WIDGET_TYPE = {
    GRAPH: 'graph',
    TABLE: 'table',
    MARKDOWN: 'markdown',
    ALERT: 'alert',
};

/**
 * informing errors that should not interfere with the display of relations
 */
const ALLOWED_ERRORS = [
    ERROR_CODE.NO_DATA,
    ERROR_CODE.TOO_MANY_LINES,
    ERROR_CODE.UI_SANDBOX_EXECUTION_TIMEOUT,
];

export type WidgetLoadedData =
    | (Omit<
          Partial<DashkitMetaDataItemBase> & Widget & ChartsData,
          'type' | 'usedParams' | 'defaultParams'
      > & {
          type: ChartContentProps['widgetType'];
          usedParams: ChartsData['usedParams'];
      })
    | null;

/**
 * We get duplicated from api, need to clear it to prevent problems with doubles
 * (in ex. in dash relations)
 * @param datasets
 */
const getUniqDatasetsFields = (datasets?: Array<DatasetsData>) => {
    datasets?.forEach((dataset) => {
        if (!dataset.fieldsList) {
            return;
        }
        const guids: Record<string, string> = {};
        const newFieldList: DatasetsFieldsListData[] = [];

        dataset.fieldsList.forEach((fieldItem) => {
            if (!(fieldItem.guid in guids)) {
                guids[fieldItem.guid] = '';
                newFieldList.push(fieldItem);
            }
        });
        dataset.fieldsList = newFieldList;
    });
    return datasets;
};

export const getChartSimpleLoadedData = ({
    widget,
    loadedData,
}: {
    widget?: WidgetDataRef['current'];
    loadedData: LoadedWidgetData<unknown>;
}) => {
    const convertedToTableData = convertChartToTable({
        widget,
        widgetData: loadedData,
    });

    let queries;

    if (loadedData && 'sources' in loadedData) {
        const sources = loadedData.sources ?? {};
        const sourceValues = Object.values(sources);

        queries = sourceValues.map((source) => {
            return 'info' in source ? source?.info : '';
        });
    }

    let data: object | null = convertedToTableData;
    if (isEmpty(convertedToTableData)) {
        switch (loadedData?.type) {
            case 'metric':
            case 'metric2':
            case 'markup':
            case 'markdown': {
                data = loadedData?.data;
                break;
            }
            case 'ymap': {
                data = getMapSimpleConfig({
                    widgetData: loadedData as LoadedWidgetData<unknown>,
                });
                break;
            }
            default: {
                data = loadedData;
                break;
            }
        }
    }

    return {queries, data};
};

// Builds metadata for a single dashboard tab widget (chart).
export const getTabMeta = ({
    id,
    tabWidget,
    loadData,
    savedData,
    error,
    widgetDataRef,
    currentTabChartId,
}: {
    id: string;
    tabWidget: CurrentTab;
    loadData: LoadedWidgetData<ChartsData>;
    savedData?: LoadedWidgetData<ChartsData>;
    error: CombinedError | null;
    widgetDataRef?: WidgetDataRef;
    currentTabChartId?: string;
}) => {
    let loadedData: WidgetLoadedData = null;
    let loadedWithError = false;

    // Pick the matching data source: prefer freshly loaded data, fall back to saved (cached) data.
    if (loadData?.entryId === tabWidget.chartId) {
        const loadDataError = (loadData as unknown as AxiosResponse<ResponseError>)?.data?.error;

        loadedWithError = Boolean(loadDataError);
        loadedData = loadData;
    } else if (savedData?.entryId === tabWidget.chartId) {
        loadedData = savedData;
        const savedDataError = (savedData as unknown as AxiosResponse<ResponseError>)?.data?.error;
        loadedWithError = Boolean(savedDataError);
    }

    // For the currently active tab (or when no specific tab is targeted),
    // propagate external errors unless the error code is explicitly allowed.
    if (currentTabChartId === tabWidget.chartId || !currentTabChartId) {
        const errorCode = (error as DatalensChartkitCustomError)?.code;

        loadedWithError =
            loadedWithError ||
            Boolean(
                error && (typeof errorCode !== 'string' || !ALLOWED_ERRORS.includes(errorCode)),
            );
    }

    // No data resolved at all — treat as an error state (only for active tab)
    if (!loadedData && currentTabChartId === tabWidget.chartId) {
        loadedWithError = true;
    }

    const metaInfo: Omit<DashkitMetaDataItemBase, 'defaultParams'> = {
        layoutId: id,
        chartId: tabWidget.chartId,
        widgetId: tabWidget.id,
        title: tabWidget.title,
        label: (loadedData?.key && Utils.getEntryNameFromKey(loadedData?.key || '')) || '',
        params: tabWidget.params,
        enableFiltering: tabWidget.enableActionParams || false,
        loaded: Boolean(loadedData),
        entryId: tabWidget.chartId,
        usedParams: loadedData?.usedParams
            ? Object.keys(loadedData?.usedParams || {}) || null
            : null,
        datasets:
            getUniqDatasetsFields(loadedData?.datasets || loadedData?.extra?.datasets) || null,
        datasetId:
            (loadedData?.sources as ResponseSourcesSuccess)?.fields?.datasetId ||
            loadedData?.extra?.datasets?.[0]?.id ||
            '',
        type: (loadedData?.type as DashTabItemType) || null,
        visualizationType: loadedData?.libraryConfig?.chart?.type || null,
        loadError: loadedWithError,
        isWizard: Boolean(loadedData?.isNewWizard || loadedData?.isOldWizard),
        isEditor: Boolean(loadedData?.isEditor),
        isQL: Boolean(loadedData?.isQL),
        getSimpleLoadedData: () => {
            return getChartSimpleLoadedData({
                widget: widgetDataRef?.current,
                loadedData: loadedData as LoadedWidgetData<unknown>,
            });
        },
    };

    return metaInfo;
};

/**
 * For new (by flag relations) for charts only
 * @param tabs
 * @param id
 * @param loadData
 * @param savedData
 */
export const getWidgetMeta = ({
    tabs,
    id,
    loadData,
    savedData,
    error,
    widgetDataRef,
    currentTabChartId,
}: {
    tabs: Array<CurrentTab>;
    id: string;
    loadData: LoadedWidgetData<ChartsData>;
    savedData: LoadedWidgetData<ChartsData>;
    error: CombinedError | null;
    widgetDataRef?: WidgetDataRef;
    currentTabChartId: string;
}) => {
    return tabs.map((tabWidget) => {
        return getTabMeta({
            id,
            tabWidget,
            loadData,
            savedData,
            error,
            widgetDataRef,
            currentTabChartId,
        });
    });
};

export const getWidgetSelectorMeta = ({
    id,
    chartId,
    loadedData,
    error,
    widgetParamsDefaults,
    data,
}: {
    id: string;
    chartId: string;
    loadedData: ResolveWidgetControlDataRefArgs | null;
    error: CombinedError | null;
    widgetParamsDefaults: StringParams;
    data: WidgetPluginDataWithTabs | ConfigItemData;
}) => {
    const loadedWithError = Boolean(
        (loadedData as unknown as AxiosResponse<ResponseError>)?.data?.error ||
            error ||
            !loadedData,
    );

    let title = '';

    if (isExternalControl(data)) {
        title = data.title;
    }

    const metaInfo: Omit<DashkitMetaDataItemBase, 'defaultParams'> & {widgetParams: StringParams} =
        {
            layoutId: id,
            chartId,
            widgetId: id,
            title,
            label: (loadedData?.key && Utils.getEntryNameFromKey(loadedData?.key || '')) || '',
            params: loadedData?.params || {},
            widgetParams: widgetParamsDefaults || {},
            enableFiltering: false,
            loaded: Boolean(loadedData),
            entryId: chartId,
            usedParams: loadedData?.usedParams
                ? Object.keys(loadedData?.usedParams || {}) || null
                : null,
            datasets: loadedData?.datasets || null,
            datasetId: (loadedData?.sources as ResponseSourcesSuccess)?.fields?.datasetId || '',
            type: (loadedData?.type as DashTabItemType) || null,
            visualizationType: null,
            loadError: loadedWithError,
            getSimpleLoadedData: () => loadedData,
        };

    return metaInfo;
};

/**
 * helper for preparing set of constants for chart & widget render
 * @param props
 */
export const getPreparedConstants = (props: {
    isLoading: ChartState['isLoading'];
    error: ChartState['error'];
    loadedData: ChartContentProps['loadedData'];
    isReloadWithNoVeil: ChartState['isReloadWithNoVeil'];
    noVeil: ChartContentProps['noVeil'];
    noLoader: ChartContentProps['noLoader'];
    isSilentReload: ChartState['isSilentReload'];
    widgetId?: string;
    history?: History<unknown>;
    hideTitle?: boolean | undefined;
    disableChartLoader?: boolean;
    needWaitForDSFields?: boolean;
}) => {
    const {
        loadedData,
        hideTitle,
        widgetId,
        history,
        isLoading,
        error,
        isReloadWithNoVeil,
        noVeil,
        noLoader,
        isSilentReload,
        disableChartLoader,
        needWaitForDSFields,
    } = props;
    const widgetType = loadedData?.type;
    let isFullscreen = false;
    let noControls = false;
    if (history) {
        const searchParams = new URLSearchParams(history.location.search);
        isFullscreen = widgetId === searchParams.get(FOCUSED_WIDGET_PARAM_NAME) && DL.IS_MOBILE;
        noControls = isTrueArg(searchParams.get(URL_OPTIONS.NO_CONTROLS));
    }

    const isFirstLoadOrAfterError = loadedData === null;

    const showLoader =
        Boolean(isLoading || needWaitForDSFields) &&
        !disableChartLoader &&
        ((!isSilentReload && !noLoader) || isFirstLoadOrAfterError);

    const mods = {
        'no-tabs': !isFullscreen && hideTitle,
        fullscreen: isFullscreen,
        'no-controls': noControls,
        [String(widgetType)]: Boolean(widgetType),
        'default-mobile': DL.IS_MOBILE && !isFullscreen,
    };
    const hasVeil = Boolean(loadedData && !error && !noVeil && !isReloadWithNoVeil);

    const noOverlay =
        !DL.IS_MOBILE || !loadedData || error || isWidgetTypeDoNotNeedOverlay(widgetType || '');
    const showOverlayWithControlsOnEdit = !noOverlay && !isFullscreen;

    return {
        mods,
        widgetBodyClassName: DL.IS_MOBILE ? 'dl-chartkit-body-mix' : undefined,
        hasHiddenClassMod: Boolean(isLoading && error),
        veil: hasVeil,
        showLoader,
        isFullscreen,
        widgetType,
        showOverlayWithControlsOnEdit,
        noControls,
    };
};

export const removeEmptyNDatasetFieldsProperties = (
    obj: StringParams,
    removeFieldsList?: string[] | null,
) => {
    const checkRemoveList = Boolean(removeFieldsList?.length);
    return Object.entries(obj).reduce((result, [key, value]) => {
        if (value !== null && value !== undefined) {
            if (checkRemoveList) {
                if (!removeFieldsList?.includes(key)) {
                    result[key] = value;
                }
            } else {
                result[key] = value;
            }
        }
        return result;
    }, {} as StringParams);
};

/**
 * method copied from DL Chartkit
 * should call when data.status === 'success'
 * @param data
 * @param scope
 * @param chartsDataProvider
 */
export const pushStats = (
    data: ChartKitWrapperOnLoadProps,
    scope: ChartsStats['scope'],
    chartsDataProvider: ChartWithProviderProps['dataProvider'],
) => {
    if (isEnabledFeature(Feature.EnableDashChartStat)) {
        chartsDataProvider.pushStats?.(data as ChartKitLoadSuccess<ChartsData>, {
            groupId: DL.REQUEST_ID,
            scope,
        });
    }
};

/**
 * get current tab index from tabs
 * @param tabs
 * @param tabId
 */
export const getTabIndex = (tabs: WidgetPluginDataWithTabs['tabs'], tabId: string | null) => {
    if (!tabs) {
        return 0;
    }
    const index = tabId
        ? tabs.findIndex(({id}: {id: string}) => id === tabId)
        : tabs.findIndex(({isDefault}: {isDefault: boolean}) => isDefault);

    return index === -1 ? 0 : index;
};

/**
 * widgets with autoheight setting
 * @param widgetType
 */
export const isWidgetTypeWithAutoHeight = (widgetType: string) => {
    return (
        widgetType === DASH_WIDGET_TYPES.TABLE ||
        widgetType === DASH_WIDGET_TYPES.MARKDOWN ||
        widgetType === DASH_WIDGET_TYPES.METRIC ||
        widgetType === DASH_WIDGET_TYPES.METRIC2 ||
        widgetType === DASH_WIDGET_TYPES.MARKUP
    );
};

/**
 * method copied from DL Dashkit
 * @param type
 * @param tabItem
 * @param widgetId
 * @param rootNode
 * @param gridLayout
 * @param layout
 * @param cb
 */
export const updateImmediateLayout = ({
    type,
    autoHeight,
    widgetId,
    rootNode,
    gridLayout,
    layout,
    cb,
}: AdjustWidgetLayoutProps & {
    type?: string;
    autoHeight?: boolean;
}) => {
    if (!type) {
        return;
    }
    const newAutoHeight = isWidgetTypeWithAutoHeight(type) ? autoHeight : false;
    adjustWidgetLayout({
        widgetId,
        needSetDefault: !newAutoHeight,
        rootNode,
        gridLayout,
        layout,
        cb,
    });
};

export const isAllParamsEmpty = (params?: StringParams | null) => {
    const res = Object.values(params || {}).every((item) => {
        if (Array.isArray(item)) {
            return isEmpty(item.filter((val) => !isEmpty(String(val).trim())));
        } else {
            return isEmpty(String(item).trim());
        }
    });
    return Boolean(res);
};
