import $ from "jquery";
import { i18n } from "../localization";
import GUI, { TABS } from "../gui";
import { get as getConfig, set as setConfig } from "../ConfigStorage";
import { get as getStorage, set as setStorage } from "../SessionStorage";
import BuildApi from "../BuildApi";
import ConfigInserter from "../ConfigInserter.js";
import { tracking } from "../Analytics";
import PortHandler from "../port_handler";
import { gui_log } from "../gui_log";
import semver from "semver";
import { urlExists } from "../utils/common";
import read_hex_file from "../workers/hex_parser.js";
import Sponsor from "../Sponsor";
import FileSystem from "../FileSystem";
import STM32 from "../protocols/webstm32";
import DFU from "../protocols/webusbdfu";
import AutoBackup from "../utils/AutoBackup.js";
import AutoDetect from "../utils/AutoDetect.js";
import { EventBus } from "../../components/eventBus";
import { ispConnected } from "../utils/connection.js";
import FC from "../fc";

const firmware_flasher = {
    targets: null,
    buildApi: new BuildApi(),
    sponsor: new Sponsor(),
    localFirmwareLoaded: false,
    selectedBoard: undefined,
    cloudBuildKey: null,
    cloudBuildOptions: null,
    intel_hex: undefined, // standard intel hex in string format
    parsed_hex: undefined, // parsed raw hex in array format
    isConfigLocal: false, // Set to true if the user loads one locally
    filename: null,
    configFilename: null,
    config: {},
    developmentFirmwareLoaded: false, // Is the firmware to be flashed from the development branch?
    cancelBuild: false,
    // Properties to preserve firmware state during flashing
    preFlashingMessage: null,
    preFlashingMessageType: null,
    logHead: "[FIRMWARE_FLASHER]",
    // Event handlers to allow removal on tab change
    detectedUsbDevice: function (device) {
        const isFlashOnConnect = $("input.flash_on_connect").is(":checked");

        console.log(`${firmware_flasher.logHead} Detected USB device:`, device);

        // If another operation is in progress, ignore port events (unless we're resuming from a reboot)
        if (GUI.connect_lock && !STM32.rebootMode) {
            console.log(`${firmware_flasher.logHead} Port event ignored due to active operation (connect_lock)`);
            return;
        }

        // Proceed if we're resuming a reboot sequence or if flash-on-connect is enabled and no operation is active
        if (STM32.rebootMode || isFlashOnConnect) {
            const wasReboot = !!STM32.rebootMode;
            STM32.rebootMode = 0;
            // Only clear the global connect lock when we are resuming from a reboot
            // so we don't accidentally interrupt another active operation.
            if (wasReboot) {
                GUI.connect_lock = false;
            }
            firmware_flasher.startFlashing?.();
        }
    },
    onDeviceRemoved: function (devicePath) {
        console.log(`${firmware_flasher.logHead} Device removed:`, devicePath);

        // Avoid clearing when removal is expected during flashing/reboot
        if (GUI.connect_lock || STM32.rebootMode) {
            return;
        }

        $('select[name="board"]').val("0").trigger("change");
        firmware_flasher.clearBufferedFirmware?.();
    },
};

