import { Weekday } from "@prisma/client";
import { ANCHOR_ISO, MS_PER_DAY, PERIODS_PER_YEAR, PERIOD_DAYS } from "src/common/utils/constants.utils";
import { WEEKDAY_MAP } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";

//lenght of a shift, rouded to nearest 'x' minute
export function computeHours(start: Date, end: Date, roundToMinutes?: number): number {
    const diffMs = end.getTime() - start.getTime();
    const totalMinutes = diffMs / 60000;
    const minutes = roundToMinutes ?
        Math.round(totalMinutes / roundToMinutes) * roundToMinutes :
        totalMinutes;
    return +(minutes / 60);
}

//round the amount of hours to quarter
export function roundToQuarterHour(hours: number): number {
    return Math.round(hours * 4) / 4;
}

// fetch firts day of the week (Sunday)
export function getWeekStart(date: Date, firstDayOfWeek = 0): Date {
    const d = new Date(date);
    const day = d.getDay();
    const diff = (day < firstDayOfWeek ? 7 : 0) + (day - firstDayOfWeek);
    d.setDate(d.getDate() - diff);
    d.setHours(0, 0, 0, 0);
    return d;
}

//fetch last day of the week (Saturday)
export function getWeekEnd(startOfWeek: Date): Date {
    const d = new Date(startOfWeek);
    d.setDate(d.getDate() + 6);
    d.setHours(23, 59, 59, 999);
    return d;
}

//returns january 1st of the selected date's year
export function getYearStart(date: Date): Date {
    return new Date(date.getFullYear(), 0, 1, 0, 0, 0, 0);
}

//to be used to calculate breaks time
export function hoursBetweenSameDay(day: Date, startTime: Date, endTime: Date): number {
    const start = new Date(day); start.setHours(startTime.getHours(),
        startTime.getMinutes(),
        startTime.getSeconds(),
        startTime.getMilliseconds());
    const end = new Date(day); end.setHours(endTime.getHours(),
        endTime.getMinutes(),
        endTime.getSeconds(),
        endTime.getMilliseconds());
    const ms = Math.max(0, end.getTime() - start.getTime());
    return ms / 3_600_000; // decimal hours
}

//ensures the week starts from sunday
export function weekStartSunday(date_local: Date): Date {
    const start_date = new Date();
    start_date.setUTCFullYear(date_local.getUTCFullYear(), date_local.getUTCMonth());
    start_date.setDate(date_local.getUTCDate() - date_local.getUTCDay());
    return start_date;
}

//converts string to HHmm format
export const toStringFromHHmm = (date: Date): string => {
    const hh = date.getUTCHours().toString().padStart(2, '0');
    const mm = date.getUTCMinutes().toString().padStart(2, '0');
    return `${hh}:${mm}`;
}

//converts string to Date format
export const toStringFromDate = (date: Date) =>
    date.toISOString().slice(0, 10);



//converts HHmm format to string
export const toDateFromHHmm = (hhmm: string): Date => {
    const [hh, mm] = hhmm.split(':').map(Number);
    const date = new Date('1970-01-01T00:00:00.000Z');
    date.setUTCHours(hh, mm, 0, 0);
    return new Date(date);
}

//converts string to HHmm format
export const toHHmmFromDate = (input: Date | string): string => {
    const date = new Date(input);
    const hh = String(date.getUTCHours()).padStart(2, '0');
    const mm = String(date.getUTCMinutes()).padStart(2, '0');
    return `${hh}:${mm}`;
}

//converts to Date format from string
export const toDateFromString = (ymd: string | Date): Date => {
    return new Date(ymd);
}

export const toUTCDateFromString = (iso: string | Date) => {
    const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso;
    return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
};

export const sevenDaysFrom = (date: Date | string): Date[] => {
    return Array.from({ length: 7 }, (_, i) => {
        const d = new Date(date);
        d.setUTCDate(d.getUTCDate() + i);
        return d;
    });
}

export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number {
    const ANCHOR = toUTCDateFromString(anchorISO);
    const d = toUTCDateFromString(date);
    const days = Math.floor((+d - +ANCHOR) / MS_PER_DAY);
    const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS));
    return ANCHOR.getUTCFullYear() + 1 + cycles;
}
//compute labels for periods
export function computePeriod(pay_year: number, period_no: number, anchorISO = ANCHOR_ISO) {
    const ANCHOR = toUTCDateFromString(anchorISO);
    const cycles = pay_year - (ANCHOR.getUTCFullYear() + 1);
    const offsetPeriods = cycles * PERIODS_PER_YEAR + (period_no - 1);
    const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY);
    const end = new Date(+start + (PERIOD_DAYS - 1) * MS_PER_DAY);
    const pay = new Date(end.getTime() + 6 * MS_PER_DAY);
    return {
        period_no: period_no,
        pay_year: pay_year,
        payday: toStringFromDate(pay),
        period_start: toStringFromDate(start),
        period_end: toStringFromDate(end),
        label: `${toStringFromDate(start)}.${toStringFromDate(end)}`,
        start, end,
    };
}

//list of all 26 periods for a full year
export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) {
    return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO));
}

//checks for shifts overlaping one another
export const overlaps = (a: { start: Date; end: Date, date?: Date; }, b: { start: Date; end: Date; date?: Date; }) =>
    ((a.date?.getTime() === b.date?.getTime()) && !(a.end <= b.start || a.start >= b.end));

export const is_same_week_day = (date: Date, week_day: Weekday): boolean => {
    return date.getUTCDay() !== WEEKDAY_MAP[week_day];
}

export const addHourstoDateString = (start_time: string, hours: number): string => {
    const start = toDateFromHHmm(start_time);
    const end = new Date(start.getTime() + hours * 60 * 60 * 1000);
    const hh = String(end.getUTCHours()).padStart(2, '0');
    const mm = String(end.getUTCMinutes()).padStart(2, '0');
    return `${hh}:${mm}:00`;
}