/**
 * Represents a timespan with a start and end time.
 */
export class TimeSpan {
    public readonly start: number;
    public readonly end: number;
    // constructors
    constructor(start = Date.now(), end = Date.now()) {
        if (start > end) {
            throw new Error("Start time must be less than or equal to end time.");
        }
        this.start = start;
        this.end = end;
    }

    static fromDates(startDate: number, endDate: number) {
        return new TimeSpan(startDate, endDate);
    }

    static fromMillis(millis: number) {
        return new TimeSpan(0, millis);
    }

    static fromSeconds(seconds: number) {
        return TimeSpan.fromMillis(seconds * 1000);
    }

    // methods
    get totalMillis() {
        return this.end - this.start;
    }

    get millis() {
        return Math.floor(this.totalMillis % 1000);
    }

    get totalSeconds() {
        return Math.floor(this.totalMillis / 1000);
    }

    get seconds() {
        return Math.floor((this.totalMillis / 1000) % 60);
    }

    get totalMinutes() {
        return Math.floor(this.totalMillis / 1000 / 60);
    }

    get minutes() {
        return Math.floor((this.totalMillis / 1000 / 60) % 60);
    }

    get totalHours() {
        return Math.floor(this.totalMillis / 1000 / 60 / 60);
    }

    get hours() {
        return Math.floor((this.totalMillis / 1000 / 60 / 60) % 24);
    }

    get totalDays() {
        return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24);
    }

    get days() {
        return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 30.44); // Average days in a month
    }

    get weekDays() {
        return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24) % 7);
    }

    get totalWeeks() {
        return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 7);
    }

    get weeks() {
        return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 7) % 4.345); // Average weeks in a month
    }

    get totalMonths() {
        return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 30.44); // Average days in a month
    }

    get months() {
        return Math.floor((this.totalMillis / 1000 / 60 / 60 / 24 / 30.44) % 12); // Average days in a month
    }

    get totalYears() {
        return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year
    }

    get years() {
        return Math.floor(this.totalMillis / 1000 / 60 / 60 / 24 / 365.25); // Average days in a year
    }

    toString(includeWeeks = true, includeMillis = true) {
        const parts = [];
        if (this.totalYears >= 1) parts.push(`${this.totalYears} years`);
        if (this.totalMonths >= 1) parts.push(`${this.months} months`);
        if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks} weeks`);
        if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days} days`);
        if (this.totalHours >= 1) parts.push(`${this.hours} hours`);
        if (this.totalMinutes >= 1) parts.push(`${this.minutes} minutes`);
        if (this.totalSeconds >= 1) parts.push(`${this.seconds} seconds`);
        if (includeMillis) parts.push(`${this.millis} milliseconds`);
        return parts.join(", ");
    }

    toShortString(includeWeeks = true, includeMillis = true, withSpaces = false) {
        const parts = [];
        if (this.totalYears >= 1) parts.push(`${this.totalYears}y`);
        if (this.totalMonths >= 1) parts.push(`${this.months}mo`);
        if (includeWeeks && this.totalWeeks >= 1) parts.push(`${this.weeks}w`);
        if (this.totalDays >= 1) parts.push(`${includeWeeks ? this.weekDays : this.days}d`);
        if (this.totalHours >= 1) parts.push(`${this.hours}h`);
        if (this.totalMinutes >= 1) parts.push(`${this.minutes}m`);
        if (this.totalSeconds >= 1) parts.push(`${this.seconds}s`);
        if (includeMillis) parts.push(`${this.millis}ms`);
        return parts.join(withSpaces ? " " : "");
    }

    get startDate() {
        return new Date(this.start);
    }

    get endDate() {
        return new Date(this.end);
    }
}