firmware_flasher.initialize = async function (callback) {
    const self = this;

    if (GUI.active_tab !== "firmware_flasher") {
        GUI.active_tab = "firmware_flasher";
    }

    // reset on tab change
    self.selectedBoard = undefined;

    self.cloudBuildKey = null;
    self.cloudBuildOptions = null;

    self.localFirmwareLoaded = false;
    self.isConfigLocal = false;
    self.intel_hex = undefined;
    self.parsed_hex = undefined;

    function getExtension(key) {
        if (!key) {
            return undefined;
        }
        const lower = key.toLowerCase();
        return lower.split("?")[0].split("#")[0].split(".").pop();
    }

    async function onDocumentLoad() {
        function parseHex(str, callback) {
            self.intel_hex = str;
            self.firmware_type = "HEX";
            read_hex_file(str).then((data) => {
                callback(data);
            });
        }

        function showLoadedFirmware(filename, bytes) {
            self.filename = filename;

            if (self.localFirmwareLoaded) {
                self.flashingMessage(
                    i18n.getMessage("firmwareFlasherFirmwareLocalLoaded", {
                        filename: filename,
                        bytes: bytes,
                    }),
                    self.FLASH_MESSAGE_TYPES.NEUTRAL,
                );
            } else {
                self.flashingMessage(
                    `<a class="save_firmware" href="#" title="${i18n.getMessage("firmwareFlasherTooltipSaveFirmware")}">${i18n.getMessage(
                        "firmwareFlasherFirmwareOnlineLoaded",
                        { filename: filename, bytes: bytes },
                    )}</a>`,
                    self.FLASH_MESSAGE_TYPES.NEUTRAL,
                );
            }
            self.enableFlashButton(true);

            tracking.sendEvent(tracking.EVENT_CATEGORIES.FLASHING, "FirmwareLoaded", {
                firmwareSize: bytes,
                firmwareName: filename,
                firmwareSource: self.localFirmwareLoaded ? "file" : "http",
                selectedTarget: self.targetDetail?.target,
                selectedRelease: self.targetDetail?.release,
            });
        }

        function showReleaseNotes(summary) {
            if (summary.manufacturer) {
                $("div.release_info #manufacturer").text(summary.manufacturer);
                $("div.release_info #manufacturerInfo").show();
            } else {
                $("div.release_info #manufacturerInfo").hide();
            }

            $("div.release_info .target").text(summary.target);
            $("div.release_info .name").text(summary.release).prop("href", summary.releaseUrl);
            $("div.release_info .date").text(summary.date);
            $("div.release_info #targetMCU").text(summary.mcu);
            $("div.release_info .configFilename").text(self.isConfigLocal ? self.configFilename : "[default]");

            if (summary.cloudBuild) {
                $("div.release_info #cloudTargetInfo").show();
                $("div.release_info #cloudTargetLog").text("");
                $("div.release_info #cloudTargetStatus").text("pending");
            } else {
                $("div.release_info #cloudTargetInfo").hide();
            }

            if (self.targets) {
                $("div.release_info").slideDown();
                $(".tab-firmware_flasher .content_wrapper").animate(
                    { scrollTop: $("div.release_info").position().top },
                    1000,
                );
            }
        }

        function clearBoardConfig() {
            self.config = {};
            self.isConfigLocal = false;
            self.configFilename = null;
        }

        function setBoardConfig(config, filename) {
            self.config = config.join("\n");
            self.isConfigLocal = filename !== undefined;
            self.configFilename = filename !== undefined ? filename : null;
        }

        function loadFailed() {
            $("span.progressLabel")
                .attr("i18n", "firmwareFlasherFailedToLoadOnlineFirmware")
                .removeClass("i18n-replaced");
            self.enableLoadRemoteFileButton(true);
            $("a.load_remote_file").text(i18n.getMessage("firmwareFlasherButtonLoadOnline"));
            i18n.localizePage();
        }

        function processHex(data, key) {
            self.firmware_type = "HEX";
            const bytes = data instanceof Uint8Array ? data : data instanceof ArrayBuffer ? new Uint8Array(data) : null;

            if (!bytes || bytes.byteLength === 0) {
                loadFailed();
                return;
            }

            const decoder = new TextDecoder("utf-8");
            self.intel_hex = decoder.decode(bytes);

            parseHex(self.intel_hex, function (data) {
                self.parsed_hex = data;

                if (self.parsed_hex) {
                    showLoadedFirmware(key, self.parsed_hex.bytes_total);
                } else {
                    self.flashingMessage(
                        i18n.getMessage("firmwareFlasherHexCorrupted"),
                        self.FLASH_MESSAGE_TYPES.INVALID,
                    );
                    self.enableFlashButton(false);
                }
            });
        }

        async function processUf2(data, key) {
            self.firmware_type = "UF2";

            const toBytes = (buf) => {
                return buf instanceof Uint8Array
                    ? buf
                    : buf instanceof ArrayBuffer
                        ? new Uint8Array(buf)
                        : buf.arrayBuffer
                            ? new Uint8Array(buf.arrayBuffer ? undefined : buf)
                            : buf; // File/Blob typically have arrayBuffer()
            };

            const bytes = data
                ? data instanceof Blob
                    ? new Uint8Array(await data.arrayBuffer())
                    : toBytes(data)
                : undefined;

            if (!bytes || bytes.byteLength === 0) {
                loadFailed();
                return;
            }
            self.uf2_binary = bytes;
            showLoadedFirmware(key, bytes.byteLength);
        }

        async function processFile(data, key) {
            if (!data || !key) {
                loadFailed();
                return;
            }

            switch (getExtension(key)) {
                case "hex":
                    processHex(data, key);
                    break;
                case "uf2":
                    await processUf2(data, key);
                    break;
                default:
                    self.flashingMessage(
                        i18n.getMessage("firmwareFlasherInvalidFileFormat") || "Invalid file format",
                        self.FLASH_MESSAGE_TYPES.INVALID,
                    );
                    loadFailed();
            }

            self.enableLoadRemoteFileButton(true);
            $("a.load_remote_file").text(i18n.getMessage("firmwareFlasherButtonLoadOnline"));
        }

        async function populateTargetList(targets) {
            if (!targets || !ispConnected()) {
                $('select[name="board"]').empty().append('<option value="0">Offline</option>');
                $('select[name="firmware_version"]').empty().append('<option value="0">Offline</option>');

                return;
            }

            const boards_e = $('select[name="board"]');
            boards_e.empty();
            boards_e.append(
                $(`<option value='0'>${i18n.getMessage("firmwareFlasherOptionLabelSelectBoard")}</option>`),
            );

            const versions_e = $('select[name="firmware_version"]');
            versions_e.empty();
            versions_e.append(
                $(`<option value='0'>${i18n.getMessage("firmwareFlasherOptionLabelSelectFirmwareVersion")}</option>`),
            );

            const groupOrder = {
                supported: 0,
                unsupported: 1,
                legacy: 2,
            };

            const groupLabels = {
                supported: i18n.getMessage("firmwareFlasherOptionLabelVerifiedPartner"),
                unsupported: i18n.getMessage("firmwareFlasherOptionLabelVendorCommunity"),
                legacy: i18n.getMessage("firmwareFlasherOptionLabelLegacy"),
            };

            const groupTargets = Object.groupBy(targets, (descriptor) =>
                descriptor.group ? descriptor.group : "unsupported",
            );

            const groupSorted = Object.keys(groupTargets).sort((a, b) => {
                const groupA = groupOrder[a] ?? 999;
                const groupB = groupOrder[b] ?? 999;
                return groupA - groupB;
            });

            groupSorted.forEach((groupKey) => {
                const groupItems = groupTargets[groupKey];
                const optgroup = $("<optgroup>").attr("label", groupLabels[groupKey] || groupKey);
                const sortedTargets = [...groupItems].sort((a, b) => a.target.localeCompare(b.target));
                sortedTargets.forEach(function (descriptor) {
                    const select_e = $("<option>").val(descriptor.target).text(descriptor.target);
                    optgroup.append(select_e);
                });
                boards_e.append(optgroup);
            });

            TABS.firmware_flasher.targets = targets;

            // For discussion. Rather remove build configuration and let user use auto-detect. Often I think already had pressed the button.
            $("div.build_configuration").slideUp();
        }

        function buildOptionsList(select_e, options) {
            select_e.empty();
            options.forEach((option) => {
                if (option.default) {
                    select_e.append($(`<option value='${option.value}' selected>${option.name}</option>`));
                } else {
                    select_e.append($(`<option value='${option.value}'>${option.name}</option>`));
                }
            });
        }

        function toggleTelemetryProtocolInfo() {
            const radioProtocol = $('select[name="radioProtocols"] option:selected').val();
            const hasTelemetryEnabledByDefault = [
                "USE_SERIALRX_CRSF",
                "USE_SERIALRX_FPORT",
                "USE_SERIALRX_GHST",
                "USE_SERIALRX_JETIEXBUS",
            ].includes(radioProtocol);

            $('select[name="telemetryProtocols"]').attr("disabled", hasTelemetryEnabledByDefault);

            if (hasTelemetryEnabledByDefault) {
                if ($('select[name="telemetryProtocols"] option[value="-1"]').length === 0) {
                    $('select[name="telemetryProtocols"]').prepend(
                        $("<option>", {
                            value: "-1",
                            selected: "selected",
                            text: i18n.getMessage("firmwareFlasherOptionLabelTelemetryProtocolIncluded"),
                        }),
                    );
                } else {
                    $('select[name="telemetryProtocols"] option:first')
                        .attr("selected", "selected")
                        .text(i18n.getMessage("firmwareFlasherOptionLabelTelemetryProtocolIncluded"));
                }
            } else if ($('select[name="telemetryProtocols"] option[value="-1"]').length) {
                $('select[name="telemetryProtocols"] option:first').remove();
            }
        }

        function updateOsdProtocolColor() {
            const osdProtocol = $('select[name="osdProtocols"] option:selected').val();
            $('select[name="osdProtocols"]')
                .next(".select2-container")
                .find(".select2-selection__rendered")
                .attr("style", osdProtocol === "" ? "color: red !important" : "");
        }

        function buildOptions(data) {
            if (!ispConnected()) {
                return;
            }

            // extract osd protocols from general options and add to osdProtocols
            console.log(`${self.logHead} buildOptions`, FC.CONFIG.buildOptions);
            self.cloudBuildOptions = FC.CONFIG.buildOptions || [];
            data.osdProtocols = data.generalOptions
                .filter((option) => option.group === "OSD")
                .map((option) => {
                    option.name = option.groupedName;
                    option.default = self.cloudBuildOptions?.includes(option.value);
                    return option;
                });

            // add None option to osdProtocols as first option
            data.osdProtocols.unshift({ name: "None", value: "" });

            // remove osdProtocols from generalOptions
            data.generalOptions = data.generalOptions.filter((option) => !option.group);

            buildOptionsList($('select[name="radioProtocols"]'), data.radioProtocols);
            buildOptionsList($('select[name="telemetryProtocols"]'), data.telemetryProtocols);
            buildOptionsList($('select[name="osdProtocols"]'), data.osdProtocols);
            buildOptionsList($('select[name="options"]'), data.generalOptions);
            buildOptionsList($('select[name="motorProtocols"]'), data.motorProtocols);

            // Using setTimeout to ensure this runs after Select2 has finished initializing/rendering
            setTimeout(updateOsdProtocolColor, 0);

            // Add change handler to update color when selection changes
            $('select[name="osdProtocols"]').on("change", updateOsdProtocolColor);

            if (!self.validateBuildKey()) {
                preselectRadioProtocolFromStorage();
            }

            toggleTelemetryProtocolInfo();
        }

        function preselectRadioProtocolFromStorage() {
            const storedRadioProtocol = getConfig("ffRadioProtocol").ffRadioProtocol;
            if (storedRadioProtocol) {
                const valueExistsInSelect =
                    $('select[name="radioProtocols"] option').filter(function (i, o) {
                        return o.value === storedRadioProtocol;
                    }).length !== 0;
                if (valueExistsInSelect) {
                    $('select[name="radioProtocols"]').val(storedRadioProtocol);
                }
            }
        }

        let buildTypesToShow;
        const buildType_e = $('select[name="build_type"]');
        function buildBuildTypeOptionsList() {
            buildType_e.empty();
            buildTypesToShow.forEach(({ tag, title }, index) => {
                buildType_e.append($(`<option value='${index}'>${tag ? i18n.getMessage(tag) : title}</option>`));
            });
        }

        const buildTypes = [
            {
                tag: "firmwareFlasherOptionLabelBuildTypeRelease",
            },
            {
                tag: "firmwareFlasherOptionLabelBuildTypeReleaseCandidate",
            },
            {
                tag: "firmwareFlasherOptionLabelBuildTypeDevelopment",
            },
        ];

        function showOrHideBuildTypes() {
            const showExtraReleases = $(this).is(":checked");

            if (showExtraReleases) {
                $("tr.build_type").show();
            } else {
                $("tr.build_type").hide();
                buildType_e.val(0).trigger("change");
            }
        }

        function showOrHideExpertMode() {
            const expertModeChecked = $(this).is(":checked");

            if (expertModeChecked) {
                buildTypesToShow = buildTypes;
            } else {
                buildTypesToShow = buildTypes.slice(0, 2);
            }

            buildBuildTypeOptionsList();
            buildType_e.val(0).trigger("change");

            setTimeout(() => {
                $("tr.expertOptions").toggle(expertModeChecked);
                $("div.expertOptions").toggle(expertModeChecked);
            }, 0);

            setConfig({ expertMode: expertModeChecked });
        }

        const expertMode_e = $(".tab-firmware_flasher input.expert_mode");
        const expertMode = getConfig("expertMode").expertMode;

        expertMode_e.prop("checked", expertMode);
        expertMode_e.on("change", showOrHideExpertMode).trigger("change");

        $("input.show_development_releases").change(showOrHideBuildTypes).change();

        // translate to user-selected language
        i18n.localizePage();

        await self.sponsor.loadSponsorTile("flash", $("div.tab_sponsor"));

        buildType_e.on("change", async function () {
            self.enableLoadRemoteFileButton(false);

            const build_type = buildType_e.val();
            const currentlySelectedBoard = $('select[name="board"]').val();

            $('select[name="board"]')
                .empty()
                .append($(`<option value='0'>${i18n.getMessage("firmwareFlasherOptionLoading")}</option>`));

            $('select[name="firmware_version"]')
                .empty()
                .append($(`<option value='0'>${i18n.getMessage("firmwareFlasherOptionLoading")}</option>`));

            if (!GUI.connect_lock) {
                try {
                    await populateTargetList(await self.buildApi.loadTargets());

                    // Restore the previously selected board if it was selected and still exists
                    if (
                        currentlySelectedBoard &&
                        currentlySelectedBoard !== "0" &&
                        $(`select[name="board"] option[value="${currentlySelectedBoard}"]`).length > 0
                    ) {
                        $('select[name="board"]').val(currentlySelectedBoard).trigger("change");
                    }
                } catch (err) {
                    console.error(err);
                }
            }

            setConfig({ selected_build_type: build_type });
        });

        async function selectFirmware(release) {
            $("div.build_configuration").slideUp();
            $("div.release_info").slideUp();

            if (!self.localFirmwareLoaded) {
                self.enableFlashButton(false);
                self.flashingMessage(
                    i18n.getMessage("firmwareFlasherLoadFirmwareFile"),
                    self.FLASH_MESSAGE_TYPES.NEUTRAL,
                );
                if (self.parsed_hex && self.parsed_hex.bytes_total) {
                    // Changing the board triggers a version change, so we need only dump it here.
                    console.log(`${self.logHead} throw out loaded hex`);
                    self.intel_hex = undefined;
                    self.parsed_hex = undefined;
                }
            }

            const target = $('select[name="board"] option:selected').val();

            async function LoadTargetDetail(detail) {
                if (!detail) {
                    self.enableLoadRemoteFileButton(false);
                    return;
                }

                self.targetDetail = detail;
                if (detail.cloudBuild === true) {
                    $("div.build_configuration").slideDown();

                    const expertMode = expertMode_e.is(":checked");
                    if (expertMode) {
                        if (detail.releaseType === "Unstable") {
                            let commits = await self.buildApi.loadCommits(detail.release);
                            if (commits) {
                                const select_e = $('select[name="commits"]');
                                select_e.empty();
                                commits.forEach((commit) => {
                                    select_e.append(
                                        $(`<option value='${commit.sha}'>${commit.message.split("\n")[0]}</option>`),
                                    );
                                });
                            }

                            $("div.commitSelection").show();
                        } else {
                            $("div.commitSelection").hide();
                        }
                    }

                    $("div.expertOptions").toggle(expertMode);
                    // Need to reset core build mode
                    $("input.corebuild_mode").trigger("change");
                }

                if (detail.configuration && !self.isConfigLocal) {
                    setBoardConfig(detail.configuration);
                }

                self.enableLoadRemoteFileButton(true);
            }

            try {
                let targetDetail = await self.buildApi.loadTarget(target, release);
                await LoadTargetDetail(targetDetail);
            } catch (error) {
                console.error("Failed to load target:", error);
                loadFailed();
                return;
            }

            try {
                if (self.validateBuildKey()) {
                    let options = await self.buildApi.loadOptionsByBuildKey(release, self.cloudBuildKey);
                    if (options) {
                        buildOptions(options);
                        return;
                    }
                }
                buildOptions(await self.buildApi.loadOptions(release));
            } catch (error) {
                console.error("Failed to load build options:", error);
                return;
            }
        }

        function populateReleases(versions_element, target) {
            const sortReleases = function (a, b) {
                return -semver.compareBuild(a.release, b.release);
            };

            versions_element.empty();
            const releases = target.releases;
            if (releases.length > 0) {
                versions_element.append(
                    $(
                        `<option value='0'>${i18n.getMessage("firmwareFlasherOptionLabelSelectFirmwareVersionFor")} ${
                            target.target
                        }</option>`,
                    ),
                );

                const build_type = $('select[name="build_type"]').val();

                releases
                    .sort(sortReleases)
                    .filter((r) => {
                        return (
                            (r.type === "Unstable" && build_type > 1) ||
                            (r.type === "ReleaseCandidate" && build_type > 0) ||
                            r.type === "Stable"
                        );
                    })
                    .forEach(function (release) {
                        const releaseName = release.release;

                        const select_e = $(`<option value='${releaseName}'>${releaseName} [${release.label}]</option>`);
                        const summary = `${target}/${release}`;
                        select_e.data("summary", summary);
                        versions_element.append(select_e);
                    });

                // Assume flashing latest, so default to it.
                versions_element.prop("selectedIndex", 1);
                selectFirmware(versions_element.val());
            }
        }

        function clearBufferedFirmware() {
            clearBoardConfig();
            self.intel_hex = undefined;
            self.parsed_hex = undefined;
            self.uf2_binary = undefined;
            self.firmware_type = undefined;
            self.localFirmwareLoaded = false;
            self.filename = null;
        }

        $('select[name="board"]').select2();
        $('select[name="osdProtocols"]').select2();
        $('select[name="radioProtocols"]').select2();
        $('select[name="telemetryProtocols"]').select2();
        $('select[name="motorProtocols"]').select2();
        $('select[name="options"]').select2({ tags: false, closeOnSelect: false });
        $('select[name="commits"]').select2({ tags: true });

        $('select[name="options"]')
            .on("select2:opening", function () {
                const searchfield = $(this).parent().find(".select2-search__field");
                searchfield.prop("disabled", false);
            })
            .on("select2:closing", function () {
                const searchfield = $(this).parent().find(".select2-search__field");
                searchfield.prop("disabled", true);
            });

        $('select[name="radioProtocols"]').on("select2:select", function () {
            const selectedProtocol = $('select[name="radioProtocols"] option:selected').first().val();
            if (selectedProtocol) {
                setConfig({ ffRadioProtocol: selectedProtocol });
            }

            toggleTelemetryProtocolInfo();
        });

        $('select[name="board"]').on("change", async function () {
            self.enableLoadRemoteFileButton(false);
            let target = $(this).val();

            // exception for board flashed with local custom firmware
            if (target === null) {
                target = "0";
                $(this).val(target).trigger("change");
            }

            if (!GUI.connect_lock) {
                self.selectedBoard = target;
                console.log(`${self.logHead} board changed to`, target);

                self.flashingMessage(
                    i18n.getMessage("firmwareFlasherLoadFirmwareFile"),
                    self.FLASH_MESSAGE_TYPES.NEUTRAL,
                ).flashProgress(0);

                $("div.release_info").slideUp();
                $("div.build_configuration").slideUp();

                if (!self.localFirmwareLoaded) {
                    self.enableFlashButton(false);
                }

                const versions_e = $('select[name="firmware_version"]');
                if (target === "0") {
                    // target is 0 is the "Choose a Board" option. Throw out anything loaded
                    clearBufferedFirmware();

                    versions_e.empty();
                    versions_e.append(
                        $(
                            `<option value='0'>${i18n.getMessage(
                                "firmwareFlasherOptionLabelSelectFirmwareVersion",
                            )}</option>`,
                        ),
                    );
                } else {
                    // Show a loading message as there is a delay in loading a configuration
                    versions_e.empty();
                    versions_e.append(
                        $(`<option value='0'>${i18n.getMessage("firmwareFlasherOptionLoading")}</option>`),
                    );

                    populateReleases(versions_e, await self.buildApi.loadTargetReleases(target));
                }
            }
        });
        // when any of the select2 elements is opened, force a focus on that element's search box
        const select2Elements = [
            'select[name="board"]',
            'select[name="radioProtocols"]',
            'select[name="telemetryProtocols"]',
            'select[name="osdProtocols"]',
            'select[name="motorProtocols"]',
            'select[name="options"]',
            'select[name="commits"]',
        ];

        $(document).on("select2:open", select2Elements.join(","), () => {
            const allFound = document.querySelectorAll(".select2-container--open .select2-search__field");
            $(this).one("mouseup keyup", () => {
                setTimeout(() => {
                    allFound[allFound.length - 1].focus();
                }, 0);
            });
        });

        function cleanUnifiedConfigFile(input) {
            let output = [];
            let inComment = false;
            for (let i = 0; i < input.length; i++) {
                if (input.charAt(i) === "\n" || input.charAt(i) === "\r") {
                    inComment = false;
                }

                if (input.charAt(i) === "#") {
                    inComment = true;
                }

                if (!inComment && input.charCodeAt(i) > 255) {
                    self.flashingMessage(
                        i18n.getMessage("firmwareFlasherConfigCorrupted"),
                        self.FLASH_MESSAGE_TYPES.INVALID,
                    );
                    gui_log(i18n.getMessage("firmwareFlasherConfigCorruptedLogMessage"));
                    return null;
                }

                if (input.charCodeAt(i) > 255) {
                    output.push("_");
                } else {
                    output.push(input.charAt(i));
                }
            }
            return output.join("").split("\n");
        }

        // Expose the local startFlashing implementation to module callers/tests so
        // module-scoped handlers can safely call firmware_flasher.startFlashing()
        // even if those callers ran before initialize() completed.
        firmware_flasher.startFlashing = startFlashing;
        firmware_flasher.clearBufferedFirmware = clearBufferedFirmware;

        EventBus.$on("port-handler:auto-select-usb-device", firmware_flasher.detectedUsbDevice);
        EventBus.$on("port-handler:device-removed", firmware_flasher.onDeviceRemoved);

        async function saveFirmware() {
            const fileType = self.firmware_type;
            try {
                const file = await FileSystem.pickSaveFile(
                    self.filename,
                    i18n.getMessage("fileSystemPickerFiles", { typeof: fileType.toUpperCase() }),
                    `.${fileType.toLowerCase()}`,
                );
                if (!file) return false; // user cancelled

                console.log(`${self.logHead} Saving firmware to:`, file.name);
                await FileSystem.writeFile(file, fileType === "UF2" ? self.uf2_binary : self.intel_hex);
                return true;
            } catch (err) {
                console.error(err);
                return false;
            }
        }

        async function flashHexFirmware(firmware) {
            const options = {};

            if ($("input.erase_chip").is(":checked") || expertMode_e.is(":not(:checked)")) {
                options.erase_chip = true;
            }

            const port = PortHandler.portPicker.selectedPort;
            const isSerial = port.startsWith("serial");
            const isDFU = port.startsWith("usb");

            console.log(`${self.logHead} Selected port:`, port);

            if (isDFU) {
                tracking.sendEvent(tracking.EVENT_CATEGORIES.FLASHING, "DFU Flashing", {
                    filename: self.filename || null,
                });
                DFU.connect(port, firmware, options);
            } else if (isSerial) {
                if ($("input.updating").is(":checked")) {
                    options.no_reboot = true;
                } else {
                    options.reboot_baud = PortHandler.portPicker.selectedBauds;
                }

                let baud = 115200;
                if ($("input.flash_manual_baud").is(":checked")) {
                    baud = parseInt($("#flash_manual_baud_rate").val()) || 115200;
                }

                tracking.sendEvent(tracking.EVENT_CATEGORIES.FLASHING, "Flashing", { filename: self.filename || null });

                STM32.connect(port, baud, firmware, options);
            } else {
                // Maybe the board is in DFU mode, but it does not have permissions. Ask for them.
                console.log(`${self.logHead} No valid port detected, asking for permissions`);

                DFU.requestPermission()
                    .then((device) => {
                        DFU.connect(device.path, firmware, options);
                    })
                    .catch((error) => {
                        console.error("Permission request failed", error);
                        firmware_flasher.resetFlashingState();
                    });
            }

            GUI.interval_resume("sponsor");
        }

        let result = getConfig("erase_chip");
        $("input.erase_chip").prop("checked", result.erase_chip); // users can override this during the session

        $("input.erase_chip")
            .change(function () {
                setConfig({ erase_chip: $(this).is(":checked") });
            })
            .change();

        result = getConfig("show_development_releases");
        $("input.show_development_releases")
            .prop("checked", result.show_development_releases)
            .change(function () {
                setConfig({ show_development_releases: $(this).is(":checked") });
            })
            .change();

        result = getConfig("selected_build_type");
        // ensure default build type is selected
        buildType_e.val(result.selected_build_type || 0).trigger("change");

        result = getConfig("no_reboot_sequence");
        if (result.no_reboot_sequence) {
            $("input.updating").prop("checked", true);
            $(".flash_on_connect_wrapper").show();
        } else {
            $("input.updating").prop("checked", false);
        }

        // bind UI hook so the status is saved on change
        $("input.updating").change(function () {
            const status = $(this).is(":checked");

            if (status) {
                $(".flash_on_connect_wrapper").show();
            } else {
                $("input.flash_on_connect").prop("checked", false).change();
                $(".flash_on_connect_wrapper").hide();
            }

            setConfig({ no_reboot_sequence: status });
        });

        $("input.updating").change();

        result = getConfig("flash_manual_baud");
        if (result.flash_manual_baud) {
            $("input.flash_manual_baud").prop("checked", true);
        } else {
            $("input.flash_manual_baud").prop("checked", false);
        }

        $("input.corebuild_mode").change(function () {
            const status = $(this).is(":checked");

            $(".hide-in-core-build-mode").toggle(!status);
            $("div.expertOptions").toggle(!status && expertMode_e.is(":checked"));
        });
        $("input.corebuild_mode").change();

        // bind UI hook so the status is saved on change
        $("input.flash_manual_baud").change(function () {
            const status = $(this).is(":checked");
            setConfig({ flash_manual_baud: status });
        });

        $("input.flash_manual_baud").change();

        result = getConfig("flash_manual_baud_rate");
        $("#flash_manual_baud_rate").val(result.flash_manual_baud_rate);

        // bind UI hook so the status is saved on change
        $("#flash_manual_baud_rate").change(function () {
            const baud = parseInt($("#flash_manual_baud_rate").val());
            setConfig({ flash_manual_baud_rate: baud });
        });

        $("input.flash_manual_baud_rate").change();

        // UI Hooks
        $("a.load_file").on("click", async function () {
            // Reset button when loading a new firmware
            self.enableFlashButton(false);
            self.enableLoadRemoteFileButton(false);

            self.developmentFirmwareLoaded = false;

            try {
                const file = await FileSystem.pickOpenFile(i18n.getMessage("fileSystemPickerFirmwareFiles"), [
                    ".hex",
                    ".uf2",
                ]);

                if (!file) {
                    return; // user cancelled
                }
                console.log(`${self.logHead} loading firmware from:`, file.name);

                const extension = getExtension(file.name);
                if (extension === "uf2") {
                    const data = await FileSystem.readFileAsBlob(file);
                    self.localFirmwareLoaded = true;
                    processUf2(data, file.name);
                } else {
                    const data = await FileSystem.readFile(file);
                    if (extension === "hex") {
                        parseHex(data, function (data) {
                            self.parsed_hex = data;

                            if (self.parsed_hex) {
                                self.localFirmwareLoaded = true;

                                showLoadedFirmware(file.name, self.parsed_hex.bytes_total);
                            } else {
                                self.flashingMessage(
                                    i18n.getMessage("firmwareFlasherHexCorrupted"),
                                    self.FLASH_MESSAGE_TYPES.INVALID,
                                );
                            }
                        });
                    } else {
                        clearBufferedFirmware();

                        let config = cleanUnifiedConfigFile(data);
                        if (config !== null) {
                            setBoardConfig(config, file.name);

                            if (self.isConfigLocal && !self.parsed_hex) {
                                self.flashingMessage(
                                    i18n.getMessage("firmwareFlasherLoadedConfig"),
                                    self.FLASH_MESSAGE_TYPES.NEUTRAL,
                                );
                            }

                            if (
                                (self.isConfigLocal && self.parsed_hex && !self.localFirmwareLoaded) ||
                                self.localFirmwareLoaded
                            ) {
                                self.enableFlashButton(true);
                                self.flashingMessage(
                                    i18n.getMessage("firmwareFlasherFirmwareLocalLoaded", {
                                        filename: file.name,
                                        bytes: self.parsed_hex.bytes_total,
                                    }),
                                    self.FLASH_MESSAGE_TYPES.NEUTRAL,
                                );
                            }
                        }
                    }
                }
            } catch (error) {
                console.error("Error reading file:", error);
                self.enableLoadRemoteFileButton(true);
                self.enableLoadFileButton(true);
            }
        });
        /**
         * Lock / Unlock the firmware download button according to the firmware selection dropdown.
         */
        $('select[name="firmware_version"]').change((evt) => {
            selectFirmware($("option:selected", evt.target).val());
        });

        $("a.cloud_build_cancel").on("click", function () {
            $("a.cloud_build_cancel").toggleClass("disabled", true);
            self.cancelBuild = true;
        });

        async function enforceOSDSelection() {
            const firmwareVersion = $('select[name="firmware_version"] option:selected').text();

            // Skip OSD selection enforcement for firmware versions 4.3.x
            if (firmwareVersion.startsWith("4.3.")) {
                return true;
            }

            if ($('select[name="osdProtocols"] option:selected').val() === "") {
                return new Promise((resolve) => {
                    GUI.showYesNoDialog({
                        title: i18n.getMessage("firmwareFlasherOSDProtocolNotSelected"),
                        text: i18n.getMessage("firmwareFlasherOSDProtocolNotSelectedDescription"),
                        buttonYesText: i18n.getMessage("firmwareFlasherOSDProtocolNotSelectedContinue"),
                        buttonNoText: i18n.getMessage("firmwareFlasherOSDProtocolSelect"),
                        buttonYesCallback: () => resolve(true),
                        buttonNoCallback: () => resolve(false),
                    });
                });
            } else {
                return true; // No issue with OSD selection
            }
        }

        $("a.load_remote_file").on("click", async function () {
            if (!self.selectedBoard) {
                return;
            }

            // Ensure the user has selected an OSD protocol
            const shouldContinue = await enforceOSDSelection();

            if (!shouldContinue) {
                return;
            }

            // Reset button when loading a new firmware
            self.enableFlashButton(false);
            self.enableLoadRemoteFileButton(false);

            self.localFirmwareLoaded = false;
            self.developmentFirmwareLoaded =
                buildTypesToShow[$('select[name="build_type"]').val()].tag ===
                "firmwareFlasherOptionLabelBuildTypeDevelopment";

            if ($('select[name="firmware_version"]').val() === "0") {
                gui_log(i18n.getMessage("firmwareFlasherNoFirmwareSelected"));
                return;
            }

            function updateStatus(status, key, val, showLog) {
                if (showLog === true) {
                    $("div.release_info #cloudTargetLog")
                        .text(i18n.getMessage(`firmwareFlasherCloudBuildLogUrl`))
                        .prop("href", `https://build.betaflight.com/api/builds/${key}/log`);
                }
                $("div.release_info #cloudTargetStatus").text(i18n.getMessage(`firmwareFlasherCloudBuild${status}`));
                $(".buildProgress").val(val);
            }

            async function processBuildSuccess(response, statusResponse, suffix) {
                if (statusResponse.status !== "success") {
                    return;
                }
                updateStatus(`Success${suffix}`, response.key, 100, true);
                if (statusResponse.configuration !== undefined && !self.isConfigLocal) {
                    setBoardConfig(statusResponse.configuration);
                }
                processFile(await self.buildApi.loadTargetFirmware(response.url), response.file);
            }

            async function requestCloudBuild(targetDetail) {
                let request = {
                    target: targetDetail.target,
                    release: targetDetail.release,
                    options: [],
                };

                const coreBuild =
                    targetDetail.cloudBuild !== true || $('input[name="coreBuildModeCheckbox"]').is(":checked");
                if (coreBuild === true) {
                    request.options.push("CORE_BUILD");
                } else {
                    request.options.push("CLOUD_BUILD");
                    $('select[name="radioProtocols"] option:selected').each(function () {
                        request.options.push($(this).val());
                    });

                    $('select[name="telemetryProtocols"] option:selected').each(function () {
                        request.options.push($(this).val());
                    });

                    $('select[name="options"] option:selected').each(function () {
                        request.options.push($(this).val());
                    });

                    $('select[name="osdProtocols"] option:selected').each(function () {
                        request.options.push($(this).val());
                    });

                    $('select[name="motorProtocols"] option:selected').each(function () {
                        request.options.push($(this).val());
                    });

                    if (expertMode_e.is(":checked")) {
                        if (targetDetail.releaseType === "Unstable") {
                            request.commit = $('select[name="commits"] option:selected').val();
                        }

                        $('input[name="customDefines"]')
                            .val()
                            .split(" ")
                            .map((element) => element.trim())
                            .forEach((v) => {
                                request.options.push(v);
                            });
                    }
                }

                console.info("Build request:", request);
                let response = await self.buildApi.requestBuild(request);
                if (!response) {
                    updateStatus("FailRequest", "", 0, false);
                    loadFailed();
                    return;
                }

                console.info("Build response:", response);

                // Complete the summary object to be used later
                self.targetDetail.file = response.file;

                if (!targetDetail.cloudBuild) {
                    // it is a previous release, so simply load the file
                    processFile(await self.buildApi.loadTargetFirmware(response.url), response.file);
                    return;
                }

                updateStatus("Pending", response.key, 0, false);
                self.cancelBuild = false;

                let statusResponse = await self.buildApi.requestBuildStatus(response.key);

                if (statusResponse.status === "success") {
                    // will be cached already, no need to wait.
                    await processBuildSuccess(response, statusResponse, "Cached");
                    return;
                }

                self.enableCancelBuildButton(true);
                const retrySeconds = 5;
                let retries = 1;
                let processing = false;
                let timeout = 120;
                const timer = setInterval(async () => {
                    retries++;
                    let statusResponse = await self.buildApi.requestBuildStatus(response.key);

                    if (!statusResponse) {
                        return;
                    }

                    if (statusResponse.timeOut !== undefined) {
                        if (!processing) {
                            processing = true;
                            retries = 1;
                        }
                        timeout = statusResponse.timeOut;
                    }
                    const retryTotal = timeout / retrySeconds;

                    if (statusResponse.status !== "queued" || retries > retryTotal || self.cancelBuild) {
                        self.enableCancelBuildButton(false);
                        clearInterval(timer);

                        if (statusResponse.status === "success") {
                            processBuildSuccess(response, statusResponse, "");
                            return;
                        }

                        let suffix = "";
                        if (retries > retryTotal) {
                            suffix = "TimeOut";
                        }

                        if (self.cancelBuild) {
                            suffix = "Cancel";
                        }
                        updateStatus(`Fail${suffix}`, response.key, 0, true);
                        loadFailed();
                        return;
                    }

                    if (processing) {
                        updateStatus("Processing", response.key, retries * (100 / retryTotal), false);
                    }
                }, retrySeconds * 1000);
            }

            if (self.targetDetail) {
                // undefined while list is loading or while running offline
                $("a.load_remote_file").text(i18n.getMessage("firmwareFlasherButtonDownloading"));
                self.enableLoadRemoteFileButton(false);

                showReleaseNotes(self.targetDetail);

                await requestCloudBuild(self.targetDetail);
            } else {
                $("span.progressLabel")
                    .attr("i18n", "firmwareFlasherFailedToLoadOnlineFirmware")
                    .removeClass("i18n-replaced");
                i18n.localizePage();
            }
        });

        const exitDfuElement = $("a.exit_dfu");

        exitDfuElement.on("click", function () {
            self.enableDfuExitButton(false);

            if (!GUI.connect_lock) {
                // button disabled while flashing is in progress
                try {
                    console.log(`${self.logHead} Closing DFU`);
                    DFU.requestPermission().then((device) => {
                        DFU.connect(device.path, self.parsed_hex, { exitDfu: true });
                    });
                } catch (e) {
                    console.log(`${self.logHead} Exiting DFU failed: ${e.message}`);
                }
            }
        });

        const targetSupportInfo = $("#targetSupportInfoUrl");

        targetSupportInfo.on("click", function () {
            let urlSupport = "https://betaflight.com/docs/wiki/boards/archive/Missing"; // general board missing
            const urlBoard = `https://betaflight.com/docs/wiki/boards/current/${self.selectedBoard}`; // board description
            if (urlExists(urlBoard)) {
                urlSupport = urlBoard;
            }
            targetSupportInfo.attr("href", urlSupport);
        });

        const detectBoardElement = $("a.detect-board");

        detectBoardElement.on("click", () => {
            detectBoardElement.toggleClass("disabled", true);

            /**
             *
             *    Auto-detect board and set the dropdown to the correct value
             */

            if (!GUI.connect_lock) {
                AutoDetect.verifyBoard(PortHandler.portPicker.selectedPort);
            }

            // prevent spamming the button
            setTimeout(() => detectBoardElement.toggleClass("disabled", false), 2000);
        });

        function initiateFlashing() {
            if (self.developmentFirmwareLoaded) {
                checkShowAcknowledgementDialog();
            } else {
                startFlashing();
            }
        }

        // Backup not available in DFU, manual, virtual mode or when using flash on connect

        function startBackup(callback) {
            // prevent connection while backup is in progress
            GUI.connect_lock = true;

            const aborted = function (message) {
                GUI.connect_lock = false;
                self.enableFlashButton(true);
                self.enableLoadRemoteFileButton(true);
                self.enableLoadFileButton(true);
                GUI.interval_resume("sponsor");
                self.flashingMessage(i18n.getMessage(message), self.FLASH_MESSAGE_TYPES.INVALID);
            };

            const callBackWhenPortAvailable = function () {
                const startTime = Date.now();
                const interval = setInterval(() => {
                    if (PortHandler.portAvailable) {
                        clearInterval(interval);
                        callback();
                    } else if (Date.now() - startTime > 5000) {
                        clearInterval(interval);
                        // failed to connect
                        aborted("portsSelectNone");
                    }
                }, 100);
            };

            AutoBackup.execute((result) => {
                GUI.connect_lock = false;
                if (result) {
                    // wait for the port to be available again - timeout after 5 seconds
                    callBackWhenPortAvailable();
                } else {
                    aborted("firmwareFlasherCanceledBackup");
                }
            });
        }

        function checkShowAcknowledgementDialog() {
            const DAY_MS = 86400 * 1000;
            const storageTag = "lastDevelopmentWarningTimestamp";

            function setAcknowledgementTimestamp() {
                const storageObj = {};
                storageObj[storageTag] = Date.now();
                setStorage(storageObj);
            }

            result = getStorage(storageTag);
            if (!result[storageTag] || Date.now() - result[storageTag] > DAY_MS) {
                showAcknowledgementDialog(setAcknowledgementTimestamp);
            } else {
                startFlashing();
            }
        }

        function showAcknowledgementDialog(acknowledgementCallback) {
            const dialog = $("#dialogUnstableFirmwareAcknowledgement")[0];
            const flashButtonElement = $("#dialogUnstableFirmwareAcknowledgement-flashbtn");
            const acknowledgeCheckboxElement = $('input[name="dialogUnstableFirmwareAcknowledgement-acknowledge"]');

            acknowledgeCheckboxElement.change(function () {
                if ($(this).is(":checked")) {
                    flashButtonElement.removeClass("disabled");
                } else {
                    flashButtonElement.addClass("disabled");
                }
            });

            flashButtonElement.click(function () {
                dialog.close();

                if (acknowledgeCheckboxElement.is(":checked")) {
                    if (acknowledgementCallback) {
                        acknowledgementCallback();
                    }

                    startFlashing();
                }
            });

            $("#dialogUnstableFirmwareAcknowledgement-cancelbtn").click(function () {
                dialog.close();
            });

            dialog.addEventListener("close", function () {
                acknowledgeCheckboxElement.prop("checked", false).change();
            });

            dialog.showModal();
        }

        function startFlashing() {
            if (!GUI.connect_lock) {
                // button disabled while flashing is in progress
                if (self.parsed_hex) {
                    try {
                        if (self.config && !self.parsed_hex.configInserted) {
                            const configInserter = new ConfigInserter();

                            if (configInserter.insertConfig(self.parsed_hex, self.config)) {
                                self.parsed_hex.configInserted = true;
                            } else {
                                console.log(`${self.logHead} Firmware does not support custom defaults.`);
                                clearBoardConfig();
                            }
                        }

                        flashHexFirmware(self.parsed_hex);
                    } catch (e) {
                        console.log(`${self.logHead} Flashing failed: ${e.message}`);
                    }
                    // Disable flash on connect after flashing to prevent continuous flashing
                    $("input.flash_on_connect").prop("checked", false).change();
                } else {
                    $("span.progressLabel")
                        .attr("i18n", "firmwareFlasherFirmwareNotLoaded")
                        .removeClass("i18n-replaced");
                    i18n.localizePage();
                }
            }
        }

        $("a.flash_firmware").on("click", async function () {
            if (GUI.connect_lock) {
                return;
            }

            // Preserve current firmware message state before flashing
            self.preservePreFlashingState();

            GUI.interval_pause("sponsor");

            self.enableFlashButton(false);
            self.enableDfuExitButton(false);
            self.enableLoadRemoteFileButton(false);
            self.enableLoadFileButton(false);

            if (self.firmware_type === "UF2") {
                // due to save dialoque security requirements
                // we need to do this within proximity to the
                // user action hence here.

                tracking.sendEvent(tracking.EVENT_CATEGORIES.FLASHING, "UF2 Flashing", {
                    filename: self.filename || null,
                });
                const saved = await saveFirmware();
                self.flashingMessage(
                    saved
                        ? i18n.getMessage("firmwareFlasherUF2SaveSuccess")
                        : i18n.getMessage("firmwareFlasherUF2SaveFailed"),
                    saved ? self.FLASH_MESSAGE_TYPES.VALID : self.FLASH_MESSAGE_TYPES.INVALID,
                );
                GUI.interval_resume("sponsor");
                self.enableFlashButton(true);
                self.enableLoadRemoteFileButton(true);
                self.enableLoadFileButton(true);
                self.enableDfuExitButton(PortHandler.dfuAvailable);
                return;
            }

            const isFlashOnConnect = $("input.flash_on_connect").is(":checked");

            if (isFlashOnConnect || !PortHandler.portAvailable) {
                startFlashing();
                return;
            }

            // backupOnFlash:
            // 0: disabled (default)
            // 1: backup without dialog
            // 2: backup with dialog

            const backupOnFlash = getConfig("backupOnFlash", 1).backupOnFlash;

            switch (backupOnFlash) {
                case 1:
                    // prevent connection while backup is in progress
                    startBackup(initiateFlashing);
                    break;
                case 2:
                    GUI.showYesNoDialog({
                        title: i18n.getMessage("firmwareFlasherRemindBackupTitle"),
                        text: i18n.getMessage("firmwareFlasherRemindBackup"),
                        buttonYesText: i18n.getMessage("firmwareFlasherBackup"),
                        buttonNoText: i18n.getMessage("firmwareFlasherBackupIgnore"),
                        buttonYesCallback: () => {
                            startBackup(initiateFlashing);
                        },
                        buttonNoCallback: initiateFlashing,
                    });
                    break;
                default:
                    initiateFlashing();
                    break;
            }
        });

        $("span.progressLabel").on("click", "a.save_firmware", saveFirmware);

        self.flashingMessage(i18n.getMessage("firmwareFlasherLoadFirmwareFile"), self.FLASH_MESSAGE_TYPES.NEUTRAL);

        if (PortHandler.dfuAvailable) {
            $("a.exit_dfu").removeClass("disabled");
        }

        GUI.content_ready(callback);
    }

    console.log(`${self.logHead} Targets loaded`);
    $("#content").load("./tabs/firmware_flasher.html", onDocumentLoad);
};

