const std = @import("std");
const testing = std.testing;
const lib = @import("../lib.zig");
const page = @import("../page.zig");
const Cell = page.Cell;
const color = @import("../color.zig");
const style_c = @import("style.zig");
const Result = @import("result.zig").Result;

/// C: GhosttyCell
pub const CCell = u64;

/// C: GhosttyCellsView
pub const CellsView = extern struct {
    ptr: ?[*]const CCell,
    len: usize,
};

/// C: GhosttyCellContentTag
pub const ContentTag = enum(c_int) {
    codepoint = 0,
    codepoint_grapheme = 1,
    bg_color_palette = 2,
    bg_color_rgb = 3,
};

/// C: GhosttyCellWide
pub const Wide = enum(c_int) {
    narrow = 0,
    wide = 1,
    spacer_tail = 2,
    spacer_head = 3,
};

/// C: GhosttyCellSemanticContent
pub const SemanticContent = enum(c_int) {
    output = 0,
    input = 1,
    prompt = 2,
};

/// C: GhosttyCellData
pub const CellData = enum(c_int) {
    invalid = 0,

    /// The codepoint of the cell (0 if empty or bg-color-only).
    /// Output type: uint32_t * (stored as u21, zero-extended)
    codepoint = 1,

    /// The content tag describing what kind of content is in the cell.
    /// Output type: GhosttyCellContentTag *
    content_tag = 2,

    /// The wide property of the cell.
    /// Output type: GhosttyCellWide *
    wide = 3,

    /// Whether the cell has text to render.
    /// Output type: bool *
    has_text = 4,

    /// Whether the cell has styling (non-default style).
    /// Output type: bool *
    has_styling = 5,

    /// The style ID for the cell (for use with style lookups).
    /// Output type: uint16_t *
    style_id = 6,

    /// Whether the cell has a hyperlink.
    /// Output type: bool *
    has_hyperlink = 7,

    /// Whether the cell is protected.
    /// Output type: bool *
    protected = 8,

    /// The semantic content type of the cell (from OSC 133).
    /// Output type: GhosttyCellSemanticContent *
    semantic_content = 9,

    /// The palette index for the cell's background color.
    /// Only valid when content_tag is bg_color_palette.
    /// Output type: GhosttyColorPaletteIndex *
    color_palette = 10,

    /// The RGB value for the cell's background color.
    /// Only valid when content_tag is bg_color_rgb.
    /// Output type: GhosttyColorRgb *
    color_rgb = 11,

    /// Output type expected for querying the data of the given kind.
    pub fn OutType(comptime self: CellData) type {
        return switch (self) {
            .invalid => void,
            .codepoint => u32,
            .content_tag => ContentTag,
            .wide => Wide,
            .has_text, .has_styling, .has_hyperlink, .protected => bool,
            .style_id => u16,
            .semantic_content => SemanticContent,
            .color_palette => u8,
            .color_rgb => color.RGB.C,
        };
    }
};

pub fn get(
    cell_: CCell,
    data: CellData,
    out: ?*anyopaque,
) callconv(lib.calling_conv) Result {
    if (comptime std.debug.runtime_safety) {
        _ = std.enums.fromInt(CellData, @intFromEnum(data)) orelse {
            return .invalid_value;
        };
    }

    return switch (data) {
        .invalid => .invalid_value,
        inline else => |comptime_data| getTyped(
            cell_,
            comptime_data,
            @ptrCast(@alignCast(out)),
        ),
    };
}

pub fn get_multi(
    cell_: CCell,
    count: usize,
    keys: ?[*]const CellData,
    values: ?[*]?*anyopaque,
    out_written: ?*usize,
) callconv(lib.calling_conv) Result {
    const k = keys orelse return .invalid_value;
    const v = values orelse return .invalid_value;

    for (0..count) |i| {
        const result = get(cell_, k[i], v[i]);
        if (result != .success) {
            if (out_written) |w| w.* = i;
            return result;
        }
    }
    if (out_written) |w| w.* = count;
    return .success;
}

