// Copyright © 2013-2026 Jonathan Vasquez <jon@xyinn.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.

//! The Cactus Frontend
const std = @import("std");
const builtin = @import("builtin");
const dvui = @import("dvui");
const Rect = dvui.Rect;
const GridWidget = dvui.GridWidget;
const Options = dvui.Options;
const themes = @import("themes/cactus.zig");
const Shard = @import("shard.zig");
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const CellStyle = GridWidget.CellStyle;
const Cactus = @import("root.zig");
const Font = dvui.Font;
const Color = dvui.Color;
const Icon = @import("icons/icon.zig");

const log = std.log.scoped(.frontend);

// Globals

// The Cactus Backend is defined here and initialized in appInit. Its defer
// is defined in appDeinit.
var cactus: Cactus = undefined;

const version = "5.6.1";

pub const std_options: std.Options = .{
    .log_level = .info,
    .logFn = logFn,

    // Set our internal log levels. Can customize on an individual basis
    // depending on what we are doing.
    // .log_scope_levels = &[_]std.log.ScopeLevel{
    //     .{ .scope = .frontend, .level = .debug },
    //     .{ .scope = .backend, .level = .debug },
    // },
};

pub fn logFn(
    comptime level: std.log.Level,
    comptime scope: @EnumLiteral(),
    comptime format: []const u8,
    args: anytype,
) void {
    // Filter out third party library logs.
    if (scope == .dvui or
        scope == .SDLBackend or
        scope == .SDL_SYSTEM or
        scope == .SDL_RENDER) return;

    std.log.defaultLog(level, scope, format, args);
}

// Did the application failed early on launch?
var did_early_boot_fail: bool = false;
var was_early_boot_log_shown: bool = false;

// Last selected valid index must be global so other UI elements can easily run
// actions on it. Valid in this case means that it isn't in the "ghosted" area
// of the grid that still contain "ghost" rows that also have an index. This
// ensures we are always acting upon a valid shard object. This row will also
// always be highlighted in the UI.
var currently_selected_row_index: ?usize = null;

// Controls whether or not on the next frame render, if the viewport of the
// shards box should try to auto-scroll downwards (ex: if someone adds a new
// shard to the list).
var should_scroll_shards_list: bool = false;

// Will be used to automatically adjust Light/Dark mode colors at runtime.
var window_base_color: dvui.Color = undefined;
var grid_banded_style: GridWidget.CellStyle.Banded = undefined;
var separator_color: dvui.Color = undefined;
var default_highlight_style: GridWidget.CellStyle.HoveredRow = undefined;
var selected_highlight_style: GridWidget.CellStyle.HoveredRow = undefined;
var icon_inactive: []const u8 = undefined;
var disabled_color: dvui.Color = undefined;
var checkbox_hover_color: dvui.Color = undefined;
var button_text_color: dvui.Color = undefined;

const main_window_size: dvui.Size = .{ .w = 1150.0, .h = 682.0 };

var button_options: Options = undefined;
var cactus_bold_font: Font = undefined;
var cactus_common_font_options: Options = undefined;

// Should show respective window?
var should_show_detail_window: bool = false;
var should_show_settings_window: bool = false;
var should_show_about_window: bool = false;
var should_show_dialog_window: bool = false;

// This is the message that will be displayed in the dialog window.
var dialog_message: []const u8 = "";
var should_free_dialog_message_when_done: bool = false;

// This is the error that was captured and will be shown in the UI/CLI logs.
var dialog_error: ?anyerror = null;

var adjusted_first_entry_on_launch: bool = false;

// Whenever we open a modal window, we need to stop the processing of all
// grid events temporarily so that we don't get mouse movements in the background
// as we navigate inside the modal.
var stop_processing_events_in_grid: bool = false;

pub const dvui_app: dvui.App = .{
    .config = .{
        .options = .{
            .size = main_window_size,
            .min_size = main_window_size,
            .max_size = main_window_size,
            .title = "Cactus",
            .icon = Icon.cactus,
            .window_init_options = .{
                .theme = themes.light,
            },
        },
    },
    .frameFn = appFrame,
    .initFn = appInit,
    .deinitFn = appDeinit,
};
pub const main = dvui.App.main;
pub const panic = dvui.App.panic;

// Start up the Cactus Backend with any necessary resources.
pub fn appInit(_: *dvui.Window) !void {
    cactus = Cactus.init(dvui.App.main_init.?, version) catch |e| switch (e) {
        error.InvalidCharacterDetected => blk: {
            // This is a critical failure. The 'cactus' variable will remain
            // undefined and should not be used. We are just letting the app
            // live long enough for us to visually display an error message for
            // the user.
            did_early_boot_fail = true;
            break :blk undefined;
        },
        else => return e,
    };

    setupTheme();
}

// Clean up the application resources on shutdown.
pub fn appDeinit() void {
    cactus.deinit();
}

// All of this logic runs on each frame to generate the UI.
pub fn appFrame() !dvui.App.Result {
    // Main App Box
    var app_box = dvui.box(
        @src(),
        .{ .dir = .vertical },
        .{
            .style = .window,
            .background = true,
            .expand = .both,
            .color_fill = window_base_color,
        },
    );
    defer app_box.deinit();

    if (did_early_boot_fail) {
        // Application will terminate after the user clicks the Ok button.
        const message = "Invalid characters detected in one of the Shards inside your Cactus.json. Please manually remove all invalid characters from all Shard values and try again. Check logs for more info.";
        if (!was_early_boot_log_shown) {
            was_early_boot_log_shown = true;
            log.err(message, .{});
        }
        showEarlyBootFailureMessage(message);
    } else {
        // Shards Section
        if (try shardsSection()) |res| return res;

        _ = dvui.separator(@src(), .{
            .expand = .horizontal,
            .color_fill = separator_color,
        });

        // Buttons Section
        if (buttonsSection()) |res| return res;
    }

    return .ok;
}

/// Theme initialization logic.
fn setupTheme() void {
    const use_dark_mode = cactus.settings.use_dark_mode;

    if (use_dark_mode) {
        dvui.themeSet(themes.dark);
        window_base_color = themes.dark_window_base_color;
        grid_banded_style = themes.dark_grid_band;
        default_highlight_style = themes.dark_default_highlight_style;
        selected_highlight_style = themes.dark_selected_highlight_style;
        icon_inactive = Icon.inactive_dark;
        disabled_color = themes.dark_disabled_color;
        checkbox_hover_color = themes.dark.control.fill.?;
        button_text_color = themes.dark.text;
        separator_color = selected_highlight_style.cell_opts.color_fill_hover.?;
    } else {
        dvui.themeSet(themes.light);
        window_base_color = themes.window_base_color;
        grid_banded_style = themes.light_grid_band;
        default_highlight_style = themes.light_default_highlight_style;
        selected_highlight_style = themes.light_selected_highlight_style;
        icon_inactive = Icon.inactive_light;
        disabled_color = themes.light_disabled_color;
        checkbox_hover_color = themes.light.control.fill.?;
        button_text_color = themes.light.control.text.?;
        separator_color = Color.fromHex("#cccccc");
    }

    button_options = .{
        .color_text = button_text_color,
        .font = dvui.Font.theme(.body).withWeight(.bold),
        .padding = .{ .x = 20, .y = 10, .h = 10, .w = 20 },
    };

    cactus_bold_font = dvui.Font.find(.{ .family = "Cactus", .weight = .bold });
    cactus_common_font_options = .{ .font = cactus_bold_font };
}

