declare const FRIENDS_URL: string;
declare const LOCALE: string;

/**
 * Object that we get back from the friend API.
 */
interface JsonFriend {
    name: string,
    id: number,
}

interface Window {
    fietsboekImageIndex: number,
    fietsboekCurrentImage: HTMLDivElement | null,
}

// Make eslint happy about the Window redefinition
(_: Window) => null;

/**
 * Type alias to make it clear what kind of string we are expecting.
 */
type Selector = string;

/**
 * Installs a listener to the given DOM objects.
 *
 * @param selector - The query selector to find the DOM objects.
 * @param event - The event name to listen to.
 * @param handler - The handler function.
 */
function addHandler<K extends keyof GlobalEventHandlersEventMap>(
    selector: Selector,
    event: K,
    handler: (ev: GlobalEventHandlersEventMap[K]) => any,
) {
    document.querySelectorAll(selector).
        forEach((obj) => obj.addEventListener(event, handler as EventListener));
}

/**
 * Handler for when a tag is clicked. Removes the tag from the tag list.
 *
 * @param event - The triggering event.
 */
function tagClicked(event: MouseEvent) {
    const span = (event.target as HTMLElement).closest('span')!;
    span.parentNode!.removeChild(span);
}

addHandler(".tag-badge", "click", tagClicked);

/**
 * Handler to add a new tag when the button is pressed.
 */
function addTag() {
    const newTag = document.querySelector("#new-tag") as HTMLInputElement;
    if (newTag.value === "") {
        return;
    }
    const node = document.createElement("span");
    node.classList.add("tag-badge");
    node.classList.add("badge");
    node.classList.add("rounded-pill");
    node.classList.add("bg-info");
    node.classList.add("text-dark");
    node.addEventListener("click", tagClicked);
    const text = document.createTextNode(newTag.value);
    node.appendChild(text);
    const icon = document.createElement("i");
    icon.classList.add("bi");
    icon.classList.add("bi-x");
    node.appendChild(icon);
    const input = document.createElement("input");
    input.hidden = true;
    input.name = "tag[]";
    input.value = newTag.value;
    node.appendChild(input);
    document.querySelector("#formTags")?.appendChild(node);
    const space = document.createTextNode(" ");
    document.querySelector("#formTags")?.appendChild(space);
    newTag.value = "";
}

addHandler("#add-tag-btn", "click", addTag);
// Also add a tag when enter is pressed
addHandler("#new-tag", "keypress", (event) => {
    if (event.code == "Enter") {
        event.preventDefault();
        addTag();
    }
});

/**
 * Function to check for password validity.
 *
 * @param main - Selector for the actual entered password input.
 * @param repeat - Selector for the repeated password, must match `main`.
 */
function checkPasswordValidity(main: Selector, repeat: Selector) {
    const mainPassword = document.querySelector(main) as HTMLInputElement;
    const repeatPassword = document.querySelector(repeat) as HTMLInputElement;

    const form = mainPassword.closest('form')!;
    form.classList.remove('was-validated');

    // Check password requirements. The validation errors are not actually
    // displayed, as the HTML template contains pre-filled texts for that.
    if (mainPassword.value.length != 0 && mainPassword.value.length < 8) {
        mainPassword.setCustomValidity('Too short');
    } else {
        mainPassword.setCustomValidity('');
    }

    if (mainPassword.value != repeatPassword.value) {
        repeatPassword.setCustomValidity('Needs to match');
    } else {
        repeatPassword.setCustomValidity('');
    }
}

// This function is used via a HTML onchange= handler, so make eslint happy
checkPasswordValidity;

/**
 * Function to check for name validity.
 *
 * @param name - Selector name that should be checked.
 */
function checkNameValidity(name: Selector) {
    const nameField = document.querySelector(name) as HTMLInputElement;
    if (nameField.value.length == 0) {
        nameField.setCustomValidity('Needs a name');
    }
}

// This function is used via a HTML onchange= handler, so make eslint happy
checkNameValidity;

/**
 * Hit the endpoint to search for friends. This populates the friend selector
 * when tagging friends.
 */
