#include "logo/logo.h"
#include "common/io.h"
#include "common/printing.h"
#include "common/processing.h"
#include "common/textModifier.h"
#include "common/stringUtils.h"
#include "detection/media/media.h"
#include "detection/os/os.h"
#include "detection/terminalshell/terminalshell.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

static bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError) {
    FFOptionsLogo* options = &instance.config.logo;
    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();

    if (!options->width || !options->height) {
        if (options->position == FF_LOGO_POSITION_LEFT) {
            ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1);
        } else if (options->position == FF_LOGO_POSITION_TOP) {
            ffStrbufAppendNC(&buf, options->paddingTop, '\n');
            ffStrbufAppendNC(&buf, options->paddingLeft, ' ');
        } else if (options->position == FF_LOGO_POSITION_RIGHT) {
            if (!options->width) {
                if (printError) {
                    fputs("Logo (image-raw): Must set logo width when using position right\n", stderr);
                }
                return false;
            }
            ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width);
        }
        ffStrbufAppendNS(&buf, (uint32_t) length, data);
        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);

        if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) {
            uint16_t X = 0, Y = 0;
            // Windows Terminal doesn't report `\e` for some reason
            const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X); // %*[^0-9]: ignore optional \e[
            if (error) {
                if (printError) {
                    fprintf(stderr, "\nLogo (image-raw): fail to query cursor position: %s\n", error);
                }
                return true;
            }
            if (options->position == FF_LOGO_POSITION_LEFT) {
                if (options->width + options->paddingLeft > X) {
                    X = (uint16_t) (options->width + options->paddingLeft);
                }
                instance.state.logoWidth = X + instance.config.logo.paddingRight - 1;
            }
            instance.state.logoHeight = Y;
            fputs("\e[H", stdout);
        } else if (options->position == FF_LOGO_POSITION_TOP) {
            instance.state.logoWidth = instance.state.logoHeight = 0;
            ffPrintCharTimes('\n', options->paddingRight);
        }
    } else {
        ffStrbufAppendNC(&buf, options->paddingTop, '\n');

        if (options->position == FF_LOGO_POSITION_RIGHT) {
            ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width);
        } else if (options->paddingLeft) {
            ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft);
        }

        ffStrbufAppendNS(&buf, (uint32_t) length, data);
        ffStrbufAppendC(&buf, '\n');

        if (options->position == FF_LOGO_POSITION_LEFT) {
            instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight;
            instance.state.logoHeight = options->paddingTop + options->height;
            ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight);
        } else if (options->position == FF_LOGO_POSITION_TOP) {
            instance.state.logoWidth = instance.state.logoHeight = 0;
            ffStrbufAppendNC(&buf, options->paddingRight, '\n');
        } else if (options->position == FF_LOGO_POSITION_RIGHT) {
            instance.state.logoWidth = instance.state.logoHeight = 0;
            ffStrbufAppendF(&buf, "\e[%uA", (unsigned) options->height);
        }
        ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
    }

    return true;
}

