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

#if defined(__WATCOMC__)
#include <i86.h>
#endif

#include "pastfetch.h"

#define INFO_LABEL_COL       12
#define DEFAULT_SCREEN_WIDTH 80
#define TEXT_OFFSET          46
#define MAX_INFO_LINES       21
#define MAX_TOTAL_LINES      24
#define MAX_LOGO_CANDIDATES   8
#define MAX_LOGO_VISIBLE     (TEXT_OFFSET > 1 ? (TEXT_OFFSET - 1) : 45)

typedef struct {
    char label[INFO_LABEL_COL + 1];
    char *value;
} InfoEntry;

typedef struct {
    InfoEntry *items;
    size_t count;
    size_t capacity;
} InfoBuffer;

#include "logos_data.inc"

typedef struct {
    const char *name;
    const char *const *lines;
} LogoEntry;

// fallback ASCII logo
static const char *const ascii_logo_dos[] = {
    "88888888ba,     ,ad8888ba,    ad88888ba",
    "88      `\"8b   d8\"'    `\"8b  d8\"     \"8b",
    "88        `8b d8'        `8b Y8,",
    "88         88 88          88 `Y8aaaaa,",
    "88         88 88          88   `\"\"\"\"\"8b,",
    "88         8P Y8,        ,8P         `8b",
    "88      .a8P   Y8a.    .a8P  Y8a     a8P",
    "88888888Y\"'     `\"Y8888Y\"'    \"Y88888P\"",
    NULL,
};

static size_t ansi_visible_width(const char *text);

static const LogoEntry logo_table[] = {
    {"86_dos", logo_86_dos},
    {"concurrentdos", logo_concurrentdos},
    {"dos", logo_dos},
    {"dosbian", logo_dosbian},
    {"dosbox", logo_dosbox},
    {"dosbox_staging", logo_dosbox_staging},
    {"dosbox_x", logo_dosbox_x},
    {"dr_dos", logo_dr_dos},
    {"freedos", logo_freedos},
    {"ms_dos", logo_ms_dos},
    {"novell_dos", logo_novell_dos},
    {"pc_dos", logo_pc_dos},
    {NULL, NULL},
};

// Drop empty edges and cap logo height
static void logo_trim_height(StringList *logo) {
    while (logo && logo->count > 0) {
        size_t width = ansi_visible_width(logo->items[0]);
        if (width == 0) {
            strlist_remove(logo, 0);
        } else {
            break;
        }
    }

    while (logo && logo->count > 0) {
        size_t width = ansi_visible_width(logo->items[logo->count - 1]);
        if (width == 0) {
            strlist_remove(logo, logo->count - 1);
        } else {
            break;
        }
    }

    while (logo && logo->count > MAX_TOTAL_LINES) {
        strlist_remove(logo, logo->count - 1);
    }
}

// Load the built-in ASCII fallback logo.
static void load_ascii_logo(StringList *logo) {
    size_t i;
    if (!logo) {
        return;
    }
    for (i = 0; ascii_logo_dos[i] != NULL; ++i) {
        strlist_add(logo, ascii_logo_dos[i]);
    }
}

// Check whether a string segment contains any non-space character.
static int has_visible_chars_n(const char *text, size_t len) {
    size_t i;
    if (!text) {
        return 0;
    }
    for (i = 0; i < len; ++i) {
        if (!isspace((unsigned char)text[i])) {
            return 1;
        }
    }
    return 0;
}

// Initialize the info buffer structure.
static void info_buffer_init(InfoBuffer *buffer) {
    buffer->items = NULL;
    buffer->count = 0;
    buffer->capacity = 0;
}

// Release storage held by info buffer entries.
static void info_buffer_free(InfoBuffer *buffer) {
    size_t i;
    if (!buffer) {
        return;
    }
    for (i = 0; i < buffer->count; ++i) {
        free(buffer->items[i].value);
        buffer->items[i].value = NULL;
    }
    free(buffer->items);
    buffer->items = NULL;
    buffer->count = 0;
    buffer->capacity = 0;
}