// Helper functions

firmware_flasher.validateBuildKey = function () {
    return this.cloudBuildKey?.length === 32 && ispConnected();
};

firmware_flasher.cleanup = function (callback) {
    // unbind "global" events
    $(document).unbind("keypress");
    $(document).off("click", "span.progressLabel a");

    const cleanupHandler = (evt, property) => {
        const handler = firmware_flasher[property];
        if (handler) {
            EventBus.$off(evt, handler);
        }
    };

    cleanupHandler("port-handler:auto-select-usb-device", "detectedUsbDevice");
    cleanupHandler("port-handler:device-removed", "onDeviceRemoved");

    if (callback) callback();
};

firmware_flasher.enableCancelBuildButton = function (enabled) {
    $("a.cloud_build_cancel").toggleClass("disabled", !enabled);
    firmware_flasher.cancelBuild = false; // remove the semaphore
};

firmware_flasher.enableFlashButton = function (enabled) {
    $("a.flash_firmware").toggleClass("disabled", !enabled);
};

firmware_flasher.enableLoadRemoteFileButton = function (enabled) {
    $("a.load_remote_file").toggleClass("disabled", !enabled);
};

firmware_flasher.enableLoadFileButton = function (enabled) {
    $("a.load_file").toggleClass("disabled", !enabled);
};

