/**
 * DateTime - Immutable Date/Time Utilities
 * Provides chainable, type-safe date/time operations with multiple format support
 */
import { ErrorFactory } from '../exceptions/ZintrustError.js';
/**
 * Create a DateTime instance from a Date or string
 */
const monthNames = [
    'January',
    'February',
    'March',
    'April',
    'May',
    'June',
    'July',
    'August',
    'September',
    'October',
    'November',
    'December',
];
const monthNamesShort = [
    'Jan',
    'Feb',
    'Mar',
    'Apr',
    'May',
    'Jun',
    'Jul',
    'Aug',
    'Sep',
    'Oct',
    'Nov',
    'Dec',
];
const padZero = (num, length = 2) => {
    return String(num).padStart(length, '0');
};
const getDayOfYearHelper = (date) => {
    const start = new Date(date.getFullYear(), 0, 0);
    const diff = date.getTime() - start.getTime();
    const oneDay = 1000 * 60 * 60 * 24;
    return Math.floor(diff / oneDay);
};
const calculateAgo = (value) => {
    const now = new Date();
    const diffMs = now.getTime() - value.getTime();
    if (diffMs < 1000)
        return 'just now';
    if (diffMs < 60000)
        return `${Math.floor(diffMs / 1000)} seconds ago`;
    if (diffMs < 3600000)
        return `${Math.floor(diffMs / 60000)} minutes ago`;
    if (diffMs < 86400000)
        return `${Math.floor(diffMs / 3600000)} hours ago`;
    if (diffMs < 604800000)
        return `${Math.floor(diffMs / 86400000)} days ago`;
    if (diffMs < 2592000000)
        return `${Math.floor(diffMs / 604800000)} weeks ago`;
    if (diffMs < 31536000000)
        return `${Math.floor(diffMs / 2592000000)} months ago`;
    return `${Math.floor(diffMs / 31536000000)} years ago`;
};
const formatRelativeTime = (diffMs, isFuture) => {
    const unit = (count, label) => isFuture ? `in ${count} ${label}` : `${count} ${label} ago`;
    if (diffMs < 1000)
        return 'just now';
    if (diffMs < 60000)
        return unit(Math.floor(diffMs / 1000), 'seconds');
    if (diffMs < 3600000)
        return unit(Math.floor(diffMs / 60000), 'minutes');
    if (diffMs < 86400000)
        return unit(Math.floor(diffMs / 3600000), 'hours');
    if (diffMs < 604800000)
        return unit(Math.floor(diffMs / 86400000), 'days');
    if (diffMs < 2592000000)
        return unit(Math.floor(diffMs / 604800000), 'weeks');
    if (diffMs < 31536000000)
        return unit(Math.floor(diffMs / 2592000000), 'months');
    return unit(Math.floor(diffMs / 31536000000), 'years');
};
const calculateRelative = (value, other) => {
    const otherDate = other instanceof Date ? other : other.toDate();
    const diffMs = otherDate.getTime() - value.getTime();
    if (diffMs < 0) {
        return formatRelativeTime(Math.abs(diffMs), false);
    }
    return formatRelativeTime(diffMs, true);
};
const createAddMethods = (value) => {
    return {
        addDays: (days) => {
            const newDate = new Date(value);
            newDate.setDate(newDate.getDate() + days);
            return createDateTime(newDate);
        },
        addHours: (hours) => {
            const newDate = new Date(value);
            newDate.setHours(newDate.getHours() + hours);
            return createDateTime(newDate);
        },
        addMinutes: (minutes) => {
            const newDate = new Date(value);
            newDate.setMinutes(newDate.getMinutes() + minutes);
            return createDateTime(newDate);
        },
        addSeconds: (seconds) => {
            const newDate = new Date(value);
            newDate.setSeconds(newDate.getSeconds() + seconds);
            return createDateTime(newDate);
        },
        addMonths: (months) => {
            const newDate = new Date(value);
            newDate.setMonth(newDate.getMonth() + months);
            return createDateTime(newDate);
        },
        addYears: (years) => {
            const newDate = new Date(value);
            newDate.setFullYear(newDate.getFullYear() + years);
            return createDateTime(newDate);
        },
    };
};
const createCompareMethods = (value) => {
    return {
        isBefore: (other) => {
            const otherDate = other instanceof Date ? other : other.toDate();
            return value.getTime() < otherDate.getTime();
        },
        isAfter: (other) => {
            const otherDate = other instanceof Date ? other : other.toDate();
            return value.getTime() > otherDate.getTime();
        },
        isSame: (other) => {
            const otherDate = other instanceof Date ? other : other.toDate();
            return (value.getFullYear() === otherDate.getFullYear() &&
                value.getMonth() === otherDate.getMonth() &&
                value.getDate() === otherDate.getDate());
        },
        isBetween: (start, end) => {
            const startDate = start instanceof Date ? start : start.toDate();
            const endDate = end instanceof Date ? end : end.toDate();
            return value.getTime() >= startDate.getTime() && value.getTime() <= endDate.getTime();
        },
    };
};
const createDiffMethods = (value) => {
    const getOtherDate = (other) => other instanceof Date ? other : other.toDate();
    return {
        diffMs: (other) => {
            return value.getTime() - getOtherDate(other).getTime();
        },
        diffSeconds: (other) => {
            return Math.floor((value.getTime() - getOtherDate(other).getTime()) / 1000);
        },
        diffMinutes: (other) => {
            return Math.floor((value.getTime() - getOtherDate(other).getTime()) / 60000);
        },
        diffHours: (other) => {
            return Math.floor((value.getTime() - getOtherDate(other).getTime()) / 3600000);
        },
        diffDays: (other) => {
            return Math.floor((value.getTime() - getOtherDate(other).getTime()) / 86400000);
        },
    };
};
const createBoundaryMethods = (value) => {
    return {
        startOfDay: () => {
            const newDate = new Date(value);
            newDate.setHours(0, 0, 0, 0);
            return createDateTime(newDate);
        },
        endOfDay: () => {
            const newDate = new Date(value);
            newDate.setHours(23, 59, 59, 999);
            return createDateTime(newDate);
        },
        startOfMonth: () => {
            const newDate = new Date(value);
            newDate.setDate(1);
            newDate.setHours(0, 0, 0, 0);
            return createDateTime(newDate);
        },
        endOfMonth: () => {
            const newDate = new Date(value);
            newDate.setMonth(newDate.getMonth() + 1);
            newDate.setDate(0);
            newDate.setHours(23, 59, 59, 999);
            return createDateTime(newDate);
        },
    };
};
const createDateTime = (date) => {
    // Create a copy to ensure immutability
    const value = new Date(date);
    const addMethods = createAddMethods(value);
    const compareMethods = createCompareMethods(value);
    const diffMethods = createDiffMethods(value);
    const boundaryMethods = createBoundaryMethods(value);
    return {
        toDate: () => new Date(value),
        format: (pattern) => {
            let result = pattern;
            const year = value.getFullYear();
            const month = value.getMonth();
            const day = value.getDate();
            const hours = value.getHours();
            const minutes = value.getMinutes();
            const seconds = value.getSeconds();
            const milliseconds = value.getMilliseconds();
            // Replace tokens in the pattern
            result = result.replaceAll('YYYY', String(year));
            result = result.replaceAll('YY', String(year).slice(-2));
            result = result.replaceAll('MMMM', monthNames[month] ?? '');
            result = result.replaceAll('MMM', monthNamesShort[month] ?? '');
            result = result.replaceAll('MM', padZero(month + 1));
            result = result.replaceAll('M', String(month + 1));
            result = result.replaceAll('DD', padZero(day));
            result = result.replaceAll('D', String(day));
            result = result.replaceAll('HH', padZero(hours));
            result = result.replaceAll('H', String(hours));
            result = result.replaceAll('mm', padZero(minutes));
            result = result.replaceAll('m', String(minutes));
            result = result.replaceAll('ss', padZero(seconds));
            result = result.replaceAll('s', String(seconds));
            result = result.replaceAll('SSS', padZero(milliseconds, 3));
            result = result.replaceAll('S', String(milliseconds));
            return result;
        },
        toISO: () => value.toISOString(),
        toRFC3339: () => value.toISOString(),
        ago: () => calculateAgo(value),
        relative: (other) => calculateRelative(value, other),
        ...addMethods,
        ...compareMethods,
        ...diffMethods,
        ...boundaryMethods,
        clone: () => createDateTime(new Date(value)),
        getTime: () => value.getTime(),
        getYear: () => value.getFullYear(),
        getMonth: () => value.getMonth(),
        getDate: () => value.getDate(),
        getHours: () => value.getHours(),
        getMinutes: () => value.getMinutes(),
        getSeconds: () => value.getSeconds(),
        getMilliseconds: () => value.getMilliseconds(),
        getDayOfWeek: () => value.getDay(),
        getDayOfYear: () => getDayOfYearHelper(value),
    };
};
/**
 * Parse a date string into a DateTime object
 * Supports: ISO 8601, RFC 3339, common formats
 */
const parse = (dateString) => {
    // Try to parse as ISO 8601 / RFC 3339
    const parsedDate = new Date(dateString);
    if (Number.isNaN(parsedDate.getTime())) {
        throw ErrorFactory.createValidationError(`Invalid date string: ${dateString}`);
    }
    return createDateTime(parsedDate);
};
/**
 * DateTime namespace - sealed namespace object grouping all DateTime operations
 * Immutable by design - all operations return new IDateTime instances
 */
export const DateTime = Object.freeze({
    /**
     * Create a DateTime from a Date object
     */
    create(date = new Date()) {
        return createDateTime(date);
    },
    /**
     * Get current DateTime
     */
    now() {
        return createDateTime(new Date());
    },
    /**
     * Parse a date string
     */
    parse(dateString) {
        return parse(dateString);
    },
    /**
     * Create DateTime from timestamp (milliseconds since epoch)
     */
    fromTimestamp(timestamp) {
        return createDateTime(new Date(timestamp));
    },
    /**
     * Create DateTime from components
     */
    fromComponents(year, month, day = 1, hours = 0, minutes = 0, seconds = 0) {
        return createDateTime(new Date(year, month - 1, day, hours, minutes, seconds));
    },
});
