const std = @import("std");
const args = @import("args.zig");
const Action = @import("ghostty.zig").Action;
const Config = @import("../config/Config.zig");
const configpkg = @import("../config.zig");
const themepkg = @import("../config/theme.zig");
const tui = @import("tui.zig");
const global_state = &@import("../global.zig").state;

const vaxis = @import("vaxis");
const zf = @import("zf");

// When the number of filtered themes is less than or equal to this threshold,
// the window position will be reset to 0 to show all results from the top.
// This ensures better visibility for small result sets while maintaining
// scroll position for larger lists.
const SMALL_LIST_THRESHOLD = 10;

const ColorScheme = enum { all, dark, light };

pub const Options = struct {
    /// If true, print the full path to the theme.
    path: bool = false,

    /// If true, force a plain list of themes.
    plain: bool = false,

    /// Specifies the color scheme of the themes to include in the list.
    color: ColorScheme = .all,

    pub fn deinit(self: Options) void {
        _ = self;
    }

    /// Enables "-h" and "--help" to work.
    pub fn help(self: Options) !void {
        _ = self;
        return Action.help_error;
    }
};

const ThemeListElement = struct {
    location: themepkg.Location,
    path: []const u8,
    theme: []const u8,
    rank: ?f64 = null,

    fn lessThan(_: void, lhs: @This(), rhs: @This()) bool {
        // TODO: use Unicode-aware comparison
        return std.ascii.orderIgnoreCase(lhs.theme, rhs.theme) == .lt;
    }

    pub fn toUri(self: *const ThemeListElement, alloc: std.mem.Allocator) ![]const u8 {
        const uri = std.Uri{
            .scheme = "file",
            .host = .{ .raw = "" },
            .path = .{ .raw = self.path },
        };
        var buf: std.Io.Writer.Allocating = .init(alloc);
        errdefer buf.deinit();
        try uri.writeToStream(
            &buf.writer,
            .{ .scheme = true, .authority = true, .path = true },
        );
        return buf.toOwnedSlice();
    }
};

/// The `list-themes` command is used to preview or list all the available
/// themes for Ghostty.
///
/// If this command is run from a TTY, a TUI preview of the themes will be
/// shown. While in the preview, `F1` will bring up a help screen and `ESC` will
/// exit the preview. Other keys that can be used to navigate the preview are
/// listed in the help screen.
///
/// If this command is not run from a TTY, or the output is piped to another
/// command, a plain list of theme names will be printed to the screen. A plain
/// list can be forced using the `--plain` CLI flag.
///
/// Two different directories will be searched for themes.
///
/// The first directory is the `themes` subdirectory of your Ghostty
/// configuration directory. This is `$XDG_CONFIG_HOME/ghostty/themes` or
/// `~/.config/ghostty/themes`.
///
/// The second directory is the `themes` subdirectory of the Ghostty resources
/// directory. Ghostty ships with a multitude of themes that will be installed
/// into this directory. On macOS, this directory is the
/// `Ghostty.app/Contents/Resources/ghostty/themes`. On Linux, this directory
/// is the `share/ghostty/themes` (wherever you installed the Ghostty "share"
/// directory). If you're running Ghostty from the source, this is the
/// `zig-out/share/ghostty/themes` directory.
///
/// You can also set the `GHOSTTY_RESOURCES_DIR` environment variable to point
/// to the resources directory.
///
/// Flags:
///
///   * `--path`: Show the full path to the theme.
///
///   * `--plain`: Force a plain listing of themes.
///
///   * `--color`: Specify the color scheme of the themes included in the list.
///                This can be `dark`, `light`, or `all`. The default is `all`.
pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
    var opts: Options = .{};
    defer opts.deinit();

    {
        var iter = try args.argsIterator(gpa_alloc);
        defer iter.deinit();
        try args.parse(Options, gpa_alloc, &opts, &iter);
    }

    var arena = std.heap.ArenaAllocator.init(gpa_alloc);
    const alloc = arena.allocator();

    var stdout_buf: [4096]u8 = undefined;
    var stdout_file: std.fs.File = .stdout();
    var stdout_writer = stdout_file.writer(&stdout_buf);
    const stdout = &stdout_writer.interface;

    var stderr_buf: [4096]u8 = undefined;
    var stderr_writer = std.fs.File.stderr().writer(&stderr_buf);
    const stderr = &stderr_writer.interface;

    const resources_dir = global_state.resources_dir.app();
    if (resources_dir == null)
        try stderr.print("Could not find the Ghostty resources directory. Please ensure " ++
            "that Ghostty is installed correctly.\n", .{});

    var count: usize = 0;

    var themes: std.ArrayList(ThemeListElement) = .empty;

    var it: themepkg.LocationIterator = .{ .arena_alloc = arena.allocator() };

    while (try it.next()) |loc| {
        var dir = std.fs.cwd().openDir(loc.dir, .{ .iterate = true }) catch |err| switch (err) {
            error.FileNotFound => continue,
            else => {
                std.debug.print("error trying to open {s}: {}\n", .{ loc.dir, err });
                continue;
            },
        };
        defer dir.close();

        var walker = dir.iterate();

        while (try walker.next()) |entry| {
            switch (entry.kind) {
                .file, .sym_link => {
                    if (std.mem.eql(u8, entry.name, ".DS_Store"))
                        continue;
                    count += 1;

                    const path = try std.fs.path.join(alloc, &.{ loc.dir, entry.name });
                    try themes.append(alloc, .{
                        .path = path,
                        .location = loc.location,
                        .theme = try alloc.dupe(u8, entry.name),
                    });
                },
                else => {},
            }
        }
    }

    if (count == 0) {
        try stderr.print("No themes found, check to make sure that the themes were installed correctly.", .{});
        return 1;
    }

    std.mem.sortUnstable(ThemeListElement, themes.items, {}, ThemeListElement.lessThan);

    if (tui.can_pretty_print and !opts.plain and stdout_file.isTty()) {
        try preview(gpa_alloc, themes.items, opts.color);
        return 0;
    }

    var theme_config = try Config.default(gpa_alloc);
    defer theme_config.deinit();
    for (themes.items) |theme| {
        try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
        if (!shouldIncludeTheme(opts.color, theme_config)) {
            continue;
        }
        if (opts.path)
            try stdout.print("{s} ({t}) {s}\n", .{ theme.theme, theme.location, theme.path })
        else
            try stdout.print("{s} ({t})\n", .{ theme.theme, theme.location });
    }

    // Don't forget to flush!
    try stdout.flush();
    return 0;
}

