const std = @import("std");
const builtin = @import("builtin");
const assert = @import("../../quirks.zig").inlineAssert;
const Allocator = std.mem.Allocator;
const cimgui = @import("dcimgui");
const widgets = @import("../widgets.zig");
const units = @import("../units.zig");
const terminal = @import("../../terminal/main.zig");
const stylepkg = @import("../../terminal/style.zig");

/// Window names for the screen dockspace.
const window_info = "Info";
const window_cell = "Cell";
const window_pagelist = "PageList";

/// Screen information inspector widget.
pub const Info = struct {
    pagelist: widgets.pagelist.Inspector,
    cell_chooser: widgets.pagelist.CellChooser,

    pub const empty: Info = .{
        .pagelist = .empty,
        .cell_chooser = .empty,
    };

    /// Draw the screen info contents.
    pub fn draw(self: *Info, open: bool, data: struct {
        /// The screen that we're inspecting.
        screen: *terminal.Screen,

        /// Which screen key we're viewing.
        key: terminal.ScreenSet.Key,

        /// Which screen is active (primary or alternate).
        active_key: terminal.ScreenSet.Key,

        /// Whether xterm modify other keys mode 2 is enabled.
        modify_other_keys_2: bool,

        /// Color palette for cursor color resolution.
        color_palette: *const terminal.color.DynamicPalette,
    }) void {
        // Create the dockspace for this screen
        const dockspace_id = cimgui.c.ImGui_GetID("Screen Dockspace");
        _ = createDockSpace(dockspace_id);

        const screen = data.screen;

        // Info window
        info: {
            defer cimgui.c.ImGui_End();
            if (!cimgui.c.ImGui_Begin(
                window_info,
                null,
                cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
            )) break :info;

            if (cimgui.c.ImGui_CollapsingHeader(
                "Cursor",
                cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
            )) {
                cursorTable(&screen.cursor);
                cimgui.c.ImGui_Separator();
                cursorStyle(
                    &screen.cursor,
                    &data.color_palette.current,
                );
            }

            if (cimgui.c.ImGui_CollapsingHeader(
                "Keyboard",
                cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
            )) keyboardTable(
                screen,
                data.modify_other_keys_2,
            );

            if (cimgui.c.ImGui_CollapsingHeader(
                "Semantic Prompt",
                cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
            )) semanticPromptTable(&screen.semantic_prompt);

            if (cimgui.c.ImGui_CollapsingHeader(
                "Kitty Graphics",
                cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
            )) kittyGraphicsTable(&screen.kitty_images);

            if (cimgui.c.ImGui_CollapsingHeader(
                "Other Screen State",
                cimgui.c.ImGuiTreeNodeFlags_DefaultOpen,
            )) internalStateTable(screen);
        }

        // Cell window
        cell: {
            defer cimgui.c.ImGui_End();
            if (!cimgui.c.ImGui_Begin(
                window_cell,
                null,
                cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
            )) break :cell;
            self.cell_chooser.draw(&screen.pages);
        }

        // PageList window
        pagelist: {
            defer cimgui.c.ImGui_End();
            if (!cimgui.c.ImGui_Begin(
                window_pagelist,
                null,
                cimgui.c.ImGuiWindowFlags_NoFocusOnAppearing,
            )) break :pagelist;
            self.pagelist.draw(&screen.pages);
        }

        // The remainder is the open state
        if (!open) return;

        // Show warning if viewing an inactive screen
        if (data.key != data.active_key) {
            cimgui.c.ImGui_TextColored(
                .{ .x = 1.0, .y = 0.8, .z = 0.0, .w = 1.0 },
                "⚠ Viewing inactive screen",
            );
            cimgui.c.ImGui_Separator();
        }
    }

    /// Create the dock space for the screen inspector. This creates
    /// a dedicated dock space for the screen inspector windows. But they
    /// can of course be undocked and moved around as desired.
    fn createDockSpace(dockspace_id: cimgui.c.ImGuiID) bool {
        // Check if we need to set up the dockspace
        const setup = cimgui.ImGui_DockBuilderGetNode(dockspace_id) == null;

        if (setup) {
            // Register our dockspace node
            assert(cimgui.ImGui_DockBuilderAddNodeEx(
                dockspace_id,
                cimgui.ImGuiDockNodeFlagsPrivate.DockSpace,
            ) == dockspace_id);

            // Dock windows into the space
            cimgui.ImGui_DockBuilderDockWindow(window_info, dockspace_id);
            cimgui.ImGui_DockBuilderDockWindow(window_cell, dockspace_id);
            cimgui.ImGui_DockBuilderDockWindow(window_pagelist, dockspace_id);
            cimgui.ImGui_DockBuilderFinish(dockspace_id);
        }

        // Create the dockspace
        assert(cimgui.c.ImGui_DockSpaceEx(
            dockspace_id,
            .{ .x = 0, .y = 0 },
            cimgui.c.ImGuiDockNodeFlags_None,
            null,
        ) == dockspace_id);
        return setup;
    }
};