/// This contains the shards table.
pub fn shardsSection() !?dvui.App.Result {
    const local = struct {
        // The below settings are applied later to all individual cells.

        // The base column width splat for the columns. These will be dynamically
        // recalculated to get optimal column widths later.
        var col_widths: [5]f32 = @splat(0);

        // This is the last selected index regardless of DVUI ghosting.
        var last_selected_index_with_ghosting: isize = -1;

        // Workaround: Even with the work to preserve the scroll_info. There
        // still seems to be a situation where scrolling to the end in a way
        // that reveals the last element is still getting clipped off when we
        // trying to select an element at the start of the app. We are implementing
        // our workaround here. Doing a manual refresh didn't work as well.
        var last_row_select_workaround: bool = false;

        // Shard section keyboard navigation and summoning.
        var was_up_pressed = false;
        var was_down_pressed = false;
        var was_enter_pressed = false;
    };

    monitorKeyEvents(&local.was_up_pressed, &local.was_down_pressed, &local.was_enter_pressed);

    // Capture the scroll information so we can scroll to the bottom cleanly
    // and automatically with our end of function logic.
    const parent_unique_id = dvui.parentGet().extendId(@src(), 0);
    var scroll_info = dvui.dataGetPtrDefault(null, parent_unique_id, "scroll_info", dvui.ScrollInfo, .{});

    {
        var grid = dvui.grid(
            @src(),
            .colWidths(&local.col_widths),
            .{ .scroll_opts = .{ .scroll_info = scroll_info } },
            .{
                .expand = .both,
                .background = false,
                .max_size_content = .{ .w = 1040, .h = 540 },

                // The right side of the UI will look as if there is some extra margin
                // space there. This is because DVUI is actually leaving room for the
                // scrollbar in that spot. So we will need to adjust the left side a bit
                // more so it looks visually symmetrical.
                .padding = .{ .x = 10 },
            },
        );
        defer grid.deinit();

        dvui.columnLayoutProportional(&.{ 100, 260, 280, 220, -1 }, &local.col_widths, grid.data().contentRect().w);

        if (!stop_processing_events_in_grid)
            default_highlight_style.processEvents(grid);

        if (createShardRows(
            grid,
            grid_banded_style,
            cactus.settings.shards.items,
        )) |res| return res;

        const row_clicked: ?usize = blk: {
            for (dvui.events()) |*e| {
                if (!dvui.eventMatchSimple(e, grid.data())) continue;
                if (e.evt != .mouse) continue;
                const me = e.evt.mouse;
                if (me.action != .press) continue;
                if (grid.pointToCell(me.p)) |cell| {
                    if (cell.col_num > 0) break :blk cell.row_num;
                }
            }
            break :blk null;
        };

        if (row_clicked) |selected_index| {
            const was_double_click = if (local.last_selected_index_with_ghosting == selected_index) true else false;

            // Update index for future clicks.
            local.last_selected_index_with_ghosting = @intCast(selected_index);

            // Clicking on an empty area in the DVUI grid will still generate a
            // click event with the index being what would have been there if there
            // was an entry. This seems to be an intentional DVUI design most likely.
            // We will ignore any click events coming from indices that are outside
            // of our actual known shards length.
            if (selected_index < cactus.settings.getShardsLength()) {
                log.debug("Selected Index: {}", .{selected_index});
                currently_selected_row_index = selected_index;

                // Update settings with the currently selected row index.
                _ = cactus.updateSettings(.{ .last_selected_shard = currently_selected_row_index }) catch |e| {
                    log.warn("There was an error updating the settings file with the currently selected row index: {}", .{e});
                };

                // We should still highlight on a single click, and run on a double click.
                if (was_double_click) {
                    log.debug("Double Click Detected", .{});
                    try cactus.summon(selected_index);

                    // Reset the index so we can get a fresh double click event
                    // if we happen to click on the same shard again.
                    local.last_selected_index_with_ghosting = -1;
                } else {
                    log.debug("Single Click Detected", .{});
                }
            }
        }
    }

    // Ergonomically automatically scroll when someone adds enough shards that
    // it causes the scrollbar to appear.
    if (should_scroll_shards_list or local.last_row_select_workaround) {
        // Using an odd number so the divisions align better when determining
        // how many times to scroll. It just so happens that our current grid
        // element icon size, we can fit 9 in a screen.
        const max_shards_in_viewport = 9;

        if (currently_selected_row_index) |selected_index| {
            // If the index is within the last page, just scroll to the end.
            // We will also implement our workaround for when an element is on
            // the last page or is the last element. We need to call this a second
            // time during the next frame so we can get all of the elements
            // displayed. Normally the last row keeps getting cut off for whatever
            // reason.
            const total_shards = cactus.settings.getShardsLength() - 1;

            if ((total_shards - selected_index) < max_shards_in_viewport) {
                log.debug("Index falls within last page. Jumping to end: Total: {d}, Selected: {d}", .{ total_shards, selected_index });
                scroll_info.scrollToOffset(.vertical, scroll_info.scrollMax(.vertical));

                // Trigger workaround. Should properly show the last row on the next frame.
                if (should_scroll_shards_list and !local.last_row_select_workaround) {
                    log.debug("Triggering last row / last page workaround ...", .{});
                    local.last_row_select_workaround = true;
                } else if (local.last_row_select_workaround) {
                    local.last_row_select_workaround = false;
                }
            } else {
                log.debug("Index is not on the last page. Navigating. MSIV: {d}, Selected: {d}, Total: {d}", .{
                    max_shards_in_viewport,
                    selected_index,
                    total_shards,
                });

                // Reset the scrolling and start at the top so we can show things properly
                // even when people do a shift up movement.
                scroll_info.scrollToOffset(.vertical, 0);

                // Scroll towards direction at page segments (so every 9~).
                const times_to_scroll = selected_index / max_shards_in_viewport;
                for (0..times_to_scroll) |_| {
                    log.debug("Scrolling ..", .{});
                    scroll_info.scrollPageDown(.vertical);

                    // Scroll back just a bit back so that we can get the 10th
                    // row in full range without extra elements if someone were
                    // to select the 10th row (assuming we can fit 9 in a page),
                    // and then person were to close the app and open it again.
                    // This behavior applies throughout scrolling, we will always
                    // scroll a bit back to get things to visually align.
                    const shard_row_height_neg = -75;
                    scroll_info.scrollByOffset(.vertical, shard_row_height_neg);
                }
            }
        }
        should_scroll_shards_list = false;
    }

    // Ergonomically select last remembered selection.
    if (!adjusted_first_entry_on_launch and cactus.settings.getShardsLength() != 0) {
        if (cactus.settings.last_selected_shard) |last_selected_shard| {
            if (last_selected_shard < cactus.settings.getShardsLength()) {
                currently_selected_row_index = last_selected_shard;
                should_scroll_shards_list = true;
            } else {
                currently_selected_row_index = 0;
            }
        } else {
            currently_selected_row_index = 0;
        }
        adjusted_first_entry_on_launch = true;
    }

    // Allow keyboard up/down/enter navigation in main grid.
    if (local.was_up_pressed) {
        local.was_up_pressed = false;
        if (currently_selected_row_index) |selected_index| {
            if (selected_index > 0) {
                currently_selected_row_index = selected_index - 1;

                // Update settings with the currently selected row index.
                _ = cactus.updateSettings(.{ .last_selected_shard = currently_selected_row_index }) catch |e| {
                    log.warn("There was an error updating the settings file with the currently selected row index: {}", .{e});
                };
            }
        }
    } else if (local.was_down_pressed) {
        local.was_down_pressed = false;
        if (currently_selected_row_index) |selected_index| {
            if (selected_index < cactus.settings.getShardsLength() - 1) {
                currently_selected_row_index = selected_index + 1;

                // Update settings with the currently selected row index.
                _ = cactus.updateSettings(.{ .last_selected_shard = currently_selected_row_index }) catch |e| {
                    log.warn("There was an error updating the settings file with the currently selected row index: {}", .{e});
                };
            }
        }
    } else if (local.was_enter_pressed) {
        local.was_enter_pressed = false;
        if (currently_selected_row_index) |selected_index| {
            try cactus.summon(selected_index);
        }
    }

    return null;
}