// Append a label/value pair to the info buffer.
static int info_buffer_add(InfoBuffer *buffer, const char *label, const char *value) {
    InfoEntry *entry;
    InfoEntry *new_items;
    const char *safe_label = label ? label : "";
    const char *safe_value = value ? value : "";
    size_t new_capacity;
    char *value_copy;

    if (!buffer) {
        return 0;
    }

    if (buffer->count >= MAX_INFO_LINES) {
        return 0;
    }

    if (buffer->count == buffer->capacity) {
        new_capacity = buffer->capacity == 0 ? 16 : buffer->capacity * 2;
        new_items = (InfoEntry *)realloc(buffer->items, new_capacity * sizeof(InfoEntry));
        if (!new_items) {
            return 0;
        }
        buffer->items = new_items;
        buffer->capacity = new_capacity;
    }

    entry = &buffer->items[buffer->count];
    strncpy(entry->label, safe_label, INFO_LABEL_COL);
    entry->label[INFO_LABEL_COL] = '\0';

    value_copy = (char *)malloc(strlen(safe_value) + 1); // yikes
    if (!value_copy) {
        return 0;
    }
    strcpy(value_copy, safe_value);
    entry->value = value_copy;

    buffer->count++;
    return 1;
}

// Format a label/value pair into a fixed-width line and append it.
// Lines that would be entirely blank are skipped, not stored.
static int text_block_add(StringList *block, const char *label, const char *value,
                          size_t value_len) {
    size_t total_len;
    char *line;
    const char *safe_label = label ? label : "";
    const char *safe_value = value ? value : "";

    if (!block) {
        return 0;
    }
    if (!has_visible_chars_n(safe_label, strlen(safe_label)) &&
        !has_visible_chars_n(safe_value, value_len)) {
        return 1;
    }

    total_len = INFO_LABEL_COL + 1 + value_len;
    line = (char *)malloc(total_len + 1);
    if (!line) {
        return 0;
    }
    snprintf(line, total_len + 1, "%-*s %.*s", INFO_LABEL_COL, safe_label, (int)value_len,
             safe_value);
    if (!strlist_adopt(block, line)) {
        free(line);
        return 0;
    }
    return 1;
}

// Append a feature string into a buffer separated by newlines.
static void append_feature(char *buffer, size_t buffer_len, const char *feature) {
    size_t existing;
    if (!buffer || buffer_len == 0 || !feature || !*feature) {
        return;
    }
    existing = strlen(buffer);
    if (existing > 0) {
        if (existing + 1 < buffer_len) {
            buffer[existing++] = '\n';
            buffer[existing] = '\0';
        } else {
            return;
        }
    }
    strncat(buffer, feature, buffer_len - strlen(buffer) - 1);
}

// Render a byte count into human-readable units.
static void format_size(unsigned long bytes, char *buffer, size_t buffer_len) {
    const unsigned long KB = 1024UL;
    const unsigned long MB = 1024UL * KB;
    const unsigned long GB = 1024UL * MB;

    if (bytes >= GB) {
        unsigned long integral = bytes / GB;
        unsigned long fractional = (bytes % GB) * 100UL / GB;
        snprintf(buffer, buffer_len, "%lu.%02lu GB", integral, fractional);
    } else if (bytes >= MB) {
        unsigned long integral = bytes / MB;
        unsigned long fractional = (bytes % MB) * 100UL / MB;
        snprintf(buffer, buffer_len, "%lu.%02lu MB", integral, fractional);
    } else if (bytes >= KB) {
        unsigned long integral = bytes / KB;
        unsigned long fractional = (bytes % KB) * 100UL / KB;
        snprintf(buffer, buffer_len, "%lu.%02lu KB", integral, fractional);
    } else {
        snprintf(buffer, buffer_len, "%lu bytes", bytes);
    }
}

// Normalize a flavor string into a filename-friendly form.
static void sanitize_flavor(const char *input, char *output, size_t output_len) {
    size_t i;
    size_t pos = 0;

    if (!input || !*input) {
        str_copy(output, output_len, "default");
        return;
    }

    for (i = 0; input[i] != '\0' && pos < output_len - 1; ++i) {
        char c = (char)tolower((unsigned char)input[i]);
        if (isalnum((unsigned char)c)) {
            output[pos++] = c;
        } else if (c == ' ' || c == '-' || c == '_' || c == '.') {
            if (pos > 0 && output[pos - 1] != '_') {
                output[pos++] = '_';
            }
        }
    }

    if (pos == 0) {
        str_copy(output, output_len, "default");
    } else {
        output[pos] = '\0';
    }
}

