#include "fastfetch.h"
#include "common/io.h"
#include "common/processing.h"
#include "common/properties.h"
#include "common/path.h"
#include "common/strutil.h"
#include "common/binary.h"

#include <ctype.h>
#include <stdint.h>
#ifdef __FreeBSD__
    #include <paths.h>
    #ifndef _PATH_LOCALBASE
        #define _PATH_LOCALBASE "/usr/local"
    #endif
#elif __OpenBSD__
    #define _PATH_LOCALBASE "/usr/local"
#elif __NetBSD__
    #define _PATH_LOCALBASE "/usr/pkg"
#elif _WIN32

    #include "common/windows/version.h"
    #include <windows.h>

static bool getFileVersion(const FFstrbuf* exePath, const wchar_t* stringName, FFstrbuf* version) {
    wchar_t exePathW[PATH_MAX + 1];
    if (!NT_SUCCESS(RtlUTF8ToUnicodeN(exePathW, (ULONG) sizeof(exePathW), NULL, exePath->chars, (ULONG) exePath->length + 1))) {
        return false;
    }
    return ffGetFileVersion(exePathW, stringName, version);
}

#elif __HAIKU__
    #include "common/haiku/version.h"
#elif __APPLE__
    #include "common/apple/version.h"
#endif

static bool getExeVersionRaw(FFstrbuf* exe, FFstrbuf* version) {
    return ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) == NULL;
}

static bool getExeVersionGeneral(FFstrbuf* exe, FFstrbuf* version) {
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    ffStrbufSubstrAfterFirstC(version, ' ');
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return true;
}

static bool extractBashVersion(const char* line, FF_A_UNUSED uint32_t len, void* userdata) {
    if (!ffStrStartsWith(line, "@(#)Bash version ")) {
        return true;
    }
    const char* start = line + strlen("@(#)Bash version ");
    const char* end = strchr(start, '(');
    if (!end) {
        return true;
    }
    ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (end - start), start);
    return false;
}

static bool getShellVersionBash(FFstrbuf* exe, FFstrbuf* version) {
    ffBinaryExtractStrings(exe->chars, extractBashVersion, version, (uint32_t) strlen("@(#)Bash version 0.0.0(0) release GNU"));
    if (version->length > 0) {
        return true;
    }

    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    // GNU bash, version 5.1.16(1)-release (x86_64-pc-msys)\nCopyright...
    ffStrbufSubstrBeforeFirstC(version, '('); // GNU bash, version 5.1.16
    ffStrbufSubstrAfterLastC(version, ' ');   // 5.1.16
    return true;
}

static bool getShellVersionFish(FFstrbuf* exe, FFstrbuf* version) {
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    // fish, version 4.0.2-1 (Built by MSYS2 project) // version can be localized if LC_ALL is set
    if (version->length < strlen("fish, v") || !ffStrbufStartsWithS(version, "fish")) {
        return false;
    }
    uint32_t index = ffStrbufNextIndexC(version, strlen("fish, "), ' ');
    ffStrbufSubstrAfter(version, index);
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return true;
}

static bool getShellVersionPwsh(FFstrbuf* exe, FFstrbuf* version) {
    // Requires manually setting $POWERSHELL_VERSION
    // $env:POWERSHELL_VERSION = $PSVersionTable.PSVersion.ToString(); fastfetch.exe
    const char* env = getenv("POWERSHELL_VERSION");
    if (env) {
        ffStrbufSetS(version, env);
        return true;
    }

#ifdef _WIN32
    if (getFileVersion(exe, NULL, version)) {
        ffStrbufSubstrBeforeLastC(version, '.');
        return true;
    }
#endif

    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    ffStrbufSubstrAfterLastC(version, ' ');
    return true;
}

static bool getShellVersionKsh(FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "--version", NULL }) == NULL && ffStrbufSubstrAfterFirstS(version, " (AT&T Research) ")) {
        // version         sh (AT&T Research) 93u+ 2012-08-01
        ffStrbufSubstrBeforeFirstC(version, ' ');
        return true;
    }

    ffStrbufClear(version);
    if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-c", "echo $KSH_VERSION", NULL }) == NULL && ffStrbufSubstrAfterFirstS(version, " KSH ")) {
        // OKSH: @(#)PD KSH v5.2.14 99/07/13.2
        // MKSH: @(#)MIRBSD KSH R59 2025/04/26 +Debian
        // $OKSH_VERSION doesn't exist on OpenBSD
        ffStrbufSubstrBeforeFirstC(version, ' ');
        ffStrbufTrimLeft(version, 'v');
        return true;
    }

    return false;
}

