import type {
    ChartOptions as ChartJsChartOptions,
    CartesianScaleOptions,
    RadialLinearScaleOptions,
    Scale,
    Tick,
    GridLineOptions,
} from 'chart.js';
import { DateTime } from 'luxon';
import type { DeepPartial } from 'chart.js/dist/types/utils';
import { assureMaxLength, defaultCompactNumberFormat } from 'components/utils/formatter';
import type {
    CartesianAxisConfiguration,
    ChartOptions,
    GridLinesConfiguration,
    TicksConfiguration,
    TimeCartesianAxisConfiguration,
    TimeDisplayFormats,
} from './types';
import { defaultValue, singleChartJsColor } from './utils';

const TICK_MAX_LENGTH = 40;

function computeFormatTick(
    displayTick: TicksConfiguration['display'],
    type: CartesianAxisConfiguration['type'],
    formatNumber: (value: number) => string
) {
    if (type === 'time') {
        return null;
    }

    function formatTick(this: Scale, tickValue: number, _index: number, ticks: Tick[]) {
        const minAbsTickValue = Math.min(...ticks.map((tick) => Math.abs(tick.value)));
        if (displayTick === 'single' && tickValue !== minAbsTickValue) {
            return '';
        }
        if (type === 'category') {
            return assureMaxLength(this.getLabelForValue(tickValue), TICK_MAX_LENGTH);
        }
        return formatNumber(tickValue);
    }
    return formatTick;
}

const computeGridLineColor: (
    display: GridLinesConfiguration['display']
) => GridLineOptions['color'] = (display) => (context) => {
    if (!context?.scale?.ticks) return 'rgba(0, 0, 0, 0)';
    const ticksAbsoluteValues = context.scale.ticks.map((tick) => Math.abs(tick.value));
    const minAbsoluteTicksIndex = ticksAbsoluteValues.indexOf(Math.min(...ticksAbsoluteValues));
    if (display) {
        if (context.index === minAbsoluteTicksIndex) return 'rgba(0, 0, 0, 0.4)';
        if (display !== 'single') return 'rgba(0, 0, 0, 0.1)';
    }
    return 'rgba(0, 0, 0, 0)';
};

const DATE_TOOLTIP_FORMATS = {
    millisecond: 'h:mm:ss.SSS a',
    second: DateTime.DATETIME_MED_WITH_SECONDS,
    minute: DateTime.DATETIME_MED,
    hour: DateTime.DATETIME_MED,
    day: { day: 'numeric', month: 'long' },
    week: "'W'WW yyyy",
    month: { month: 'long', year: 'numeric' },
    quarter: "'Q'q - yyyy",
    year: { year: 'numeric' },
};

function getDateTooltipFormat(
    unit?: TimeCartesianAxisConfiguration['timeUnit'],
    customFormats?: TimeDisplayFormats
) {
    return unit ? customFormats?.[unit] || DATE_TOOLTIP_FORMATS[unit] : undefined;
}