/// Render cursor state with a table of cursor-specific fields.
pub fn cursorTable(
    cursor: *const terminal.Screen.Cursor,
) void {
    if (!cimgui.c.ImGui_BeginTable(
        "table_cursor",
        2,
        cimgui.c.ImGuiTableFlags_None,
    )) return;
    defer cimgui.c.ImGui_EndTable();

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Position (x, y)");
    cimgui.c.ImGui_SameLine();
    widgets.helpMarker("The current cursor position in the terminal grid (0-indexed).");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("(%d, %d)", cursor.x, cursor.y);

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Hyperlink");
    cimgui.c.ImGui_SameLine();
    widgets.helpMarker("The active OSC8 hyperlink for newly printed characters.");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    if (cursor.hyperlink) |link| {
        cimgui.c.ImGui_Text("%.*s", link.uri.len, link.uri.ptr);
    } else {
        cimgui.c.ImGui_TextDisabled("(none)");
    }

    {
        cimgui.c.ImGui_TableNextRow();
        _ = cimgui.c.ImGui_TableSetColumnIndex(0);
        cimgui.c.ImGui_Text("Pending Wrap");
        cimgui.c.ImGui_SameLine();
        widgets.helpMarker("The 'last column flag' (LCF). If set, the next character will force a soft-wrap to the next line.");
        _ = cimgui.c.ImGui_TableSetColumnIndex(1);
        var value: bool = cursor.pending_wrap;
        _ = cimgui.c.ImGui_Checkbox("##pending_wrap", &value);
    }

    {
        cimgui.c.ImGui_TableNextRow();
        _ = cimgui.c.ImGui_TableSetColumnIndex(0);
        cimgui.c.ImGui_Text("Protected");
        cimgui.c.ImGui_SameLine();
        widgets.helpMarker("If enabled, new characters will have the protected attribute set, preventing erasure by certain sequences.");
        _ = cimgui.c.ImGui_TableSetColumnIndex(1);
        var value: bool = cursor.protected;
        _ = cimgui.c.ImGui_Checkbox("##protected", &value);
    }
}

/// Render cursor style information using the shared style table.
pub fn cursorStyle(cursor: *const terminal.Screen.Cursor, palette: ?*const terminal.color.Palette) void {
    widgets.style.table(cursor.style, palette);
}

/// Render keyboard information with a table.
fn keyboardTable(
    screen: *const terminal.Screen,
    modify_other_keys_2: bool,
) void {
    if (!cimgui.c.ImGui_BeginTable(
        "table_keyboard",
        2,
        cimgui.c.ImGuiTableFlags_None,
    )) return;
    defer cimgui.c.ImGui_EndTable();

    const kitty_flags = screen.kitty_keyboard.current();

    {
        cimgui.c.ImGui_TableNextRow();
        {
            _ = cimgui.c.ImGui_TableSetColumnIndex(0);
            cimgui.c.ImGui_Text("Mode");
        }
        {
            _ = cimgui.c.ImGui_TableSetColumnIndex(1);
            const mode = if (kitty_flags.int() != 0) "kitty" else "legacy";
            cimgui.c.ImGui_Text("%s", mode.ptr);
        }
    }

    if (kitty_flags.int() != 0) {
        const Flags = @TypeOf(kitty_flags);
        inline for (@typeInfo(Flags).@"struct".fields) |field| {
            {
                const value = @field(kitty_flags, field.name);

                cimgui.c.ImGui_TableNextRow();
                {
                    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
                    const field_name = std.fmt.comptimePrint("{s}", .{field.name});
                    cimgui.c.ImGui_Text("%s", field_name.ptr);
                }
                {
                    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
                    cimgui.c.ImGui_Text(
                        "%s",
                        if (value) "true".ptr else "false".ptr,
                    );
                }
            }
        }
    } else {
        {
            cimgui.c.ImGui_TableNextRow();
            {
                _ = cimgui.c.ImGui_TableSetColumnIndex(0);
                cimgui.c.ImGui_Text("Xterm modify keys");
            }
            {
                _ = cimgui.c.ImGui_TableSetColumnIndex(1);
                cimgui.c.ImGui_Text(
                    "%s",
                    if (modify_other_keys_2) "true".ptr else "false".ptr,
                );
            }
        }
    } // keyboard mode info
}