static bool getShellVersionOksh(FFstrbuf* exe, FFstrbuf* version) {
    // Homebrew version
    if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-c", "echo $OKSH_VERSION", NULL }) != NULL) {
        return false;
    }

    // oksh 7.3
    ffStrbufSubstrAfterFirstC(version, ' ');
    return true;
}

static bool getShellVersionOils(FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) {
        return false;
    }

    // Oils 0.18.0		https://www.oilshell.org/...
    ffStrbufSubstrAfterFirstC(version, ' ');
    ffStrbufSubstrBeforeFirstC(version, '\t');
    return true;
}

static bool getShellVersionNushell(FFstrbuf* exe, FFstrbuf* version) {
    ffStrbufSetS(version, getenv("NU_VERSION"));
    if (version->length) {
        return true;
    }
    return getExeVersionRaw(exe, version); // 0.73.0
}

static bool extractBusyboxVersion(const char* line, uint32_t len, void* userdata) {
    if (!ffStrStartsWith(line, "BusyBox v")) {
        return true;
    }

    line += strlen("BusyBox v");
    len -= (uint32_t) strlen("BusyBox v");
    const char* space = memchr(line, ' ', len);
    if (space) {
        len = (uint32_t) (space - line);
    }

    ffStrbufSetNS((FFstrbuf*) userdata, len, line);
    return false;
}

static bool getShellVersionAsh(FFstrbuf* exe, FFstrbuf* version) {
    ffBinaryExtractStrings(exe->chars, extractBusyboxVersion, version, (uint32_t) strlen("BusyBox v0.0.0"));

    const char* error = ffStrbufEndsWithS(exe, "busybox")
        ? ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "ash", "--help", NULL })
        : ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "--help", NULL });
    if (error != NULL) {
        return false;
    }

    // BusyBox v1.36.1 (2023-11-07 18:53:09 UTC) multi-call binary...
    ffStrbufSubstrAfterFirstC(version, ' ');
    ffStrbufSubstrBeforeFirstC(version, ' ');
    ffStrbufTrimLeft(version, 'v');
    return true;
}

static bool getShellVersionXonsh(FF_A_UNUSED FFstrbuf* exe, FFstrbuf* version) {
    ffStrbufSetS(version, getenv("XONSH_VERSION"));
    if (version->length) {
        return true;
    }

    // exe is python here
    if (ffProcessAppendStdErr(version, (char* const[]) { "xonsh", "--version", NULL }) != NULL) {
        return false;
    }

    // xonsh/0.14.1
    ffStrbufSubstrAfterFirstC(version, '/');
    return true;
}

static bool extractZshVersion(const char* line, FF_A_UNUSED uint32_t len, void* userdata) {
    if (!ffStrStartsWith(line, "zsh-")) {
        return true;
    }
    const char* start = line + strlen("zsh-");
    const char* end = strchr(start, '-');
    if (!end) {
        return true;
    }

    ffStrbufSetNS((FFstrbuf*) userdata, (uint32_t) (end - start), start);
    return false;
}

static bool getShellVersionZsh(FFstrbuf* exe, FFstrbuf* version) {
    ffBinaryExtractStrings(exe->chars, extractZshVersion, version, (uint32_t) strlen("zsh-0.0-0"));
    if (version->length) {
        return true;
    }

    return getExeVersionGeneral(exe, version); // zsh 5.9 (arm-apple-darwin21.3.0)
}

#ifdef _WIN32
static bool getShellVersionWinPowerShell(FFstrbuf* exe, FFstrbuf* version) {
    const char* env = getenv("POWERSHELL_VERSION");
    if (env) {
        ffStrbufSetS(version, env);
        return true;
    }

    return ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-NoLogo", "-NoProfile", "-Command", "$PSVersionTable.PSVersion.ToString()", NULL }) == NULL;
}
#endif

bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version) {
    if (ffStrEqualsIgnCase(exeName, "sh")) { // #849
        return false;
    }

    if (ffStrEqualsIgnCase(exeName, "bash")) {
        return getShellVersionBash(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "zsh")) {
        return getShellVersionZsh(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "fish")) {
        return getShellVersionFish(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "pwsh")) {
        return getShellVersionPwsh(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "csh") || ffStrEqualsIgnCase(exeName, "tcsh")) {
        return getExeVersionGeneral(exe, version); // tcsh 6.24.07 (Astron) 2022-12-21 (aarch64-apple-darwin) options wide,nls,dl,al,kan,sm,rh,color,filec
    }
    if (ffStrEqualsIgnCase(exeName, "nu")) {
        return getShellVersionNushell(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "ksh") || ffStrEqualsIgnCase(exeName, "mksh")) {
        return getShellVersionKsh(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "oksh")) {
        return getShellVersionOksh(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "oil.ovm")) {
        return getShellVersionOils(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "elvish")) {
        return getExeVersionRaw(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "ash") || ffStrEqualsIgnCase(exeName, "busybox")) {
        return getShellVersionAsh(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "xonsh")) {
        return getShellVersionXonsh(exe, version);
    }
    if (ffStrEqualsIgnCase(exeName, "brush")) {
        return getExeVersionGeneral(exe, version); // brush 0.2.23 (git:2835487)
    }

#ifdef _WIN32
    if (ffStrEqualsIgnCase(exeName, "powershell") || ffStrEqualsIgnCase(exeName, "powershell_ise")) {
        return getShellVersionWinPowerShell(exe, version);
    }

    return getFileVersion(exe, NULL, version);
#endif

    return false;
}

FF_A_UNUSED static bool getTerminalVersionTermux(FFstrbuf* version) {
    ffStrbufSetS(version, getenv("TERMUX_VERSION"));
    return version->length > 0;
}

static bool extractGeneralVersion(const char* str, FF_A_UNUSED uint32_t len, void* userdata) {
    if (!ffCharIsDigit(str[0])) {
        return true;
    }
    int count = 0;
    sscanf(str, "%*d.%*d.%*d%n", &count);
    if (count == 0) {
        return true;
    }
    ffStrbufSetS((FFstrbuf*) userdata, str);
    return false;
}

FF_A_UNUSED static bool getTerminalVersionGnome(FFstrbuf* exe, FFstrbuf* version) {
    if (ffIsAbsolutePath(exe->chars)) {
        ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0"));
        if (version->length) {
            return true;
        }
    }

    if (ffProcessAppendStdOut(version, (char* const[]) { "gnome-terminal", "--version", NULL })) {
        return false;
    }

    // # GNOME Terminal 3.46.7 using VTE 0.70.2 +BIDI +GNUTLS +ICU +SYSTEMD
    ffStrbufSubstrAfterFirstS(version, "Terminal ");
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return true;
}

FF_A_UNUSED static bool getTerminalVersionXfce4Terminal(FFstrbuf* exe, FFstrbuf* version) {
    if (ffIsAbsolutePath(exe->chars)) {
        ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0"));
        if (version->length) {
            return true;
        }
    }

    return getExeVersionGeneral(exe, version); // xfce4-terminal 1.0.4 (Xfce 4.18)...
}

FF_A_UNUSED static bool getTerminalVersionKgx(FFstrbuf* version) {
    if (ffProcessAppendStdOut(version, (char* const[]) { "kgx", "--version", NULL })) {
        return false;
    }

    // # KGX 45.0 using VTE 0.74.0 +BIDI +GNUTLS +ICU +SYSTEMD
    ffStrbufSubstrAfterFirstS(version, "KGX ");
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return true;
}

FF_A_UNUSED static bool getTerminalVersionKonsole(FFstrbuf* exe, FFstrbuf* version) {
    const char* konsoleVersion = getenv("KONSOLE_VERSION");
    if (konsoleVersion) {
        // 221201
        long major = strtol(konsoleVersion, NULL, 10);
        if (major >= 0) {
            long patch = major % 100;
            major /= 100;
            long minor = major % 100;
            major /= 100;
            ffStrbufSetF(version, "%ld.%ld.%ld", major, minor, patch);
            return true;
        }
    }

    // Likely TDE konsole. See #2319
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }
    return ffStrbufSubstrAfterLastC(version, ' ');
}

FF_A_UNUSED static bool getTerminalVersionFoot(FFstrbuf* exe, FFstrbuf* version) {
    uint32_t major = 0, minor = 0, patch = 0;
    if (ffGetTerminalResponse("\e[>c", 3, "\e[>1;%2u%2u%2u;0c", &major, &minor, &patch) == NULL) {
        ffStrbufSetF(version, "%u.%u.%u", major, minor, patch);
        return true;
    }

    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    // foot version: 1.13.1 -pgo +ime -graphemes -assertions
    ffStrbufSubstrAfterFirstS(version, "version: ");
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return true;
}