firmware_flasher.enableDfuExitButton = function (enabled) {
    $("a.exit_dfu").toggleClass("disabled", !enabled);
};

firmware_flasher.resetFlashingState = function () {
    console.log(`${this.logHead} Reset flashing state`);
    this.enableFlashButton(!!this.parsed_hex || !!this.uf2_binary); // Only enable if firmware is loaded
    this.enableDfuExitButton(PortHandler.dfuAvailable);
    this.enableLoadRemoteFileButton(true);
    this.enableLoadFileButton(true);

    // Restore pre-flashing message if firmware is still loaded, otherwise show "not loaded"
    if (this.parsed_hex || this.uf2_binary) {
        if (this.preFlashingMessage && this.preFlashingMessageType) {
            this.flashingMessage(this.preFlashingMessage, this.preFlashingMessageType);
        }
    } else {
        this.flashingMessage(i18n.getMessage("firmwareFlasherFirmwareNotLoaded"), this.FLASH_MESSAGE_TYPES.NEUTRAL);
    }

    GUI.interval_resume("sponsor");
};

firmware_flasher.preservePreFlashingState = function () {
    // Preserve the current firmware message and type before flashing starts
    const progressLabel = $("span.progressLabel");
    this.preFlashingMessage = progressLabel.html();

    // Determine the current message type based on CSS classes
    if (progressLabel.hasClass("valid")) {
        this.preFlashingMessageType = this.FLASH_MESSAGE_TYPES.VALID;
    } else if (progressLabel.hasClass("invalid")) {
        this.preFlashingMessageType = this.FLASH_MESSAGE_TYPES.INVALID;
    } else if (progressLabel.hasClass("actionRequired")) {
        this.preFlashingMessageType = this.FLASH_MESSAGE_TYPES.ACTION;
    } else {
        this.preFlashingMessageType = this.FLASH_MESSAGE_TYPES.NEUTRAL;
    }
};