pub fn createShardRows(
    grid: *GridWidget,
    banded_style: dvui.GridWidget.CellStyle.Banded,
    shards: []const Shard,
) ?dvui.App.Result {
    for (shards, 0..) |shard, index| {
        // We should always have the single clicked row selected if there is one.
        var cell_num: dvui.GridWidget.Cell = undefined;
        var highlight_style: *CellStyle.HoveredRow = undefined;

        if (currently_selected_row_index) |lscr| {
            if (lscr == index) {
                cell_num = .colRow(0, lscr);
                selected_highlight_style.highlighted_row = lscr;
                highlight_style = &selected_highlight_style;
            } else {
                cell_num = .colRow(0, index);
                setShardRowColor(cell_num, &highlight_style, banded_style);
            }
        } else {
            cell_num = .colRow(0, index);
            setShardRowColor(cell_num, &highlight_style, banded_style);
        }

        // Generate all the rows: Active, Etc..
        {
            var cell = grid.bodyCell(@src(), cell_num, highlight_style.cellOptions(cell_num));
            defer cell.deinit();

            const process_active_icon: dvui.ImageSource = .{ .imageFile = .{ .bytes = Icon.active, .name = "active" } };
            const process_inactive_icon: dvui.ImageSource = .{ .imageFile = .{ .bytes = icon_inactive, .name = "inactive" } };
            const icon = if (shard.active) process_active_icon else process_inactive_icon;

            _ = dvui.image(@src(), .{ .source = icon }, .{ .min_size_content = .{ .w = 48, .h = 48 }, .gravity_x = 0.25 });

            cell_num.col_num += 1;
        }

        createShardRow(grid, &cell_num, shard.worldstone, highlight_style);
        createShardRow(grid, &cell_num, shard.name, highlight_style);
        createShardRow(grid, &cell_num, shard.flags, highlight_style);
        createShardRow(grid, &cell_num, shard.notes, highlight_style);
    }

    return null;
}

pub fn createShardRow(
    grid: *GridWidget,
    cell_num: *GridWidget.Cell,
    name: []const u8,
    highlight_style: *CellStyle.HoveredRow,
) void {
    var cell = grid.bodyCell(@src(), cell_num.*, highlight_style.cellOptions(cell_num.*));
    defer cell.deinit();
    dvui.labelNoFmt(@src(), name, .{}, .{ .font = cactus_bold_font.withSize(12), .gravity_y = 0.5 });
    cell_num.col_num += 1;
}

/// This function prepares the color for the row. The logic is a bit complicated
/// and nasty but we need to be able to support alternate row highlighting, while
/// at the same time being able to show the appropriate on hover fill color
/// for when we hover over but haven't selected an entry that's either odd/even
/// row, or if we select a row that's odd/even, it should be rendered properly.
///
/// This can probably be cleaned up and simplified in the future.
pub fn setShardRowColor(
    cell: dvui.GridWidget.Cell,
    highlight_style: **CellStyle.HoveredRow,
    banded_style: GridWidget.CellStyle.Banded,
) void {
    highlight_style.* = &default_highlight_style;
    highlight_style.*.cell_opts = banded_style.cellOptions(cell);

    // Override the "color_fill_hover" if needed.
    const even: bool = cell.row_num % 2 == 0;
    if (!even) {
        highlight_style.*.cell_opts.color_fill_hover = banded_style.cell_opts.color_fill_hover;
        highlight_style.*.cell_opts.color_border = banded_style.cell_opts.color_border;
    }
}