FF_A_UNUSED static bool getTerminalVersionMateTerminal(FFstrbuf* exe, FFstrbuf* version) {
    ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0"));
    if (version->length > 0) {
        return true;
    }

    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    // MATE Terminal 1.26.1
    ffStrbufSubstrAfterLastC(version, ' ');
    return version->length > 0;
}

FF_A_UNUSED static bool getTerminalVersionCockpit(FFstrbuf* exe, FFstrbuf* version) {
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    // Version: 295\n...
    ffStrbufSubstrBeforeFirstC(version, '\n');
    ffStrbufSubstrAfterFirstC(version, ' ');
    return version->length > 0;
}

FF_A_UNUSED static bool getTerminalVersionXterm(FFstrbuf* exe, FFstrbuf* version) {
    ffStrbufSetS(version, getenv("XTERM_VERSION"));
    if (!version->length) {
        if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-v", NULL })) {
            return false;
        }
    }

    // xterm(273)
    ffStrbufTrimRight(version, ')');
    ffStrbufSubstrAfterFirstC(version, '(');
    return version->length > 0;
}

FF_A_UNUSED static bool getTerminalVersionBlackbox(FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL })) {
        return false;
    }

    // BlackBox version 0.14.0 (flatpak)
    ffStrbufSubstrAfterFirstS(version, "version ");
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return version->length > 0;
}

FF_A_UNUSED static bool getTerminalVersionUrxvt(FF_A_UNUSED FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdErr(version, (char* const[]) { "urxvt", // Don't use exe because of urxvtd
                                           "-invalid",
                                           NULL })) {
        return false;
    }

    // urxvt: "invalid": unknown or malformed option.
    // rxvt-unicode (urxvt) v9.31 - released: 2023-01-02
    ffStrbufSubstrAfterFirstS(version, "(urxvt) v");
    ffStrbufSubstrBeforeFirstC(version, ' ');

    return version->length > 0;
}

FF_A_UNUSED static bool getTerminalVersionSt(FF_A_UNUSED FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "-v", NULL })) {
        return false;
    }

    // st 0.9
    ffStrbufSubstrAfterFirstC(version, ' ');

    return version->length > 0;
}

FF_A_UNUSED static bool getTerminalVersionLxterminal(FFstrbuf* exe, FFstrbuf* version) {
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }
    // lxterminal 0.3.2
    ffStrbufSubstrAfterFirstC(version, ' ');
    return version->length > 0;
}

FF_A_UNUSED static bool getTerminalVersionWeston(FF_A_UNUSED FFstrbuf* exe, FFstrbuf* version) {
    // weston-terminal doesn't report a version, use weston version instead
    if (ffProcessAppendStdOut(version, (char* const[]) { "weston", "--version", NULL })) {
        return false;
    }

    // weston 8.0.0
    ffStrbufSubstrAfterFirstC(version, ' ');

    return version->length > 0;
}

static bool getTerminalVersionContour(FFstrbuf* exe, FFstrbuf* version) {
    const char* env = getenv("TERMINAL_VERSION_STRING");
    if (env) {
        ffStrbufAppendS(version, env);
        return true;
    }
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }
    // Contour Terminal Emulator 0.3.12.262
    ffStrbufSubstrAfterLastC(version, ' ');
    return version->length > 0;
}

static bool getTerminalVersionScreen(FFstrbuf* exe, FFstrbuf* version) {
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }
    // Screen version 4.09.01 (GNU) 20-Aug-23
    ffStrbufSubstrAfter(version, (uint32_t) strlen("Screen version ") - 1);
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return version->length > 0;
}

static bool getTerminalVersionTmux(FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-V", NULL }) != NULL) {
        return false;
    }

    // tmux 3.4
    ffStrbufSubstrAfterFirstC(version, ' ');
    return version->length > 0;
}

static bool getTerminalVersionZellij(FFstrbuf* exe, FFstrbuf* version) {
    if (!getExeVersionRaw(exe, version)) {
        return false;
    }

    // zellij 0.39.2
    ffStrbufSubstrAfterFirstC(version, ' ');
    return version->length > 0;
}