function searchFriends() {
    const searchPattern = (document.querySelector("#friendSearchQuery") as HTMLInputElement).
        value.toLowerCase();
    const friendSearch = document.querySelector("#friendSearch")!;
    friendSearch.innerHTML = "";
    fetch(FRIENDS_URL)
        .then((response) => response.json())
        .then((response: [JsonFriend]) => {
            const blueprint = document.querySelector("#friendSearchBlueprint") as HTMLLIElement;

            // Only show friends with a matching name
            const friends = response.filter(
                (obj) => obj.name.toLowerCase().indexOf(searchPattern) != -1
            );

            friends.forEach((friend) => {
                const copy = blueprint.cloneNode(true) as HTMLLIElement;
                copy.removeAttribute("id");
                (copy.querySelector(".friend-name") as HTMLSpanElement).textContent = friend.name;
                copy.querySelector("button")?.addEventListener("click", (event: MouseEvent) => {
                    const button = (event.target as HTMLElement).closest("button")!;
                    button.parentNode!.parentNode!.removeChild(button.parentNode!);

                    const added = document.querySelector("#friendAddedBlueprint")!.
                        cloneNode(true) as HTMLLIElement;
                    added.removeAttribute("id");
                    (added.querySelector(".friend-name") as HTMLSpanElement).
                        textContent = friend.name;
                    added.querySelector("input")!.value = friend.id.toString();
                    added.querySelector("input")!.removeAttribute("disabled");
                    added.querySelector("button")!.addEventListener("click", removeFriendClicked);
                    document.querySelector('#taggedFriends')!.appendChild(added);
                });
                friendSearch.appendChild(copy);
            });
        });
}

addHandler("#add-friend-btn", "click", () => searchFriends());
// Also trigger the search on Enter keypress
addHandler("#friendSearchQuery", "keypress", (event) => {
    if (event.code == "Enter") {
        event.preventDefault();
        searchFriends();
    }
});

/**
 * Handler for when a "Remove friend" button is clicked.
 *
 * @param event - The triggering event.
 */
function removeFriendClicked(event: MouseEvent) {
    const button = (event.target as HTMLElement).closest("button")!;
    button.parentNode!.parentNode!.removeChild(button.parentNode!);
}

addHandler(".remove-friend-button", "click", removeFriendClicked);

/**
 * Handler for when the image input is changed.
 *
 * This handler splits the multiple images up into single input fields, such
 * that each one can be removed individually. It also adds preview images, and
 * adds the button to delete and edit the image's description.
 *
 * @param event - The triggering event.
 */
function imageSelectorChanged(event: Event) {
    console.log(event);
    const target = event.target as HTMLInputElement;
    for (const file of Array.from(target.files ?? [])) {
        window.fietsboekImageIndex++;

        const input = document.createElement("input");
        input.type = "file";
        input.hidden = true;
        input.name = `image[${window.fietsboekImageIndex}]`;

        const transfer = new DataTransfer();
        transfer.items.add(file);
        input.files = transfer.files;

        const preview = document.querySelector("#trackImagePreviewBlueprint")!.
            cloneNode(true) as HTMLDivElement;
        preview.removeAttribute("id");
        preview.querySelector("img")!.src = URL.createObjectURL(file);
        preview.querySelector("button.delete-image")!.
            addEventListener("click", deleteImageButtonClicked as EventListener);
        preview.querySelector("button.edit-image-description")!.
            addEventListener("click", editImageDescriptionClicked as EventListener);
        (preview.querySelector("input.image-description-input") as HTMLInputElement).
            name = `image-description[${window.fietsboekImageIndex}]`;
        preview.appendChild(input);

        document.querySelector("#trackImageList")!.appendChild(preview);
    }

    target.value = "";
}

addHandler("#imageSelector", "change", imageSelectorChanged);

/**
 * Handler to remove a picture from a track.
 *
 * @param event - The triggering event.
 */
function deleteImageButtonClicked(event: MouseEvent) {
    const preview = (event.target as HTMLElement).closest("div.track-image-preview")!;
    /* If this was a image yet to be uploaded, simply remove it */
    const input = preview.querySelector("input[type=file]");
    if (input) {
        preview.parentNode!.removeChild(preview);
        return;
    }

    /* Otherwise, we need to remove it but also insert a "delete-image" input */
    const deleter = preview.querySelector("input.image-deleter-input")!;
    deleter.removeAttribute("disabled");
    preview.removeChild(deleter);
    preview.parentNode!.appendChild(deleter);
    preview.parentNode!.removeChild(preview);
}

addHandler("button.delete-image", "click", deleteImageButtonClicked);

/**
 * Handler to show the image description editor.
 *
 * @param event - The triggering event.
 */
function editImageDescriptionClicked(event: MouseEvent) {
    window.fietsboekCurrentImage = (event.target as HTMLElement).closest("div")!;

    const imageInput = (
        <HTMLInputElement>
        window.fietsboekCurrentImage.querySelector("input.image-description-input")
    );
    const currentDescription = imageInput.value;
    const modalDom = document.getElementById("imageDescriptionModal")!;
    modalDom.querySelector("textarea")!.value = currentDescription;

    const modal = bootstrap.Modal.getOrCreateInstance(modalDom, {});
    modal.show();
}

addHandler("button.edit-image-description", "click", editImageDescriptionClicked);

/**
 * Handler to save the image description of the currently edited image.
 *
 * @param event - The triggering event.
 */