// If result is NULL, calculate logo width
// Returns logo height
static uint32_t logoAppendChars(const char* data, bool doColorReplacement, FFstrbuf* result) {
    FFOptionsLogo* options = &instance.config.logo;
    uint32_t currentlineLength = options->type == FF_LOGO_TYPE_IMAGE_CHAFA ? 0 : options->width; // For chafa, unit of options->width is pixels
    uint32_t logoHeight = 0;

    if (result) {
        if (options->position != FF_LOGO_POSITION_RIGHT) {
            ffStrbufAppendNC(result, options->paddingLeft, ' ');
        } else {
            ffStrbufAppendF(result, "\e[9999999C\e[%dD", options->paddingRight + instance.state.logoWidth);
        }
    }

    while (*data != '\0') {
        // We are at the end of a line. Print paddings and update max line length
        if (*data == '\n' || (*data == '\r' && *(data + 1) == '\n')) {
            // We have \r\n, skip the \r
            if (*data == '\r') {
                ++data;
            }

            if (result) {
                ffStrbufAppendC(result, '\n');
            }
            ++data;

            if (result) {
                if (options->position != FF_LOGO_POSITION_RIGHT) {
                    ffStrbufAppendNC(result, options->paddingLeft, ' ');
                } else {
                    ffStrbufAppendF(result, "\e[9999999C\e[%dD", options->paddingRight + instance.state.logoWidth);
                }
            }

            if (currentlineLength > instance.state.logoWidth) {
                instance.state.logoWidth = currentlineLength;
            }

            currentlineLength = 0;
            ++logoHeight;
            continue;
        }

        // Always print tabs as 4 spaces, to have consistent spacing
        if (*data == '\t') {
            if (result) {
                ffStrbufAppendNC(result, 4, ' ');
            }
            ++data;
            continue;
        }

        // We have an escape sequence directly as bytes. We print it, but don't increase the line length
        if (*data == '\e' && *(data + 1) == '[') {
            const char* start = data;

            if (result) {
                ffStrbufAppendS(result, "\e[");
            }
            data += 2;

            while (ffCharIsDigit(*data) || *data == ';') {
                if (result) {
                    ffStrbufAppendC(result, *data); // number
                }
                ++data;
            }

            // We have a valid control sequence, print it and continue with next char
            if (isascii(*data)) {
                if (result) {
                    ffStrbufAppendC(result, *data); // single letter, end of control sequence
                }
                ++data;
                continue;
            }

            // Invalid control sequence, try to get most accurate length
            currentlineLength += (uint32_t) (data - start - 1); //-1 for \033 which for sure doesn't take any space

            // Don't continue here, print the char after the letters with the unicode printing
        }

        // We have a fastfetch color placeholder. Replace it with the esacape sequence, don't increase the line length
        if (doColorReplacement && *data == '$') {
            ++data;

            // If we have $$, or $\0, print it as single $
            if (*data == '$' || *data == '\0') {
                if (result) {
                    ffStrbufAppendC(result, '$');
                }
                ++currentlineLength;
                ++data;
                continue;
            }

            if (!instance.config.display.pipe) {
                // Map the number to an array index, so that '1' -> 0, '2' -> 1, etc.
                int index = *data - '1';

                // If the index is valid, print the color. Otherwise continue as normal
                if (index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS) {
                    if (result) {
                        ffStrbufAppendC(result, '$');
                    }
                    ++currentlineLength;
                    // Don't continue here, we want to print the current char as unicode
                } else {
                    if (result) {
                        ffStrbufAppendF(result, "\e[%sm", options->colors[index].chars);
                    }
                    ++data;
                    continue;
                }
            } else {
                ++data;
                continue;
            }
        }

        // Do the printing, respecting unicode

        ++currentlineLength;

        int codepoint = (unsigned char) *data;
        uint8_t bytes;

        if (codepoint <= 127) {
            bytes = 1;
        } else if ((codepoint & 0xE0) == 0xC0) {
            bytes = 2;
        } else if ((codepoint & 0xF0) == 0xE0) {
            bytes = 3;
        } else if ((codepoint & 0xF8) == 0xF0) {
            bytes = 4;
        } else {
            bytes = 1; // Invalid utf8, print it as is, byte by byte
        }

        for (uint8_t i = 0; i < bytes; ++i) {
            if (*data == '\0') {
                break;
            }

            if (result) {
                ffStrbufAppendC(result, *data);
            }
            ++data;
        }
    }
    // Happens if the last line is the longest
    if (currentlineLength > instance.state.logoWidth) {
        instance.state.logoWidth = currentlineLength;
    }

    return options->type != FF_LOGO_TYPE_IMAGE_CHAFA && options->height > logoHeight ? options->height : logoHeight;
}

