import { i18n } from "../localization";
import BFClipboard from "../Clipboard";
import { generateFilename } from "../utils/generate_filename";
import GUI, { TABS } from "../gui";
import BuildApi from "../BuildApi";
import { reinitializeConnection } from "../serial_backend";
import CONFIGURATOR from "../data_storage";
import CliAutoComplete from "../CliAutoComplete";
import { gui_log } from "../gui_log";
import $ from "jquery";
import { serial } from "../serial";
import FileSystem from "../FileSystem";
import { ispConnected } from "../utils/connection";
import { initializeModalDialog } from "../utils/initializeModalDialog";
import { get as getConfig } from "../ConfigStorage";

const cli = {
    lineDelayMs: 5,
    profileSwitchDelayMs: 100,
    outputHistory: "",
    cliBuffer: "",
    startProcessing: false,
    GUI: {
        snippetPreviewWindow: null,
        copyButton: null,
        windowWrapper: null,
    },
    lastArrival: 0,
    lastSupportId: null,
};

function removePromptHash(promptText) {
    return promptText.replace(/^# /, "");
}

function cliBufferCharsToDelete(command, buffer) {
    let commonChars = 0;
    for (let i = 0; i < buffer.length; i++) {
        if (command[i] === buffer[i]) {
            commonChars++;
        } else {
            break;
        }
    }

    return buffer.length - commonChars;
}

function commandWithBackSpaces(command, buffer, noOfCharsToDelete) {
    const backspace = String.fromCharCode(127);
    return backspace.repeat(noOfCharsToDelete) + command.substring(buffer.length - noOfCharsToDelete, command.length);
}

function getCliCommand(command, cliBuffer) {
    const buffer = removePromptHash(cliBuffer);
    const bufferRegex = new RegExp(`^${buffer}`, "g");
    if (command.match(bufferRegex)) {
        return command.replace(bufferRegex, "");
    }

    const noOfCharsToDelete = cliBufferCharsToDelete(command, buffer);

    return commandWithBackSpaces(command, buffer, noOfCharsToDelete);
}

function copyToClipboard(text) {
    function onCopySuccessful() {
        const button = TABS.cli.GUI.copyButton;
        const origText = button.text();
        const origWidth = button.css("width");
        button.text(i18n.getMessage("cliCopySuccessful"));
        button.css({
            width: origWidth,
            textAlign: "center",
        });
        setTimeout(() => {
            button.text(origText);
            button.css({
                width: "",
                textAlign: "",
            });
        }, 1500);
    }

    function onCopyFailed(ex) {
        console.warn(ex);
    }

    BFClipboard.writeText(text, onCopySuccessful, onCopyFailed);
}

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

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

    self.outputHistory = "";
    self.cliBuffer = "";
    self.startProcessing = false;

    // Reset modal dialog reference since DOM gets rebuilt on tab switch
    self.GUI.snippetPreviewWindow = null;

    const enterKeyCode = 13;

    function clearHistory() {
        self.outputHistory = "";
        self.GUI.windowWrapper.empty();
    }

    async function executeCommands(outString) {
        self.history.add(outString.trim());

        function sendCommandIterative(commandArray) {
            const command = commandArray.shift();

            let line = command.trim();
            let processingDelay = self.lineDelayMs;
            if (line.toLowerCase().startsWith("profile")) {
                processingDelay = self.profileSwitchDelayMs;
            }
            const isLastCommand = outputArray.length === 0;
            if (isLastCommand && self.cliBuffer) {
                line = getCliCommand(line, self.cliBuffer);
            }

            self.sendLine(line);

            if (!isLastCommand) {
                GUI.timeout_add(
                    "CLI_send_slowly",
                    function () {
                        sendCommandIterative(commandArray);
                    },
                    processingDelay,
                );
            }
        }

        const outputArray = outString.split("\n");
        sendCommandIterative(outputArray);
    }

    async function loadFile() {
        const previewArea = $("#snippetpreviewcontent textarea#preview");

        function executeSnippet() {
            const commands = previewArea.val();
            executeCommands(commands);
            self.GUI.snippetPreviewWindow.close();
        }

        function previewCommands(result, fileName) {
            if (!self.GUI.snippetPreviewWindow) {
                self.GUI.snippetPreviewWindow = initializeModalDialog(
                    null,
                    "#snippetpreviewdialog",
                    "cliConfirmSnippetDialogTitle",
                    { fileName: fileName },
                );
                $("#snippetpreviewcontent a.confirm").on("click", executeSnippet);
            }

            previewArea.val(result);
            self.GUI.snippetPreviewWindow.showModal();
        }

        const file = await FileSystem.pickOpenFile(i18n.getMessage("fileSystemPickerFiles", { typeof: "TXT" }), ".txt");
        const contents = await FileSystem.readFile(file);
        previewCommands(contents, file.name);
    }

    async function saveFile(filename, content) {
        const file = await FileSystem.pickSaveFile(
            filename,
            i18n.getMessage("fileSystemPickerFiles", { typeof: "TXT" }),
            ".txt",
        );
        await FileSystem.writeFile(file, content);
    }

    $("#content").load("./tabs/cli.html", function () {
        // translate to user-selected language
        i18n.localizePage();

        TABS.cli.adaptPhones();

        CONFIGURATOR.cliActive = true;

        self.GUI.copyButton = $("a.copy");
        self.GUI.windowWrapper = $(".tab-cli .window .wrapper");

        const textarea = $('.tab-cli textarea[name="commands"]');

        CliAutoComplete.initialize(textarea, self.sendLine.bind(self), writeToOutput);
        $(CliAutoComplete).on("build:start", function () {
            textarea.val("").attr("placeholder", i18n.getMessage("cliInputPlaceholderBuilding")).prop("disabled", true);
        });
        $(CliAutoComplete).on("build:stop", function () {
            textarea.attr("placeholder", i18n.getMessage("cliInputPlaceholder")).prop("disabled", false).focus();
        });

        function formatContentWithSupportId(content, supportId) {
            if (supportId) {
                content = `# Support ID: ${supportId}\n\n${content}`;
            }
            return content;
        }

        $("a.save").on("click", function () {
            const filename = generateFilename("cli", "txt");
            const content = formatContentWithSupportId(self.outputHistory, self.lastSupportId);

            saveFile(filename, content);
        });

        $("a.clear").click(function () {
            clearHistory();
        });

        self.GUI.copyButton.click(function () {
            const content = formatContentWithSupportId(self.outputHistory, self.lastSupportId);
            copyToClipboard(content);
        });

        $("a.load").on("click", function () {
            loadFile();
        });

        $("a.support")
            .toggle(ispConnected())
            .on("click", function () {
                async function submitSupportData(data) {
                    clearHistory();
                    const api = new BuildApi();

                    let commands = await api.getSupportCommands();
                    if (!commands) {
                        alert("An error has occurred");
                        return;
                    }

                    commands = [`###\n# Problem description\n# ${data}\n###`, ...commands];
                    await executeCommands(commands.join("\n"));
                    const delay = setInterval(async () => {
                        const time = new Date().getTime();
                        if (self.lastArrival < time - 250) {
                            clearInterval(delay);
                            const text = self.outputHistory;
                            let key = await api.submitSupportData(text);
                            if (!key) {
                                writeToOutput(i18n.getMessage("buildServerSupportRequestSubmission", ["** error **"]));
                                return;
                            }
                            self.lastSupportId = key;
                            writeToOutput(i18n.getMessage("buildServerSupportRequestSubmission", [key]));
                        }
                    }, 250);
                }

                self.supportWarningDialog(submitSupportData);
            });

        // Tab key detection must be on keydown,
        // `keypress`/`keyup` happens too late, as `textarea` will have already lost focus.
        textarea.keydown(function (event) {
            const tabKeyCode = 9;
            if (event.which === tabKeyCode) {
                // prevent default tabbing behaviour
                event.preventDefault();

                if (!CliAutoComplete.isEnabled()) {
                    // Native FC autoComplete
                    const outString = textarea.val();
                    const lastCommand = outString.split("\n").pop();
                    const command = getCliCommand(lastCommand, self.cliBuffer);
                    if (command) {
                        self.sendNativeAutoComplete(command);
                        textarea.val("");
                    }
                } else if (!CliAutoComplete.isOpen() && !CliAutoComplete.isBuilding()) {
                    // force show autocomplete on Tab
                    CliAutoComplete.openLater(true);
                }
            }
        });

        textarea.keypress(function (event) {
            if (event.which === enterKeyCode) {
                event.preventDefault(); // prevent the adding of new line

                if (CliAutoComplete.isBuilding()) {
                    return; // silently ignore commands if autocomplete is still building
                }

                const outString = textarea.val();
                executeCommands(outString);
                textarea.val("");
            }
        });

        textarea.keyup(function (event) {
            const keyUp = { 38: true };
            const keyDown = { 40: true };

            if (CliAutoComplete.isOpen()) {
                return; // disable history keys if autocomplete is open
            }

            if (event.keyCode in keyUp) {
                textarea.val(self.history.prev());
            }

            if (event.keyCode in keyDown) {
                textarea.val(self.history.next());
            }
        });

        // give input element user focus
        textarea.focus();

        GUI.timeout_add(
            "enter_cli",
            function enter_cli() {
                // Enter CLI mode
                const bufferOut = new ArrayBuffer(1);
                const bufView = new Uint8Array(bufferOut);

                bufView[0] = 0x23; // #

                serial.send(bufferOut);
            },
            250,
        );

        GUI.content_ready(callback);
    });
};

