import type {ChartData, ChartSeries, ChartSeriesData} from '@gravity-ui/chartkit/gravity-charts';
import {transformParamsToActionParams} from '@gravity-ui/dashkit/helpers';
import get from 'lodash/get';
import isEqual from 'lodash/isEqual';

import type {GraphWidgetEventScope, StringParams} from '../../../../../../shared';
import type {GraphWidget, LoadedWidgetData, RunActivityFn} from '../../../types';
import type {ChartKitAdapterProps} from '../../types';
import {addParams, subtractParameters} from '../action-params-handlers';
import {getEscapedActionParams, getNormalizedClickActions} from '../utils';

import {getPointActionParams, isPointSelected} from './utils';

type OnClickHandlerArgs = {
    widgetData: LoadedWidgetData;
    point: unknown;
    series: ChartSeries | undefined;
    event: PointerEvent;
    onChange?: ChartKitAdapterProps['onChange'];
    runActivity?: RunActivityFn;
};

export function handleClick(args: OnClickHandlerArgs) {
    const {widgetData, event, point, series, onChange, runActivity} = args;
    const drillDownData = widgetData?.config?.drillDown;

    if (drillDownData) {
        const params: StringParams = widgetData?.params ?? {};
        const drillDownLevel = params.drillDownLevel || ['0'];

        let filters = (params.drillDownFilters || []) as string[];
        if (!filters.some(Boolean)) {
            filters = new Array(drillDownData?.breadcrumbs.length).fill('');
        }

        const level = Number(drillDownLevel[0]);
        if (level === drillDownData.breadcrumbs.length - 1) {
            return;
        }

        const drillDownFilters = filters.map((filter, index) => {
            if (level === index) {
                const drillDownFilterValue =
                    get(point, 'drillDownFilterValue') ??
                    get(series, 'drillDownFilterValue') ??
                    get(series, 'custom.drillDownFilterValue') ??
                    '';
                return drillDownFilterValue;
            }

            return filter;
        });

        onChange?.(
            {
                type: 'PARAMS_CHANGED',
                data: {
                    params: {
                        drillDownLevel: String(level + 1),
                        drillDownFilters,
                    },
                },
            },
            {forceUpdate: true},
            true,
            true,
        );
        return;
    }

    const clickActions = getNormalizedClickActions(widgetData as GraphWidget);

    clickActions.forEach((action) => {
        const handlers = Array.isArray(action.handler) ? action.handler : [action.handler];
        handlers.forEach((handler) => {
            switch (handler.type) {
                case 'setActionParams': {
                    const actionParams = getEscapedActionParams(widgetData);
                    const clickScope: GraphWidgetEventScope = get(action, 'scope', 'point');
                    setActionParamsByClick({
                        event,
                        onChange,
                        actionParams,
                        clickScope,
                        chartData: widgetData?.data as ChartData,
                        point: point as ChartSeriesData,
                        series,
                    });
                    break;
                }
                case 'runActivity': {
                    if (runActivity) {
                        runActivity({
                            params: point as ChartSeriesData,
                        });
                    }

                    break;
                }
            }
        });
    });
}

function setActionParamsByClick(args: {
    chartData?: ChartData;
    point?: ChartSeriesData;
    series?: ChartSeries;
    clickScope: GraphWidgetEventScope;
    event: {metaKey?: boolean};
    onChange?: ChartKitAdapterProps['onChange'];
    actionParams: StringParams;
}) {
    const {
        chartData,
        clickScope,
        event,
        point,
        series,
        onChange,
        actionParams: prevActionParams,
    } = args;
    const multiSelect = Boolean(event.metaKey);
    let newActionParams: StringParams = prevActionParams;

    switch (clickScope) {
        case 'point': {
            if (!point) {
                return;
            }

            const currentPoint = point;
            const currentSeries = series;
            const currentPointParams = getPointActionParams(currentPoint, currentSeries);
            const chartSeries = chartData?.series.data || [];
            const hasSomePointSelected = chartSeries.some((s) =>
                s.data.some((p) => isPointSelected(p, s, prevActionParams)),
            );

            if (hasSomePointSelected) {
                if (isPointSelected(currentPoint, currentSeries, prevActionParams)) {
                    if (multiSelect) {
                        newActionParams = subtractParameters(newActionParams, currentPointParams);
                        chartSeries.forEach((s) => {
                            s.data.forEach((p) => {
                                if (isPointSelected(p, s, newActionParams)) {
                                    const pointParams = getPointActionParams(p, s);
                                    newActionParams = addParams(newActionParams, pointParams);
                                }
                            });
                        });
                    } else {
                        newActionParams = {};
                    }
                } else {
                    if (!multiSelect) {
                        // should remove the selection from the previously selected points
                        chartSeries.forEach((s) => {
                            s.data.forEach((p) => {
                                if (isPointSelected(p, s, prevActionParams)) {
                                    const pointParams = getPointActionParams(p, s);
                                    newActionParams = subtractParameters(
                                        newActionParams,
                                        pointParams,
                                    );
                                }
                            });
                        });
                    }

                    newActionParams = addParams(newActionParams, currentPointParams);
                }
            } else {
                newActionParams = addParams(newActionParams, currentPointParams);
            }

            break;
        }
    }

    if (isEqual(prevActionParams, newActionParams)) {
        return;
    }

    const params = transformParamsToActionParams(newActionParams);
    onChange?.(
        {
            type: 'PARAMS_CHANGED',
            data: {params},
        },
        {forceUpdate: true},
        true,
        true,
    );
}