void ffLogoPrintChars(const char* data, bool doColorReplacement) {
    FFOptionsLogo* options = &instance.config.logo;

    if (options->position == FF_LOGO_POSITION_RIGHT) {
        logoAppendChars(data, doColorReplacement, NULL);
    }

    FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(2048);

    if (!instance.config.display.pipe && instance.config.display.brightColor) {
        ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_BOLT);
    }

    ffStrbufAppendNC(&result, options->paddingTop, '\n');

    // Use logoColor[0] as the default color
    if (doColorReplacement && !instance.config.display.pipe) {
        ffStrbufAppendF(&result, "\e[%sm", options->colors[0].chars);
    }

    instance.state.logoHeight = options->paddingTop + logoAppendChars(data, doColorReplacement, &result);

    if (!instance.config.display.pipe) {
        ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET);
    }

    if (options->position == FF_LOGO_POSITION_LEFT) {
        instance.state.logoWidth += options->paddingLeft + options->paddingRight;

        // Go to the leftmost position and go up the height
        ffStrbufAppendF(&result, "\e[1G\e[%uA", instance.state.logoHeight);
    } else if (options->position == FF_LOGO_POSITION_RIGHT) {
        instance.state.logoWidth = 0;

        // Go to the leftmost position and go up the height
        ffStrbufAppendF(&result, "\e[1G\e[%uA", instance.state.logoHeight);
    } else if (options->position == FF_LOGO_POSITION_TOP) {
        instance.state.logoWidth = instance.state.logoHeight = 0;
        ffStrbufAppendNC(&result, options->paddingRight, '\n');
    }

    ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result);
}

static void logoApplyColors(const FFlogo* logo, bool replacement) {
    if (instance.config.display.colorTitle.length == 0) {
        ffStrbufAppendS(&instance.config.display.colorTitle, logo->colorTitle ?: logo->colors[0]);
    }

    if (instance.config.display.colorKeys.length == 0) {
        ffStrbufAppendS(&instance.config.display.colorKeys, logo->colorKeys ?: logo->colors[1]);
    }

    if (replacement) {
        FFOptionsLogo* options = &instance.config.logo;

        const char* const* colors = logo->colors;
        for (int i = 0; *colors != NULL && i < FASTFETCH_LOGO_MAX_COLORS; i++, colors++) {
            if (options->colors[i].length == 0) {
                ffStrbufAppendS(&options->colors[i], *colors);
            }
        }
    }
}

static bool logoHasName(const FFlogo* logo, const FFstrbuf* name, bool small) {
    for (
        const char* const* logoName = logo->names;
        *logoName != NULL && logoName <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];
        ++logoName) {
        if (small) {
            uint32_t logoNameLength = (uint32_t) (strlen(*logoName) - strlen("_small"));
            if (name->length == logoNameLength && strncasecmp(*logoName, name->chars, logoNameLength) == 0) {
                return true;
            }
        }
        if (ffStrbufIgnCaseEqualS(name, *logoName)) {
            return true;
        }
    }

    return false;
}

static const FFlogo* logoGetBuiltin(const FFstrbuf* name, FFLogoSize size) {
    if (name->length == 0 || !isalpha(name->chars[0])) {
        return NULL;
    }

    for (const FFlogo* logo = ffLogoBuiltins[toupper(name->chars[0]) - 'A']; *logo->names; ++logo) {
        switch (size) {
            // Never use alternate logos
            case FF_LOGO_SIZE_NORMAL:
                if (logo->type != FF_LOGO_LINE_TYPE_NORMAL) {
                    continue;
                }
                break;
            case FF_LOGO_SIZE_SMALL:
                if (logo->type != FF_LOGO_LINE_TYPE_SMALL_BIT) {
                    continue;
                }
                break;
            default:
                break;
        }

        if (logoHasName(logo, name, size == FF_LOGO_SIZE_SMALL)) {
            return logo;
        }
    }

    return NULL;
}

static const FFlogo* logoGetBuiltinDetected(FFLogoSize size) {
    const FFOSResult* os = ffDetectOS();

    const FFlogo* logo = logoGetBuiltin(&os->id, size);
    if (logo != NULL) {
        return logo;
    }

    logo = logoGetBuiltin(&os->name, size);
    if (logo != NULL) {
        return logo;
    }

    if (ffStrbufContainC(&os->idLike, ' ')) {
        FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
        for (
            uint32_t start = 0, end = ffStrbufFirstIndexC(&os->idLike, ' ');
            true;
            start = end + 1, end = ffStrbufNextIndexC(&os->idLike, start, ' ')) {
            ffStrbufSetNS(&buf, end - start, os->idLike.chars + start);
            logo = logoGetBuiltin(&buf, size);
            if (logo != NULL) {
                return logo;
            }

            if (end >= os->idLike.length) {
                break;
            }
        }
    } else {
        logo = logoGetBuiltin(&os->idLike, size);
        if (logo != NULL) {
            return logo;
        }
    }

    logo = logoGetBuiltin(&instance.state.platform.sysinfo.name, size);
    if (logo != NULL) {
        return logo;
    }

    return &ffLogoUnknown;
}

