/**
 * usage: calculatedHours = calculateHours(duration, type, dayType)
 * 
 * Rules based on IT Group Collective Agreement (IT-01 to IT-04):
 * - Workday OT: 1.5x for first 7.5h, 2.0x thereafter.
 * - Rest Day 1 (Sat): 1.5x for first 7.5h, 2.0x thereafter.
 * - Rest Day 2 (Sun): 2.0x for all hours.
 * - Holiday: 1.5x for first 7.5h, 2.0x thereafter (plus regular pay if applicable, but we track OT here).
 * - Call-back: Minimum 3h pay (at applicable rate).
 * - Reporting Pay: Minimum 4h pay (at straight time) or worked time at OT rate, whichever is greater. 
 *   (Simplified: If worked < 3h? IT rules say 3h min for Callback. 4h min for Reporting on Rest Day).
 * 
 * Assumptions:
 * - This function returns the "Banked Equivalent" hours. 
 *   e.g., 1h worked at 1.5x = 1.5 banked hours.
 * - Non-Contiguous Hours (Workday): Greater of (Actual * OT) or (2h * Straight Time).
 */

export const calculateBankedHours = (duration, type, dayType) => {
    let multiplier = 1.0;
    let earned = 0;

    // Convert duration to number
    const hours = parseFloat(duration) || 0;
    if (hours <= 0) return 0;

    // --- STANDBY ---
    if (type === 'standby') {
        // Article 11.01: 0.5h pay for each 4h period. => 0.125 multiplier.
        // Or we can just store it as 1h for every 8. 
        // Let's use exact calculation: hours * (0.5 / 4)
        return hours * 0.125;
    }

    // --- CALL-BACK ---
    if (type === 'callback') {
        // Article 10.01: Greater of 3h pay or time worked at OT rate.
        // We calculate OT rate first.
        let workedValue = calculateOvertimeValue(hours, dayType);

        // Clause 10.01 (a) says "three (3) hours' pay at the *applicable rate for overtime*"
        const applicableRate = getBaseOvertimeRate(dayType);
        const minPay = 3 * applicableRate;

        // If we work long enough to hit 2.0x threshold on a 1.5x day, we need exact calculation
        return Math.max(minPay, workedValue);
    }

    // --- NON-CONTIGUOUS HOURS ---
    if (type === 'non_contiguous') {
        // Article 9.01 (4): Greater of "time actually worked"(at OT rate) OR "minimum of two (2) hours' pay at straight time".
        // Note: 9.01(4) generally applies "on that day" (workday).
        // 2h pay at straight time = 2.0 banked hours.

        let workedValue = calculateOvertimeValue(hours, dayType);

        // Minimum is 2h pay at STRAIGHT time (1.0x).
        const minPay = 2.0;

        return Math.max(minPay, workedValue);
    }

    // --- REGULAR / OVERTIME ---
    if (type === 'overtime' || type === 'regular') {
        // "Regular" on a Time Tracker for Overtime usually implies "Regular Overtime" unless it is standard hours?
        // If it's standard 7.5h workday, multiplier is 1.0.
        // But the user tool is for "Overtime and Back hours". 
        // If type is 'regular' and day is 'workday', it's just 1.0x?
        // Let's assume 'regular' means 1.0x (standard work).
        if (type === 'regular') {
            return hours * 1.0;
        }

        // Overtime Logic
        return calculateOvertimeValue(hours, dayType);
    }

    return hours;
};

function getBaseOvertimeRate(dayType) {
    if (dayType === 'rest_day_2') return 2.0;
    return 1.5;
}

function calculateOvertimeValue(hours, dayType) {
    // Second Day of Rest (Sunday) = All 2.0x
    if (dayType === 'rest_day_2') {
        return hours * 2.0;
    }

    // Workday, Rest Day 1, Holiday
    // First 7.5h @ 1.5x
    // Remaining @ 2.0x
    if (hours <= 7.5) {
        return hours * 1.5;
    } else {
        const firstPart = 7.5 * 1.5;
        const secondPart = (hours - 7.5) * 2.0;
        return firstPart + secondPart;
    }
}

/**
 * Returns the maximum allowed standby hours based on the day type.
 * - Workday: 16 hours (24 - 8 regular work).
 * - Rest Days / Holidays: 24 hours.
 */
export const getMaxStandbyHours = (dayType) => {
    if (dayType === 'workday') return 16;
    return 24;
};

/**
 * Helper to check if a date is a Canadian Statutory Holiday.
 * Covers fixed dates and major variable dates (Good Friday, Victoria Day, Labour Day, Thanksgiving, Easter Monday, Civic Holiday).
 */