/// This contains the buttons section.
pub fn buttonsSection() ?dvui.App.Result {
    const local = struct {
        // This is the shard that will be used for editing.
        var shard_to_modify: ?*Shard = null;
    };

    var fbox = dvui.flexbox(
        @src(),
        .{ .justify_content = .center },
        .{
            .expand = .horizontal,
            .margin = .{ .y = 10, .h = 10 },
        },
    );
    defer fbox.deinit();

    var button_box = dvui.box(
        @src(),
        .{ .dir = .horizontal },
        .{
            .style = .window,
            .expand = .horizontal,
        },
    );
    defer button_box.deinit();

    if (imageButton(@src(), "Create", Icon.create, .{}, button_options)) {
        // Unset in case someone modified something previously.
        local.shard_to_modify = null;

        should_show_detail_window = true;

        resetFocus();
    }

    if (imageButton(@src(), "Modify", Icon.modify, .{}, button_options)) {
        // Show the edit window, and capture the arguments for the shard to
        // edit. We can only modify the launcher/flags/notes. Modifying the
        // platform and label is not allowed.
        if (currently_selected_row_index) |selected_index| {
            local.shard_to_modify = cactus.settings.getShard(selected_index);
            should_show_detail_window = true;
        } else {
            setShowDialogWindowAndLog("There is no shard selected to modify.", null);
        }

        resetFocus();
    }

    if (imageButton(@src(), "Clone", Icon.clone, .{}, button_options)) {
        if (currently_selected_row_index) |selected_index| {
            const result = cactus.clone(selected_index) catch |e| blk: {
                dialog_error = e;
                break :blk .failed;
            };

            switch (result) {
                .success => updateSelectedRow(selected_index + 1),
                .required_field_missing => setShowDialogWindowAndLog("A required field was missing. Check logs for more info.", null),
                .required_file_missing => setShowDialogWindowAndLog("A required file was missing. Check logs for more info.", null),
                .invalid_character_detected => unreachable,
                .failed => setShowDialogWindowAndLog("There was an error cloning the shard. Check logs for more info.", dialog_error.?),
            }
        } else {
            setShowDialogWindowAndLog("There is no shard selected to clone.", null);
        }
        resetFocus();
    }

    if (imageButton(@src(), "Destroy", Icon.destroy, .{}, button_options)) {
        if (currently_selected_row_index) |selected_index| {
            const result = cactus.destroy(selected_index) catch |e| blk: {
                dialog_error = e;
                break :blk .failed;
            };
            switch (result) {
                .success => {
                    // Ergonomically select the next row.
                    currently_selected_row_index = blk: {
                        if (cactus.settings.getShardsLength() > 0) {
                            if (selected_index == 0) {
                                break :blk 0;
                            } else {
                                break :blk selected_index - 1;
                            }
                        } else {
                            break :blk null;
                        }
                    };

                    // Update settings with the currently selected row index.
                    // TODO: this function is called three times, maybe a func in frontend to simplify this call.
                    _ = cactus.updateSettings(.{ .last_selected_shard = currently_selected_row_index }) catch |e| {
                        log.warn("There was an error updating the settings file with the currently selected row index: {}", .{e});
                    };
                },
                .active_shard => {
                    setShowDialogWindowAndLog("Can't destroy a shard that is currently active.", null);
                },
                .failed => {
                    setShowDialogWindowAndLog("There was an error destroying the shard. Check logs for more info.", dialog_error.?);
                },
            }
        } else {
            setShowDialogWindowAndLog("There is no shard selected to destroy.", null);
        }
        resetFocus();
    }

    if (imageButton(@src(), "Up", Icon.up, .{}, button_options)) {
        if (currently_selected_row_index) |selected_index| {
            const new_index = cactus.shift(selected_index, .up) catch |e| blk: {
                setShowDialogWindowAndLog("There was an error shifting the shard. Check logs for more info.", e);
                break :blk null;
            };

            if (new_index) |index| {
                updateSelectedRow(index);
            }
        } else {
            setShowDialogWindowAndLog("There is no shard selected to shift up.", null);
        }
        resetFocus();
    }

    if (imageButton(@src(), "Down", Icon.down, .{}, button_options)) {
        if (currently_selected_row_index) |selected_index| {
            const new_index = cactus.shift(selected_index, .down) catch |e| blk: {
                setShowDialogWindowAndLog("There was an error shifting the shard. Check logs for more info.", e);
                break :blk null;
            };

            if (new_index) |index| {
                updateSelectedRow(index);
            }
        } else {
            setShowDialogWindowAndLog("There is no shard selected to shift down.", null);
        }
        resetFocus();
    }

    // Reinstalls the Shard Folder. Deletes it if it exists, and copies it.
    if (imageButton(@src(), "Reset", Icon.reset, .{}, button_options)) {
        if (currently_selected_row_index) |selected_index| {
            const result = cactus.reset(selected_index) catch |e| blk: {
                dialog_error = e;
                break :blk .failed;
            };
            switch (result) {
                .success => setShowDialogWindow("The shard has been successfully reset. Your saves have been preserved.", false),
                .active_shard => setShowDialogWindowAndLog("Can't reset a shard that is currently summoned.", null),
                .active_aliased_shard => setShowDialogWindowAndLog("Can't reset a shard that has another aliased shard summoned.", null),
                .failed => setShowDialogWindowAndLog("There was an error resetting the shard. Check logs for more info.", dialog_error.?),
            }
        } else {
            setShowDialogWindowAndLog("There is no shard selected to reset.", null);
        }
        resetFocus();
    }

    if (imageButton(@src(), "Settings", Icon.settings, .{}, button_options)) {
        should_show_settings_window = true;
        resetFocus();
    }

    if (imageButton(@src(), "Backup", Icon.backup, .{}, button_options)) {
        if (currently_selected_row_index) |selected_index| {
            const full_path_to_backup = cactus.backup(selected_index) catch |e| blk: {
                setShowDialogWindowAndLog("There was an error backing up the shard. Check logs for more info.", e);
                break :blk null;
            };

            if (full_path_to_backup != null) {
                defer cactus.gpa.free(full_path_to_backup.?);
                const message: ?[]const u8 = std.fmt.allocPrint(cactus.gpa, "Backup was successfully created at:\n\n{s}", .{full_path_to_backup.?}) catch |e| blk: {
                    setShowDialogWindowAndLog("There was allocating memory for backup path message. Check logs for more info.", e);
                    break :blk null;
                };

                if (message != null) {
                    // TODO: figure out a better way to pass heap memory.
                    // not freeing message here since we need it. Will be freed later.
                    setShowDialogWindow(message.?, true);
                }
            }
        } else {
            setShowDialogWindowAndLog("There is no shard selected to backup.", null);
        }
        resetFocus();
    }

    if (imageButton(@src(), "Summon", Icon.summon, .{}, button_options)) {
        if (currently_selected_row_index) |selected_index| {
            cactus.summon(selected_index) catch |e| {
                setShowDialogWindowAndLog("There was an error summoning the shard. Check logs for more info.", e);
            };
        } else {
            setShowDialogWindowAndLog("There is no shard selected to summon.", null);
        }
        resetFocus();
    }

    if (imageButton(@src(), "About", Icon.about, .{}, button_options)) {
        log.debug("About\n", .{});
        should_show_about_window = true;
        resetFocus();
    }

    if (imageButton(@src(), "Donate", Icon.donate, .{}, button_options)) {
        _ = dvui.openURL(.{ .url = "https://liberapay.com/fearedbliss/donate", .new_window = true });
        resetFocus();
    }

    // Show any window needed to be shown. Only one will be active at any given
    // time given the sequential nature of the application.
    showDetailWindowIfNeeded(local.shard_to_modify);
    showAboutWindowIfNeeded();
    showSettingsWindowIfNeeded();
    showDialogWindowIfNeeded();

    return null;
}