static bool getTerminalVersionZed(FFstrbuf* exe, FFstrbuf* version) {
    FF_STRBUF_AUTO_DESTROY cli = ffStrbufCreateCopy(exe);
    ffStrbufSubstrBeforeLastC(&cli, '/');
    ffStrbufAppendS(&cli, "/cli"
#ifdef _WIN32
                          ".exe"
#endif
    );

    if (ffProcessAppendStdOut(version, (char* const[]) { cli.chars, "--version", NULL }) != NULL) {
        return false;
    }

    // Zed 0.142.6 – /Applications/Zed.app
    ffStrbufSubstrAfterFirstC(version, ' ');
    ffStrbufSubstrBeforeFirstC(version, ' ');
    return true;
}

static bool extractSshdVersion(const char* str, FF_A_UNUSED uint32_t len, void* userdata) {
    if (!ffStrStartsWith(str, "OpenSSH_") || !ffCharIsDigit(str[strlen("OpenSSH_")])) {
        return true;
    }
    str += strlen("OpenSSH_");
    int count = 0;
    sscanf(str, "%*d.%*dp%*d%n", &count);
    if (count == 0) {
        return true;
    }
    ffStrbufSetS((FFstrbuf*) userdata, str);
    return false;
}

static bool getTerminalVersionSshd(FFstrbuf* exe, FFstrbuf* version) {
    FF_STRBUF_AUTO_DESTROY exePath = ffStrbufCreate();
    if (ffIsAbsolutePath(exe->chars)) {
        ffStrbufSet(&exePath, exe);
    } else if (ffFindExecutableInPath("sshd", &exePath) != NULL) {
        return false;
    }

    ffBinaryExtractStrings(exePath.chars, extractSshdVersion, version, (uint32_t) strlen("OpenSSH0.0"));
    if (version->length) {
        return true;
    }

    if (ffProcessAppendStdOut(version, (char* const[]) { exePath.chars, "-V", NULL }) != NULL) {
        return false;
    }

    if (ffStrbufStartsWithS(version, "unknown ")) { // `unknown option -- V` (ancient OpenSSH version)
        ffStrbufSubstrAfterFirstC(version, '\n');
    }

    // OpenSSH_10.0p2 Ubuntu-5ubuntu5, OpenSSL 3.5.3 16 Sep 2025
    ffStrbufSubstrAfterFirstC(version, '_');
    ffStrbufSubstrBeforeFirstC(version, ',');
    return true;
}

#ifndef _WIN32
static bool getTerminalVersionKitty(FFstrbuf* exe, FFstrbuf* version) {
    #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__GNU__)
    char buffer[1024] = {};
    if (
        #if __linux__ || __GNU__
        ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib64/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer) ||
        ffReadFileData(FASTFETCH_TARGET_DIR_USR "/lib/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
        #else
        ffReadFileData(_PATH_LOCALBASE "/share/kitty/kitty/constants.py", ARRAY_SIZE(buffer) - 1, buffer)
        #endif
    ) {
        // Starts from version 0.17.0
        // https://github.com/kovidgoyal/kitty/blob/master/kitty/constants.py#L25
        const char* p = memmem(buffer, ARRAY_SIZE(buffer) - 1, "version: Version = Version(", strlen("version: Version = Version("));
        if (p) {
            p += strlen("version: Version = Version(");
            int major, minor, patch;
            if (sscanf(p, "%d,%d,%d", &major, &minor, &patch) == 3) {
                ffStrbufSetF(version, "%d.%d.%d", major, minor, patch);
                return true;
            }
        }
    }
    #elif __APPLE__
    if (ffGetAppNameAndVersion(exe->chars, NULL, version)) {
        return true;
    }
    #endif

    char versionHex[64];
    // https://github.com/fastfetch-cli/fastfetch/discussions/1030#discussioncomment-9845233
    if (ffGetTerminalResponse(
            "\eP+q6b697474792d71756572792d76657273696f6e\e\\", // kitty-query-version
            1,
            "\eP1+r%*[^=]=%63[^\e]\e\\\\",
            versionHex) == NULL) {
        // decode hex string
        for (const char* p = versionHex; p[0] && p[1]; p += 2) {
            unsigned value;
            if (sscanf(p, "%2x", &value) == 1) {
                ffStrbufAppendC(version, (char) value);
            }
        }
        return true;
    }

    // kitty 0.21.2 created by Kovid Goyal
    return getExeVersionGeneral(exe, version);
}