static void logoPrintStruct(const FFlogo* logo) {
    logoApplyColors(logo, true);

    ffLogoPrintChars(logo->lines, true);
}

static void logoPrintNone(void) {
    if (!instance.config.display.pipe) {
        logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
    }
    instance.state.logoHeight = 0;
    instance.state.logoWidth = 0;
}

static bool logoPrintBuiltinIfExists(const FFstrbuf* name, FFLogoSize size) {
    if (name->chars[0] == '~' || name->chars[0] == '.' || name->chars[0] == '/'
#if _WIN32
        || (ffCharIsEnglishAlphabet(name->chars[0]) && name->chars[1] == ':') // Windows drive letter
#endif
    )
        return false; // Paths

    if (ffStrbufIgnCaseEqualS(name, "none")) {
        logoPrintNone();
        return true;
    }

    const FFlogo* logo = ffLogoGetBuiltinForName(name, size);
    if (logo == NULL) {
        return false;
    }

    logoPrintStruct(logo);

    return true;
}

static inline void logoPrintDetected(FFLogoSize size) {
    logoPrintStruct(logoGetBuiltinDetected(size));
}

static bool logoPrintData(bool doColorReplacement, FFstrbuf* source) {
    if (source->length == 0) {
        return false;
    }

    logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
    ffLogoPrintChars(source->chars, doColorReplacement);
    return true;
}

static bool updateLogoPath(void) {
    FFOptionsLogo* options = &instance.config.logo;

    if (ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) {
        return true;
    }

    if (ffStrbufEqualS(&options->source, "-")) { // stdin
        return true;
    }

    if (ffStrbufIgnCaseEqualS(&options->source, "media-cover")) {
        const FFMediaResult* media = ffDetectMedia(true);
        if (media->cover.length == 0) {
            return false;
        }
        ffStrbufSet(&options->source, &media->cover);
        return true;
    }

    FF_STRBUF_AUTO_DESTROY fullPath = ffStrbufCreateA(128);
    if (ffPathExpandEnv(options->source.chars, &fullPath) && ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) {
        ffStrbufDestroy(&options->source);
        ffStrbufInitMove(&options->source, &fullPath);
        return true;
    }

    FF_LIST_FOR_EACH (FFstrbuf, dataDir, instance.state.platform.dataDirs) {
        // We need to copy it, because multiple threads might be using dataDirs at the same time
        ffStrbufSet(&fullPath, dataDir);
        ffStrbufAppendS(&fullPath, "fastfetch/logos/");
        ffStrbufAppend(&fullPath, &options->source);

        if (ffPathExists(fullPath.chars, FF_PATHTYPE_FILE)) {
            ffStrbufDestroy(&options->source);
            ffStrbufInitMove(&options->source, &fullPath);
            return true;
        }
    }

    return false;
}

static bool logoPrintFileIfExists(bool doColorReplacement, bool raw) {
    FFOptionsLogo* options = &instance.config.logo;

    FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();

    if (ffStrbufEqualS(&options->source, "-")
            ? !ffAppendFDBuffer(FFUnixFD2NativeFD(STDIN_FILENO), &content)
            : !ffAppendFileBuffer(options->source.chars, &content)) {
        if (instance.config.display.showErrors) {
            fprintf(stderr, "Logo: Failed to load file content from logo source: %s\n", options->source.chars);
        }
        return false;
    }

    logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), doColorReplacement);
    if (raw) {
        return ffLogoPrintCharsRaw(content.chars, content.length, instance.config.display.showErrors);
    }

    ffLogoPrintChars(content.chars, doColorReplacement);
    return true;
}