cli.adaptPhones = function () {
    if ($(window).width() < 575) {
        const backdropHeight = $(".note").height() + 22 + 38;
        $(".backdrop").css("height", `calc(100% - ${backdropHeight}px)`);
    }
};

cli.history = {
    history: [],
    index: 0,
};

cli.history.add = function (str) {
    this.history.push(str);
    this.index = this.history.length;
};

cli.history.prev = function () {
    if (this.index > 0) {
        this.index -= 1;
    }
    return this.history[this.index];
};

cli.history.next = function () {
    if (this.index < this.history.length) {
        this.index += 1;
    }
    return this.history[this.index - 1];
};

const backspaceCode = 8;
const lineFeedCode = 10;
const carriageReturnCode = 13;

function writeToOutput(text) {
    TABS.cli.GUI.windowWrapper.append(text);
    const cliWindow = $(".tab-cli .window");
    cliWindow.scrollTop(cliWindow.prop("scrollHeight"));
}

function writeLineToOutput(text) {
    if (CliAutoComplete.isBuilding()) {
        CliAutoComplete.builderParseLine(text);
        return; // suppress output if in building state
    }

    if (text.startsWith("###ERROR")) {
        writeToOutput(`<span class="error_message">${text}</span><br>`);
    } else {
        writeToOutput(`${text}<br>`);
    }
}