fn showDetailWindowIfNeeded(shard: ?*Shard) void {
    if (!should_show_detail_window) return;
    stopProcessingGridEvents();

    const local = struct {
        var was_enter_pressed: bool = false;
        var was_escape_pressed: bool = false;
    };

    const in_create_mode = if (shard == null) true else false;

    const window = dvui.floatingWindow(@src(), .{
        .modal = true,
        .resize = .none,
        .open_flag = &should_show_about_window,
    }, .{
        .expand = .both,
        .min_size_content = .{ .w = 400, .h = 420 },
        .color_border = separator_color,
    });
    defer window.deinit();

    var vb = dvui.box(@src(), .{ .dir = .vertical }, .{
        .style = .window,
        .background = true,
        .expand = .both,
        .color_fill = window_base_color,
        .padding = .all(20),
    });
    defer vb.deinit();

    var worldstone: []const u8 = "";
    var name: []const u8 = "";
    var launcher: []const u8 = "";
    var flags: []const u8 = "";
    var notes: []const u8 = "";

    if (in_create_mode) {
        EntryRow(0, "Worldstone *", null, "", &worldstone, false, &local.was_enter_pressed, true);
        EntryRow(1, "Shard *", null, "", &name, false, &local.was_enter_pressed, true);
        EntryRow(2, "Launcher *", null, "Game.exe", &launcher, false, &local.was_enter_pressed, true);
        EntryRow(3, "Flags", null, "", &flags, false, &local.was_enter_pressed, true);
        EntryRow(4, "Notes", null, "", &notes, false, &local.was_enter_pressed, true);
    } else {
        EntryRow(0, "Worldstone *", shard.?.worldstone, "", null, true, &local.was_enter_pressed, false);
        EntryRow(1, "Shard *", shard.?.name, "", null, true, &local.was_enter_pressed, false);
        EntryRow(2, "Launcher *", shard.?.launcher, "Game.exe", &launcher, false, &local.was_enter_pressed, false);
        EntryRow(3, "Flags", shard.?.flags, "", &flags, false, &local.was_enter_pressed, false);
        EntryRow(4, "Notes", shard.?.notes, "", &notes, false, &local.was_enter_pressed, false);
    }

    monitorEscapeKeyEvent(&local.was_escape_pressed);

    const confirm_button_name = if (in_create_mode) "Create" else "Modify";

    const hb = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal, .margin = .{ .y = 10 } });
    defer hb.deinit();

    if (dvui.button(@src(), confirm_button_name, .{}, button_options.override(.{ .gravity_x = 0.3 })) or local.was_enter_pressed) {
        local.was_enter_pressed = false;

        log.info("Worldstone: {s}", .{worldstone});
        log.info("Shard: {s}", .{name});

        // Default
        if (std.mem.eql(u8, std.mem.trim(u8, launcher, " "), "")) {
            launcher = "Game.exe";
            log.info("Launcher (Default): {s}", .{launcher});
        } else {
            log.info("Launcher: {s}", .{launcher});
        }

        log.info("Flags: {s}", .{flags});
        log.info("Notes: {s}", .{notes});

        if (in_create_mode) {
            createShardIfNeeded(worldstone, name, launcher, flags, notes);
        } else {
            modifyShardIfNeeded(launcher, flags, notes);
        }
    }

    if (dvui.button(@src(), "Cancel", .{}, button_options.override(.{ .gravity_x = 0.65 })) or local.was_escape_pressed) {
        local.was_escape_pressed = false;
        should_show_detail_window = false;
        startProcessingGridEvents();
    }
}

fn showSettingsWindowIfNeeded() void {
    if (!should_show_settings_window) return;
    stopProcessingGridEvents();

    const local = struct {
        var was_enter_pressed: bool = false;
        var was_escape_pressed: bool = false;
        var use_wayland = true;
        var show_stderr = false;
        var enable_automatic_backups = true;
        var wine_dll_overrides: []const u8 = "";
        var backup_path: ?[]const u8 = null;
        var should_enable_running: bool = false;
        var should_hide_mini_panel: bool = false;
        var should_hide_help_menu_button: bool = false;
        var use_dark_mode: bool = false;
    };

    const window = dvui.floatingWindow(@src(), .{
        .modal = true,
        .resize = .none,
        .open_flag = &should_show_about_window,
    }, .{
        .expand = .both,
        .min_size_content = .{ .w = 400, .h = 255 },
        .color_border = separator_color,
    });
    defer window.deinit();

    var box = dvui.box(@src(), .{ .dir = .vertical }, .{
        .style = .window,
        .background = true,
        .expand = .both,
        .color_fill = window_base_color,
        .padding = .all(20),
    });
    defer box.deinit();

    monitorEscapeKeyEvent(&local.was_escape_pressed);

    {
        _ = dvui.label(@src(), "Backup Path", .{}, .{ .gravity_x = 0.5, .font = cactus_bold_font });

        var te: *dvui.TextEntryWidget = dvui.textEntry(@src(), .{ .placeholder = cactus.path_builder.getBackupsPath() }, .{ .expand = .horizontal });
        defer te.deinit();
        if (dvui.firstFrame(te.data().id)) {
            if (cactus.settings.backup_path) |backup_path| {
                te.textSet(backup_path, false);
            }

            // Focus this box on window creation.
            dvui.focusWidget(te.data().id, null, null);
        }

        local.backup_path = te.textGet();

        if (local.was_enter_pressed != true and te.enter_pressed) local.was_enter_pressed = true;
    }
    _ = dvui.separator(@src(), .{ .expand = .horizontal, .color_fill = separator_color, .margin = .{ .y = 20, .h = 10 } });
    {
        _ = dvui.label(@src(), "Wine DLL Overrides", .{}, .{ .gravity_x = 0.5, .font = cactus_bold_font });

        var te: *dvui.TextEntryWidget = dvui.textEntry(@src(), .{}, .{ .expand = .horizontal });
        defer te.deinit();
        if (dvui.firstFrame(te.data().id)) {
            if (cactus.settings.wine_dll_overrides) |wine_dll_overrides| {
                te.textSet(wine_dll_overrides, false);
            } else {
                te.textSet("", false);
            }
        }

        local.wine_dll_overrides = te.textGet();

        if (local.was_enter_pressed != true and te.enter_pressed) local.was_enter_pressed = true;
    }
    _ = dvui.separator(@src(), .{ .expand = .horizontal, .color_fill = separator_color, .margin = .{ .y = 20, .h = 10 } });
    {
        var gbox = dvui.groupBox(@src(), "Options", .{ .expand = .horizontal, .font = cactus_bold_font });
        defer gbox.deinit();

        if (dvui.firstFrame(gbox.data().id)) {
            local.use_wayland = cactus.settings.use_wayland;
            local.show_stderr = cactus.settings.show_stderr;
            local.enable_automatic_backups = cactus.settings.enable_automatic_backups;
            local.use_dark_mode = cactus.settings.use_dark_mode;
        }

        // All checkboxes need to have color_fill set so that the hover color
        // matches the light/dark mode theme. For whatever reason it isn't
        // automatically picking it up even though the theme is dynamically set.
        // Explicitly overriding hover colors for this control at runtime.
        _ = dvui.checkbox(@src(), &local.use_wayland, "Use Wine Wayland Driver", cactus_common_font_options.override(.{ .color_fill = checkbox_hover_color }));
        _ = dvui.checkbox(@src(), &local.show_stderr, "Show Standard Error Messages", cactus_common_font_options.override(.{ .color_fill = checkbox_hover_color }));
        _ = dvui.checkbox(@src(), &local.enable_automatic_backups, "Enable Automatic Backups", cactus_common_font_options.override(.{ .color_fill = checkbox_hover_color }));
        _ = dvui.checkbox(@src(), &local.use_dark_mode, "Use Dark Mode", cactus_common_font_options.override(.{ .color_fill = checkbox_hover_color }));
    }
    _ = dvui.separator(@src(), .{ .expand = .horizontal, .color_fill = separator_color, .margin = .{ .y = 20, .h = 10 } });
    {
        var gbox = dvui.groupBox(@src(), "Shard Reset Defaults", .{ .expand = .horizontal, .font = cactus_bold_font });
        defer gbox.deinit();

        if (dvui.firstFrame(gbox.data().id)) {
            local.should_enable_running = cactus.settings.should_enable_running;
            local.should_hide_mini_panel = cactus.settings.should_hide_mini_panel;
            local.should_hide_help_menu_button = cactus.settings.should_hide_help_menu_button;
        }

        _ = dvui.checkbox(@src(), &local.should_enable_running, "Enable Running", cactus_common_font_options.override(.{ .color_fill = checkbox_hover_color }));
        _ = dvui.checkbox(@src(), &local.should_hide_mini_panel, "Hide Mini Panel", cactus_common_font_options.override(.{ .color_fill = checkbox_hover_color }));
        _ = dvui.checkbox(@src(), &local.should_hide_help_menu_button, "Hide Help Menu Button", cactus_common_font_options.override(.{ .color_fill = checkbox_hover_color }));
    }

    var button_box = dvui.box(@src(), .{ .dir = .horizontal }, .{ .expand = .horizontal, .margin = .{ .y = 10 } });
    defer button_box.deinit();

    if (dvui.button(@src(), "Save", .{}, button_options.override(.{ .gravity_x = 0.3 })) or local.was_enter_pressed) {
        local.was_enter_pressed = false;

        log.debug("Use Wine Wayland Driver: {}", .{local.use_wayland});
        log.debug("Show Standard Error Messages: {}", .{local.show_stderr});
        log.debug("Enable Automatic Backups: {}", .{local.enable_automatic_backups});
        log.debug("Wine DLL Overrides: {s}", .{local.wine_dll_overrides});
        log.debug("Backup Path: {s}", .{local.backup_path.?});
        log.debug("Enable Running: {}", .{local.should_enable_running});
        log.debug("Hide Mini Panel: {}", .{local.should_hide_mini_panel});
        log.debug("Hide Help Menu Button: {}", .{local.should_hide_help_menu_button});
        log.debug("Use Dark Mode: {}", .{local.use_dark_mode});

        const result = cactus.updateSettings(.{
            .use_wayland = local.use_wayland,
            .show_stderr = local.show_stderr,
            .enable_automatic_backups = local.enable_automatic_backups,
            .wine_dll_overrides = local.wine_dll_overrides,
            .backup_path = local.backup_path,
            .should_enable_running = local.should_enable_running,
            .should_hide_mini_panel = local.should_hide_mini_panel,
            .should_hide_help_menu_button = local.should_hide_help_menu_button,
            .use_dark_mode = local.use_dark_mode,
        }) catch |e| blk: {
            dialog_error = e;
            break :blk .failed;
        };

        switch (result) {
            .success => {
                setupTheme();
                should_show_settings_window = false;
                startProcessingGridEvents();
            },
            .not_absolute_path => {
                setShowDialogWindowAndLog("The backup path specified is not an absolute path or relies on shell expansion.\n\nValid: /home/jon/Backups\nInvalid: Backups or ~/Backups", null);
            },
            .access_denied => {
                setShowDialogWindowAndLog("You do not have write permissions for the backup path.", null);
            },
            .failed => {
                setShowDialogWindowAndLog("There was an error while saving settings. Check logs for more info.", dialog_error.?);
            },
        }
    }

    if (dvui.button(@src(), "Cancel", .{}, button_options.override(.{ .gravity_x = 0.62 })) or local.was_escape_pressed) {
        local.was_escape_pressed = false;
        should_show_settings_window = false;
        startProcessingGridEvents();
    }
}