void list_available_logos(void) {
    size_t i;
    puts("Available logos:");
    for (i = 0; logo_table[i].name; ++i) {
        puts(logo_table[i].name);
    }
}

// Validate a logo name and optionally return its normalized form.
int logo_name_exists(const char *name, char *normalized, size_t normalized_len) {
    char sanitized[32];
    size_t i;
    if (!name) {
        return 0;
    }
    sanitize_flavor(name, sanitized, sizeof(sanitized));
    if (!sanitized[0]) {
        return 0;
    }
    for (i = 0; logo_table[i].name; ++i) {
        if (strcmp(sanitized, logo_table[i].name) == 0) {
            str_copy(normalized, normalized_len, sanitized);
            return 1;
        }
    }
    return 0;
}

// Compute printable width of a string ignoring ANSI escape sequences
static size_t ansi_visible_width(const char *text) {
    size_t width = 0;
    const unsigned char *ptr = (const unsigned char *)text;

    if (!text) {
        return 0;
    }

    while (*ptr) {
        if (*ptr == '\x1b') {
            ++ptr;
            if (*ptr == '[') {
                ++ptr;
                while (*ptr && ((*ptr >= '0' && *ptr <= '9') || *ptr == ';' || *ptr == '?' ||
                                *ptr == ':' || *ptr == '=')) {
                    ++ptr;
                }
                if (*ptr) {
                    ++ptr;
                }
            } else if (*ptr && (*ptr == '(' || *ptr == ')' || *ptr == '*' || *ptr == '+' ||
                                *ptr == '-' || *ptr == '.' || *ptr == '/')) {
                ++ptr;
                if (*ptr) {
                    ++ptr;
                }
            } else {
                if (*ptr) {
                    ++ptr;
                }
            }
            continue;
        }
        if (*ptr == '\r' || *ptr == '\n') {
            ++ptr;
            continue;
        }
        ++width;
        ++ptr;
    }

    return width;
}

// Append text to a buffer inserting a separator when needed.
static void append_with_separator(char *dest, size_t dest_size, const char *text,
                                  const char *separator) {
    if (!dest || !text || !*text || dest_size == 0) {
        return;
    }
    {
        size_t current = strlen(dest);
        size_t remaining;
        if (current >= dest_size - 1) {
            return;
        }
        remaining = dest_size - current - 1;
        if (*dest != '\0' && separator && *separator) {
            size_t sep_len = strlen(separator);
            if (sep_len > remaining) {
                sep_len = remaining;
            }
            strncat(dest, separator, sep_len);
            current = strlen(dest);
            if (current >= dest_size - 1) {
                return;
            }
            remaining = dest_size - current - 1;
        }
        strncat(dest, text, remaining);
    }
}

// Write unsigned int into a buffer without stdio formatting
static size_t append_uint(char *dest, unsigned int value) {
    char tmp[10];
    size_t len = 0;
    size_t written = 0;

    if (!dest) {
        return 0;
    }

    do {
        if (len < sizeof(tmp)) {
            tmp[len++] = (char)('0' + (value % 10));
        }
        value /= 10;
    } while (value > 0);

    while (len > 0) {
        *dest++ = tmp[--len];
        ++written;
    }
    return written;
}

// Emit ANSI cursor move sequence to 1-based row/col.
static void emit_cursor_move(unsigned int row, unsigned int col) {
    char seq[24];
    char *ptr = seq;

    if (row == 0) {
        row = 1;
    }
    if (col == 0) {
        col = 1;
    }

    *ptr++ = '\x1b';
    *ptr++ = '[';
    ptr += append_uint(ptr, row);
    *ptr++ = ';';
    ptr += append_uint(ptr, col);
    *ptr++ = 'H';
    *ptr = '\0';

    fputs(seq, stdout);
}