export default function buildScales(options: ChartOptions): ChartJsChartOptions['scales'] {
    const scales: ChartJsChartOptions['scales'] = {};

    // X Axis
    if (options.axis?.x) {
        scales.x = {
            border: { display: false },
            ...(options.axis.x.type === 'linear' && {
                beginAtZero: defaultValue(options?.axis?.x?.beginAtZero, true),
            }),
            stacked: options.axis?.assemblage?.stacked,
            max:
                options?.axis?.x?.type === 'linear' && options.axis?.assemblage?.percentaged
                    ? 100
                    : undefined,
            type: options?.axis?.x?.type,
            ...(options?.axis?.x?.type === 'time'
                ? {
                      time: {
                          unit: options?.axis?.x?.timeUnit,
                          tooltipFormat: getDateTooltipFormat(
                              options?.axis?.x?.timeUnit,
                              options?.tooltip?.timeDisplayFormats
                          ),
                          isoWeekday: true,
                          displayFormats: {
                              ...(options?.axis?.x?.timeDisplayFormats || {}),
                          },
                      },
                  }
                : {}),
            display: options?.axis?.x?.display,
            offset: defaultValue(options?.axis?.x?.offset, false),
            title: {
                display: options?.axis?.x?.title?.display,
                color: defaultValue(options?.axis?.x?.title?.color, 'rgb(0, 0, 0)'),
                align: options?.axis?.x?.title?.align,
                text: options?.axis?.x?.title?.text,
                font: {
                    weight: defaultValue(options?.axis?.x?.title?.font?.weight, '400'),
                    size: defaultValue(options?.axis?.x?.title?.font?.size, 12),
                },
            },
            grid: {
                display: !!defaultValue(options.axis?.x?.gridLines?.display, true),
                offset: false,
                color: computeGridLineColor(
                    defaultValue(options.axis?.x?.gridLines?.display, true)
                ),
            },
            ticks: {
                display: !!defaultValue(options?.axis?.x?.ticks?.display, true),
                color: defaultValue(options?.axis?.x?.ticks?.color, 'rgb(86, 86, 86)'),
                callback: computeFormatTick(
                    defaultValue(options?.axis?.x?.ticks?.display, true),
                    options?.axis?.x?.type,
                    defaultValue(options?.axis?.x?.ticks?.format, defaultCompactNumberFormat)
                ),
            },
        } as DeepPartial<CartesianScaleOptions>;
    }

    // Y Axis
    if (options.axis?.y) {
        scales.y = {
            border: { display: false },
            ...(options.axis.y.type === 'linear' && {
                beginAtZero: defaultValue(options?.axis?.y?.beginAtZero, true),
            }),
            stacked: options.axis?.assemblage?.stacked,
            max:
                options?.axis?.y?.type === 'linear' && options.axis?.assemblage?.percentaged
                    ? 100
                    : undefined,
            type: options?.axis?.y?.type,
            display: options?.axis?.y?.display,
            title: {
                display: options?.axis?.y?.title?.display,
                align: options?.axis?.y?.title?.align,
                text: options?.axis?.y?.title?.text,
                color: defaultValue(options?.axis?.y?.title?.color, 'rgb(0, 0, 0)'),
                font: {
                    weight: defaultValue(options?.axis?.y?.title?.font?.weight, '400'),
                    size: defaultValue(options?.axis?.y?.title?.font?.size, 12),
                },
            },
            grid: {
                display: !!defaultValue(options.axis?.y?.gridLines?.display, true),
                color: computeGridLineColor(
                    defaultValue(options.axis?.y?.gridLines?.display, true)
                ),
            },
            ticks: {
                display: !!defaultValue(options?.axis?.y?.ticks?.display, true),
                color: defaultValue(
                    singleChartJsColor(options?.axis?.y?.ticks?.color),
                    'rgb(86, 86, 86)'
                ),
                callback: computeFormatTick(
                    defaultValue(options?.axis?.y?.ticks?.display, true),
                    options?.axis?.y?.type,
                    defaultValue(options?.axis?.y?.ticks?.format, defaultCompactNumberFormat)
                ),
            },
        } as DeepPartial<CartesianScaleOptions>;
    } else {
        scales.y = { display: false };
    }

    // R Axis
    if (options.axis?.r) {
        scales.r = {
            border: { display: false },
            beginAtZero: defaultValue(options?.axis?.r?.beginAtZero, true),
            ticks: {
                display: defaultValue(options?.axis?.r?.ticks?.display, true),
                color: defaultValue(options?.axis?.r?.ticks?.color, 'rgb(86, 86, 86)'),
                callback: computeFormatTick(
                    defaultValue(options?.axis?.r?.ticks?.display, true),
                    undefined,
                    defaultValue(options?.axis?.r?.ticks?.format, defaultCompactNumberFormat)
                ),
            },
            grid: {
                display: defaultValue(options.axis?.r?.gridLines?.display, true),
                offset: false,
                color: computeGridLineColor(
                    defaultValue(options.axis?.r?.gridLines?.display, true)
                ),
            },
        } as DeepPartial<RadialLinearScaleOptions>;
    }

    return scales;
}
