const std = @import("std");
const lib = @import("lib.zig");

/// The color scheme reported in response to a CSI ? 996 n query.
pub const ColorScheme = lib.Enum(lib.target, &.{
    "light",
    "dark",
});

/// The visibility state reported in response to a CSI ? 998 n query or when
/// DEC mode 2033 is enabled.
pub const Visibility = lib.Enum(lib.target, &.{
    "potentially_visible",
    "not_visible",
});

/// Maximum number of bytes that `encodeColorSchemeReport` will write.
pub const max_color_scheme_report_encode_size = max: {
    var result: usize = 0;
    for (@typeInfo(ColorScheme).@"enum".fields) |field| {
        var discarding: std.Io.Writer.Discarding = .init(&.{});
        encodeColorSchemeReport(
            &discarding.writer,
            @enumFromInt(field.value),
        ) catch unreachable;
        result = @max(result, @as(usize, @intCast(discarding.count)));
    }

    break :max result;
};

/// Encode a color scheme report response for CSI ? 996 n queries.
pub fn encodeColorSchemeReport(
    writer: *std.Io.Writer,
    scheme: ColorScheme,
) std.Io.Writer.Error!void {
    try writer.writeAll(switch (scheme) {
        .dark => "\x1B[?997;1n",
        .light => "\x1B[?997;2n",
    });
}

/// Maximum number of bytes that `encodeVisibilityReport` will write.
pub const max_visibility_report_encode_size = "\x1B[?999;2n".len;

/// Encode a visibility report response for CSI ? 998 n queries and DEC mode
/// 2033 notifications.
pub fn encodeVisibilityReport(
    writer: *std.Io.Writer,
    visibility: Visibility,
) std.Io.Writer.Error!void {
    try writer.writeAll(switch (visibility) {
        .potentially_visible => "\x1B[?999;1n",
        .not_visible => "\x1B[?999;2n",
    });
}

/// An enum(u16) of the available device status requests.
pub const Request = dsr_enum: {
    var names: [entries.len][]const u8 = undefined;
    var values: [entries.len]Tag.Backing = undefined;

    for (entries, &names, &values) |entry, *name, *value| {
        name.* = entry.name;
        value.* = @bitCast(Tag{
            .value = entry.value,
            .question = entry.question,
        });
    }

    break :dsr_enum @Enum(Tag.Backing, .exhaustive, &names, &values);
};

/// The tag type for our enum is a u16 but we use a packed struct
/// in order to pack the question bit into the tag. The "u16" size is
/// chosen somewhat arbitrarily to match the largest expected size
/// we see as a multiple of 8 bits.
pub const Tag = packed struct(u16) {
    pub const Backing = @typeInfo(@This()).@"struct".backing_integer.?;
    value: u15,
    question: bool = false,

    test "order" {
        const t: Tag = .{ .value = 1 };
        const int: Backing = @bitCast(t);
        try std.testing.expectEqual(@as(Backing, 1), int);
    }
};

pub fn reqFromInt(v: u16, question: bool) ?Request {
    inline for (entries) |entry| {
        if (entry.value == v and entry.question == question) {
            const tag: Tag = .{ .question = question, .value = entry.value };
            const int: Tag.Backing = @bitCast(tag);
            return @enumFromInt(int);
        }
    }

    return null;
}

/// A single entry of a possible device status request we support. The
/// "question" field determines if it is valid with or without the "?"
/// prefix.
const Entry = struct {
    name: [:0]const u8,
    value: comptime_int,
    question: bool = false, // "?" request
};

/// The full list of device status request entries.
const entries: []const Entry = &.{
    .{ .name = "operating_status", .value = 5 },
    .{ .name = "cursor_position", .value = 6 },
    .{ .name = "color_scheme", .value = 996, .question = true },
    .{ .name = "visibility", .value = 998, .question = true },
};

test "encode color scheme report dark" {
    try std.testing.expectEqual(@as(usize, 9), max_color_scheme_report_encode_size);

    var buf: [max_color_scheme_report_encode_size]u8 = undefined;
    var writer: std.Io.Writer = .fixed(&buf);
    try encodeColorSchemeReport(&writer, .dark);
    try std.testing.expectEqualStrings("\x1B[?997;1n", writer.buffered());
}

test "encode color scheme report light" {
    var buf: [max_color_scheme_report_encode_size]u8 = undefined;
    var writer: std.Io.Writer = .fixed(&buf);
    try encodeColorSchemeReport(&writer, .light);
    try std.testing.expectEqualStrings("\x1B[?997;2n", writer.buffered());
}

test "encode visibility report" {
    var buf: [max_visibility_report_encode_size]u8 = undefined;
    var writer: std.Io.Writer = .fixed(&buf);
    try encodeVisibilityReport(&writer, .potentially_visible);
    try std.testing.expectEqualStrings("\x1B[?999;1n", writer.buffered());

    writer = .fixed(&buf);
    try encodeVisibilityReport(&writer, .not_visible);
    try std.testing.expectEqualStrings("\x1B[?999;2n", writer.buffered());
}