FF_A_UNUSED static bool getTerminalVersionPtyxis(FF_A_UNUSED FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdOut(version, (char* const[]) { "ptyxis", "--version", NULL }) != NULL) {
        return false;
    }

    ffStrbufSubstrBeforeFirstC(version, '\n');
    ffStrbufSubstrAfterFirstC(version, ' ');
    return true;
}

FF_A_UNUSED static bool getTerminalVersionTilix(FFstrbuf* exe, FFstrbuf* version) {
    if (ffIsAbsolutePath(exe->chars)) {
        ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0"));
        if (version->length) {
            return true;
        }
    }

    if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) {
        return false;
    }

    uint32_t index = ffStrbufFirstIndexS(version, "Tilix version: ");
    if (index == version->length) {
        return false;
    }

    index += (uint32_t) strlen("Tilix version:");
    uint32_t end = ffStrbufNextIndexC(version, index, '\n');

    ffStrbufSubstrBefore(version, end);
    ffStrbufSubstrAfter(version, index);
    return true;
}

FF_A_UNUSED static bool getTerminalVersionSakura(FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdErr(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) { // sakura version is 3.8.8
        return false;
    }

    ffStrbufSubstrAfterLastC(version, ' ');
    return true;
}

FF_A_UNUSED static bool getTerminalVersionTermite(FFstrbuf* exe, FFstrbuf* version) {
    if (ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "--version", NULL }) != NULL) { // termite v16.9\nvte 0.78.1 +BIDI +GNUTLS +ICU +SYSTEMD
        return false;
    }

    ffStrbufSubstrBeforeFirstC(version, '\n');
    ffStrbufSubstrAfterLastC(version, 'v');
    return true;
}
#endif

#ifdef _WIN32

static bool getTerminalVersionWindowsTerminal(FFstrbuf* exe, FFstrbuf* version) {
    FF_STRBUF_AUTO_DESTROY buildInfoPath;
    ffStrbufInitNS(&buildInfoPath, ffStrbufLastIndexC(exe, '\\') + 1, exe->chars);
    ffStrbufAppendS(&buildInfoPath, "BuildInfo.xml");

    if (ffParsePropFile(buildInfoPath.chars, "StoreVersion=\"", version)) {
        ffStrbufTrimRight(version, '"');
        return true;
    }

    return getFileVersion(exe, NULL, version);
}

static bool getTerminalVersionConEmu(FFstrbuf* exe, FFstrbuf* version) {
    ffStrbufSetS(version, getenv("ConEmuBuild"));

    if (version->length) {
        return true;
    }

    return getFileVersion(exe, NULL, version);
}

#endif