fn createShardIfNeeded(
    worldstone: []const u8,
    name: []const u8,
    launcher: []const u8,
    flags: []const u8,
    notes: []const u8,
) void {
    const result = cactus.create(
        worldstone,
        name,
        launcher,
        flags,
        notes,
        null,
    ) catch |e| blk: {
        dialog_error = e;
        break :blk .failed;
    };

    switch (result) {
        .success => {
            should_show_detail_window = false;
            startProcessingGridEvents();
            updateSelectedRow(null);
        },
        .required_field_missing => setShowDialogWindowAndLog("A required field was missing. Check logs for more info.", null),
        .required_file_missing => setShowDialogWindowAndLog("A required file was missing. Check logs for more info.", null),
        .invalid_character_detected => setShowDialogWindowAndLog(invalid_chars_message, null),
        .failed => setShowDialogWindowAndLog("There was an error creating the shard. Check logs for more info.", dialog_error.?),
    }
}

fn modifyShardIfNeeded(launcher: []const u8, flags: []const u8, notes: []const u8) void {
    const result = cactus.modify(currently_selected_row_index.?, launcher, flags, notes) catch |e| blk: {
        dialog_error = e;
        break :blk .failed;
    };

    switch (result) {
        .success => {
            should_show_detail_window = false;
            startProcessingGridEvents();
        },
        .required_field_missing => setShowDialogWindowAndLog("A required field was missing. Check logs for more info.", null),
        .invalid_character_detected => setShowDialogWindowAndLog(invalid_chars_message, null),
        .failed => setShowDialogWindowAndLog("There was an error modifying the shard. Check logs for more info.", dialog_error.?),
    }
}

fn showAboutWindowIfNeeded() void {
    if (!should_show_about_window) return;
    stopProcessingGridEvents();

    const local = struct {
        var was_escape_pressed: bool = false;
        var col_widths: [2]f32 = @splat(0);
    };

    const window = dvui.floatingWindow(@src(), .{
        .modal = true,
        .resize = .none,
        .open_flag = &should_show_about_window,
    }, .{
        .expand = .both,
        .min_size_content = .{ .h = 320, .w = 370 },
        .color_border = separator_color,
    });
    defer window.deinit();

    var grid = dvui.grid(@src(), .colWidths(&local.col_widths), .{}, .{
        .style = .window,
        .background = true,
        .expand = .both,
        .color_fill = window_base_color,
        .padding = .all(20),
    });
    defer grid.deinit();

    dvui.columnLayoutProportional(&.{ 100, -1 }, &local.col_widths, grid.data().contentRect().w);

    LinkRow(grid, 0, "Author", "Jonathan Vasquez (fearedbliss)", "https://xyinn.org/");
    LabelRow(grid, 1, "Version", version);
    LinkRow(grid, 2, "License", "Simplified BSD License", "https://opensource.org/license/bsd-2-clause");
    LinkRow(grid, 3, "Home", "Cactus", "https://codeberg.org/fearedbliss/Cactus");
    LinkRow(grid, 4, "Forum", "The Moving Caravan", "https://themovingcaravan.com/");
    LinkRow(grid, 5, "Mods", "Project Sunrise", "https://codeberg.org/fearedbliss/Cactus/src/branch/main/Sunrise/README.md");

    monitorEscapeKeyEvent(&local.was_escape_pressed);

    var cell_num: dvui.GridWidget.Cell = .colRow(0, 6);
    {
        {
            // Empty cell to simulate "cellspan=2"
            var cell = grid.bodyCell(@src(), cell_num, .{});
            defer cell.deinit();
            cell_num.col_num += 1;
        }
        {
            var cell = grid.bodyCell(@src(), cell_num, .{});
            defer cell.deinit();
            if (focusedButton(@src(), "Close", .{}, button_options.override(.{ .gravity_x = 0.15 })) or local.was_escape_pressed) {
                local.was_escape_pressed = false;
                should_show_about_window = false;
                startProcessingGridEvents();
            }
        }
    }
}

