AUA Courses Plugin

AUA Courses Plugin helps you find and filter courses more quickly and with greater flexibility.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         AUA Courses Plugin 
// @namespace    https://github.com/rezocrypt/aua-utils/
// @version      1.0.0
// @description  AUA Courses Plugin helps you find and filter courses more quickly and with greater flexibility.
// @match        https://auasonis.jenzabarcloud.com/GENSRsC.cfm*
// @run-at       document-end
// @grant        none
// @icon         https://aua.am/favicon.ico
// @author       rezocrypt 
// @license      GPL-3.0
// @supportURL   https://t.me/rezocrypt
// ==/UserScript==

(function () {
    "use strict";

    // ----- Global Variables And Constants -----

    let courses = [];
    let matchingCourses = [];
    let showOnly = false;
    let checkedDays = [];

    const dayCheckboxes = {};
    const availableTimes = [];
    const timeCheckboxes = {};

    const AUA_BLUE = "#002147";

    // This function generates a params object for fetching courses. This is complex because Jenzabar
    // doesn't provide automaticaly correct semester and year parameters so we need to find it by
    // ourselves.
    function getRequestParams(year = null, semester = null) {
        // Defining semesters information
        const semesters = [
            {
                id: "spring",
                index: 3,
                coursesBegin: "January 19",
                coursesEnd: "May 14",
            },
            {
                id: "summer",
                index: 4,
                coursesBegin: "June 15",
                coursesEnd: "August 7",
            },
            {
                id: "fall",
                index: 1,
                coursesBegin: "August 20",
                coursesEnd: "December 9",
            },
        ];

        // Here will be final result stored
        let matchedSemester = null;

        // Finding out for which semester we must show courses now (which idk why jenzabar DIDN'T)
        semesters.forEach((semester, index) => {
            // Parsing dates
            const semesterEndDate = semester.coursesEnd;
            const nextSemesterEndDate =
                semesters[index + 1]?.coursesEnd || semesters[0].coursesEnd;

            // Getting date objects
            const startDate = new Date(`${semesterEndDate}, 1900`);
            const endDate = new Date(
                `${nextSemesterEndDate}, ${1900 + (semesters[index + 1] ? 0 : 1)}`,
            );

            // Defining current date to compare with something but with year 1900
            const currentDate = new Date();
            const currentYear = currentDate.getFullYear();
            let formattedYear = null;
            const compareDate = new Date(`${semesters[0].coursesEnd}, 1900`);

            // Setting full year for comparing
            currentDate.setFullYear(1900);

            // Comparing to get the full year for request which needs to be something like 202526
            if (currentDate <= compareDate) {
                formattedYear = `${currentYear - 1}${currentYear % 100}`;
            } else {
                formattedYear = `${currentYear}${(currentYear % 100) + 1}`;
            }

            // Then setting to one more year if it is actually in spring semester (its too complex here)
            currentDate.setFullYear(1900 + (currentDate <= compareDate ? 1 : 0));

            // Finally checking if we are between the ends of our semester and the following semester
            if (startDate <= currentDate && currentDate <= endDate) {
                matchedSemester = semesters[semesters[index + 1] ? index + 1 : 0];
                matchedSemester["year"] = formattedYear;
            }
        });

        /* EXAMPLE const params = {chkschyear: "202526", chkschsem: "3", search: "" }; */

        // Defining finally the parameters
        const params = {
            chkschyear: year || matchedSemester.year,
            chkschsem: semester || matchedSemester.index,
            search: "",
        };

        // Returning
        return params;
    }

    // Defining initial params for first load of the plugin
    const params = getRequestParams();


    // ========== HTML Generation and Manipulation Logic ==========

    // Here I decided to use one function to create the entire window to keep things organized
    // which is not done in gened-plugin.js.
    function createWindow() {
        // ----- Main Window Section -----

        // Creating main window element inside which everything will be placed
        const win = document.createElement("div");
        win.id = "aua-courses-plugin-window";
        win.style.cssText = `
            position: fixed;
            top: 100px;
            right: 100px;
            height: auto;
            max-height: 80vh;
            width: 300px;
            background: white;
            color: #fff;
            border: 1px solid #333;
            border-radius: 6px;
            font-size: 13px;
            overflow: auto;
            z-index: 999999;
            resize: both;     /* bottom-right corner resize */
        `;



        // ----- Header Section -----

        // Creating header
        const header = document.createElement("div");
        header.style.cssText = `
            background: ${AUA_BLUE};
            color: #ffffff;
            padding: 10px 8px 8px 8px;
            cursor: move;
            border-top-left-radius: 6px;
            border-top-right-radius: 6px;
            position: sticky;
            top: 0;
            z-index: 10;
        `;

        // Creating image container for AUA log
        const imageContainer = document.createElement("div");
        imageContainer.style.cssText = `
            display: flex;
            justify-content: center;
            margin-bottom: 5px;
        `;

        // Creating the actual image using aua.am/favicon.ico in case of change
        const image = document.createElement("img");
        image.src = "https://aua.am/favicon.ico";
        image.style.cssText = `
            width: 32px;
            height: 32px;
        `;
        imageContainer.appendChild(image);

        // Appending image container to header
        header.appendChild(imageContainer);

        // Creating header text for plugin
        const headerText = document.createElement("div");
        headerText.textContent = "AUA Courses Plugin";
        headerText.style.cssText = `
            font-size: 16px;
            font-weight: bold;
            margin-top: 4px;
            text-align: center;
        `;

        // Appending header text to header
        header.appendChild(headerText);

        // Movable behavior which lets to move window by dragging the header
        let dx, dy;
        header.onpointerdown = (e) => {
            dx = e.clientX - win.offsetLeft;
            dy = e.clientY - win.offsetTop;
            document.onpointermove = (e) => {
                win.style.left = e.clientX - dx + "px";
                win.style.top = e.clientY - dy + "px";
            };
            document.onpointerup = () => {
                document.onpointermove = null;
                document.onpointerup = null;
            };
        };

        // Close button of header 
        const closeButton = document.createElement("button");
        closeButton.textContent = "×";
        closeButton.title = "Close Window";
        closeButton.style.cssText = `
            position: absolute;
            top: 8px;
            right: 8px;
            width: 24px;
            height: 24px;
            border: none;
            background: #d32f2f;
            color: #fff;
            font-weight: bold;
            font-size: 16px;
            border-radius: 4px;
            cursor: pointer;
            line-height: 20px;
            text-align: center;
            padding: 0;
            transition: 0.2s;
        `;

        // Adding event listeners to close button for hover effect and click action
        closeButton.addEventListener("mouseenter", () => {
            closeButton.style.opacity = "0.8";
        });
        closeButton.addEventListener("mouseleave", () => {
            closeButton.style.opacity = "1";
        });
        closeButton.addEventListener("click", () => closeWindow());

        // Appending close button to header
        header.appendChild(closeButton);



        // ----- Content Section -----

        // Creating content container inside which will other interactive elements be placed
        const content = document.createElement("div");
        content.style.cssText = `
            padding: 10px;
            text-align: left;
            background: white;
            overflow: auto;
        `;



        // ----- Semester Section -----

        // Defining wrapper inside which will be semester title and select
        const semesterWrapper = document.createElement("div");
        semesterWrapper.style.cssText = `
            display: flex;
            align-items: center;
            margin-top: 10px;
            margin-bottom: 6px;
        `;

        // Defining semester title
        const semesterTitle = document.createElement("div");
        semesterTitle.textContent = "Semester";
        semesterTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            font-size: 16px;
        `;

        // Appending semester title to wrapper
        semesterWrapper.appendChild(semesterTitle);

        // Defining semester select where will be semesters as options
        const selectSemester = document.createElement("select");
        selectSemester.id = "semesterSelect";
        selectSemester.style.cssText = `
            margin-left: auto;
            width: 120px;  // adjust width for semester
            border: 1px solid ${AUA_BLUE};
            border-radius: 4px;
            padding: 4px 6px;
            background: #ffffff;
            color: ${AUA_BLUE};
            font-weight: normal;
            font-size: 13px;
            cursor: pointer;
            transition: all 0.2s ease;
        `;

        // Defining semester options (for some reason semester values are 3,4,1 for spring, summer, fall)
        // ask to jenzabar why
        const semesters = [
            { value: "3", text: "Spring" },
            { value: "4", text: "Summer" },
            { value: "1", text: "Fall" },
        ];

        // Appending semesters as options to select
        semesters.forEach((sem) => {
            const opt = document.createElement("option");
            opt.value = sem.value;
            opt.textContent = sem.text;
            selectSemester.appendChild(opt);
        });


        // Append after 100ms to avoid JCF interference (otherwise JCF overrides it)
        setTimeout(() => {
            semesterWrapper.appendChild(selectSemester);
        }, 100);


        // Appending semesterWrapper to content
        content.appendChild(semesterWrapper);



        // ----- Year Section -----

        // Defining wrapper for year title and input
        const yearWrapper = document.createElement("div");
        yearWrapper.style.cssText = `
            display: flex;
            align-items: center;
            margin-top: 10px;
            margin-bottom: 6px;
        `;

        // Defining year title element
        const yearTitle = document.createElement("div");
        yearTitle.textContent = "Year";
        yearTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            font-size: 16px;
        `;

        // Appending year title to wrapper
        yearWrapper.appendChild(yearTitle);

        // Defining year input element (first I was going to use select but 
        // someone may want to enter custom year like 202021 and that would be impossible.
        // So input is better here (at least I think so).    
        const inputYear = document.createElement("input");
        inputYear.id = "yearInput";
        inputYear.type = "text";
        inputYear.value = params.chkschyear;
        inputYear.style.cssText = `
            margin-left: auto;
            width: 120px;  
            border: 1px solid ${AUA_BLUE};
            border-radius: 4px;
            padding: 4px 6px;
            background: #ffffff;
            color: ${AUA_BLUE};
            font-weight: normal;
            font-size: 13px;
            box-sizing: border-box;
            cursor: text;
            transition: all 0.2s ease;
        `;

        // Appending year input to wrapper
        yearWrapper.appendChild(inputYear);

        // Append after 100ms to avoid JCF interference
        yearTitle.insertAdjacentElement("afterend", inputYear);

        // Appending yearWrapper to content
        content.appendChild(yearWrapper);


        // ----- Reload Button Section -----

        // This butto sends data to fetchCourses function to fetch courses with current semester and year
        // that user entered in inputs selectSemester and inputYear.
        const reloadButton = document.createElement("button");
        reloadButton.textContent = "Reload";
        reloadButton.style.cssText = `
            width: 100%;
            margin-top: 10px;
            margin-bottom: 5px;
            padding: 8px;
            border: 1px solid ${AUA_BLUE};
            background: #fff;
            cursor: pointer;
            color: ${AUA_BLUE};
            font-weight: bold;
            transition: all 0.2s ease;
        `;

        // Appending relad button to content
        content.appendChild(reloadButton);

        // Hover effect
        reloadButton.addEventListener("mouseenter", () => {
            reloadButton.style.background = AUA_BLUE;
            reloadButton.style.color = "#fff";
        });
        reloadButton.addEventListener("mouseleave", () => {
            reloadButton.style.background = "#fff";
            reloadButton.style.color = AUA_BLUE;
        });

        // Click effect
        reloadButton.addEventListener("mousedown", () => {
            reloadButton.style.transform = "scale(0.97)";
        });
        reloadButton.addEventListener("mouseup", () => {
            reloadButton.style.transform = "scale(1)";
        });

        // The event that calls fetchCourses function
        reloadButton.addEventListener("click", () => fetchCourses());



        // ----- Show Only Matching Button Section -----

        // This button shows only matching courses when clicked and hides non-matching ones.

        // Creates container for show only matching button
        const bottomContainer = document.createElement("div");
        bottomContainer.style.cssText = `
            margin-top: 5px;
            padding-top: 6px;
            text-align: center;
        `;

        // Creating the actual button
        const showOnlyButton = document.createElement("button");
        showOnlyButton.innerHTML = `
            Show only matching ( <span style="font-size: 12px;" id="matchedCount">0</span> )
        `;
        showOnlyButton.id = "showOnlyButton";
        showOnlyButton.style.cssText = `
            width: 100%;
            padding: 6px;
            border: 1px solid ${AUA_BLUE};
            border-radius: 4px;
            background: #ffffff;
            color: ${AUA_BLUE} !important;
            font-weight: bold;
            cursor: pointer;
            transition: 0.2s;
            margin-bottom: 15px;
        `;

        // Hover effect
        showOnlyButton.addEventListener("mouseenter", () => {
            showOnlyButton.style.background = AUA_BLUE;
            showOnlyButton.style.color = "#fff";
        });
        showOnlyButton.addEventListener("mouseleave", () => {
            if (!showOnly) {
                showOnlyButton.style.background = "#fff";
                showOnlyButton.style.color = AUA_BLUE;
            }
        });

        // Click effect
        showOnlyButton.addEventListener("mousedown", () => {
            showOnlyButton.style.transform = "scale(0.97)";
        });
        showOnlyButton.addEventListener("mouseup", () => {
            showOnlyButton.style.transform = "scale(1)";
        });

        // Toggle functionality
        showOnlyButton.addEventListener("click", () => {
            // Toggling showOnly variable
            showOnly = !showOnly;

            // Updating button styles based on state
            if (showOnly) {
                showOnlyButton.style.setProperty(
                    "background",
                    `${AUA_BLUE}`,
                    "important",
                );
                showOnlyButton.style.setProperty("color", "#ffffff", "important");
            } else {
                showOnlyButton.style.setProperty("background", "#ffffff", "important");
                showOnlyButton.style.setProperty("color", `${AUA_BLUE}`, "important");
            }

            // Finally calling highlightRows to apply the changes
            highlightRows();
        });

        // Appending show only button to bottom container and then to content
        bottomContainer.appendChild(showOnlyButton);
        content.appendChild(bottomContainer);



        // ----- Search Input Section -----

        // Creating search title
        const searchTitle = document.createElement("div");
        searchTitle.textContent = "Search";
        searchTitle.style.cssText = `
            font-size: 15px;
            font-weight: bold;
            color: #002147;
            margin-bottom: 4px;
        `;
        content.appendChild(searchTitle);

        // Creating search input
        const searchInput = document.createElement("input");
        searchInput.autofocus = true;
        searchInput.type = "text";
        searchInput.id = "searchInput";
        searchInput.placeholder = "Search keywords";
        searchInput.style.cssText = `
            width: 100%;
            box-sizing: border-box;
        `;

        // Using debounce to avoid too many calls while user is typing
        // so if it is typing like f r e s h m a n it won't call highlightRows
        // for each letter but only after user stops typing for 500ms
        let debounceTimer;
        searchInput.addEventListener("input", () => {
            clearTimeout(debounceTimer);
            debounceTimer = setTimeout(() => {
                highlightRows();
            }, 500); // 500ms delay after user stops typing
        });

        // Appending search input to content
        content.appendChild(searchInput);




        // ----- Clear Filters Button Section -----

        // This button clears all filters (search input, day checkboxes, time checkboxes)

        // Creating the actual button
        const clearFiltersButton = document.createElement("button");
        clearFiltersButton.textContent = "Clear Filters";
        clearFiltersButton.style.cssText = `
            width: 100%;
            margin-top: 15px;
            margin-bottom: 5px;
            padding: 8px;
            border: 1px solid ${AUA_BLUE};
            background: #fff;
            cursor: pointer;
            color: ${AUA_BLUE};
            font-weight: bold;
            transition: all 0.2s ease;
        `;

        // Appending clear filters button to content
        content.appendChild(clearFiltersButton);

        // Hover effect
        clearFiltersButton.addEventListener("mouseenter", () => {
            clearFiltersButton.style.background = AUA_BLUE;
            clearFiltersButton.style.color = "#fff";
        });
        clearFiltersButton.addEventListener("mouseleave", () => {
            clearFiltersButton.style.background = "#fff";
            clearFiltersButton.style.color = AUA_BLUE;
        });

        // Click effect
        clearFiltersButton.addEventListener("mousedown", () => {
            clearFiltersButton.style.transform = "scale(0.97)";
        });
        clearFiltersButton.addEventListener("mouseup", () => {
            clearFiltersButton.style.transform = "scale(1)";
        });

        // Call your function
        clearFiltersButton.addEventListener("click", () => clearFilters());




        // ----- Weekdays Filter (TOGGLE) Section -----

        // Weekdays title (clickable for toggle)
        const weekdaysTitle = document.createElement("div");
        weekdaysTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            margin-top: 20px;
            margin-bottom: 6px;
            font-size: 16px;
            cursor: pointer;
            user-select: none;
            display: flex;
            align-items: center;
            gap: 6px;
        `;

        // Arrow for toggling the weekdays container as dropdown
        const arrow = document.createElement("span");
        arrow.textContent = "▾"; // open
        arrow.style.fontSize = "12px";

        // Title text (I played lot with Chatgpt that's why it is created separately two titles)
        const titleText = document.createElement("span");
        titleText.textContent = "Weekdays";

        // Appending arrow and title to weekdays title
        weekdaysTitle.appendChild(arrow);
        weekdaysTitle.appendChild(titleText);

        // Appending weekdays title to content
        content.appendChild(weekdaysTitle);

        // Creating container for weekdays' checkboxes
        const weekdaysContainer = document.createElement("div");
        weekdaysContainer.style.cssText = `
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 6px 12px;
            margin-bottom: 10px;
        `;

        // Appending weekdays container to content
        content.appendChild(weekdaysContainer);

        // Logic for toggling weekdays container when clicking the title
        let weekdaysVisible = true;
        weekdaysTitle.addEventListener("click", () => {
            weekdaysVisible = !weekdaysVisible;
            weekdaysContainer.style.display = weekdaysVisible ? "grid" : "none";
            arrow.textContent = weekdaysVisible ? "▾" : "▸";
        });

        // Weekday options (I used these codes because they are present in course times but
        // for names I think it is better to have full names. I haven't use to be determined
        // because it is too long and I do not know CSS good enough).
        const days = [
            { code: "MON", name: "Monday" },
            { code: "TUE", name: "Tuesday" },
            { code: "WED", name: "Wednesday" },
            { code: "THU", name: "Thursday" },
            { code: "FRI", name: "Friday" },
            { code: "SAT", name: "Saturday" },
            { code: "TBD", name: "Unknown" },
        ];

        // Creating checkboxes for each weekday using the days array and foreach
        days.forEach((day) => {
            // Creating label for checkbox and text
            const label = document.createElement("label");
            label.style.cssText = `
                display: flex;
                align-items: center;
                font-size: 13px;
                cursor: pointer;
                color: ${AUA_BLUE};
            `;

            // Creating the actual checkbox element
            const checkbox = document.createElement("input");
            checkbox.type = "checkbox";
            checkbox.value = day.code;
            checkbox.style.marginRight = "6px";

            // Adding event listener to checkbox to update checkedDays and highlight rows when changed
            checkbox.addEventListener("change", () => highlightRows());

            // Appending checkbox and text to label
            label.appendChild(checkbox);
            label.appendChild(document.createTextNode(day.name));

            // Appending label to weekdays container
            weekdaysContainer.appendChild(label);

            // Storing checkbox in dayCheckboxes object for later use and reference
            dayCheckboxes[day.code] = checkbox;
        });





        // ----- Times Filter (TOGGLE) Section -----

        // This is actually complex thing. Depending on available times in courses matching
        // the search and weekdays filters, the available times checkboxes are generated 

        // Times title (clickable for toggle)
        const timesTitle = document.createElement("div");
        timesTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            margin-top: 20px;
            margin-bottom: 6px;
            font-size: 16px;
            cursor: pointer;
            user-select: none;
            display: flex;
            align-items: center;
            gap: 6px;
`;

        // Arrow for toggling times container
        const timesArrow = document.createElement("span");
        timesArrow.textContent = "▸"; // collapsed initially
        timesArrow.style.fontSize = "12px";

        // Title text
        const timesText = document.createElement("span");
        timesText.textContent = "Times";

        // Appending arrow and title to times title
        timesTitle.appendChild(timesArrow);
        timesTitle.appendChild(timesText);

        // Appending times title to content
        content.appendChild(timesTitle);

        // Container for actual time options (checkboxes)
        const timesContainer = document.createElement("div");
        timesContainer.id = "timesContainer";
        timesContainer.style.cssText = `
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
            gap: 6px;
            margin-bottom: 10px;
        `;

        // Appending times container to content
        content.appendChild(timesContainer);

        // Logic for toggling times container when clicking the title
        let timesVisible = false; // default not visible
        timesContainer.style.display = "none"; // hide initially
        timesTitle.addEventListener("click", () => {
            timesVisible = !timesVisible;
            timesContainer.style.display = timesVisible ? "grid" : "none";
            timesArrow.textContent = timesVisible ? "▾" : "▸";
        });







        // ----- Footer Section -----

        // This is footer part of the window which contains social icons (yet)
        const footer = document.createElement("div");
        footer.style.cssText = `
            width: 100%;
            background: #002147;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 14px;
            padding: 10px 0;
            border-bottom-left-radius: 6px;
            border-bottom-right-radius: 6px;
            box-sizing: border-box;
            flex-shrink: 0; /* keep footer height fixed */
        `;

        // Function to create clickable social icons
        function createIcon(src, title, url) {
            // Creating image element for icon
            const icon = document.createElement("img");
            icon.src = src;
            icon.style.cssText = `
                        width: 22px;
                        height: 22px;
                        filter: invert(1);
                        cursor: pointer;
                `;
            icon.title = title;

            // Adding click event to open the url in new tab
            icon.addEventListener("click", () => {
                window.open(url, "_blank");
            });

            // Returning the created icon element
            return icon;
        }

        // Creating GitHub icon using createIcon function
        const githubIcon = createIcon(
            "https://cdn.jsdelivr.net/gh/simple-icons/simple-icons/icons/github.svg",
            "GitHub",
            "https://github.com/rezocrypt/aua-utils",
        );

        // Creating Telegram icon using createIcon function
        const telegramIcon = createIcon(
            "https://cdn.jsdelivr.net/gh/simple-icons/simple-icons/icons/telegram.svg",
            "Telegram",
            "https://t.me/rezocrypt",
        );

        // Adding created social icons to footer
        footer.appendChild(githubIcon);
        footer.appendChild(telegramIcon);



        // ----- Window Assembly Section -----

        // Appending created sections to main window (only big sections because
        // smaller ones are already inside bigger ones)
        win.appendChild(header);
        win.appendChild(content);
        win.appendChild(footer);

        // Finally appending the window to body
        document.body.appendChild(win);
    }


    // ========== HTML Generation and Manipulation Logic ==========




    // ========== Logic Functionality ==========

    // This function is for clearing unnecessary things from the page before loading
    function clearFromUnnecessaryElements() {
        // This is main table where the rows will be REPLACED with new fetched ones (not clearing yet)
        const coursesDiv = document.querySelector(".table-responsive");

        // This form for search is not necessary and must be removed
        document.querySelector(".update-form")?.remove();

        // This heading text Courses By Semester is not necessary and must be removed
        document.querySelector(".heading-wrap")?.remove();
    }

    // Main function that is called when we got fetched
    function afterFetch(html) {
        // Preventing multiple fetches at the same time
        window.nowIsFetching = true;

        // Creating parser that parses the html content to doc variable
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, "text/html");

        // Defining table that we got from fetch and table which we need to replace.
        const coursesTableParsed = doc.getElementById("crsbysemester");
        const coursesDiv = document.querySelector(".table-responsive");

        //Clearing original table content
        if (coursesDiv) {
            coursesDiv.innerHTML = "";
        }

        // Checking if both exist and replacing the original table with fetched one.
        if (coursesTableParsed && coursesDiv) {

            // Appending fetched table to the page (replacing with fetched courses)
            coursesDiv.appendChild(coursesTableParsed);

            // Updating courses array
            updateCoursesArray();
        } else {
            // In case of something went wrong but idk why code enters here because it calls afterFetch few times
            // Maybe because of fetchCourses being called multiple times.
            console.log("Either crsbysemester or table-responsive not found.");

            // Stopping loading animation
            stopLoadingAnimation();

            // Allowing new fetches
            window.nowIsFetching = false;
        }



        // ----- Setting Inputs to Current Semester and Year -----

        // Setting semester and year inputs to current values
        const selectSemester = document.querySelector("#semesterSelect"); // or your reference
        const inputYear = document.querySelector("#yearInput");

        // Change select to current semester and year (if by any reason they are empty)
        if (selectSemester.value === "") {
            selectSemester.value = params.chkschsem;
        }
        if (inputYear.value === "") {
            inputYear.value = params.chkschyear;
        }

        // Clearing elements that are not needed
        clearFromUnnecessaryElements();

        // Generating available times for courses
        generateAvailableTimesOfAllCourses();

        // Parsing available times to checkboxes
        parseAvailableTimesToCheckboxes(availableTimes);

        // Clicking show only matches button for by default showing only matching courses
        document.getElementById("showOnlyButton")?.click();

        // Stopping loading animation
        stopLoadingAnimation();

        // Allowing new fetches
        window.nowIsFetching = true;
    }

    // Fetching to same origin to request courses.
    // By default we get all courses because in params "search=" which means
    // send me all courses. Believe me, now browsers are fast enough in terms of 
    // rendering and performing network requests for each filter it will take 
    // decades. So fetching all courses once and then filtering them on client side is
    // the best approach here (at least I think so).
    async function fetchCourses() {
        // Starting loading animation
        startLoadingAnimation();

        // Getting current values from inputs
        const selectSemester = document.querySelector("#semesterSelect");
        const inputYear = document.querySelector("#yearInput");

        // Getting params from inputs
        const currentParams = getRequestParams(
            inputYear?.value || null,
            selectSemester?.value || null,
        );

        // Fetching with new params FETCH WORKS BECAUSE IT IS NOT CROSS-ORIGIN and that's
        // the reason why I chose to make plugin and not static website.
        fetch("https://auasonis.jenzabarcloud.com/GENSRsC.cfm", {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams(currentParams),
        })
            .then((res) => res.text())
            .then(afterFetch)
            .catch((err) => console.log("Error fetching courses:", err));
    }

    // Update courses array from the table
    function updateCoursesArray() {

        // Getting all matching rows
        const rows = getRows();

        // Clearing previous courses
        courses = [];

        // Parsing each row to get course details
        rows.forEach((row) => {

            // Getting all cells in the row
            const cells = row.querySelectorAll("td");

            // Getting title and section spans
            const titleSpan = cells[0].querySelector(".title");
            const sectionSpan = cells[0].querySelector(".crsbysemester_course_id");

            // Defining the course data object where all parsed data will be stored
            const courseObj = {
                name: titleSpan?.textContent.trim() || "",
                code: sectionSpan?.textContent.replace(/[()]/g, "").trim() || "",
                section: cells[1]?.textContent.trim() || "",
                session: cells[2]?.textContent.trim() || "",
                credits: cells[3]?.textContent.trim() || "",
                campus: cells[4]?.textContent.trim() || "",
                instructor: cells[5]?.textContent.trim() || "",
                times: cells[6]?.textContent.trim() || "",
                takenSeats: cells[7]?.textContent.trim() || "",
                deliveryMethod: cells[10]?.textContent.trim() || "",
                location: cells[11]?.textContent.trim() || "",
            };

            // Pushing course object to courses array
            courses.push(courseObj);
        });
    }

    // Function for updating checked weekdays
    function updateCheckedWeekdays() {
        const checkboxKeywords = [];

        // Looping through dayCheckboxes to see which are checked
        // and marking each as keyword (because the codes that i used
        // are present in course times string)
        for (const code in dayCheckboxes) {
            // If checked, add to keywords
            if (dayCheckboxes[code].checked) {
                checkboxKeywords.push(code);
            }
        }

        // Finally updating checkedDays array with new keywords
        checkedDays = checkboxKeywords;
    }

    // Scrolling to nearest matching row
    function scrollToNearestMatching() {

        // Getting all matching rows
        const rows = getMatchingRows();

        // Scrolling to the first visible matching row
        for (const row of rows) {
            if (row.offsetParent !== null) {
                row.scrollIntoView({ behavior: "smooth", block: "center" });
                break;
            }
        }
    }

    // Variable to keep track of last scrolled index for Enter key navigation
    let lastScrolledIndex = -1;

    // Setting up Enter key navigation in search input
    function setupSearchInputEnterNavigation() {
        const searchInputElement = document.getElementById("searchInput");

        searchInputElement.addEventListener("keydown", (e) => {
            if (e.key !== "Enter") return;

            const rows = Array.from(getMatchingRows() || []).filter(row => row.offsetParent !== null);
            if (!rows.length) return;

            // Clear old temporary highlights
            rows.forEach(r => {
                if (r.dataset.tempHighlight === "true") {
                    // Restore the previous color stored in data attribute
                    r.style.backgroundColor = r.dataset.originalBg || "";
                    delete r.dataset.tempHighlight;
                    delete r.dataset.originalBg;
                }
            });

            // Update index
            if (e.shiftKey) {
                lastScrolledIndex = (lastScrolledIndex - 1 + rows.length) % rows.length;
            } else {
                lastScrolledIndex = (lastScrolledIndex + 1) % rows.length;
            }

            const row = rows[lastScrolledIndex];

            // Scroll to row
            row.scrollIntoView({ behavior: "smooth", block: "center" });

            // Temporary highlight
            row.dataset.tempHighlight = "true";
            // Store current color before temporary highlight
            row.dataset.originalBg = getComputedStyle(row).backgroundColor;
            row.style.backgroundColor = "#ffcc33";

            setTimeout(() => {
                row.style.backgroundColor = row.dataset.originalBg;
                delete row.dataset.tempHighlight;
                delete row.dataset.originalBg;
            }, 500);
        });
    }

    // Function for extracting the rows from courses table
    function getRows() {
        // Getting the table
        const table = document.getElementById("crsbysemester");

        // If table not found, return but for sure this will cause lot of errors
        if (!table) return;

        // Getting all rows with matching class
        const rows = table.querySelectorAll("tbody tr");

        // Returning the rows
        return rows;
    }

    // Function for extracting matching rows from courses table
    function getMatchingRows() {
        // Getting the table
        const table = document.getElementById("crsbysemester");

        // If table not found, return but for sure this will cause lot of errors
        if (!table) return;

        // Getting all rows with matching class
        const rows = table.querySelectorAll("tbody tr.matching");

        // Returning the rows
        return rows;
    }

    // Generating available times for courses. It goes through courses
    // and extracts times using regex and adds to availableTimes array
    // if they match the regex and are not already present.
    function generateAvailableTimesOfAllCourses() {
        courses.forEach((course) => {
            // Selecting the matching times using regex. So we will get extracted array 
            // of times like ["9:00am-10:15am", "11:00am-12:15pm"]
            const times = course.times.match(
                /\b\d{1,2}:\d{2}(?:am|pm)-\d{1,2}:\d{2}(?:am|pm)\b/gi,
            );

            // If no times found, skip
            if (times === null) return;

            // Adding each time to availableTimes if not already present
            times.forEach((time) => {
                if (!availableTimes.includes(time)) {
                    availableTimes.push(time);
                }
            });
        });

        // Sorting available times to be from earliest to latest but I forgot
        // to implement proper time sorting so it is just alphabetical for now
        // but I think it is not too complex to fix.
        availableTimes.sort();
    }

    // Parse avialbale times to checkboxes
    function parseAvailableTimesToCheckboxes(availableTimes) {

        // Getting times container where we will add/remove checkboxes
        const timesContainer = document.getElementById("timesContainer");

        // Remove unchecked checkboxes (like this is helpful when from old matching some
        // times are checked by user and its not good to remove them automatically)
        Object.keys(timeCheckboxes).forEach((time) => {
            const checkbox = timeCheckboxes[time];
            if (!checkbox.checked) {
                checkbox.parentElement.remove(); // remove label from DOM
                delete timeCheckboxes[time];
            }
        });

        // Add new checkboxes
        availableTimes.forEach((time) => {

            // Skip if already exists (or checked)
            if (timeCheckboxes[time]) {
                return;
            }

            // Creating label for checkbox and text
            const label = document.createElement("label");
            label.style.cssText = `
                display: flex;
                align-items: center;
                font-size: 13px;
                cursor: pointer;
                color: ${AUA_BLUE};
            `;

            // Creating the actual checkbox element
            const checkbox = document.createElement("input");
            checkbox.type = "checkbox";
            checkbox.value = time;
            checkbox.style.marginRight = "6px";

            // Adding event listener to checkbox to update checkedDays and highlight rows when changed
            checkbox.addEventListener("change", () => highlightRows("time-filter"));

            // Appending checkbox and text to label
            label.appendChild(checkbox);
            label.appendChild(document.createTextNode(time));

            // Appending label to times container
            timesContainer.appendChild(label);

            // Storing checkbox in timeCheckboxes object for later use and reference
            timeCheckboxes[time] = checkbox;
        });

    }


    // Function for updating matched count
    function updateMatchedCount() {
        const matchedCountSpan = document.getElementById("matchedCount");
        if (matchedCountSpan) {
            matchedCountSpan.textContent = matchingCourses.length;
        }
    }

    // Function for closing the plugin window
    function closeWindow() {
        // Getting the window element
        const win = document.getElementById("aua-courses-plugin-window");
        if (win) win.remove();

        // Creating notification element to notify that this is one time action and after
        // refreshing it will still be enabled.
        const notif = document.createElement("div");
        notif.innerHTML = "⚠ Plugin not disabled! Disable it manually from the <b>Extension</b> or from <b>Tampermonkey</b>.";
        notif.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%) translateY(-20px);
            background: #fff3cd;
            color: #856404;
            padding: 10px 20px;
            border-left: 5px solid #ffc107;
            border-radius: 5px;
            font-size: 14px;
            box-shadow: 0 2px 6px rgba(0,0,0,0.15);
            opacity: 0;
            transition: opacity 0.4s ease, transform 0.4s ease;
            z-index: 9999;
            max-width: 90%;
            text-align: center;
            font-family: sans-serif;
        `;

        // Appending notification to body
        document.body.appendChild(notif);

        // Triggering animation
        requestAnimationFrame(() => {
            notif.style.opacity = "1";
            notif.style.transform = "translateX(-50%) translateY(0)";
        });

        // Removing notification after 4 seconds
        setTimeout(() => {
            notif.style.opacity = "0";
            notif.style.transform = "translateX(-50%) translateY(-20px)";
            notif.addEventListener("transitionend", () => notif.remove());
        }, 4000);
    }


    // Function for clearing all filters
    function clearFilters() {
        // Clear search input
        const searchInput = document.getElementById("searchInput");
        if (searchInput) {
            searchInput.value = "";
        }

        // Clear day checkboxes
        for (const code in dayCheckboxes) {
            dayCheckboxes[code].checked = false;
        }

        // Clear time checkboxes
        for (const time in timeCheckboxes) {
            timeCheckboxes[time].checked = false;
        }

        // Update checked days
        updateCheckedWeekdays();

        // Highlight rows again
        highlightRows();
    }


    // Start loading animation (which is AUA logo just rotating in the middle with blue border)
    function startLoadingAnimation() {

        // If it exists already, do not create another
        if (document.getElementById("centerLoader")) return;

        // Creating the main loader element
        const loader = document.createElement("div");
        loader.id = "centerLoader";
        loader.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 50px;
            height: 50px;
            border: 5px solid #e0e0e0;
            border-top: 5px solid #002147;
            border-radius: 50%;
            animation: spinLoader 1s linear infinite;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 9999;
        `;

        // Creating the image element inside loader
        const img = document.createElement("img");
        img.src = "https://play-lh.googleusercontent.com/lPU7TDhLFw2IfDxV_lEFw-YGSXnN7SgR-ybL4hQsNneceUR3mNxcwKV4NdZuKBf1daU=w240-h480-rw";
        img.style.cssText = `
            width: 80%;
            height: 80%;
            border-radius: 50%;
        `;

        // Appending image to loader
        loader.appendChild(img);

        // Appending loader to body
        document.body.appendChild(loader);

        // Adding keyframes for spin animation if not already present
        if (!document.getElementById("centerLoaderKeyframes")) {
            const style = document.createElement("style");
            style.id = "centerLoaderKeyframes";
            style.textContent = `
                @keyframes spinLoader {
                    0% { transform: translate(-50%, -50%) rotate(0deg); }
                    100% { transform: translate(-50%, -50%) rotate(360deg); }
                }
            `;

            // Appending style to head
            document.head.appendChild(style);
        }
    }

    // Function that stops loading animation (by just removing the loader element)
    function stopLoadingAnimation() {
        // Defining loader element
        const loader = document.getElementById("centerLoader");

        // Removing loader if exists
        if (loader) loader.remove();
    }

    // Function for highlighting rows based on search and filters
    function highlightRows(calledBy = null) {
        console.log("I at least entered highlightRows");
        // Defining array for available times
        let availableTimes = [];

        // Clearing previous matching courses
        matchingCourses = [];



        // ----- Search Input -----

        // Getting as text
        const searchInputText = document
            .getElementById("searchInput")
            .value.toLowerCase();

        // Extracting keywords from search input
        const searchInputKeywords = searchInputText
            .split(" ")
            .filter((kw) => kw.trim() !== "");



        // Updating checked weekdays
        updateCheckedWeekdays();

        // Getting matching rows
        const rows = getRows();

        // Iterating through rows and courses
        rows.forEach((row, index) => {
            const course = courses[index];
            let searchMatches = true;

            //------Search matching------

            // Checking each keyword from search input to see if it is present
            for (const keyword of searchInputKeywords) {
                const classKeywords = [];

                // Collect all fields to search in
                for (const key in course) {
                    if (course.hasOwnProperty(key)) {
                        classKeywords.push(course[key].toLowerCase());
                    }
                }

                // Check if keyword is found in any field
                let foundInAnyField = classKeywords.some((field) =>
                    field.includes(keyword),
                );

                // If keyword not found in any field, mark searchMatches as false
                if (!foundInAnyField && searchInputKeywords.length > 0) {
                    searchMatches = false;
                }
            }



            //------Weekdays matching------

            // The variable that will decide weather daysMatch has happend
            let daysMatch = false;

            // If no weekdays are checked, consider it a match (because maybe user just ignored that filter)
            if (checkedDays.length === 0) {
                daysMatch = true; // If no days are checked, consider it a match
            } else {
                for (const day of checkedDays) {
                    // If includes at least one checked day, it's a match
                    if (course.times.includes(day)) {
                        daysMatch = true;
                        break;
                    }
                }
            }



            //------Times matching------

            // The variable that will decide weather timesMatch has happend
            let timesMatch = false;

            // Getting checked times as array from timeCheckboxes where are only checked ones
            const checkedTimes = Object.keys(timeCheckboxes).filter(
                (time) => timeCheckboxes[time].checked,
            );

            // If no times are checked, consider it a match (because maybe user just ignored that filter)
            if (checkedTimes.length === 0) {
                timesMatch = true; // If no times are checked, consider it a match
            } else {
                for (const time of checkedTimes) {
                    if (course.times.includes(time)) {
                        timesMatch = true;
                        break;
                    }
                }
            }



            // ----- Final Decision -----

            // I think its best to check if all three matches are true. Also if you think it would 
            // be useful you can add option to choose AND/OR logic between these three filters but idk,
            // it will make everything more complicated.
            let finalMatch = searchMatches && (daysMatch && timesMatch);

            // The actual logic if finalMatch has happened
            if (finalMatch) {

                // Highlighting row if any filter is active (because if no filter is active, all rows are matching)
                if (!(searchInputKeywords.length === 0 && checkedDays.length === 0 && checkedTimes.length === 0)) {
                    row.style.setProperty("background-color", "#ffff99", "important"); // Highlight color
                }
                else {
                    row.style.setProperty("background-color", "", "important"); // Reset color
                }

                // Showing row
                row.style.display = "";

                // Adding matching class and removing not-matching class
                row.classList.add("matching");
                row.classList.remove("not-matching");

                // Adding course to matchingCourses array
                matchingCourses.push(course);

                // Extracting available times from matching courses only if not called by time-filter
                // because if one time checkbox is clicked it will remove all others from available times
                if (calledBy !== "time-filter") {
                    const times = course.times.match(
                        /\b\d{1,2}:\d{2}(?:am|pm)-\d{1,2}:\d{2}(?:am|pm)\b/gi,
                    );
                    if (times !== null) {
                        times.forEach((time) => {
                            if (!availableTimes.includes(time)) {
                                availableTimes.push(time);
                            }
                        });
                    }
                }
            } else {
                // Resetting any highlight
                row.style.setProperty("background-color", "", "important"); // Reset color
                row.style.display = showOnly ? "none" : ""; // Hide if show only matching is active

                // Adding not-matching class and removing matching class
                row.classList.add("not-matching");
                row.classList.remove("matching");
            }
        });


        // Scrolling to nearest matching row only if called not by time-filter
        scrollToNearestMatching();

        // After performing search
        availableTimes.sort();

        // Parsing available times to checkboxes only if not called by time-filter 
        // because it will remove all unchecked ones
        if (calledBy !== "time-filter") {
            parseAvailableTimesToCheckboxes(availableTimes);
        }

        // Updating matched count
        updateMatchedCount();
    }

    // Initializing the plugin
    updateCheckedWeekdays();
    fetchCourses();
    createWindow();
    setupSearchInputEnterNavigation();

})();