function saveImageDescriptionClicked(_event: MouseEvent) {
    const modalDom = document.getElementById("imageDescriptionModal")!;
    const wantedDescription = modalDom.querySelector("textarea")!.value;
    (window.fietsboekCurrentImage!.
        querySelector("input.image-description-input") as HTMLInputElement).
        value = wantedDescription;
    window.fietsboekCurrentImage!.
        querySelector("img")!.title = wantedDescription;

    const modal = bootstrap.Modal.getOrCreateInstance(modalDom, {});
    modal.hide();

    window.fietsboekCurrentImage = null;
}

addHandler("#imageDescriptionModal button.btn-success", "click", saveImageDescriptionClicked);

/**
 * Handler to toggle (collapse/expand) the yearly/monthly summary.
 *
 * @param event - The triggering event.
 */
function toggleSummary(event: MouseEvent) {
    const chevron = event.target as HTMLElement;
    const containing = chevron.closest("a")!;
    const summary = containing.nextElementSibling!;
    bootstrap.Collapse.getOrCreateInstance(summary).toggle();
    if (chevron.classList.contains("bi-chevron-down")) {
        chevron.classList.remove("bi-chevron-down");
        chevron.classList.add("bi-chevron-right");
    } else {
        chevron.classList.remove("bi-chevron-right");
        chevron.classList.add("bi-chevron-down");
    }
}

addHandler(".summary-toggler", "click", toggleSummary);

/*
 * Handler to enable the "Download archive button" ...
 */
addHandler("#archiveDownloadButton", "click", () => {
    const checked = document.querySelectorAll(".archive-checkbox:checked");
    const url = new URL("/track/archive", window.location.href);
    checked.forEach((c) => {
        url.searchParams.append("track_id[]", (c as HTMLInputElement).value);
    });
    window.location.assign(url);
});
/*
 * ... and the listeners on the checkboxes to disable and enable the button.
 */
addHandler(".archive-checkbox", "change", () => {
    const checked = document.querySelectorAll(".archive-checkbox:checked");
    const downloadButton = document.querySelector("#archiveDownloadButton") as HTMLButtonElement;
    downloadButton.disabled = (checked.length == 0);
});

/**
 * Handler to clear the input when a .button-clear-input is pressed.
 *
 * The button must be in an input-group with the input.
 *
 * @param event - The triggering event.
 */
function clearInputButtonClicked(event: MouseEvent) {
    const target = event.target as HTMLElement;
    target.closest(".input-group")!.querySelectorAll("input").forEach((i) => i.value = "");
    target.closest(".input-group")!.querySelectorAll("select").forEach((i) => i.value = "");
}

addHandler(".button-clear-input", "click", clearInputButtonClicked);


/**
 * Handler to change the sorting of the home page.
 *
 * This basically sets the cookie to signal that the home page should be
 * returned reversed, and then reloads the page.
 *
 * @param event - The triggering event.
 */
function changeHomeSorting(_event: MouseEvent) {
    const currentSorting = document.cookie.split("; ")
        .find((row) => row.startsWith("home_sorting="))
        ?.split("=")[1] ?? "asc";
    const newSorting = currentSorting == "asc" ? "desc" : "asc";
    document.cookie = `home_sorting=${newSorting}; SameSite=Lax`;
    window.location.reload();
}

addHandler("#changeHomeSorting", "click", changeHomeSorting);


document.addEventListener('DOMContentLoaded', function() {
    window.fietsboekImageIndex = 0;

    /* Enable tooltips */
    const tooltipTriggerList = [].slice.call(
        document.querySelectorAll('[data-bs-toggle="tooltip"]')
    );
    tooltipTriggerList.map((tooltipTriggerEl) => {
        return new bootstrap.Tooltip(tooltipTriggerEl, { sanitize: false });
    });

    /* Enable Bootstrap form validation */
    const forms = document.querySelectorAll('.needs-validation');
    Array.from(forms).forEach((form) => {
        form.addEventListener('submit', (event) => {
            if (!(form as HTMLFormElement).checkValidity()) {
                event.preventDefault();
                event.stopPropagation();
            }

            form.classList.add('was-validated');
        }, false);
    });

    /* Format all datetimes to the local timezone */
    document.querySelectorAll(".fietsboek-local-datetime").forEach((obj) => {
        const timestamp = parseFloat(obj.attributes.getNamedItem("data-utc-timestamp")!.value);
        const date = new Date(timestamp * 1000);
        // TypeScript complains about this, but according to MDN it is fine, at
        // least in "somewhat modern" browsers
        const intl = new Intl.DateTimeFormat(LOCALE, {
            dateStyle: "medium",
            timeStyle: "medium",
        } as any);
        obj.innerHTML = intl.format(date);
    });
});