function setPrompt(text) {
    $(".tab-cli textarea").val(text);
}

cli.read = function (readInfo) {
    /*  Some info about handling line feeds and carriage return

        line feed = LF = \n = 0x0A = 10
        carriage return = CR = \r = 0x0D = 13

        MAC only understands CR
        Linux and Unix only understand LF
        Windows understands (both) CRLF
        Chrome OS currently unknown
    */
    const data = new Uint8Array(readInfo.data ?? readInfo);
    let validateText = "";
    let sequenceCharsToSkip = 0;

    for (let i = 0; i < data.length; i++) {
        const currentChar = String.fromCharCode(data[i]);
        const isCRLF = currentChar.charCodeAt() === lineFeedCode || currentChar.charCodeAt() === carriageReturnCode;

        if (!CONFIGURATOR.cliValid && (isCRLF || this.startProcessing)) {
            // try to catch part of valid CLI enter message (firmware message starts with CRLF)
            this.startProcessing = true;
            validateText += currentChar;
            writeToOutput(currentChar);
            continue;
        }

        const escapeSequenceCode = 27;
        const escapeSequenceCharLength = 3;
        if (data[i] === escapeSequenceCode && !sequenceCharsToSkip) {
            // ESC + other
            sequenceCharsToSkip = escapeSequenceCharLength;
        }

        if (sequenceCharsToSkip) {
            sequenceCharsToSkip--;
            continue;
        }

        if (CONFIGURATOR.cliValid) {
            switch (data[i]) {
                case lineFeedCode:
                    if (GUI.operating_system === "Windows") {
                        writeLineToOutput(this.cliBuffer);
                        this.cliBuffer = "";
                    }
                    break;
                case carriageReturnCode:
                    if (GUI.operating_system !== "Windows") {
                        writeLineToOutput(this.cliBuffer);
                        this.cliBuffer = "";
                    }
                    break;
                case 60:
                    this.cliBuffer += "&lt";
                    break;
                case 62:
                    this.cliBuffer += "&gt";
                    break;
                case backspaceCode:
                    this.cliBuffer = this.cliBuffer.slice(0, -1);
                    this.outputHistory = this.outputHistory.slice(0, -1);
                    continue;

                default:
                    this.cliBuffer += currentChar;
            }
        }

        if (!CliAutoComplete.isBuilding()) {
            // do not include the building dialog into the history
            this.outputHistory += currentChar;
        }

        if (this.cliBuffer === "Rebooting") {
            CONFIGURATOR.cliActive = false;
            CONFIGURATOR.cliValid = false;
            gui_log(i18n.getMessage("cliReboot"));
            reinitializeConnection();
        }
    }

    this.lastArrival = new Date().getTime();

    if (!CONFIGURATOR.cliValid && validateText.indexOf("CLI") !== -1) {
        gui_log(i18n.getMessage(getConfig("cliOnlyMode")?.cliOnlyMode ? "cliDevEnter" : "cliEnter"));
        CONFIGURATOR.cliValid = true;
        // begin output history with the prompt (last line of welcome message)
        // this is to match the content of the history with what the user sees on this tab
        const lastLine = validateText.split("\n").pop();
        this.outputHistory = lastLine;

        if (CliAutoComplete.isEnabled() && !CliAutoComplete.isBuilding()) {
            // start building autoComplete
            CliAutoComplete.builderStart();
        }
    }

    // fallback to native autocomplete
    if (!CliAutoComplete.isEnabled()) {
        setPrompt(removePromptHash(this.cliBuffer));
    }
};