static bool logoPrintImageIfExists(FFLogoType logo, bool printError) {
    if (!ffLogoPrintImageIfExists(logo, printError)) {
        return false;
    }

    logoApplyColors(logoGetBuiltinDetected(FF_LOGO_SIZE_NORMAL), false);
    return true;
}

static bool logoTryKnownType(void) {
    FFOptionsLogo* options = &instance.config.logo;

    if (options->type == FF_LOGO_TYPE_NONE) {
        logoPrintNone();
        return true;
    }

    if (options->type == FF_LOGO_TYPE_BUILTIN) {
        return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN);
    }

    if (options->type == FF_LOGO_TYPE_SMALL) {
        return logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_SMALL);
    }

    if (options->type == FF_LOGO_TYPE_DATA) {
        return logoPrintData(true, &options->source);
    }

    if (options->type == FF_LOGO_TYPE_DATA_RAW) {
        return logoPrintData(false, &options->source);
    }

    if (options->type == FF_LOGO_TYPE_COMMAND_RAW) {
        FF_STRBUF_AUTO_DESTROY source = ffStrbufCreate();

        const char* error = ffProcessAppendStdOut(&source, (char* const[]) {
#ifdef _WIN32
                                                               "cmd.exe", "/c",
#else
                                                               "/bin/sh", "-c",
#endif
                                                               options->source.chars,
                                                               NULL });

        if (error) {
            if (instance.config.display.showErrors) {
                fprintf(stderr, "Logo: failed to execute command `%s`: %s\n", options->source.chars, error);
            }
            return false;
        }

        return logoPrintData(false, &source);
    }

    // We sure have a file, resolve relative paths
    if (!updateLogoPath()) {
        if (instance.config.display.showErrors) {
            fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars);
        }
        return false;
    }

    if (options->type == FF_LOGO_TYPE_FILE) {
        return logoPrintFileIfExists(true, false);
    }

    if (options->type == FF_LOGO_TYPE_FILE_RAW) {
        return logoPrintFileIfExists(false, false);
    }

    if (options->type == FF_LOGO_TYPE_IMAGE_RAW) {
        return logoPrintFileIfExists(false, true);
    }

    return logoPrintImageIfExists(options->type, instance.config.display.showErrors);
}

void ffLogoPrint(void) {
    const FFOptionsLogo* options = &instance.config.logo;

    if (options->type == FF_LOGO_TYPE_NONE) {
        logoPrintNone();
        return;
    }

    // If the source is not set, we can directly print the detected logo.
    if (options->source.length == 0) {
        logoPrintDetected(options->type == FF_LOGO_TYPE_SMALL ? FF_LOGO_SIZE_SMALL : FF_LOGO_SIZE_NORMAL);
        return;
    }

    // If the source and source type is set to something else than auto, always print with the set type.
    if (options->source.length > 0 && options->type != FF_LOGO_TYPE_AUTO) {
        if (!logoTryKnownType()) {
            if (instance.config.display.showErrors) {
                // Image logo should have been handled
                if (options->type == FF_LOGO_TYPE_BUILTIN || options->type == FF_LOGO_TYPE_SMALL) {
                    fprintf(stderr, "Logo: Failed to load %s logo: %s\n", options->type == FF_LOGO_TYPE_BUILTIN ? "builtin" : "builtin small", options->source.chars);
                }
            }

            logoPrintDetected(FF_LOGO_SIZE_UNKNOWN);
        }
        return;
    }

    // If source matches the name of a builtin logo, print it and return.
    if (logoPrintBuiltinIfExists(&options->source, FF_LOGO_SIZE_UNKNOWN)) {
        return;
    }

    // Make sure the logo path is set correctly.
    if (updateLogoPath()) {
        if (ffStrbufEndsWithIgnCaseS(&options->source, ".raw")) {
            if (logoPrintFileIfExists(false, true)) {
                return;
            }
        }

        if (!ffStrbufEndsWithIgnCaseS(&options->source, ".txt")) {
            const FFTerminalResult* terminal = ffDetectTerminal();

            bool supportsIterm2 = ffStrbufEqualS(&terminal->prettyName, "iTerm");

            if (supportsIterm2 && logoPrintImageIfExists(FF_LOGO_TYPE_IMAGE_ITERM, false)) {
                return;
            }

            // Terminal emulators that support kitty graphics protocol.
            bool supportsKitty =
                ffStrbufIgnCaseEqualS(&terminal->processName, "kitty") ||
                ffStrbufIgnCaseEqualS(&terminal->processName, "konsole") ||
                ffStrbufIgnCaseEqualS(&terminal->processName, "wezterm") ||
                ffStrbufIgnCaseEqualS(&terminal->processName, "wayst") ||
                ffStrbufIgnCaseEqualS(&terminal->processName, "ghostty") ||
#ifdef __APPLE__
                ffStrbufIgnCaseEqualS(&terminal->processName, "WarpTerminal") ||
#else
                ffStrbufIgnCaseEqualS(&terminal->processName, "warp") ||
#endif
                false;

            // Try to load the logo as an image. If it succeeds, print it and return.
            if (logoPrintImageIfExists(supportsKitty ? FF_LOGO_TYPE_IMAGE_KITTY : FF_LOGO_TYPE_IMAGE_CHAFA, false)) {
                return;
            }
        }

        // Try to load the logo as a file. If it succeeds, print it and return.
        if (logoPrintFileIfExists(true, false)) {
            return;
        }
    } else {
        if (instance.config.display.showErrors) {
            fprintf(stderr, "Logo: Failed to resolve logo source: %s\n", options->source.chars);
        }
    }

    logoPrintDetected(FF_LOGO_SIZE_UNKNOWN);
}