// Clear the screen using ANSI when available, else BIOS fallback.
static void clear_screen(const SystemInfo *info) {
    if (info && info->ansi_available) {
        fputs("\x1b[2J\x1b[H", stdout);
        return;
    }
#if defined(__WATCOMC__)
    {
        union REGS regs;
        regs.h.ah = 0x06;
        regs.h.al = 0;
        regs.h.bh = 0x07;
        regs.x.cx = 0;
        regs.x.dx = (24 << 8) | 79;
        int86(0x10, &regs, &regs);

        regs.h.ah = 0x02;
        regs.h.bh = 0;
        regs.h.dh = 0;
        regs.h.dl = 0;
        int86(0x10, &regs, &regs);
    }
#else
    putchar('\f');
#endif
}

// Copy the first non-empty line from a nl-delimited string.
static char *extract_first_segment(const char *text) {
    const char *cursor;
    const char *next;
    size_t len;
    char *result;

    if (!text) {
        return NULL;
    }
    cursor = text;
    while (*cursor && isspace((unsigned char)*cursor)) {
        ++cursor;
    }
    if (*cursor == '\0') {
        return NULL;
    }
    next = strchr(cursor, '\n');
    len = next ? (size_t)(next - cursor) : strlen(cursor);
    while (len > 0 && isspace((unsigned char)cursor[len - 1])) {
        --len;
    }
    result = (char *)malloc(len + 1);
    if (!result) {
        return NULL;
    }
    memcpy(result, cursor, len);
    result[len] = '\0';
    return result;
}

static void add_candidate(const char **list, size_t *count, const char *value) {
    size_t i;
    if (!value || !*value || *count >= MAX_LOGO_CANDIDATES) {
        return;
    }
    for (i = 0; i < *count; ++i) {
        if (strcmp(list[i], value) == 0) {
            return;
        }
    }
    list[(*count)++] = value;
}

/* Choose and load the best matching logo for the detected DOS flavor.
   Successively looser spellings of the name are tried against logo_table,
   each derived from the previous non-empty one:

     "MS-DOS 6.22 compat." -> ms_dos_6_22_compat  (sanitized)
                           -> ms-dos-6-22-compat  (hyphenated)
                           -> ms-dos-6-22         (compat suffix dropped)
                           -> ms                  (first word only)

   with "ms_dos" and then "dos" appended as final fallbacks. */