// Component for a row in a grid with a Label and another label.
pub fn LabelRow(
    grid: *dvui.GridWidget,
    index: usize,
    label: []const u8,
    text: []const u8,
) void {
    var cell_num: dvui.GridWidget.Cell = .colRow(0, index);
    {
        var cell = grid.bodyCell(@src(), cell_num, .{});
        defer cell.deinit();
        dvui.labelNoFmt(@src(), label, .{}, cactus_common_font_options);
    }
    cell_num.col_num += 1;
    {
        var cell = grid.bodyCell(@src(), cell_num, .{});
        defer cell.deinit();
        dvui.labelNoFmt(@src(), text, .{}, cactus_common_font_options);
    }
}

// Component for a row in a grid with a Label and a Link.
pub fn LinkRow(
    grid: *dvui.GridWidget,
    index: usize,
    label: []const u8,
    name: []const u8,
    url: []const u8,
) void {
    var cell_num: dvui.GridWidget.Cell = .colRow(0, index);
    {
        var cell = grid.bodyCell(@src(), cell_num, .{});
        defer cell.deinit();
        dvui.labelNoFmt(@src(), label, .{}, cactus_common_font_options);
    }
    cell_num.col_num += 1;
    {
        var cell = grid.bodyCell(@src(), cell_num, .{});
        defer cell.deinit();
        dvui.link(@src(), .{ .label = name, .url = url }, .{ .font = cactus_bold_font, .tab_index = 0 });
    }
}

// Component for a row in a vertical box with a Label and a text entry (or label
// if read only. Writes to buffer.
pub fn EntryRow(
    index: usize,
    label: []const u8,
    name: ?[]const u8,
    placeholder: []const u8,
    buffer: ?*[]const u8,
    readonly: bool,
    was_enter_pressed: *bool,
    in_create_mode: bool,
) void {
    dvui.labelNoFmt(@src(), label, .{}, .{ .font = cactus_bold_font, .id_extra = index });

    var te: *dvui.TextEntryWidget = undefined;

    if (!readonly) {
        te = dvui.textEntry(@src(), .{ .placeholder = placeholder }, .{ .expand = .horizontal, .id_extra = index });

        if (dvui.firstFrame(te.data().id)) {
            if (name != null) {
                te.textSet(name.?, false);
            }

            // Draw focus in create mode to the first box (Worldstone).
            if (in_create_mode and index == 0) dvui.focusWidget(te.data().id, null, null);

            // Draw focus in edit mode to the third box (Launcher)
            if (!in_create_mode and index == 2) dvui.focusWidget(te.data().id, null, null);
        }

        buffer.?.* = te.textGet();

        // Only let this go one direction so that any text entry can set it to
        // true but they can't "reset" it back to false. Only the Create/Modify
        // logic should set this event back to false. This gives the immediate
        // UI time to process that an [Enter] press happened and take appropriate
        // action.
        if (was_enter_pressed.* != true and te.enter_pressed) {
            was_enter_pressed.* = true;
        }
    } else {
        // Simulate disabled text entry by not processing any events for it, removing it
        // from tab index, and changing background color to a grayish tone.
        te = dvui.widgetAlloc(dvui.TextEntryWidget);
        te.init(@src(), .{}, .{
            .expand = .horizontal,
            .tab_index = 0,
            .id_extra = index,
            .color_fill = disabled_color,
            .font = cactus_bold_font,
        });
        if (dvui.firstFrame(te.data().id)) {
            te.textSet(name.?, false);
        }
        te.draw();
    }

    defer te.deinit();
}

pub fn updateSelectedRow(target_index: ?usize) void {
    if (target_index) |index| {
        currently_selected_row_index = index;
    } else {
        currently_selected_row_index = cactus.settings.getShardsLength() - 1;
    }

    // Signal that the grid box viewport needs to be auto scrolled.
    should_scroll_shards_list = true;

    // Update settings with the currently selected row index.
    _ = cactus.updateSettings(.{ .last_selected_shard = currently_selected_row_index }) catch |e| {
        log.warn("There was an error updating the settings file with the currently selected row index: {}", .{e});
    };
}

// There is some difficulty with getting a floating window to displayed from
// within the button region, even if I try to pass the parent window's src().
// This also makes passing arguments more difficult since I would like to pass
// comptime values directly but need to use a global instead, due to that, I
// can't log because logging requires a comptime value. We will simplify the
// logic for the consumers requesting dialog information to be shown by collecting
// the info at the point it happens, and separately doing our operations in here.
pub fn setShowDialogWindowAndLog(comptime message: []const u8, captured_error: ?anyerror) void {
    log.warn(message, .{});

    if (captured_error) |ce| {
        log.warn("{}", .{ce});
    }

    // Capture the dialog message to display in the UI.
    dialog_message = message;

    // Signal that we should show the dialog window during the next frame.
    should_show_dialog_window = true;
}

pub fn setShowDialogWindow(message: []const u8, free_memory_when_done: bool) void {
    // Capture the dialog message to display in the UI.
    dialog_message = message;

    // Signal that we should show the dialog window during the next frame.
    should_show_dialog_window = true;

    should_free_dialog_message_when_done = free_memory_when_done;
}

pub fn showDialogWindowIfNeeded() void {
    if (!should_show_dialog_window) return;
    stopProcessingGridEvents();

    const local = struct {
        var was_escape_pressed: bool = false;
    };

    var window = dvui.floatingWindow(
        @src(),
        .{
            .open_flag = &should_show_dialog_window,
            .resize = .none,
            .modal = true,
        },
        .{
            .min_size_content = .{ .w = 300, .h = 100 },
            .max_size_content = .{ .w = 600, .h = 400 },
            .color_border = separator_color,
        },
    );
    defer window.deinit();

    var fbox = dvui.flexbox(
        @src(),
        .{ .justify_content = .center },
        .{
            .expand = .both,
            .margin = .{ .x = 20, .y = 20, .w = 20, .h = 10 },
        },
    );
    defer fbox.deinit();

    monitorEscapeKeyEvent(&local.was_escape_pressed);

    var box = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .vertical, .max_size_content = .{ .w = 550, .h = 400 } });
    defer box.deinit();

    {
        var tl = dvui.textLayout(@src(), .{}, .{ .expand = .both, .background = false });
        defer tl.deinit();
        tl.addText(dialog_message, cactus_common_font_options);
    }
    {
        if (focusedButton(@src(), "Ok", .{}, button_options.override(.{ .gravity_x = 0.5 })) or local.was_escape_pressed) {
            if (should_free_dialog_message_when_done) {
                should_free_dialog_message_when_done = false;
                cactus.gpa.free(dialog_message);
            }
            local.was_escape_pressed = false;
            should_show_dialog_window = false;
            startProcessingGridEvents();
            dialog_message = "";
        }
    }
}