cli.sendLine = function (line, callback) {
    this.send(`${line}\n`, callback);
};

cli.sendNativeAutoComplete = function (line, callback) {
    this.send(`${line}\t`, callback);
};

cli.send = function (line, callback) {
    const bufferOut = new ArrayBuffer(line.length);
    const bufView = new Uint8Array(bufferOut);

    for (let cKey = 0; cKey < line.length; cKey++) {
        bufView[cKey] = line.charCodeAt(cKey);
    }

    serial.send(bufferOut, callback);
};

cli.supportWarningDialog = function (onAccept) {
    const supportWarningDialog = $(".supportWarningDialog")[0];
    const supportWarningDialogTextArea = $('.tab-cli textarea[name="supportWarningDialogInput"]');

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

        $(".cancel").on("click", function () {
            supportWarningDialog.close();
        });

        $(".submit").on("click", function () {
            supportWarningDialog.close();
            onAccept(supportWarningDialogTextArea.val());
            supportWarningDialogTextArea.val("");
        });
    }
};

cli.cleanup = function (callback) {
    if (!(CONFIGURATOR.connectionValid && CONFIGURATOR.cliValid && CONFIGURATOR.cliActive)) {
        if (callback) {
            callback();
        }

        return;
    }

    this.send(getCliCommand("exit\r", this.cliBuffer), function () {
        reinitializeConnection();
    });

    CONFIGURATOR.cliActive = false;
    CONFIGURATOR.cliValid = false;

    CliAutoComplete.cleanup();
    $(CliAutoComplete).off();
};

TABS.cli = cli;
export { cli };