static void load_logo(const char *dos_flavor, StringList *logo) {
    char sanitized[32];
    char trimmed[32];
    char hyphenated[32];
    char compat_fix[32];
    char base[32];
    const char *candidates[MAX_LOGO_CANDIDATES];
    size_t candidate_count = 0;
    const char *current;
    const char *const *lines = NULL;
    size_t i, j, len;

    if (!logo) {
        return;
    }

    sanitize_flavor(dos_flavor, sanitized, sizeof(sanitized));
    add_candidate(candidates, &candidate_count, sanitized);
    current = sanitized;

    /* Drop trailing underscores left by sanitize_flavor. */
    str_copy(trimmed, sizeof(trimmed), current);
    while ((len = strlen(trimmed)) > 0 && trimmed[len - 1] == '_') {
        trimmed[len - 1] = '\0';
    }
    if (trimmed[0]) {
        add_candidate(candidates, &candidate_count, trimmed);
        current = trimmed;
    }

    /* Logo names in logo_table use underscores, but accept hyphens too. */
    str_copy(hyphenated, sizeof(hyphenated), current);
    for (i = 0; hyphenated[i] != '\0'; ++i) {
        if (hyphenated[i] == '_') {
            hyphenated[i] = '-';
        }
    }
    if (hyphenated[0]) {
        add_candidate(candidates, &candidate_count, hyphenated);
        current = hyphenated;
    }

    /* "MS-DOS compat." is a fallback label, not a distinct flavour. */
    str_copy(compat_fix, sizeof(compat_fix), current);
    if (str_ends_ci(compat_fix, "-compat") || str_ends_ci(compat_fix, "_compat")) {
        compat_fix[strlen(compat_fix) - 7] = '\0';
    }
    if (compat_fix[0]) {
        add_candidate(candidates, &candidate_count, compat_fix);
        current = compat_fix;
    }

    /* Last resort before the generic fallbacks: the leading word alone. */
    str_copy(base, sizeof(base), current);
    for (i = 0; base[i] != '\0'; ++i) {
        if (base[i] == '_' || base[i] == '-') {
            base[i] = '\0';
            break;
        }
    }
    add_candidate(candidates, &candidate_count, base);

    if (strncmp(sanitized, "ms_dos", 6) == 0) {
        add_candidate(candidates, &candidate_count, "ms_dos");
    }
    add_candidate(candidates, &candidate_count, "dos");

    for (i = 0; i < candidate_count && !lines; ++i) {
        for (j = 0; logo_table[j].name; ++j) {
            if (strcmp(candidates[i], logo_table[j].name) == 0) {
                lines = logo_table[j].lines;
                break;
            }
        }
    }
    if (!lines) {
        lines = logo_dos;
    }

    /* Copy each line, truncating at MAX_LOGO_VISIBLE printable characters so
       the art cannot run into the info column. Escape sequences are copied
       through without counting toward the visible width. */
    for (i = 0; lines[i] != NULL; ++i) {
        const char *src = lines[i];
        char buffer[512];
        char *dst = buffer;
        char *limit = buffer + sizeof(buffer) - 1;
        size_t visible = 0;

        while (*src && visible < MAX_LOGO_VISIBLE && dst < limit) {
            if (*src == '\x1b') {
                *dst++ = *src++;
                while (*src && dst < limit) {
                    *dst++ = *src;
                    if (isalpha((unsigned char)*src)) {
                        src++;
                        break;
                    }
                    src++;
                }
                continue;
            }
            *dst++ = *src++;
            visible++;
        }
        *dst = '\0';
        strlist_add(logo, buffer);
    }

    logo_trim_height(logo);
}

// Build the list of system information lines to render.
static void collect_info_lines(const SystemInfo *info, const StringList *drivers,
                               InfoBuffer *lines) {
    char version[32];
    char total_space[32];
    char free_space[32];
    char version_flags[32];
    char buffer[64];

    if (!info || !drivers || !lines) {
        return;
    }

    version_flags[0] = '\0';
    if (info->dos_flags & 0x08) {
        append_feature(version_flags, sizeof(version_flags), "ROM");
    }
    if (info->dos_flags & 0x10) {
        append_feature(version_flags, sizeof(version_flags), "HMA");
    }

    if (info->dos_major || info->dos_minor) {
        if (info->dos_revision > 0) {
            snprintf(version, sizeof(version), "%u.%u.%u", (unsigned int)info->dos_major,
                     (unsigned int)info->dos_minor, (unsigned int)info->dos_revision);
        } else {
            snprintf(version, sizeof(version), "%u.%u", (unsigned int)info->dos_major,
                     (unsigned int)info->dos_minor);
        }
    } else {
        str_copy(version, sizeof(version), "Unknown");
    }

    if (version_flags[0]) {
        strncat(version, " (", sizeof(version) - strlen(version) - 1);
        strncat(version, version_flags, sizeof(version) - strlen(version) - 1);
        strncat(version, ")", sizeof(version) - strlen(version) - 1);
    }

    format_size(info->disk_total_bytes, total_space, sizeof(total_space));
    format_size(info->disk_free_bytes, free_space, sizeof(free_space));

    info_buffer_add(lines, "DOS", info->dos_flavor);
    info_buffer_add(lines, "Kernel", version);
    if (info->ver_string[0]) {
        info_buffer_add(lines, "VER cmd", info->ver_string);
    }
    info_buffer_add(lines, "Shell", info->shell);
    info_buffer_add(lines, "Model", info->computer_model);
    info_buffer_add(lines, "CPU", info->cpu);
    if (info->display_mode[0]) {
        info_buffer_add(lines, "Display", info->display_mode);
        if (lines->count < MAX_INFO_LINES) {
            char graphics[256];
            graphics[0] = '\0';
            if (info->display_adapter[0]) {
                append_with_separator(graphics, sizeof(graphics), info->display_adapter, " | ");
            }
            if (info->display_vbe[0]) {
                append_with_separator(graphics, sizeof(graphics), info->display_vbe, " | ");
            }
            if (info->palette_info[0]) {
                char *palette_summary = extract_first_segment(info->palette_info);
                if (palette_summary) {
                    append_with_separator(graphics, sizeof(graphics), palette_summary, " | ");
                    free(palette_summary);
                }
            }
            if (graphics[0]) {
                info_buffer_add(lines, "Graphics", graphics);
            }
        }
    }
    snprintf(buffer, sizeof(buffer), "%u KB", info->conventional_kb);
    info_buffer_add(lines, "RAM", buffer);

    snprintf(buffer, sizeof(buffer), "%lu KB", info->extended_kb);
    info_buffer_add(lines, "Ext. RAM", buffer);

    snprintf(buffer, sizeof(buffer), "%s total", total_space);
    info_buffer_add(lines, "Drive C:", buffer);

    snprintf(buffer, sizeof(buffer), "%s free", free_space);
    info_buffer_add(lines, "", buffer);

    if (lines->count < MAX_INFO_LINES) {
        if (drivers->count == 0) {
            info_buffer_add(lines, "Config", "No DEVICE/INSTALL entries found");
        } else {
            char summary[256];
            if (drivers->count == 1) {
                str_copy(summary, sizeof(summary), drivers->items[0]);
            } else {
                snprintf(summary, sizeof(summary), "%u entries (first: %s)",
                         (unsigned)drivers->count, drivers->items[0]);
            }
            info_buffer_add(lines, "Config", summary);
        }
    }
}