firmware_flasher.refresh = function (callback) {
    const self = this;

    GUI.tab_switch_cleanup(function () {
        self.initialize();

        if (callback) {
            callback();
        }
    });
};

firmware_flasher.showDialogVerifyBoard = function (selected, verified, onAccept, onAbort) {
    const dialogVerifyBoard = $("#dialog-verify-board")[0];

    $("#dialog-verify-board-content").html(
        i18n.getMessage("firmwareFlasherVerifyBoard", { selected_board: selected, verified_board: verified }),
    );

    if (!dialogVerifyBoard.hasAttribute("open")) {
        dialogVerifyBoard.showModal();

        $("#dialog-verify-board-continue-confirmbtn").on("click", function () {
            dialogVerifyBoard.close();
            onAccept();
        });

        $("#dialog-verify-board-abort-confirmbtn").on("click", function () {
            dialogVerifyBoard.close();
            onAbort();
        });
    }
};

firmware_flasher.FLASH_MESSAGE_TYPES = {
    NEUTRAL: "NEUTRAL",
    VALID: "VALID",
    INVALID: "INVALID",
    ACTION: "ACTION",
};

firmware_flasher.flashingMessage = function (message, type) {
    let self = this;

    let progressLabel_e = $("span.progressLabel");
    switch (type) {
        case self.FLASH_MESSAGE_TYPES.VALID:
            progressLabel_e.removeClass("invalid actionRequired").addClass("valid");
            break;
        case self.FLASH_MESSAGE_TYPES.INVALID:
            progressLabel_e.removeClass("valid actionRequired").addClass("invalid");
            break;
        case self.FLASH_MESSAGE_TYPES.ACTION:
            progressLabel_e.removeClass("valid invalid").addClass("actionRequired");
            break;
        case self.FLASH_MESSAGE_TYPES.NEUTRAL:
        default:
            progressLabel_e.removeClass("valid invalid actionRequired");
            break;
    }
    if (message !== null) {
        progressLabel_e.html(message);
    }

    return self;
};

firmware_flasher.flashProgress = function (value) {
    $(".progress").val(value);

    return this;
};

firmware_flasher.injectTargetInfo = function (targetConfig, targetName, manufacturerId, commitInfo) {
    const targetInfoLineRegex = /^# config: manufacturer_id: .*, board_name: .*, version: .*$, date: .*\n/gm;

    const config = targetConfig.replace(targetInfoLineRegex, "");

    const targetInfo = `# config: manufacturer_id: ${manufacturerId}, board_name: ${targetName}, version: ${commitInfo.commitHash}, date: ${commitInfo.date}`;

    const lines = config.split("\n");
    lines.splice(1, 0, targetInfo);
    return lines.join("\n");
};

TABS.firmware_flasher = firmware_flasher;

export { firmware_flasher };
