import { isListViewGroup } from '../listView';
import {
    CUSTOM_VALUE,
    FULL_DATE,
    SHORT_DATE,
    SHORT_MONTH,
    LONG_MONTH,
    CALENDAR_ITEM_TALL_HEIGHT,
    CALENDAR_ITEM_SHORT_HEIGHT,
    CALENDAR_ITEM_NORMAL_HEIGHT,
} from './constants';
import { t } from './i18n';
import type { DatePickerOption, DatePickerOptions, Dates, DatePickerParsedOption } from './types';

export const getDateDuration = (start?: Date, end?: Date) => {
    if (!start || !end) return undefined;

    const diff = differenceInDays(start, end);
    return t('xDays', { count: diff });
};

const currentYear = new Date().getUTCFullYear();
export const parseLimit = (start: string, end: string): Required<Dates> => ({
    start: parseISO(start) ?? new Date(Date.UTC(currentYear - 1, 0, 1)),
    end: parseISO(end) ?? new Date(Date.UTC(currentYear, 11, 31)),
});

export const parseISO = (date: string): Date | undefined => {
    if (date.length !== 10) return undefined;

    const year = Number.parseInt(date.slice(0, 4), 10);
    const month = Number.parseInt(date.slice(5, 7), 10);
    const day = Number.parseInt(date.slice(8), 10);

    const parsed = new Date(Date.UTC(year, month - 1, day));
    return isFinite(+parsed) ? parsed : undefined;
};

export const parseInput = (date: string): Date | undefined => {
    // valid formats are YYYY/MM/DD, YY/MM/DD, YYYY-MM-DD, YY-MM-DD
    if (date.length < 8) return undefined;

    const [yearPart, monthPart, dayPart] = date.split(/[-/]/);

    const year = Number.parseInt(yearPart, 10);
    const month = Number.parseInt(monthPart, 10);
    const day = Number.parseInt(dayPart, 10);

    const fullYear = yearPart.trim().length === 2 ? (year > 40 ? 1900 : 2000) + year : year;

    if (fullYear < 0 || month < 1 || month > 12 || day < 0 || day > 31) return undefined;

    const parsed = new Date(Date.UTC(fullYear, month - 1, day));
    return isFinite(+parsed) ? parsed : undefined;
};

export const formatStartDate = (start: Date, end: Date, locale: string) => {
    const sameYear = start.getUTCFullYear() === end.getUTCFullYear();
    return formatDate(start, sameYear ? SHORT_DATE : FULL_DATE, locale);
};

export const formatDate = (date: Date, format: Intl.DateTimeFormatOptions, locale: string) => {
    const intl = Intl.DateTimeFormat(locale, { ...format, timeZone: 'UTC' });
    return intl.format(date);
};

export const createDateRange = (year: number, month: number): Date[] => {
    const days: Date[] = [];
    for (let i = 1; i <= 31; i++) {
        const date = new Date(Date.UTC(year, month, i));
        // All months contain at least 28 days. Above that check for overflow.
        if (i >= 29 && date.getUTCMonth() !== month) break;
        days.push(date);
    }
    return days;
};

export const createMonthRange = (limit: Required<Dates>) => {
    const startYear = limit.start.getUTCFullYear();
    const endYear = limit.end.getUTCFullYear();
    const years = [...Array(endYear - startYear + 1)].map((_, index) => startYear + index);

    return years.flatMap((year) => {
        const startMonth = year === startYear ? limit.start.getUTCMonth() : 0;
        const endMonth = year === endYear ? limit.end.getUTCMonth() : 11;
        return [...Array(endMonth - startMonth + 1)].map((_, index) => ({
            year,
            month: index + startMonth,
        }));
    });
};

export const dateToString = (date: Date) => date.toISOString().slice(0, 10);

export const formatInputDate = (date: Date) => {
    const year = date.getUTCFullYear().toString();
    const month = (date.getUTCMonth() + 1).toString().padStart(2, '0');
    const day = date.getUTCDate().toString().padStart(2, '0');
    return `${year}/${month}/${day}`;
};

export const addDays = (date: Date, amount: number): Date => {
    const clone = new Date(date);
    clone.setUTCDate(date.getUTCDate() + amount);
    return clone;
};

const dayMs = 24 * 60 * 60 * 1000;
export const differenceInDays = (start: Date, end: Date): number => {
    const diff = Math.abs(start.getTime() - end.getTime());
    // Return difference plus 1 to include end date
    return Math.ceil(diff / dayMs) + 1;
};

export const getCalendarHeight = (year: number, month: number) => {
    const firstDay = new Date(Date.UTC(year, month, 1)).getUTCDay();
    if (firstDay === 6 && LONG_MONTH.includes(month)) return CALENDAR_ITEM_TALL_HEIGHT;
    if (firstDay === 0 && (LONG_MONTH.includes(month) || SHORT_MONTH.includes(month)))
        return CALENDAR_ITEM_TALL_HEIGHT;
    // Non leap-year February with initial Monday
    if (
        month === 1 &&
        firstDay === 1 &&
        new Date(Date.UTC(year, month, 29)).getUTCMonth() !== month
    )
        return CALENDAR_ITEM_SHORT_HEIGHT;
    return CALENDAR_ITEM_NORMAL_HEIGHT;
};

export const createDateOption = (option: Partial<DatePickerOption>): DatePickerParsedOption => ({
    ...option,
    value: option.value ?? '-',
    start: option.start ? parseISO(option.start) : undefined,
    end: option.end ? parseISO(option.end) : undefined,
});

export const parseDateOptions = (
    options: DatePickerOptions,
    option: Partial<DatePickerOption>
): DatePickerParsedOption | undefined => {
    const flattenedOptions = options.flatMap((o) => (isListViewGroup(o) ? o.options : o));
    const selectedOption = flattenedOptions.find((o) => o.value === option.value);

    if (selectedOption && selectedOption.mixed)
        return {
            ...selectedOption,
            start: undefined,
            end: undefined,
        };

    if (selectedOption) return createDateOption(selectedOption);

    if (option.start)
        return createDateOption({
            start: option.start,
            end: option.end ?? '',
            mixed: option.mixed,
            value: CUSTOM_VALUE,
        });

    return undefined;
};

export const createDateOptions = (options: DatePickerOptions) =>
    options.map((optionOrGroup) =>
        isListViewGroup(optionOrGroup)
            ? {
                  ...optionOrGroup,
                  options: optionOrGroup.options.map(createDateOption),
              }
            : createDateOption(optionOrGroup)
    );