// Wrap long info values into multiple display lines.
static void wrap_info_lines(const InfoBuffer *info, size_t total_width, StringList *output) {
    size_t prefix_width = INFO_LABEL_COL + 1;
    size_t max_value_width = (total_width > prefix_width) ? (total_width - prefix_width) : 0;
    size_t i;

    if (!output || !info) {
        return;
    }

    for (i = 0; i < info->count && i < MAX_INFO_LINES; ++i) {
        const char *label = info->items[i].label;
        const char *value = info->items[i].value ? info->items[i].value : "";
        const char *cursor = value;
        int first_line = 1;

        while (*cursor && output->count < MAX_INFO_LINES) {
            size_t remaining = max_value_width ? max_value_width : strlen(cursor);
            size_t take = strlen(cursor);
            char linebuf[512];

            if (remaining == 0) {
                break;
            }
            if (remaining > sizeof(linebuf) - 1) {
                remaining = sizeof(linebuf) - 1;
            }

            if (take > remaining) {
                take = remaining;
                if (max_value_width) {
                    size_t back = take;
                    while (back > 0 && !isspace((unsigned char)cursor[back - 1])) {
                        --back;
                    }
                    if (back > 0) {
                        take = back;
                    }
                }
            }

            strncpy(linebuf, cursor, take);
            linebuf[take] = '\0';

            cursor += take;
            while (*cursor && isspace((unsigned char)*cursor)) {
                ++cursor;
            }

            text_block_add(output, first_line ? label : "", linebuf, strlen(linebuf));
            first_line = 0;
        }

        if (output->count >= MAX_INFO_LINES) {
            break;
        }
    }
}