fn resolveAutoThemePath(alloc: std.mem.Allocator) ![]u8 {
    const main_cfg_path = try configpkg.preferredDefaultFilePath(alloc);
    defer alloc.free(main_cfg_path);

    const base_dir = std.fs.path.dirname(main_cfg_path) orelse return error.BadPathName;
    return try std.fs.path.join(alloc, &.{ base_dir, "auto", "theme.ghostty" });
}

fn writeAutoThemeFile(alloc: std.mem.Allocator, theme_name: []const u8) !void {
    const auto_path = try resolveAutoThemePath(alloc);
    defer alloc.free(auto_path);

    if (std.fs.path.dirname(auto_path)) |dir| {
        try std.fs.cwd().makePath(dir);
    }

    var f = try std.fs.createFileAbsolute(auto_path, .{ .truncate = true });
    defer f.close();

    var buf: [128]u8 = undefined;
    var w = f.writer(&buf);
    try w.interface.print("theme = {s}\n", .{theme_name});
    try w.interface.flush();
}

const Event = union(enum) {
    key_press: vaxis.Key,
    mouse: vaxis.Mouse,
    color_scheme: vaxis.Color.Scheme,
    winsize: vaxis.Winsize,
};

const Preview = struct {
    allocator: std.mem.Allocator,
    should_quit: bool,
    tty: vaxis.Tty,
    vx: vaxis.Vaxis,
    mouse: ?vaxis.Mouse,
    themes: []ThemeListElement,
    filtered: std.ArrayList(usize),
    current: usize,
    window: usize,
    hex: bool,
    mode: enum {
        normal,
        help,
        search,
        save,
    },
    color_scheme: vaxis.Color.Scheme,
    text_input: vaxis.widgets.TextInput,
    theme_filter: ColorScheme,

    pub fn init(
        allocator: std.mem.Allocator,
        themes: []ThemeListElement,
        theme_filter: ColorScheme,
        buf: []u8,
    ) !*Preview {
        const self = try allocator.create(Preview);

        self.* = .{
            .allocator = allocator,
            .should_quit = false,
            .tty = try .init(buf),
            .vx = try vaxis.init(allocator, .{}),
            .mouse = null,
            .themes = themes,
            .filtered = try .initCapacity(allocator, themes.len),
            .current = 0,
            .window = 0,
            .hex = false,
            .mode = .normal,
            .color_scheme = .light,
            .text_input = .init(allocator),
            .theme_filter = theme_filter,
        };

        try self.updateFiltered();

        return self;
    }

    pub fn deinit(self: *Preview) void {
        const allocator = self.allocator;
        self.filtered.deinit(allocator);
        self.text_input.deinit();
        self.vx.deinit(allocator, self.tty.writer());
        self.tty.deinit();
        allocator.destroy(self);
    }

    pub fn run(self: *Preview) !void {
        var loop: vaxis.Loop(Event) = .{
            .tty = &self.tty,
            .vaxis = &self.vx,
        };
        try loop.init();
        try loop.start();

        const writer = self.tty.writer();

        try self.vx.enterAltScreen(writer);
        try self.vx.setTitle(writer, "👻 Ghostty Theme Preview 👻");
        try self.vx.queryTerminal(writer, 1 * std.time.ns_per_s);
        try self.vx.setMouseMode(writer, true);
        if (self.vx.caps.color_scheme_updates)
            try self.vx.subscribeToColorSchemeUpdates(writer);

        while (!self.should_quit) {
            var arena = std.heap.ArenaAllocator.init(self.allocator);
            defer arena.deinit();
            const alloc = arena.allocator();

            loop.pollEvent();
            while (loop.tryEvent()) |event| {
                try self.update(event, alloc);
            }
            try self.draw(alloc);

            try self.vx.render(writer);
            try writer.flush();
        }
    }

    fn updateFiltered(self: *Preview) !void {
        const relative = self.current -| self.window;
        var selected: []const u8 = undefined;
        if (self.filtered.items.len > 0) {
            selected = self.themes[self.filtered.items[self.current]].theme;
        }

        const hash_algorithm = std.hash.Wyhash;

        const old_digest = d: {
            var hash = hash_algorithm.init(0);
            for (self.filtered.items) |item|
                hash.update(std.mem.asBytes(&item));
            break :d hash.final();
        };

        self.filtered.clearRetainingCapacity();

        var theme_config = try Config.default(self.allocator);
        defer theme_config.deinit();
        if (self.text_input.buf.realLength() > 0) {
            const first_half = self.text_input.buf.firstHalf();
            const second_half = self.text_input.buf.secondHalf();

            const buffer = try self.allocator.alloc(u8, first_half.len + second_half.len);
            defer self.allocator.free(buffer);

            @memcpy(buffer[0..first_half.len], first_half);
            @memcpy(buffer[first_half.len..], second_half);

            const string = try std.ascii.allocLowerString(self.allocator, buffer);
            defer self.allocator.free(string);

            var tokens: std.ArrayList([]const u8) = .empty;
            defer tokens.deinit(self.allocator);

            var it = std.mem.tokenizeScalar(u8, string, ' ');
            while (it.next()) |token| try tokens.append(self.allocator, token);

            for (self.themes, 0..) |*theme, i| {
                try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
                if (!shouldIncludeTheme(self.theme_filter, theme_config)) continue;

                theme.rank = zf.rank(theme.theme, tokens.items, .{
                    .to_lower = true,
                    .plain = true,
                });
                if (theme.rank != null) try self.filtered.append(self.allocator, i);
            }
        } else {
            for (self.themes, 0..) |*theme, i| {
                try theme_config.loadFile(theme_config._arena.?.allocator(), theme.path);
                if (shouldIncludeTheme(self.theme_filter, theme_config)) {
                    try self.filtered.append(self.allocator, i);
                    theme.rank = null;
                }
            }
        }

        const new_digest = d: {
            var hash = hash_algorithm.init(0);
            for (self.filtered.items) |item|
                hash.update(std.mem.asBytes(&item));
            break :d hash.final();
        };

        if (old_digest == new_digest) return;

        if (self.filtered.items.len == 0) {
            self.current = 0;
            self.window = 0;
            return;
        }

        self.current, self.window = current: {
            if (selected.len == 0) break :current .{ 0, 0 };

            for (self.filtered.items, 0..) |index, i| {
                if (std.mem.eql(u8, self.themes[index].theme, selected)) {
                    // Keep the relative position but ensure all search results are visible
                    const new_window = i -| relative;
                    // If the new window would hide some results at the top, adjust it
                    break :current .{ i, if (self.filtered.items.len <= SMALL_LIST_THRESHOLD) 0 else new_window };
                }
            }
            break :current .{ 0, 0 };
        };
    }

    fn up(self: *Preview, count: usize) void {
        if (self.filtered.items.len == 0) {
            self.current = 0;
            return;
        }
        self.current -|= count;
    }

    fn down(self: *Preview, count: usize) void {
        if (self.filtered.items.len == 0) {
            self.current = 0;
            return;
        }
        self.current += count;
        if (self.current >= self.filtered.items.len)
            self.current = self.filtered.items.len - 1;
    }

    pub fn update(self: *Preview, event: Event, alloc: std.mem.Allocator) !void {
        switch (event) {
            .key_press => |key| {
                if (key.matches('c', .{ .ctrl = true }))
                    self.should_quit = true;
                switch (self.mode) {
                    .normal => {
                        if (key.matchesAny(&.{ 'q', vaxis.Key.escape }, .{}))
                            self.should_quit = true;
                        if (key.matchesAny(&.{ '?', vaxis.Key.f1 }, .{}))
                            self.mode = .help;
                        if (key.matches('h', .{ .ctrl = true }))
                            self.mode = .help;
                        if (key.matches('/', .{}))
                            self.mode = .search;
                        if (key.matchesAny(&.{ vaxis.Key.enter, vaxis.Key.kp_enter }, .{}))
                            self.mode = .save;
                        if (key.matchesAny(&.{ 'x', '/' }, .{ .ctrl = true })) {
                            self.text_input.buf.clearRetainingCapacity();
                            try self.updateFiltered();
                        }
                        if (key.matchesAny(&.{ vaxis.Key.home, vaxis.Key.kp_home }, .{}))
                            self.current = 0;
                        if (key.matchesAny(&.{ vaxis.Key.end, vaxis.Key.kp_end }, .{}))
                            self.current = self.filtered.items.len - 1;
                        if (key.matchesAny(&.{ 'j', '+', vaxis.Key.down, vaxis.Key.kp_down, vaxis.Key.kp_add }, .{}))
                            self.down(1);
                        if (key.matchesAny(&.{ vaxis.Key.page_down, vaxis.Key.kp_down }, .{}))
                            self.down(20);
                        if (key.matchesAny(&.{ 'k', '-', vaxis.Key.up, vaxis.Key.kp_up, vaxis.Key.kp_subtract }, .{}))
                            self.up(1);
                        if (key.matchesAny(&.{ vaxis.Key.page_up, vaxis.Key.kp_page_up }, .{}))
                            self.up(20);
                        if (key.matchesAny(&.{ 'h', 'x' }, .{}))
                            self.hex = true;
                        if (key.matches('d', .{}))
                            self.hex = false;
                        if (key.matches('c', .{}))
                            try self.vx.copyToSystemClipboard(
                                self.tty.writer(),
                                self.themes[self.filtered.items[self.current]].theme,
                                alloc,
                            )
                        else if (key.matches('c', .{ .shift = true }))
                            try self.vx.copyToSystemClipboard(
                                self.tty.writer(),
                                self.themes[self.filtered.items[self.current]].path,
                                alloc,
                            );
                        if (key.matches('f', .{})) {
                            switch (self.theme_filter) {
                                .all => self.theme_filter = .dark,
                                .dark => self.theme_filter = .light,
                                .light => self.theme_filter = .all,
                            }
                            try self.updateFiltered();
                        }
                    },
                    .help => {
                        if (key.matches('q', .{}))
                            self.should_quit = true;
                        if (key.matchesAny(&.{ '?', vaxis.Key.escape, vaxis.Key.f1 }, .{}))
                            self.mode = .normal;
                        if (key.matches('h', .{ .ctrl = true }))
                            self.mode = .normal;
                    },
                    .search => search: {
                        if (key.matchesAny(&.{ vaxis.Key.escape, vaxis.Key.enter }, .{})) {
                            self.mode = .normal;
                            break :search;
                        }
                        if (key.matchesAny(&.{ 'x', '/' }, .{ .ctrl = true })) {
                            self.text_input.clearRetainingCapacity();
                            try self.updateFiltered();
                            break :search;
                        }
                        try self.text_input.update(.{ .key_press = key });
                        try self.updateFiltered();
                    },
                    .save => {
                        if (key.matches('q', .{}))
                            self.should_quit = true;
                        if (key.matchesAny(&.{ vaxis.Key.escape, vaxis.Key.enter, vaxis.Key.kp_enter }, .{}))
                            self.mode = .normal;
                        if (key.matches('w', .{})) {
                            self.saveSelectedTheme();
                        }
                    },
                }
            },
            .color_scheme => |color_scheme| self.color_scheme = color_scheme,
            .mouse => |mouse| self.mouse = mouse,
            .winsize => |ws| try self.vx.resize(self.allocator, self.tty.writer(), ws),
        }
    }

    pub fn ui_fg(self: *Preview) vaxis.Color {
        return switch (self.color_scheme) {
            .light => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } },
            .dark => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } },
        };
    }

    pub fn ui_bg(self: *Preview) vaxis.Color {
        return switch (self.color_scheme) {
            .light => .{ .rgb = [_]u8{ 0xff, 0xff, 0xff } },
            .dark => .{ .rgb = [_]u8{ 0x00, 0x00, 0x00 } },
        };
    }

    pub fn ui_standard(self: *Preview) vaxis.Style {
        return .{
            .fg = self.ui_fg(),
            .bg = self.ui_bg(),
        };
    }

    pub fn ui_hover_bg(self: *Preview) vaxis.Color {
        return switch (self.color_scheme) {
            .light => .{ .rgb = [_]u8{ 0xbb, 0xbb, 0xbb } },
            .dark => .{ .rgb = [_]u8{ 0x22, 0x22, 0x22 } },
        };
    }

    pub fn ui_highlighted(self: *Preview) vaxis.Style {
        return .{
            .fg = self.ui_fg(),
            .bg = self.ui_hover_bg(),
        };
    }

    pub fn ui_selected_fg(self: *Preview) vaxis.Color {
        return switch (self.color_scheme) {
            .light => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } },
            .dark => .{ .rgb = [_]u8{ 0x00, 0xaa, 0x00 } },
        };
    }

    pub fn ui_selected_bg(self: *Preview) vaxis.Color {
        return switch (self.color_scheme) {
            .light => .{ .rgb = [_]u8{ 0xaa, 0xaa, 0xaa } },
            .dark => .{ .rgb = [_]u8{ 0x33, 0x33, 0x33 } },
        };
    }

    pub fn ui_selected(self: *Preview) vaxis.Style {
        return .{
            .fg = self.ui_selected_fg(),
            .bg = self.ui_selected_bg(),
        };
    }

    pub fn ui_err_fg(self: *Preview) vaxis.Color {
        return switch (self.color_scheme) {
            .light => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } },
            .dark => .{ .rgb = [_]u8{ 0xff, 0x00, 0x00 } },
        };
    }

    pub fn ui_err(self: *Preview) vaxis.Style {
        return .{
            .fg = self.ui_err_fg(),
            .bg = self.ui_bg(),
        };
    }

    pub fn draw(self: *Preview, alloc: std.mem.Allocator) !void {
        const win = self.vx.window();
        win.clear();

        self.vx.setMouseShape(.default);

        const theme_list = win.child(.{
            .x_off = 0,
            .y_off = 0,
            .width = 32,
            .height = win.height,
        });

        var highlight: ?usize = null;

        if (self.mouse) |mouse| {
            self.mouse = null;
            if (self.mode == .normal) {
                if (mouse.button == .wheel_up) {
                    self.up(1);
                }
                if (mouse.button == .wheel_down) {
                    self.down(1);
                }
                if (theme_list.hasMouse(mouse)) |_| {
                    if (mouse.button == .left and mouse.type == .release) {
                        const selection = self.window + mouse.row;
                        if (selection < self.filtered.items.len) {
                            self.current = selection;
                        }
                    }
                    highlight = mouse.row;
                }
            }
        }

        if (self.filtered.items.len == 0) {
            self.current = 0;
            self.window = 0;
        } else {
            const start = self.window;
            const end = self.window + theme_list.height - 1;
            if (self.current > end)
                self.window = self.current - theme_list.height + 1;
            if (self.current < start)
                self.window = self.current;
            if (self.window >= self.filtered.items.len)
                self.window = self.filtered.items.len - 1;
        }

        theme_list.fill(.{ .style = self.ui_standard() });

        for (0..theme_list.height) |row_capture| {
            const row: u16 = @intCast(row_capture);
            const index = self.window + row;
            if (index >= self.filtered.items.len) break;

            const theme = self.themes[self.filtered.items[index]];

            const style: enum { normal, highlighted, selected } = style: {
                if (index == self.current) break :style .selected;
                if (highlight) |h| if (h == row) break :style .highlighted;
                break :style .normal;
            };

            if (style == .selected) {
                _ = theme_list.printSegment(
                    .{
                        .text = "❯ ",
                        .style = self.ui_selected(),
                    },
                    .{
                        .row_offset = row,
                        .col_offset = 0,
                    },
                );
            }
            _ = theme_list.printSegment(
                .{
                    .text = theme.theme,
                    .style = switch (style) {
                        .normal => self.ui_standard(),
                        .highlighted => self.ui_highlighted(),
                        .selected => self.ui_selected(),
                    },
                    .link = .{
                        .uri = try theme.toUri(alloc),
                    },
                },
                .{
                    .row_offset = row,
                    .col_offset = 2,
                },
            );
            if (style == .selected) {
                if (theme.theme.len < theme_list.width - 4) {
                    for (2 + theme.theme.len..theme_list.width - 2) |i_capture| {
                        const i: u16 = @intCast(i_capture);
                        _ = theme_list.printSegment(
                            .{
                                .text = " ",
                                .style = self.ui_selected(),
                            },
                            .{
                                .row_offset = row,
                                .col_offset = i,
                            },
                        );
                    }
                }
                _ = theme_list.printSegment(
                    .{
                        .text = " ❮",
                        .style = self.ui_selected(),
                    },
                    .{
                        .row_offset = row,
                        .col_offset = theme_list.width - 2,
                    },
                );
            }
        }

        try self.drawPreview(alloc, win, theme_list.x_off + theme_list.width);

        switch (self.mode) {
            .normal => {
                win.hideCursor();
            },
            .help => {
                win.hideCursor();
                const width = 60;
                const height = 22;
                const child = win.child(
                    .{
                        .x_off = win.width / 2 -| width / 2,
                        .y_off = win.height / 2 -| height / 2,
                        .width = width,
                        .height = height,
                        .border = .{
                            .where = .all,
                            .style = self.ui_standard(),
                        },
                    },
                );

                child.fill(.{ .style = self.ui_standard() });

                const key_help = [_]struct { keys: []const u8, help: []const u8 }{
                    .{ .keys = "^C, q, ESC", .help = "Quit." },
                    .{ .keys = "F1, ?, ^H", .help = "Toggle help window." },
                    .{ .keys = "f", .help = "Cycle through theme filters." },
                    .{ .keys = "k, ↑", .help = "Move up 1 theme." },
                    .{ .keys = "ScrollUp", .help = "Move up 1 theme." },
                    .{ .keys = "PgUp", .help = "Move up 20 themes." },
                    .{ .keys = "j, ↓", .help = "Move down 1 theme." },
                    .{ .keys = "ScrollDown", .help = "Move down 1 theme." },
                    .{ .keys = "PgDown", .help = "Move down 20 themes." },
                    .{ .keys = "h, x", .help = "Show palette numbers in hexadecimal." },
                    .{ .keys = "d", .help = "Show palette numbers in decimal." },
                    .{ .keys = "c", .help = "Copy theme name to the clipboard." },
                    .{ .keys = "C", .help = "Copy theme path to the clipboard." },
                    .{ .keys = "Home", .help = "Go to the start of the list." },
                    .{ .keys = "End", .help = "Go to the end of the list." },
                    .{ .keys = "/", .help = "Start search." },
                    .{ .keys = "^X, ^/", .help = "Clear search." },
                    .{ .keys = "⏎", .help = "Save theme or close search window." },
                    .{ .keys = "w", .help = "Write theme to auto config file." },
                };

                for (key_help, 0..) |help, captured_i| {
                    const i: u16 = @intCast(captured_i);
                    _ = child.printSegment(
                        .{
                            .text = help.keys,
                            .style = self.ui_standard(),
                        },
                        .{
                            .row_offset = i + 1,
                            .col_offset = 2,
                        },
                    );
                    _ = child.printSegment(
                        .{
                            .text = "—",
                            .style = self.ui_standard(),
                        },
                        .{
                            .row_offset = i + 1,
                            .col_offset = 15,
                        },
                    );
                    _ = child.printSegment(
                        .{
                            .text = help.help,
                            .style = self.ui_standard(),
                        },
                        .{
                            .row_offset = i + 1,
                            .col_offset = 17,
                        },
                    );
                }
            },
            .search => {
                const child = win.child(.{
                    .x_off = 20,
                    .y_off = win.height - 5,
                    .width = win.width - 40,
                    .height = 3,
                    .border = .{
                        .where = .all,
                        .style = self.ui_standard(),
                    },
                });
                child.fill(.{ .style = self.ui_standard() });
                self.text_input.drawWithStyle(child, self.ui_standard());
            },
            .save => {
                const theme = self.themes[self.filtered.items[self.current]];

                const width = 92;
                const height = 17;
                const child = win.child(
                    .{
                        .x_off = win.width / 2 -| width / 2,
                        .y_off = win.height / 2 -| height / 2,
                        .width = width,
                        .height = height,
                        .border = .{
                            .where = .all,
                            .style = self.ui_standard(),
                        },
                    },
                );

                child.fill(.{ .style = self.ui_standard() });

                const save_instructions = [_][]const u8{
                    "To apply this theme, add the following line to your Ghostty configuration:",
                    "",
                    try std.fmt.allocPrint(alloc, "theme = {s}", .{theme.theme}),
                    "",
                    "Save the configuration file and then reload it to apply the new theme.",
                    "",
                    "Or press 'w' to write an auto theme file to your system's preferred default config path.",
                    "Then add the following line to your Ghostty configuration and reload:",
                    "",
                    "config-file = ?auto/theme.ghostty",
                    "",
                    "For more details on configuration and themes, visit the Ghostty documentation:",
                    "",
                    "https://ghostty.org/docs/config/reference",
                };

                for (save_instructions, 0..) |instruction, captured_i| {
                    const i: u16 = @intCast(captured_i);
                    _ = child.printSegment(
                        .{
                            .text = instruction,
                            .style = self.ui_standard(),
                        },
                        .{
                            .row_offset = i + 1,
                            .col_offset = 2,
                        },
                    );
                }
            },
        }
    }

    pub fn drawPreview(self: *Preview, alloc: std.mem.Allocator, win: vaxis.Window, x_off_unconverted: i17) !void {
        const x_off: u16 = @intCast(x_off_unconverted);
        const width: u16 = win.width - x_off;

        if (self.filtered.items.len > 0) {
            const theme = self.themes[self.filtered.items[self.current]];

            var config = try Config.default(alloc);
            defer config.deinit();

            config.loadFile(config._arena.?.allocator(), theme.path) catch |err| {
                const theme_path_len: u16 = @intCast(theme.path.len);

                const child = win.child(
                    .{
                        .x_off = x_off,
                        .y_off = 0,
                        .width = width,
                        .height = win.height,
                    },
                );
                child.fill(.{ .style = self.ui_standard() });
                const middle = child.height / 2;
                {
                    const text = try std.fmt.allocPrint(alloc, "Unable to open {s} from:", .{theme.theme});
                    const text_len: u16 = @intCast(text.len);
                    _ = child.printSegment(
                        .{
                            .text = text,
                            .style = self.ui_err(),
                        },
                        .{
                            .row_offset = middle -| 1,
                            .col_offset = child.width / 2 -| text_len / 2,
                        },
                    );
                }
                {
                    _ = child.printSegment(
                        .{
                            .text = theme.path,
                            .style = self.ui_err(),
                            .link = .{
                                .uri = try theme.toUri(alloc),
                            },
                        },
                        .{
                            .row_offset = middle,
                            .col_offset = child.width / 2 -| theme_path_len / 2,
                        },
                    );
                }
                {
                    const text = try std.fmt.allocPrint(alloc, "{}", .{err});
                    const text_len: u16 = @intCast(text.len);
                    _ = child.printSegment(
                        .{
                            .text = text,
                            .style = self.ui_err(),
                        },
                        .{
                            .row_offset = middle + 1,
                            .col_offset = child.width / 2 -| text_len / 2,
                        },
                    );
                }
                return;
            };

            var next_start: u16 = 0;

            const fg: vaxis.Color = .{
                .rgb = [_]u8{
                    config.foreground.r,
                    config.foreground.g,
                    config.foreground.b,
                },
            };
            const bg: vaxis.Color = .{
                .rgb = [_]u8{
                    config.background.r,
                    config.background.g,
                    config.background.b,
                },
            };
            const cursor_fg: vaxis.Color = if (config.@"cursor-text") |cursor_text| .{
                .rgb = [_]u8{
                    cursor_text.color.r,
                    cursor_text.color.g,
                    cursor_text.color.b,
                },
            } else bg;
            const cursor_bg: vaxis.Color = if (config.@"cursor-color") |cursor_bg| .{
                .rgb = [_]u8{
                    cursor_bg.color.r,
                    cursor_bg.color.g,
                    cursor_bg.color.b,
                },
            } else fg;
            const selection_fg: vaxis.Color = if (config.@"selection-foreground") |selection_fg| .{
                .rgb = [_]u8{
                    selection_fg.color.r,
                    selection_fg.color.g,
                    selection_fg.color.b,
                },
            } else bg;
            const selection_bg: vaxis.Color = if (config.@"selection-background") |selection_bg| .{
                .rgb = [_]u8{
                    selection_bg.color.r,
                    selection_bg.color.g,
                    selection_bg.color.b,
                },
            } else fg;
            const cursor: vaxis.Style = .{
                .fg = cursor_fg,
                .bg = cursor_bg,
            };
            const standard_selection: vaxis.Style = .{
                .fg = selection_fg,
                .bg = selection_bg,
            };
            const standard: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
            };
            const standard_bold: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .bold = true,
            };
            const standard_italic: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .italic = true,
            };
            const standard_bold_italic: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .bold = true,
                .italic = true,
            };
            const standard_underline: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .ul_style = .single,
            };
            const standard_double_underline: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .ul_style = .double,
            };
            const standard_dashed_underline: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .ul_style = .dashed,
            };
            const standard_curly_underline: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .ul_style = .curly,
            };
            const standard_dotted_underline: vaxis.Style = .{
                .fg = fg,
                .bg = bg,
                .ul_style = .dotted,
            };

            {
                const theme_len: u16 = @intCast(theme.theme.len);
                const theme_path_len: u16 = @intCast(theme.path.len);
                const child = win.child(
                    .{
                        .x_off = x_off,
                        .y_off = next_start,
                        .width = width,
                        .height = 4,
                    },
                );
                child.fill(.{ .style = standard });
                _ = child.printSegment(
                    .{
                        .text = theme.theme,
                        .style = standard_bold_italic,
                        .link = .{
                            .uri = try theme.toUri(alloc),
                        },
                    },
                    .{
                        .row_offset = 1,
                        .col_offset = child.width / 2 -| theme_len / 2,
                    },
                );
                _ = child.printSegment(
                    .{
                        .text = theme.path,
                        .style = standard,
                        .link = .{
                            .uri = try theme.toUri(alloc),
                        },
                    },
                    .{
                        .row_offset = 2,
                        .col_offset = child.width / 2 -| theme_path_len / 2,
                        .wrap = .none,
                    },
                );
                next_start += child.height;
            }

            if (config._diagnostics.items().len > 0) {
                const diagnostic_items_len: u16 = @intCast(config._diagnostics.items().len);
                const child = win.child(
                    .{
                        .x_off = x_off,
                        .y_off = next_start,
                        .width = width,
                        .height = if (config._diagnostics.items().len == 0) 0 else 2 + diagnostic_items_len,
                    },
                );
                {
                    const text = "Problems were encountered trying to load the theme:";
                    const text_len: u16 = @intCast(text.len);
                    _ = child.printSegment(
                        .{
                            .text = text,
                            .style = self.ui_err(),
                        },
                        .{
                            .row_offset = 0,
                            .col_offset = child.width / 2 -| (text_len / 2),
                        },
                    );
                }

                var buf: std.Io.Writer.Allocating = .init(alloc);
                defer buf.deinit();
                for (config._diagnostics.items(), 0..) |diag, captured_i| {
                    const i: u16 = @intCast(captured_i);
                    try diag.format(&buf.writer);
                    _ = child.printSegment(
                        .{
                            .text = buf.written(),
                            .style = self.ui_err(),
                        },
                        .{
                            .row_offset = 2 + i,
                            .col_offset = 2,
                        },
                    );
                    buf.clearRetainingCapacity();
                }
                next_start += child.height;
            }
            {
                const child = win.child(.{
                    .x_off = x_off,
                    .y_off = next_start,
                    .width = width,
                    .height = 6,
                });

                child.fill(.{ .style = standard });

                for (0..16) |captured_i| {
                    const i: u16 = @intCast(captured_i);
                    const r = i / 8;
                    const c = i % 8;
                    const text = if (self.hex)
                        try std.fmt.allocPrint(alloc, " {x:0>2}", .{i})
                    else
                        try std.fmt.allocPrint(alloc, "{d:3}", .{i});
                    _ = child.printSegment(
                        .{
                            .text = text,
                            .style = standard,
                        },
                        .{
                            .row_offset = 3 * r,
                            .col_offset = c * 8,
                        },
                    );
                    _ = child.printSegment(
                        .{
                            .text = "████",
                            .style = .{
                                .fg = color(config, i),
                                .bg = bg,
                            },
                        },
                        .{
                            .row_offset = 3 * r,
                            .col_offset = 4 + c * 8,
                        },
                    );
                    _ = child.printSegment(
                        .{
                            .text = "████",
                            .style = .{
                                .fg = color(config, i),
                                .bg = bg,
                            },
                        },
                        .{
                            .row_offset = 3 * r + 1,
                            .col_offset = 4 + c * 8,
                        },
                    );
                }
                next_start += child.height;
            }
            {
                const child = win.child(
                    .{
                        .x_off = x_off,
                        .y_off = next_start,
                        .width = width,
                        .height = 24,
                    },
                );
                const bold: vaxis.Style = .{
                    .fg = fg,
                    .bg = bg,
                    .bold = true,
                };
                const color1: vaxis.Style = .{
                    .fg = color(config, 1),
                    .bg = bg,
                };
                const color2: vaxis.Style = .{
                    .fg = color(config, 2),
                    .bg = bg,
                };
                const color3: vaxis.Style = .{
                    .fg = color(config, 3),
                    .bg = bg,
                };
                const color4: vaxis.Style = .{
                    .fg = color(config, 4),
                    .bg = bg,
                };
                const color5: vaxis.Style = .{
                    .fg = color(config, 5),
                    .bg = bg,
                };
                const color6: vaxis.Style = .{
                    .fg = color(config, 6),
                    .bg = bg,
                };
                const color6ul: vaxis.Style = .{
                    .fg = color(config, 6),
                    .bg = bg,
                    .ul_style = .single,
                };
                const color10: vaxis.Style = .{
                    .fg = color(config, 10),
                    .bg = bg,
                };
                const color12: vaxis.Style = .{
                    .fg = color(config, 12),
                    .bg = bg,
                };
                const color238: vaxis.Style = .{
                    .fg = color(config, 238),
                    .bg = bg,
                };
                child.fill(.{ .style = standard });
                _ = child.print(
                    &.{
                        .{ .text = "→", .style = color2 },
                        .{ .text = " ", .style = standard },
                        .{ .text = "bat", .style = color4 },
                        .{ .text = " ", .style = standard },
                        .{ .text = "ziggzagg.zig", .style = color6ul },
                    },
                    .{
                        .row_offset = 0,
                        .col_offset = 2,
                    },
                );
                {
                    _ = child.print(
                        &.{
                            .{
                                .text = "───────┬",
                                .style = color238,
                            },
                        },
                        .{
                            .row_offset = 1,
                            .col_offset = 2,
                        },
                    );
                    if (child.width > 10) {
                        for (10..child.width) |captured_col| {
                            const col: u16 = @intCast(captured_col);
                            _ = child.print(
                                &.{
                                    .{
                                        .text = "─",
                                        .style = color238,
                                    },
                                },
                                .{
                                    .row_offset = 1,
                                    .col_offset = col,
                                },
                            );
                        }
                    }
                }
                _ = child.print(
                    &.{
                        .{
                            .text = "       │ ",
                            .style = color238,
                        },

                        .{
                            .text = "File: ",
                            .style = standard,
                        },

                        .{
                            .text = "ziggzagg.zig",
                            .style = bold,
                        },
                    },
                    .{
                        .row_offset = 2,
                        .col_offset = 2,
                    },
                );
                {
                    _ = child.print(
                        &.{
                            .{
                                .text = "───────┼",
                                .style = color238,
                            },
                        },
                        .{
                            .row_offset = 3,
                            .col_offset = 2,
                        },
                    );
                    if (child.width > 10) {
                        for (10..child.width) |captured_col| {
                            const col: u16 = @intCast(captured_col);
                            _ = child.print(
                                &.{
                                    .{
                                        .text = "─",
                                        .style = color238,
                                    },
                                },
                                .{
                                    .row_offset = 3,
                                    .col_offset = col,
                                },
                            );
                        }
                    }
                }
                _ = child.print(
                    &.{
                        .{ .text = "   1   │ ", .style = color238 },
                        .{ .text = "const", .style = color5 },
                        .{ .text = " std ", .style = standard },
                        .{ .text = "= @import", .style = color5 },
                        .{ .text = "(", .style = standard },
                        .{ .text = "\"std\"", .style = color10 },
                        .{ .text = ");", .style = standard },
                    },
                    .{
                        .row_offset = 4,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   2   │", .style = color238 },
                    },
                    .{
                        .row_offset = 5,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   3   │ ", .style = color238 },
                        .{ .text = "pub ", .style = color5 },
                        .{ .text = "fn ", .style = color12 },
                        .{ .text = "main", .style = color2 },
                        .{ .text = "() ", .style = standard },
                        .{ .text = "!", .style = color5 },
                        .{ .text = "void", .style = color12 },
                        .{ .text = " {", .style = standard },
                    },
                    .{
                        .row_offset = 6,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   4   │     ", .style = color238 },
                        .{ .text = "const ", .style = color5 },
                        .{ .text = "stdout ", .style = standard },
                        .{ .text = "=", .style = color5 },
                        .{ .text = " std.Io.getStdOut().writer();", .style = standard },
                    },
                    .{
                        .row_offset = 7,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   5   │     ", .style = color238 },
                        .{ .text = "var ", .style = color5 },
                        .{ .text = "i:", .style = standard },
                        .{ .text = " usize", .style = color12 },
                        .{ .text = " =", .style = color5 },
                        .{ .text = " 1", .style = color4 },
                        .{ .text = ";", .style = standard },
                    },
                    .{
                        .row_offset = 8,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   6   │     ", .style = color238 },
                        .{ .text = "while ", .style = color5 },
                        .{ .text = "(i ", .style = standard },
                        .{ .text = "<= ", .style = color5 },
                        .{ .text = "16", .style = color4 },
                        .{ .text = ") : (i ", .style = standard },
                        .{ .text = "+= ", .style = color5 },
                        .{ .text = "1", .style = color4 },
                        .{ .text = ") {", .style = standard },
                    },
                    .{
                        .row_offset = 9,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   7   │         ", .style = color238 },
                        .{ .text = "if ", .style = color5 },
                        .{ .text = "(i ", .style = standard },
                        .{ .text = "% ", .style = color5 },
                        .{ .text = "15 ", .style = color4 },
                        .{ .text = "== ", .style = color5 },
                        .{ .text = "0", .style = color4 },
                        .{ .text = ") {", .style = standard },
                    },
                    .{
                        .row_offset = 10,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   8   │             ", .style = color238 },
                        .{ .text = "try ", .style = color5 },
                        .{ .text = "stdout.writeAll(", .style = standard },
                        .{ .text = "\"ZiggZagg", .style = color10 },
                        .{ .text = "\\n", .style = color12 },
                        .{ .text = "\"", .style = color10 },
                        .{ .text = ");", .style = standard },
                    },
                    .{
                        .row_offset = 11,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "   9   │         ", .style = color238 },
                        .{ .text = "} ", .style = standard },
                        .{ .text = "else if ", .style = color5 },
                        .{ .text = "(i ", .style = standard },
                        .{ .text = "% ", .style = color5 },
                        .{ .text = "3 ", .style = color4 },
                        .{ .text = "== ", .style = color5 },
                        .{ .text = "0", .style = color4 },
                        .{ .text = ") {", .style = standard },
                    },
                    .{
                        .row_offset = 12,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  10   │             ", .style = color238 },
                        .{ .text = "try ", .style = color5 },
                        .{ .text = "stdout.writeAll(", .style = standard },
                        .{ .text = "\"Zigg", .style = color10 },
                        .{ .text = "\\n", .style = color12 },
                        .{ .text = "\"", .style = color10 },
                        .{ .text = ");", .style = standard },
                    },
                    .{
                        .row_offset = 13,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  11   │         ", .style = color238 },
                        .{ .text = "} ", .style = standard },
                        .{ .text = "else if ", .style = color5 },
                        .{ .text = "(i ", .style = standard },
                        .{ .text = "% ", .style = color5 },
                        .{ .text = "5 ", .style = color4 },
                        .{ .text = "== ", .style = color5 },
                        .{ .text = "0", .style = color4 },
                        .{ .text = ") {", .style = standard },
                    },
                    .{
                        .row_offset = 14,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  12   │             ", .style = color238 },
                        .{ .text = "try ", .style = color5 },
                        .{ .text = "stdout.writeAll(", .style = standard },
                        .{ .text = "\"Zagg", .style = color10 },
                        .{ .text = "\\n", .style = color12 },
                        .{ .text = "\"", .style = color10 },
                        .{ .text = ");", .style = standard },
                    },
                    .{
                        .row_offset = 15,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  13   │         ", .style = color238 },
                        .{ .text = "} ", .style = standard },
                        .{ .text = "else ", .style = color5 },
                        .{ .text = "{", .style = standard },
                    },
                    .{
                        .row_offset = 16,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  14   │             ", .style = color238 },
                        .{ .text = "try ", .style = color5 },
                        .{ .text = "stdout.print(\"{d}\\n\", .{i})", .style = standard_selection },
                        .{ .text = ";", .style = cursor },
                    },
                    .{
                        .row_offset = 17,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  15   │         ", .style = color238 },
                        .{ .text = "}", .style = standard },
                    },
                    .{
                        .row_offset = 18,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  16   │     ", .style = color238 },
                        .{ .text = "}", .style = standard },
                    },
                    .{
                        .row_offset = 19,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "  17   │ ", .style = color238 },
                        .{ .text = "}", .style = standard },
                    },
                    .{
                        .row_offset = 20,
                        .col_offset = 2,
                    },
                );
                {
                    _ = child.print(
                        &.{
                            .{
                                .text = "───────┴",
                                .style = color238,
                            },
                        },
                        .{
                            .row_offset = 21,
                            .col_offset = 2,
                        },
                    );
                    if (child.width > 10) {
                        for (10..child.width) |captured_col| {
                            const col: u16 = @intCast(captured_col);
                            _ = child.print(
                                &.{
                                    .{
                                        .text = "─",
                                        .style = color238,
                                    },
                                },
                                .{
                                    .row_offset = 21,
                                    .col_offset = col,
                                },
                            );
                        }
                    }
                }
                _ = child.print(
                    &.{
                        .{ .text = "ghostty ", .style = color6 },
                        .{ .text = "on ", .style = standard },
                        .{ .text = " main ", .style = color4 },
                        .{ .text = "[+] ", .style = color1 },
                        .{ .text = "via ", .style = standard },
                        .{ .text = " v0.13.0 ", .style = color3 },
                        .{ .text = "via ", .style = standard },
                        .{ .text = "  impure (ghostty-env)", .style = color4 },
                    },
                    .{
                        .row_offset = 22,
                        .col_offset = 2,
                    },
                );
                _ = child.print(
                    &.{
                        .{ .text = "✦ ", .style = color4 },
                        .{ .text = "at ", .style = standard },
                        .{ .text = "10:36:15 ", .style = color3 },
                        .{ .text = "→", .style = color2 },
                    },
                    .{
                        .row_offset = 23,
                        .col_offset = 2,
                    },
                );
                next_start += child.height;
            }
            if (next_start < win.height) {
                const child = win.child(
                    .{
                        .x_off = x_off,
                        .y_off = next_start,
                        .width = width,
                        .height = win.height - next_start,
                    },
                );
                child.fill(.{ .style = standard });
                var it = std.mem.splitAny(u8, lorem_ipsum, " \n");
                var row: u16 = 1;
                var col: u16 = 2;
                while (row < child.height) {
                    const word = it.next() orelse line: {
                        it.reset();
                        break :line it.next() orelse unreachable;
                    };
                    const word_len: u16 = @intCast(word.len);
                    if (col + word.len > child.width) {
                        row += 1;
                        col = 2;
                    }
                    const style: vaxis.Style = style: {
                        if (std.mem.eql(u8, "ipsum", word)) break :style .{ .fg = color(config, 2), .bg = bg };
                        if (std.mem.eql(u8, "consectetur", word)) break :style standard_bold;
                        if (std.mem.eql(u8, "reprehenderit", word)) break :style standard_italic;
                        if (std.mem.eql(u8, "Praesent", word)) break :style standard_bold_italic;
                        if (std.mem.eql(u8, "auctor", word)) break :style standard_underline;
                        if (std.mem.eql(u8, "dui", word)) break :style standard_double_underline;
                        if (std.mem.eql(u8, "erat", word)) break :style standard_dashed_underline;
                        if (std.mem.eql(u8, "enim", word)) break :style standard_dotted_underline;
                        if (std.mem.eql(u8, "odio", word)) break :style standard_curly_underline;
                        break :style standard;
                    };
                    _ = child.printSegment(
                        .{
                            .text = word,
                            .style = style,
                        },
                        .{
                            .row_offset = row,
                            .col_offset = col,
                        },
                    );
                    col += word_len + 1;
                }
            }
        } else {
            const child = win.child(.{
                .x_off = 0,
                .y_off = 0,
                .width = win.width,
                .height = win.height,
            });
            child.fill(.{
                .style = self.ui_standard(),
            });

            _ = child.printSegment(.{
                .text = "No theme found!",
                .style = self.ui_standard(),
            }, .{
                .row_offset = win.height / 2 - 1,
                .col_offset = win.width / 2 - 7,
            });
        }
    }

    fn saveSelectedTheme(self: *Preview) void {
        if (self.filtered.items.len == 0)
            return;

        const idx = self.filtered.items[self.current];
        const theme = self.themes[idx];

        writeAutoThemeFile(self.allocator, theme.theme) catch {
            return;
        };
    }
};

fn color(config: Config, palette: usize) vaxis.Color {
    return .{
        .rgb = [_]u8{
            config.palette.value[palette].r,
            config.palette.value[palette].g,
            config.palette.value[palette].b,
        },
    };
}

const lorem_ipsum = @embedFile("lorem_ipsum.txt");

fn preview(allocator: std.mem.Allocator, themes: []ThemeListElement, theme_filter: ColorScheme) !void {
    var buf: [4096]u8 = undefined;
    var app = try Preview.init(
        allocator,
        themes,
        theme_filter,
        &buf,
    );
    defer app.deinit();
    try app.run();
}

fn shouldIncludeTheme(theme_filter: ColorScheme, theme_config: Config) bool {
    const rf = @as(f32, @floatFromInt(theme_config.background.r)) / 255.0;
    const gf = @as(f32, @floatFromInt(theme_config.background.g)) / 255.0;
    const bf = @as(f32, @floatFromInt(theme_config.background.b)) / 255.0;
    const luminance = 0.2126 * rf + 0.7152 * gf + 0.0722 * bf;
    const is_dark = luminance < 0.5;
    return (theme_filter == .all) or (theme_filter == .dark and is_dark) or (theme_filter == .light and !is_dark);
}