void ffLogoPrintLine(void) {
    if (instance.state.logoWidth > 0) {
        printf("\033[%uC", instance.state.logoWidth);
    }

    if (instance.state.dynamicInterval > 0) {
        fputs("\033[K", stdout); // Clear to the end of the line
    }

    ++instance.state.keysHeight;
}

void ffLogoPrintRemaining(void) {
    if (instance.state.keysHeight <= instance.state.logoHeight) {
        ffPrintCharTimes('\n', instance.state.logoHeight - instance.state.keysHeight + 1);
    }
    instance.state.keysHeight = instance.state.logoHeight + 1;
}

void ffLogoBuiltinPrint(void) {
    FFOptionsLogo* options = &instance.config.logo;
    options->position = FF_LOGO_POSITION_TOP;
    options->paddingRight = 2; // empty line after logo printing
    FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();

    for (uint8_t ch = 0; ch < 26; ++ch) {
        for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) {
            if (instance.config.display.pipe) {
                ffStrbufSetF(&buf, "%s:\n", logo->names[0]);
            } else {
                ffStrbufSetF(&buf, "\e[%sm%s:\e[0m\n", logo->colors[0], logo->names[0]);
            }
            ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
            logoPrintStruct(logo);

            for (uint8_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) {
                ffStrbufClear(&options->colors[i]);
            }
        }
    }
}

void ffLogoBuiltinList(void) {
    uint32_t counter = 0;
    for (uint8_t ch = 0; ch < 26; ++ch) {
        for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) {
            ++counter;
            printf("%u)%s ", counter, counter < 10 ? " " : "");

            for (
                const char* const* names = logo->names;
                *names != NULL && names <= &logo->names[FASTFETCH_LOGO_MAX_NAMES];
                ++names) {
                printf("\"%s\" ", *names);
            }

            putchar('\n');
        }
    }
}

void ffLogoBuiltinListAutocompletion(void) {
    for (uint8_t ch = 0; ch < 26; ++ch) {
        for (const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) {
            printf("%s\n", logo->names[0]);
        }
    }
}

const FFlogo* ffLogoGetBuiltinForName(const FFstrbuf* name, FFLogoSize size) {
    return ffStrbufEqualS(name, "?") ? &ffLogoUnknown : logoGetBuiltin(name, size);
}

const FFlogo* ffLogoGetBuiltinDetected(FFLogoSize size) {
    return logoGetBuiltinDetected(size);
}