fn getTyped(
    cell_: CCell,
    comptime data: CellData,
    out: *data.OutType(),
) Result {
    const cell: Cell = @bitCast(cell_);
    switch (data) {
        .invalid => return .invalid_value,
        .codepoint => out.* = @intCast(cell.codepoint()),
        .content_tag => out.* = @enumFromInt(@intFromEnum(cell.content_tag)),
        .wide => out.* = @enumFromInt(@intFromEnum(cell.wide)),
        .has_text => out.* = cell.hasText(),
        .has_styling => out.* = cell.hasStyling(),
        .style_id => out.* = cell.style_id,
        .has_hyperlink => out.* = cell.hyperlink,
        .protected => out.* = cell.protected,
        .semantic_content => out.* = @enumFromInt(@intFromEnum(cell.semantic_content)),
        .color_palette => out.* = cell.content.color_palette.data,
        .color_rgb => {
            const rgb = cell.content.color_rgb;
            out.* = .{ .r = rgb.r, .g = rgb.g, .b = rgb.b };
        },
    }

    return .success;
}

test "get codepoint" {
    const cell: CCell = @bitCast(Cell.init('A'));
    var cp: u32 = 0;
    try testing.expectEqual(Result.success, get(cell, .codepoint, @ptrCast(&cp)));
    try testing.expectEqual(@as(u32, 'A'), cp);
}

test "get has_text" {
    const cell: CCell = @bitCast(Cell.init('A'));
    var has: bool = false;
    try testing.expectEqual(Result.success, get(cell, .has_text, @ptrCast(&has)));
    try testing.expect(has);
}

test "get empty cell" {
    const cell: CCell = @bitCast(Cell.init(0));
    var has: bool = true;
    try testing.expectEqual(Result.success, get(cell, .has_text, @ptrCast(&has)));
    try testing.expect(!has);
}

test "get wide" {
    var zig_cell = Cell.init('A');
    zig_cell.wide = .wide;
    const cell: CCell = @bitCast(zig_cell);
    var w: Wide = .narrow;
    try testing.expectEqual(Result.success, get(cell, .wide, @ptrCast(&w)));
    try testing.expectEqual(Wide.wide, w);
}

test "get_multi success" {
    const cell: CCell = @bitCast(Cell.init('B'));
    var cp: u32 = 0;
    var has_text: bool = false;
    var written: usize = 0;

    const keys = [_]CellData{ .codepoint, .has_text };
    var values = [_]?*anyopaque{ @ptrCast(&cp), @ptrCast(&has_text) };
    try testing.expectEqual(Result.success, get_multi(cell, keys.len, &keys, &values, &written));
    try testing.expectEqual(keys.len, written);
    try testing.expectEqual(@as(u32, 'B'), cp);
    try testing.expect(has_text);
}

test "get_multi error sets out_written" {
    const cell: CCell = @bitCast(Cell.init('C'));
    var cp: u32 = 0;
    var written: usize = 99;

    const keys = [_]CellData{ .codepoint, .invalid };
    var values = [_]?*anyopaque{ @ptrCast(&cp), @ptrCast(&cp) };
    try testing.expectEqual(Result.invalid_value, get_multi(cell, keys.len, &keys, &values, &written));
    try testing.expectEqual(1, written);
    try testing.expectEqual(@as(u32, 'C'), cp);
}

test "get_multi null keys returns invalid_value" {
    const cell: CCell = @bitCast(Cell.init('A'));
    var cp: u32 = 0;
    var values = [_]?*anyopaque{@ptrCast(&cp)};
    try testing.expectEqual(Result.invalid_value, get_multi(cell, 1, null, &values, null));
}

test "get_multi null values returns invalid_value" {
    const cell: CCell = @bitCast(Cell.init('A'));
    const keys = [_]CellData{.codepoint};
    try testing.expectEqual(Result.invalid_value, get_multi(cell, 1, &keys, null, null));
}