fn stopProcessingGridEvents() void {
    stop_processing_events_in_grid = true;
}

fn startProcessingGridEvents() void {
    stop_processing_events_in_grid = false;
}

fn monitorEscapeKeyEvent(was_escape_pressed: *bool) void {
    const evts = dvui.events();
    for (evts) |e| {
        if (e.evt == .key and e.evt.key.code == .escape and e.evt.key.action == .down) {
            log.debug("! Escape Key Pressed !", .{});
            was_escape_pressed.* = true;
        }
    }
}

fn monitorKeyEvents(was_up_pressed: *bool, was_down_pressed: *bool, was_enter_pressed: *bool) void {
    if (stop_processing_events_in_grid) return;

    const evts = dvui.events();
    for (evts) |e| {
        if (e.evt == .key and e.evt.key.code == .up and e.evt.key.action == .down) {
            log.debug("! Up Key Pressed !", .{});
            was_up_pressed.* = true;
        } else if (e.evt == .key and e.evt.key.code == .down and e.evt.key.action == .down) {
            log.debug("! Down Key Pressed !", .{});
            was_down_pressed.* = true;
        } else if (e.evt == .key and e.evt.key.code == .enter and e.evt.key.action == .down) {
            log.debug("! Enter Key Pressed !", .{});
            was_enter_pressed.* = true;
        }
    }
}

/// Shows a critical failure message. The application will terminate after this.
///
/// This logic is essentially the same as a regular show dialog but the Ok
/// button will terminate the app and the conditions for it being displayed
/// and variables accessed internally are different.
pub fn showEarlyBootFailureMessage(comptime message: []const u8) void {
    if (!did_early_boot_fail) return;

    const local = struct {
        var was_escape_pressed: bool = false;
        const min_width = 400;
        const max_width = 600;
    };

    var window = dvui.floatingWindow(
        @src(),
        .{
            .open_flag = &did_early_boot_fail,
            .resize = .none,
            .modal = true,
        },
        .{
            .min_size_content = .{ .h = 105, .w = local.min_width },
            .max_size_content = .{ .h = 300, .w = local.max_width },
            .color_border = separator_color,
        },
    );
    defer window.deinit();

    var fbox = dvui.flexbox(
        @src(),
        .{ .justify_content = .center },
        .{
            .expand = .both,
            .margin = .{ .x = 20, .y = 20, .w = 20, .h = 10 },
        },
    );
    defer fbox.deinit();

    monitorEscapeKeyEvent(&local.was_escape_pressed);

    var box = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, .max_size_content = .width(local.max_width - 100) });
    defer box.deinit();

    {
        var tl = dvui.textLayout(@src(), .{}, .{ .expand = .both, .background = false });
        defer tl.deinit();
        tl.addText(message, cactus_common_font_options);
    }
    {
        if (focusedButton(@src(), "Exit", .{}, button_options.override(.{ .gravity_x = 0.5 })) or local.was_escape_pressed) {
            local.was_escape_pressed = false;
            std.process.exit(1);
        }
    }
}

pub fn imageButton(src: std.builtin.SourceLocation, comptime label_str: []const u8, icon_bytes: []const u8, init_opts: dvui.ButtonWidget.InitOptions, opts: Options) bool {
    // initialize widget and get rectangle from parent and make ourselves the new parent
    var ttout: dvui.WidgetData = undefined;
    var bw: dvui.ButtonWidget = undefined;

    bw.init(src, init_opts, opts.override(.{ .data_out = &ttout, .tab_index = 0 }));
    dvui.tooltip(@src(), .{ .active_rect = ttout.borderRectScale().r }, label_str, .{}, .{ .color_border = separator_color });

    // process events (mouse and keyboard)
    bw.processEvents();

    // draw icon
    const icon: dvui.ImageSource = .{ .imageFile = .{ .bytes = icon_bytes } };
    _ = dvui.image(@src(), .{ .source = icon }, .{ .min_size_content = .{ .w = 48, .h = 48 } });

    // use pressed text color if desired
    const click = bw.clicked();

    // draw focus
    //bw.drawFocus();

    // restore previous parent
    // send our min size to parent
    bw.deinit();

    return click;
}

// NOTE: It currently seems difficult to get a button to be focused in DVUI
// through the normal API. All we need is to get the id of the internally created
// "bw" object so that we can call focusWidget on it. Subclassing (copy/pasting)
// the button logic so that we can explicit call focusWidget on the internal bw id.
pub fn focusedButton(src: std.builtin.SourceLocation, label_str: []const u8, init_opts: dvui.ButtonWidget.InitOptions, opts: Options) bool {
    // initialize widget and get rectangle from parent and make ourselves the new parent
    var bw: dvui.ButtonWidget = undefined;
    bw.init(src, init_opts, opts);

    // process events (mouse and keyboard)
    bw.processEvents();

    // draw background/border
    bw.drawBackground();

    // use pressed text color if desired
    const click = bw.clicked();

    // this child widget:
    // - has bw as parent
    // - gets a rectangle from bw
    // - draws itself
    // - reports its min size to bw
    dvui.labelNoFmt(@src(), label_str, .{ .align_x = 0.5, .align_y = 0.5 }, opts.strip().override(bw.style()).override(.{ .gravity_x = 0.5, .gravity_y = 0.5 }));

    // Focus Widget
    dvui.focusWidget(bw.data().id, null, null);

    // draw focus
    bw.drawFocus();

    // restore previous parent
    // send our min size to parent
    bw.deinit();

    return click;
}

// Removes focus from whatever widget we are currently selecting.
fn resetFocus() void {
    dvui.currentWindow().focusWidget(null, null, null);
}

// Frontend Related Strings

const invalid_chars_message =
    \\ Invalid characters detected! Only the following characters are allowed,
    \\ with the exception of the note, it can contain any value. Check logs
    \\ for more info.
    \\
    \\ Allowed Characters:
    \\
    \\ (Leslie) In (Space) _ Ok -- Cool 123's Cool .. -- __ ' cool '
;

// -----------------------------------------------------------------------------

// TODO: Need to override app id and app version (dvui.App.config.startFn).
//       No way to inject these via StartOptions struct.

// TODO: implement configurable log level from cli (or maybe even from UI?)