/// Render kitty graphics information table.
pub fn kittyGraphicsTable(
    kitty_images: *const terminal.kitty.graphics.ImageStorage,
) void {
    if (!kitty_images.enabled()) {
        cimgui.c.ImGui_TextDisabled("(Kitty graphics are disabled)");
        return;
    }

    if (!cimgui.c.ImGui_BeginTable(
        "##kitty_graphics",
        2,
        cimgui.c.ImGuiTableFlags_None,
    )) return;
    defer cimgui.c.ImGui_EndTable();

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Memory Usage");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%d bytes (%d KiB)", kitty_images.total_bytes, units.toKibiBytes(kitty_images.total_bytes));

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Memory Limit");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%d bytes (%d KiB)", kitty_images.total_limit, units.toKibiBytes(kitty_images.total_limit));

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Image Count");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%d", kitty_images.images.count());

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Placement Count");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%d", kitty_images.placements.count());

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Image Loading");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%s", if (kitty_images.loading != null) "true".ptr else "false".ptr);
}

/// Render internal terminal state table.
pub fn internalStateTable(
    screen: *const terminal.Screen,
) void {
    const pages = &screen.pages;

    if (!cimgui.c.ImGui_BeginTable(
        "##terminal_state",
        2,
        cimgui.c.ImGuiTableFlags_None,
    )) return;
    defer cimgui.c.ImGui_EndTable();

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Memory Usage");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%d bytes (%d KiB)", pages.page_size, units.toKibiBytes(pages.page_size));

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Memory Limit");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%d bytes (%d KiB)", pages.maxSize(), units.toKibiBytes(pages.maxSize()));

    cimgui.c.ImGui_TableNextRow();
    _ = cimgui.c.ImGui_TableSetColumnIndex(0);
    cimgui.c.ImGui_Text("Viewport Location");
    _ = cimgui.c.ImGui_TableSetColumnIndex(1);
    cimgui.c.ImGui_Text("%s", @tagName(pages.viewport).ptr);
}

/// Render semantic prompt state table.
pub fn semanticPromptTable(
    semantic_prompt: *const terminal.Screen.SemanticPrompt,
) void {
    if (!cimgui.c.ImGui_BeginTable(
        "##semantic_prompt",
        2,
        cimgui.c.ImGuiTableFlags_None,
    )) return;
    defer cimgui.c.ImGui_EndTable();

    {
        cimgui.c.ImGui_TableNextRow();
        _ = cimgui.c.ImGui_TableSetColumnIndex(0);
        cimgui.c.ImGui_Text("Seen");
        cimgui.c.ImGui_SameLine();
        widgets.helpMarker("Whether any semantic prompt markers (OSC 133) have been seen in this screen.");
        _ = cimgui.c.ImGui_TableSetColumnIndex(1);
        var value: bool = semantic_prompt.seen;
        _ = cimgui.c.ImGui_Checkbox("##seen", &value);
    }

    {
        cimgui.c.ImGui_TableNextRow();
        _ = cimgui.c.ImGui_TableSetColumnIndex(0);
        cimgui.c.ImGui_Text("Click Handling");
        cimgui.c.ImGui_SameLine();
        widgets.helpMarker("How click events are handled in prompts. Set via 'cl' or 'click_events' options in OSC 133.");
        _ = cimgui.c.ImGui_TableSetColumnIndex(1);
        switch (semantic_prompt.click) {
            .none => cimgui.c.ImGui_TextDisabled("(none)"),
            .click_events => |click_events| cimgui.c.ImGui_Text("click_events=%s", @tagName(click_events).ptr),
            .cl => |cl| cimgui.c.ImGui_Text("cl=%s", @tagName(cl).ptr),
        }
    }
}
