/*global gettext, pgettext, get_format, interpolate, quickElement, removeChildren*/
/*
calendar.js - Calendar functions by Adrian Holovaty
depends on core.js for utility functions like removeChildren or quickElement
*/
"use strict";
{
    // CalendarNamespace -- Provides a collection of HTML calendar-related helper functions
    const CalendarNamespace = {
        monthsOfYear: [
            gettext("January"),
            gettext("February"),
            gettext("March"),
            gettext("April"),
            gettext("May"),
            gettext("June"),
            gettext("July"),
            gettext("August"),
            gettext("September"),
            gettext("October"),
            gettext("November"),
            gettext("December"),
        ],
        monthsOfYearAbbrev: [
            pgettext("abbrev. month January", "Jan"),
            pgettext("abbrev. month February", "Feb"),
            pgettext("abbrev. month March", "Mar"),
            pgettext("abbrev. month April", "Apr"),
            pgettext("abbrev. month May", "May"),
            pgettext("abbrev. month June", "Jun"),
            pgettext("abbrev. month July", "Jul"),
            pgettext("abbrev. month August", "Aug"),
            pgettext("abbrev. month September", "Sep"),
            pgettext("abbrev. month October", "Oct"),
            pgettext("abbrev. month November", "Nov"),
            pgettext("abbrev. month December", "Dec"),
        ],
        daysOfWeek: [
            gettext("Sunday"),
            gettext("Monday"),
            gettext("Tuesday"),
            gettext("Wednesday"),
            gettext("Thursday"),
            gettext("Friday"),
            gettext("Saturday"),
        ],
        daysOfWeekAbbrev: [
            pgettext("abbrev. day Sunday", "Sun"),
            pgettext("abbrev. day Monday", "Mon"),
            pgettext("abbrev. day Tuesday", "Tue"),
            pgettext("abbrev. day Wednesday", "Wed"),
            pgettext("abbrev. day Thursday", "Thur"),
            pgettext("abbrev. day Friday", "Fri"),
            pgettext("abbrev. day Saturday", "Sat"),
        ],
        daysOfWeekInitial: [
            pgettext("one letter Sunday", "S"),
            pgettext("one letter Monday", "M"),
            pgettext("one letter Tuesday", "T"),
            pgettext("one letter Wednesday", "W"),
            pgettext("one letter Thursday", "T"),
            pgettext("one letter Friday", "F"),
            pgettext("one letter Saturday", "S"),
        ],
        firstDayOfWeek: parseInt(get_format("FIRST_DAY_OF_WEEK")),
        isLeapYear: function (year) {
            return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
        },
        getDaysInMonth: function (month, year) {
            let days;
            if (
                month === 1 ||
                month === 3 ||
                month === 5 ||
                month === 7 ||
                month === 8 ||
                month === 10 ||
                month === 12
            ) {
                days = 31;
            } else if (
                month === 4 ||
                month === 6 ||
                month === 9 ||
                month === 11
            ) {
                days = 30;
            } else if (month === 2 && CalendarNamespace.isLeapYear(year)) {
                days = 29;
            } else {
                days = 28;
            }
            return days;
        },
        formatDate: function (day, month, year) {
            return interpolate(
                gettext("%(month)s %(day)s, %(year)s"),
                {
                    month: CalendarNamespace.monthsOfYear[month - 1],
                    day: day,
                    year: year,
                },
                true,
            );
        },
        // Return the current date adjusted for the server timezone.
        serverToday: function () {
            const today = new Date();
            const serverOffset = document.body.dataset.adminUtcOffset;
            if (serverOffset) {
                const localOffset = today.getTimezoneOffset() * -60;
                today.setTime(
                    today.getTime() + 1000 * (serverOffset - localOffset),
                );
            }
            return today;
        },
        draw: function (month, year, div_id, callback, selected) {
            // month = 1-12, year = 1-9999
            const today = CalendarNamespace.serverToday();
            const todayDay = today.getDate();
            const todayMonth = today.getMonth() + 1;
            const todayYear = today.getFullYear();
            let todayClass = "";

            // Use UTC functions here because the date field does not contain time
            // and using the UTC function variants prevent the local time offset
            // from altering the date, specifically the day field.  For example:
            //
            // ```
            // var x = new Date('2013-10-02');
            // var day = x.getDate();
            // ```
            //
            // The day variable above will be 1 instead of 2 in, say, US Pacific time
            // zone.
            let isSelectedMonth = false;
            if (typeof selected !== "undefined") {
                isSelectedMonth =
                    selected.getUTCFullYear() === year &&
                    selected.getUTCMonth() + 1 === month;
            }

            month = parseInt(month);
            year = parseInt(year);
            const calDiv = document.getElementById(div_id);
            removeChildren(calDiv);
            const calTable = document.createElement("table");
            quickElement(
                "caption",
                calTable,
                CalendarNamespace.monthsOfYear[month - 1] + " " + year,
            );
            const tableBody = quickElement("tbody", calTable);

            // Draw days-of-week header
            let tableRow = quickElement("tr", tableBody);
            for (let i = 0; i < 7; i++) {
                const dayAbbrev =
                    CalendarNamespace.daysOfWeekAbbrev[
                        (i + CalendarNamespace.firstDayOfWeek) % 7
                    ];
                const dayInitial =
                    CalendarNamespace.daysOfWeekInitial[
                        (i + CalendarNamespace.firstDayOfWeek) % 7
                    ];
                const th = quickElement("th", tableRow, dayInitial);
                th.setAttribute("aria-label", dayAbbrev);
            }

            const startingPos = new Date(
                year,
                month - 1,
                1 - CalendarNamespace.firstDayOfWeek,
            ).getDay();
            const days = CalendarNamespace.getDaysInMonth(month, year);

            let nonDayCell;

            // Draw blanks before first of month
            tableRow = quickElement("tr", tableBody);
            for (let i = 0; i < startingPos; i++) {
                nonDayCell = quickElement("td", tableRow, " ");
                nonDayCell.className = "nonday";
            }

            function calendarMonth(y, m) {
                function onClick(e) {
                    e.preventDefault();
                    callback(y, m, this.textContent);
                }
                return onClick;
            }

            // Draw days of month
            let currentDay = 1;
            for (let i = startingPos; currentDay <= days; i++) {
                if (i % 7 === 0 && currentDay !== 1) {
                    tableRow = quickElement("tr", tableBody);
                }
                if (
                    currentDay === todayDay &&
                    month === todayMonth &&
                    year === todayYear
                ) {
                    todayClass = "today";
                } else {
                    todayClass = "";
                }

                // use UTC function; see above for explanation.
                if (isSelectedMonth && currentDay === selected.getUTCDate()) {
                    if (todayClass !== "") {
                        todayClass += " ";
                    }
                    todayClass += "selected";
                }

                const cell = quickElement(
                    "td",
                    tableRow,
                    "",
                    "class",
                    todayClass,
                );
                const link = quickElement(
                    "a",
                    cell,
                    currentDay,
                    "role",
                    "button",
                    "href",
                    "#",
                );
                let ariaLabel = CalendarNamespace.formatDate(
                    currentDay,
                    month,
                    year,
                );
                const isToday =
                    currentDay === todayDay &&
                    month === todayMonth &&
                    year === todayYear;
                const isSelected =
                    isSelectedMonth && currentDay === selected.getUTCDate();
                if (isToday && isSelected) {
                    ariaLabel = interpolate(
                        gettext("%s (today, current selection)"),
                        [ariaLabel],
                    );
                } else if (isToday) {
                    ariaLabel = interpolate(gettext("%s (today)"), [ariaLabel]);
                } else if (isSelected) {
                    ariaLabel = interpolate(gettext("%s (current selection)"), [
                        ariaLabel,
                    ]);
                }
                link.setAttribute("aria-label", ariaLabel);
                link.addEventListener("click", calendarMonth(year, month));
                currentDay++;
            }

            // Draw blanks after end of month (optional, but makes for valid code)
            while (tableRow.childNodes.length < 7) {
                nonDayCell = quickElement("td", tableRow, " ");
                nonDayCell.className = "nonday";
            }

            calDiv.appendChild(calTable);
        },
    };

    // Calendar -- A calendar instance
    function Calendar(div_id, callback, selected) {
        // div_id (string) is the ID of the element in which the calendar will
        //     be displayed
        // callback (string) is the name of a JavaScript function that will be
        //     called with the parameters (year, month, day) when a day in the
        //     calendar is clicked
        this.div_id = div_id;
        this.callback = callback;
        this.today = CalendarNamespace.serverToday();
        this.currentMonth = this.today.getMonth() + 1;
        this.currentYear = this.today.getFullYear();
        if (typeof selected !== "undefined") {
            this.selected = selected;
        }
    }
    Calendar.prototype = {
        drawCurrent: function () {
            CalendarNamespace.draw(
                this.currentMonth,
                this.currentYear,
                this.div_id,
                this.callback,
                this.selected,
            );
        },
        drawDate: function (month, year, selected) {
            this.currentMonth = month;
            this.currentYear = year;

            if (selected) {
                this.selected = selected;
            }

            this.drawCurrent();
        },
        drawPreviousMonth: function () {
            if (this.currentMonth === 1) {
                this.currentMonth = 12;
                this.currentYear--;
            } else {
                this.currentMonth--;
            }
            this.drawCurrent();
        },
        drawNextMonth: function () {
            if (this.currentMonth === 12) {
                this.currentMonth = 1;
                this.currentYear++;
            } else {
                this.currentMonth++;
            }
            this.drawCurrent();
        },
        drawPreviousYear: function () {
            this.currentYear--;
            this.drawCurrent();
        },
        drawNextYear: function () {
            this.currentYear++;
            this.drawCurrent();
        },
    };
    window.Calendar = Calendar;
    window.CalendarNamespace = CalendarNamespace;
}