export const isCanadianHoliday = (dateObj) => {
    const year = dateObj.getFullYear();
    const month = dateObj.getMonth(); // 0-indexed
    const date = dateObj.getDate();
    const day = dateObj.getDay(); // 0 = Sun, 1 = Mon, etc.

    // --- Fixed Date Holidays ---
    // New Year's Day (Jan 1)
    if (month === 0 && date === 1) return true;
    // Canada Day (Jul 1)
    if (month === 6 && date === 1) return true;
    // Remembrance Day (Nov 11)
    if (month === 10 && date === 11) return true;
    // Christmas Day (Dec 25)
    if (month === 11 && date === 25) return true;
    // Boxing Day (Dec 26)
    if (month === 11 && date === 26) return true;

    // --- Variable Date Holidays ---

    // Good Friday & Easter Monday
    // Easter calculation (Anonymous / Meeus/Jones/Butcher's algorithm)
    const a = year % 19;
    const b = Math.floor(year / 100);
    const c = year % 100;
    const d = Math.floor(b / 4);
    const e = b % 4;
    const f = Math.floor((b + 8) / 25);
    const g = Math.floor((b - f + 1) / 3);
    const h = (19 * a + b - d - g + 15) % 30;
    const i = Math.floor(c / 4);
    const k = c % 4;
    const l = (32 + 2 * e + 2 * i - h - k) % 7;
    const m = Math.floor((a + 11 * h + 22 * l) / 451);
    const easterMonth = Math.floor((h + l - 7 * m + 114) / 31) - 1; // 0-indexed
    const easterDay = ((h + l - 7 * m + 114) % 31) + 1;

    const easterDate = new Date(year, easterMonth, easterDay);

    // Good Friday (2 days before Easter)
    const goodFriday = new Date(easterDate);
    goodFriday.setDate(easterDate.getDate() - 2);
    if (month === goodFriday.getMonth() && date === goodFriday.getDate()) return true;

    // Easter Monday (1 day after Easter) - specific to some agreements but often treated as holiday in fed gov
    const easterMonday = new Date(easterDate);
    easterMonday.setDate(easterDate.getDate() + 1);
    if (month === easterMonday.getMonth() && date === easterMonday.getDate()) return true;

    // Victoria Day (Money before May 25)
    if (month === 4 && day === 1 && date >= 18 && date <= 24) return true;

    // Canada Day Observed (if Jul 1 is Sun, then Jul 2 is holiday? - simplified, usually if falling on weekend)
    // Checking only standard dates for now as per "statutory holidays".

    // Civic Holiday (First Monday in August) - Not statutory everywhere but often observed
    if (month === 7 && day === 1 && date <= 7) return true;

    // Labour Day (First Monday in September)
    if (month === 8 && day === 1 && date <= 7) return true;

    // Thanksgiving (Second Monday in October)
    if (month === 9 && day === 1 && date >= 8 && date <= 14) return true;

    return false;
};

/**
 * Helper to detect day type from a Date object (for auto-filling).
 * Includes holiday detection.
 */
export const detectDayType = (dateObj) => {
    if (isCanadianHoliday(dateObj)) return 'holiday';

    const day = dateObj.getDay(); // 0 = Sun, 6 = Sat
    if (day === 0) return 'rest_day_2'; // Sunday
    if (day === 6) return 'rest_day_1'; // Saturday
    return 'workday';
};

/**
 * Generates a list of daily entries for a given range.
 * Used for Bulk Standby Entry.
 */
export const generateStandbyEntries = (startDateStr, endDateStr) => {
    const entries = [];
    const [sy, sm, sd] = startDateStr.split('-').map(Number);
    const [ey, em, ed] = endDateStr.split('-').map(Number);
    // Use Noon UTC to avoid timezone rollover issues
    const start = new Date(Date.UTC(sy, sm - 1, sd, 12, 0, 0));
    const end = new Date(Date.UTC(ey, em - 1, ed, 12, 0, 0));

    // Loop from start to end
    for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
        // Create local date object for holiday checking (using getFullYear/getMonth/getDate)
        // We used UTC above for iteration safety, but need local context or consistent context for day checks.
        // detectDayType uses local methods.
        // Let's create a new Date object from the UTC components to treat it as "Local Noon" for checking.
        // Actually, detectDayType uses .getDay() which is local.
        // If we want consistency, we should ensure we are checking the "intended" date.
        // The simplistic approach:
        const checkDate = new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), 12, 0, 0);

        const dateStr = d.toISOString().split('T')[0];
        const dayType = detectDayType(checkDate);
        const maxHours = getMaxStandbyHours(dayType);

        entries.push({
            date: dateStr,
            dayType: dayType,
            duration: maxHours,
            type: 'standby',
            isBanked: true // Default to banked for standby? Usually yes.
        });
    }
    return entries;
};