// Draw the logo and formatted info, with optional palette swatches
static void render_canvas(const StringList *logo, const InfoBuffer *info, int show_palette,
                          int ansi_available) {
    /* Reserve the final column. A line that exactly fills the screen makes the
       display wrap the cursor by itself, and the newline that follows then
       lands as a blank row -- the black lines through the ASCII logo under
       /noansi. 46 (logo) + 13 (label) + 21 (value) was exactly 80. */
    size_t usable_width = DEFAULT_SCREEN_WIDTH - 1;
    size_t info_width = (usable_width > TEXT_OFFSET) ? (usable_width - TEXT_OFFSET) : 0;
    StringList rendered;
    size_t rows;
    size_t i;

    strlist_init(&rendered);
    wrap_info_lines(info, info_width, &rendered);

    if (show_palette && ansi_available) {
        static const int colors[] = {30, 31, 32, 33, 34, 35, 36, 37};
        char line[256];
        size_t idx;

        line[0] = '\0';
        for (idx = 0; idx < sizeof(colors) / sizeof(colors[0]); ++idx) {
            char segment[16];
            snprintf(segment, sizeof(segment), "\x1b[%dm\xDB\xDB\x1b[0m ", colors[idx]);
            append_with_separator(line, sizeof(line), segment, "");
        }
        strlist_add(&rendered, line);

        if (rendered.count < MAX_TOTAL_LINES) {
            line[0] = '\0';
            for (idx = 0; idx < sizeof(colors) / sizeof(colors[0]); ++idx) {
                char segment[16];
                snprintf(segment, sizeof(segment), "\x1b[1;%dm\xDB\xDB\x1b[0m ", colors[idx]);
                append_with_separator(line, sizeof(line), segment, "");
            }
            strlist_add(&rendered, line);
        }
    }

    rows = rendered.count;
    if (logo && logo->count > rows) {
        rows = logo->count;
    }
    if (rows > MAX_TOTAL_LINES) {
        rows = MAX_TOTAL_LINES;
    }

    for (i = 0; i < rows; ++i) {
        const char *logo_line = (logo && i < logo->count) ? logo->items[i] : "";
        const char *info_line = (i < rendered.count) ? rendered.items[i] : NULL;
        size_t visible = ansi_visible_width(logo_line);
        size_t padding = (visible < TEXT_OFFSET) ? (TEXT_OFFSET - visible) : 1;

        if (ansi_available) {
            if (info_line) {
                emit_cursor_move((unsigned int)(i + 1U), (unsigned int)(TEXT_OFFSET + 1));
                fputs(info_line, stdout);
            }

            emit_cursor_move((unsigned int)(i + 1U), 1);
            fputs(logo_line, stdout);
            while (padding--) {
                putchar(' ');
            }
            putchar('\n');
        } else {
            const char *right = info_line ? info_line : "";
            int logo_has_text = logo_line && *logo_line;
            int info_has_text = has_visible_chars_n(right, strlen(right));

            if (!logo_has_text && !info_has_text) {
                continue;
            }

            if (logo_has_text) {
                fputs(logo_line, stdout);
            }

            if (info_has_text) {
                size_t left_len = logo_has_text ? strlen(logo_line) : 0;
                size_t space_count = (left_len < TEXT_OFFSET) ? (TEXT_OFFSET - left_len) : 1;
                while (space_count--) {
                    putchar(' ');
                }
                fputs(right, stdout);
            }

            putchar('\n');
        }
    }

    strlist_free(&rendered);

    fflush(stdout);
}

// Public entry to render detected system info and logo
void render_system(const SystemInfo *info, const StringList *drivers, const RunOptions *options) {
    StringList logo;
    InfoBuffer info_lines;
    int show_logo = 1;
    int show_palette = 0;
    int ansi_active;

    if (!info || !drivers) {
        return;
    }
    ansi_active = info->ansi_available;

    if (options) {
        show_logo = !options->no_logo;
        if (options->force_ascii) {
            ansi_active = 0;
        }
        show_palette = ansi_active;
    } else {
        show_logo = 1;
        show_palette = ansi_active;
    }

    clear_screen(info);

    info_buffer_init(&info_lines);
    collect_info_lines(info, drivers, &info_lines);

    strlist_init(&logo);
    if (show_logo) {
        if (ansi_active) {
            const char *logo_source;
            if (options && options->logo_override[0]) {
                logo_source = options->logo_override;
            } else {
                logo_source = info->logo_hint[0] ? info->logo_hint : info->dos_flavor;
            }
            load_logo(logo_source, &logo);
        } else {
            load_ascii_logo(&logo);
        }
    }

    render_canvas(show_logo ? &logo : NULL, &info_lines, show_palette, ansi_active);

    strlist_free(&logo);
    info_buffer_free(&info_lines);

    if (options && options->prompt_fix) {
        if (ansi_active) {
            fputs("\x1b[0m\r\n", stdout);
        } else {
            fputs("\r\n", stdout);
        }
        fflush(stdout);
    }
}