bool fftsGetTerminalVersion(FFstrbuf* processName, FF_A_UNUSED FFstrbuf* exe, FFstrbuf* version) {
#ifdef __ANDROID__

    if (ffStrbufEqualS(processName, "com.termux")) {
        return getTerminalVersionTermux(version);
    }

#endif

#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__HAIKU__) || defined(__GNU__)

    if (ffStrbufStartsWithIgnCaseS(processName, "gnome-terminal")) {
        return getTerminalVersionGnome(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "konsole")) {
        return getTerminalVersionKonsole(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "yakuake")) {
        return getTerminalVersionKonsole(exe, version); // yakuake shares code with konsole
    }

    if (ffStrbufIgnCaseEqualS(processName, "xfce4-terminal")) {
        return getTerminalVersionXfce4Terminal(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "terminator")) {
        return getExeVersionGeneral(exe, version); // terminator 2.1.3
    }

    if (ffStrbufIgnCaseEqualS(processName, "deepin-terminal")) {
        return getExeVersionGeneral(exe, version); // deepin-terminal 5.4.36
    }

    if (ffStrbufIgnCaseEqualS(processName, "foot")) {
        return getTerminalVersionFoot(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "qterminal")) {
        return getExeVersionRaw(exe, version); // 1.2.0
    }

    if (ffStrbufIgnCaseEqualS(processName, "mate-terminal")) {
        return getTerminalVersionMateTerminal(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "cockpit-bridge")) {
        return getTerminalVersionCockpit(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "xterm")) {
        return getTerminalVersionXterm(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "blackbox")) {
        return getTerminalVersionBlackbox(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "st")) {
        return getTerminalVersionSt(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "lxterminal")) {
        return getTerminalVersionLxterminal(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "weston-terminal")) {
        return getTerminalVersionWeston(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "urxvt") ||
        ffStrbufIgnCaseEqualS(processName, "urxvtd") ||
        ffStrbufIgnCaseEqualS(processName, "rxvt") ||
        ffStrbufIgnCaseEqualS(processName, "rxvt-unicode")) {
        return getTerminalVersionUrxvt(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "ptyxis-agent")) {
        return getTerminalVersionPtyxis(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "tilix")) {
        return getTerminalVersionTilix(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "sakura")) {
        return getTerminalVersionSakura(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "termite")) {
        return getTerminalVersionTermite(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "cosmic-term")) {
        return getTerminalVersionTmux(exe, version);
    }

#endif

#ifdef _WIN32

    if (ffStrbufIgnCaseEqualS(processName, "WindowsTerminal.exe")) {
        return getTerminalVersionWindowsTerminal(exe, version);
    }

    if (ffStrbufStartsWithIgnCaseS(processName, "ConEmu")) {
        return getTerminalVersionConEmu(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "warp.exe")) {
        return getFileVersion(exe, L"ProductVersion", version);
    }

#endif

#ifndef _WIN32

    if (ffStrbufIgnCaseEqualS(processName, "kitty")) {
        return getTerminalVersionKitty(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "Tabby") && getExeVersionRaw(exe, version)) {
        return true;
    }

#endif

    if (ffStrbufStartsWithIgnCaseS(processName, "alacritty")) {
        return getExeVersionGeneral(exe, version);
    }

    if (ffStrbufStartsWithIgnCaseS(processName, "contour")) {
        return getTerminalVersionContour(exe, version);
    }

    if (ffStrbufStartsWithIgnCaseS(processName, "screen")) {
        return getTerminalVersionScreen(exe, version);
    }

    if (ffStrbufStartsWithIgnCaseS(processName, "zellij")) {
        return getTerminalVersionZellij(exe, version);
    }

    if (ffStrbufStartsWithIgnCaseS(processName, "zed")) {
        return getTerminalVersionZed(exe, version);
    }

#if __HAIKU__
    if (ffStrbufEqualS(processName, "Terminal")) {
        return ffGetFileVersion(exe->chars, version);
    }
#endif

    const char* termProgramVersion = getenv("TERM_PROGRAM_VERSION");
    if (termProgramVersion) {
        const char* termProgram = getenv("TERM_PROGRAM");
        if (termProgram) {
            if (ffStrbufStartsWithIgnCaseS(processName, termProgram) || // processName ends with `.exe` on Windows
                (ffStrEquals(termProgram, "vscode") && ffStrbufStartsWithIgnCaseS(processName, "code")) ||
#ifdef __APPLE__
                (ffStrEquals(termProgram, "iTerm.app") && ffStrbufStartsWithIgnCaseS(processName, "iTermServer-")) ||
#elif defined(__linux__)
                (ffStrEquals(termProgram, "WarpTerminal") && ffStrbufEqualS(processName, "warp")) ||
#endif
                false) {
                ffStrbufSetS(version, termProgramVersion);
                return true;
            }
        }
    }

    termProgramVersion = getenv("LC_TERMINAL_VERSION");
    if (termProgramVersion) {
        const char* termProgram = getenv("LC_TERMINAL");
        if (termProgram) {
            if (ffStrbufStartsWithIgnCaseS(processName, termProgram) || // processName ends with `.exe` on Windows
                (ffStrEquals(termProgram, "vscode") && ffStrbufStartsWithIgnCaseS(processName, "code")) ||
                (ffStrStartsWith(termProgram, "iTerm") && ffStrbufStartsWithIgnCaseS(processName, "iTermServer-"))) {
                ffStrbufSetS(version, termProgramVersion);
                return true;
            }
        }
    }

    if (ffStrbufStartsWithIgnCaseS(processName, "tmux")) {
        return getTerminalVersionTmux(exe, version);
    }

    if (ffStrbufIgnCaseEqualS(processName, "sshd") || ffStrbufStartsWithIgnCaseS(processName, "sshd-")) {
        return getTerminalVersionSshd(exe, version);
    }

#ifdef _WIN32

    return getFileVersion(exe, NULL, version);

#elif __APPLE__

    return ffGetAppNameAndVersion(exe->chars, NULL, version);

#else

    return false;

#endif
}
