const streampkg = @This();
const std = @import("std");
const build_options = @import("terminal_options");
const assert = @import("../quirks.zig").inlineAssert;
const testing = std.testing;
const Allocator = std.mem.Allocator;
const simd = @import("../simd/main.zig");
const lib = @import("lib.zig");
const Parser = @import("Parser.zig");
const ansi = @import("ansi.zig");
const charsets = @import("charsets.zig");
const device_attributes = @import("device_attributes.zig");
const device_status = @import("device_status.zig");
const csi = @import("csi.zig");
const kitty = @import("kitty.zig");
const modes = @import("modes.zig");
const osc = @import("osc.zig");
const sgr = @import("sgr.zig");
const UTF8Decoder = @import("UTF8Decoder.zig");
const MouseShape = @import("mouse.zig").Shape;

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

/// Flip this to true when you want verbose debug output for
/// debugging terminal stream issues. In addition to louder
/// output this will also disable the SIMD optimizations in
/// order to make it easier to see every byte. So if you're
/// debugging an issue in the SIMD code then you'll need to
/// do something else.
const debug = false;

/// The possible actions that can be emitted by the Stream
/// function for handling.
pub const Action = union(Key) {
    print: Print,
    print_repeat: usize,
    bell,
    backspace,
    horizontal_tab: u16,
    horizontal_tab_back: u16,
    linefeed,
    carriage_return,
    enquiry,
    invoke_charset: InvokeCharset,
    cursor_up: CursorMovement,
    cursor_down: CursorMovement,
    cursor_left: CursorMovement,
    cursor_right: CursorMovement,
    cursor_col: CursorMovement,
    cursor_row: CursorMovement,
    cursor_col_relative: CursorMovement,
    cursor_row_relative: CursorMovement,
    cursor_pos: CursorPos,
    cursor_style: ansi.CursorStyle,
    erase_display_below: bool,
    erase_display_above: bool,
    erase_display_complete: bool,
    erase_display_scrollback: bool,
    erase_display_scroll_complete: bool,
    erase_line_right: bool,
    erase_line_left: bool,
    erase_line_complete: bool,
    erase_line_right_unless_pending_wrap: bool,
    delete_chars: usize,
    erase_chars: usize,
    insert_lines: usize,
    insert_blanks: usize,
    delete_lines: usize,
    scroll_up: usize,
    scroll_down: usize,
    tab_clear_current,
    tab_clear_all,
    tab_set,
    tab_reset,
    index,
    next_line,
    reverse_index,
    full_reset,
    set_mode: Mode,
    reset_mode: Mode,
    save_mode: Mode,
    restore_mode: Mode,
    request_mode: Mode,
    request_mode_unknown: RawMode,
    top_and_bottom_margin: Margin,
    left_and_right_margin: Margin,
    left_and_right_margin_ambiguous,
    save_cursor,
    restore_cursor,
    modify_key_format: ansi.ModifyKeyFormat,
    mouse_shift_capture: bool,
    protected_mode_off,
    protected_mode_iso,
    protected_mode_dec,
    size_report: csi.SizeReportStyle,
    title_push: u16,
    title_pop: u16,
    xtversion,
    device_attributes: device_attributes.Req,
    device_status: DeviceStatus,
    kitty_keyboard_query,
    kitty_keyboard_push: KittyKeyboardFlags,
    kitty_keyboard_pop: u16,
    kitty_keyboard_set: KittyKeyboardFlags,
    kitty_keyboard_set_or: KittyKeyboardFlags,
    kitty_keyboard_set_not: KittyKeyboardFlags,
    dcs_hook: Parser.Action.DCS,
    dcs_put: u8,
    dcs_unhook,
    apc_start,
    apc_end,
    apc_put: u8,
    end_hyperlink,
    active_status_display: ansi.StatusDisplay,
    decaln,
    window_title: WindowTitle,
    report_pwd: ReportPwd,
    show_desktop_notification: ShowDesktopNotification,
    progress_report: osc.Command.ProgressReport,
    start_hyperlink: StartHyperlink,
    clipboard_contents: ClipboardContents,
    mouse_shape: MouseShape,
    configure_charset: ConfigureCharset,
    set_attribute: sgr.Attribute,
    kitty_color_report: kitty.color.OSC,
    color_operation: ColorOperation,
    semantic_prompt: SemanticPrompt,

    pub const Key = lib.Enum(
        lib.target,
        &.{
            "print",
            "print_repeat",
            "bell",
            "backspace",
            "horizontal_tab",
            "horizontal_tab_back",
            "linefeed",
            "carriage_return",
            "enquiry",
            "invoke_charset",
            "cursor_up",
            "cursor_down",
            "cursor_left",
            "cursor_right",
            "cursor_col",
            "cursor_row",
            "cursor_col_relative",
            "cursor_row_relative",
            "cursor_pos",
            "cursor_style",
            "erase_display_below",
            "erase_display_above",
            "erase_display_complete",
            "erase_display_scrollback",
            "erase_display_scroll_complete",
            "erase_line_right",
            "erase_line_left",
            "erase_line_complete",
            "erase_line_right_unless_pending_wrap",
            "delete_chars",
            "erase_chars",
            "insert_lines",
            "insert_blanks",
            "delete_lines",
            "scroll_up",
            "scroll_down",
            "tab_clear_current",
            "tab_clear_all",
            "tab_set",
            "tab_reset",
            "index",
            "next_line",
            "reverse_index",
            "full_reset",
            "set_mode",
            "reset_mode",
            "save_mode",
            "restore_mode",
            "request_mode",
            "request_mode_unknown",
            "top_and_bottom_margin",
            "left_and_right_margin",
            "left_and_right_margin_ambiguous",
            "save_cursor",
            "restore_cursor",
            "modify_key_format",
            "mouse_shift_capture",
            "protected_mode_off",
            "protected_mode_iso",
            "protected_mode_dec",
            "size_report",
            "title_push",
            "title_pop",
            "xtversion",
            "device_attributes",
            "device_status",
            "kitty_keyboard_query",
            "kitty_keyboard_push",
            "kitty_keyboard_pop",
            "kitty_keyboard_set",
            "kitty_keyboard_set_or",
            "kitty_keyboard_set_not",
            "dcs_hook",
            "dcs_put",
            "dcs_unhook",
            "apc_start",
            "apc_end",
            "apc_put",
            "end_hyperlink",
            "active_status_display",
            "decaln",
            "window_title",
            "report_pwd",
            "show_desktop_notification",
            "progress_report",
            "start_hyperlink",
            "clipboard_contents",
            "mouse_shape",
            "configure_charset",
            "set_attribute",
            "kitty_color_report",
            "color_operation",
            "semantic_prompt",
        },
    );

    /// C ABI functions.
    const c_union = lib.TaggedUnion(
        lib.target,
        @This(),
        // TODO: Before shipping an ABI-compatible libghostty, verify this.
        // This was just arbitrarily chosen for now.
        [16]u64,
    );
    pub const Tag = c_union.Tag;
    pub const Value = c_union.Value;
    pub const C = c_union.C;
    pub const CValue = c_union.CValue;
    pub const cval = c_union.cval;

    /// Field types
    pub const Print = struct {
        cp: u21,

        pub const C = extern struct {
            cp: u32,
        };

        pub fn cval(self: Print) Print.C {
            return .{ .cp = @intCast(self.cp) };
        }
    };

    pub const InvokeCharset = lib.Struct(lib.target, struct {
        bank: charsets.ActiveSlot,
        charset: charsets.Slots,
        locking: bool,
    });

    pub const CursorMovement = extern struct {
        /// The value of the cursor movement. Depending on the tag of this
        /// union this may be an absolute value or it may be a relative
        /// value. For example, `cursor_up` is relative, but `cursor_row`
        /// is absolute.
        value: u16,
    };

    pub const CursorPos = extern struct {
        row: u16,
        col: u16,
    };

    pub const DeviceStatus = struct {
        request: device_status.Request,

        pub const C = u16;

        pub fn cval(self: DeviceStatus) DeviceStatus.C {
            return @bitCast(self.request);
        }
    };

    pub const Mode = struct {
        mode: modes.Mode,

        pub const C = u16;

        pub fn cval(self: Mode) Mode.C {
            return @bitCast(self.mode);
        }
    };

    pub const RawMode = extern struct {
        mode: u16,
        ansi: bool,
    };

    pub const Margin = extern struct {
        top_left: u16,
        bottom_right: u16,
    };

    pub const KittyKeyboardFlags = struct {
        flags: kitty.KeyFlags,

        pub const C = u8;

        pub fn cval(self: KittyKeyboardFlags) KittyKeyboardFlags.C {
            return @intCast(self.flags.int());
        }
    };

    pub const WindowTitle = struct {
        title: []const u8,

        pub const C = lib.String;

        pub fn cval(self: WindowTitle) WindowTitle.C {
            return .init(self.title);
        }
    };

    pub const ReportPwd = struct {
        url: []const u8,

        pub const C = lib.String;

        pub fn cval(self: ReportPwd) ReportPwd.C {
            return .init(self.url);
        }
    };

    pub const ShowDesktopNotification = struct {
        title: []const u8,
        body: []const u8,

        pub const C = extern struct {
            title: lib.String,
            body: lib.String,
        };

        pub fn cval(self: ShowDesktopNotification) ShowDesktopNotification.C {
            return .{
                .title = .init(self.title),
                .body = .init(self.body),
            };
        }
    };

    pub const StartHyperlink = struct {
        uri: []const u8,
        id: ?[]const u8,

        pub const C = extern struct {
            uri: lib.String,
            id: lib.String,
        };

        pub fn cval(self: StartHyperlink) StartHyperlink.C {
            return .{
                .uri = .init(self.uri),
                .id = .init(self.id orelse ""),
            };
        }
    };

    pub const ClipboardContents = struct {
        kind: u8,
        data: []const u8,

        pub const C = extern struct {
            kind: u8,
            data: lib.String,
        };

        pub fn cval(self: ClipboardContents) ClipboardContents.C {
            return .{
                .kind = self.kind,
                .data = .init(self.data),
            };
        }
    };

    pub const ConfigureCharset = lib.Struct(lib.target, struct {
        slot: charsets.Slots,
        charset: charsets.Charset,
    });

    pub const ColorOperation = struct {
        op: osc.color.Operation,
        requests: osc.color.List,
        terminator: osc.Terminator,

        pub const C = void;

        pub fn cval(_: ColorOperation) ColorOperation.C {
            return {};
        }
    };

    pub const SemanticPrompt = osc.Command.SemanticPrompt;
};

/// Returns a type that can process a stream of tty control characters.
/// This will call the `vt` function on type T with the following signature:
///
///   fn(comptime action: Action.Key, value: Action.Value(action)) void
///
/// The handler type T can choose to react to whatever actions it cares
/// about in its pursuit of implementing a terminal emulator or other
/// functionality.
///
/// The Handler type must also have a `deinit` function.
///
/// The "comptime" key is on purpose (vs. a standard Zig tagged union)
/// because it allows the compiler to optimize away unimplemented actions.
/// e.g. you don't need to pay a conditional branching cost on every single
/// action because the Zig compiler codegens separate code paths for every
/// single action at comptime.
pub fn Stream(comptime H: type) type {
    return struct {
        const Self = @This();

        pub const Action = streampkg.Action;
        pub const Handler = H;

        const T = switch (@typeInfo(Handler)) {
            .pointer => |p| p.child,
            else => Handler,
        };

        handler: Handler,
        parser: Parser,
        utf8decoder: UTF8Decoder,

        /// Initialize an allocation-free stream. This will preallocate various
        /// sizes as necessary and anything over that will be dropped. If you
        /// want to support more dynamic behavior use initAlloc instead.
        ///
        /// As a concrete example of something that requires heap allocation,
        /// consider OSC 52 (clipboard operations) which can be arbitrarily
        /// large.
        ///
        /// If you want to limit allocation size, use an allocator with
        /// a size limit with initAlloc.
        ///
        /// This takes ownership of the handler and will call deinit
        /// when the stream is deinitialized.
        pub fn init(h: Handler) Self {
            return .{
                .handler = h,
                .parser = .init(),
                .utf8decoder = .{},
            };
        }

        /// Initialize the stream that supports heap allocation as necessary.
        pub fn initAlloc(alloc: Allocator, h: Handler) Self {
            var self: Self = .init(h);
            self.parser.osc_parser.alloc = alloc;
            return self;
        }

        pub fn deinit(self: *Self) void {
            self.parser.deinit();
            self.handler.deinit();
        }

        /// Process a string of characters.
        pub inline fn nextSlice(self: *Self, input: []const u8) void {
            // Disable SIMD optimizations if build requests it or if our
            // manual debug mode is on.
            if (comptime debug or !build_options.simd) {
                for (input) |c| self.next(c);
                return;
            }

            // This is the maximum number of codepoints we can decode
            // at one time for this function call. This is somewhat arbitrary
            // so if someone can demonstrate a better number then we can switch.
            var cp_buf: [4096]u32 = undefined;

            // Split the input into chunks that fit into cp_buf.
            var i: usize = 0;
            while (true) {
                const len = @min(cp_buf.len, input.len - i);
                self.nextSliceCapped(input[i .. i + len], &cp_buf);
                i += len;
                if (i >= input.len) break;
            }
        }

        inline fn nextSliceCapped(
            self: *Self,
            input: []const u8,
            cp_buf: []u32,
        ) void {
            assert(input.len <= cp_buf.len);

            var offset: usize = 0;

            // If the scalar UTF-8 decoder was in the middle of processing
            // a code sequence, we continue until it's not.
            while (self.utf8decoder.state != 0) {
                if (offset >= input.len) return;
                self.nextUtf8(input[offset]);
                offset += 1;
            }
            if (offset >= input.len) return;

            // If we're not in the ground state then we process until
            // we are. This can happen if the last chunk of input put us
            // in the middle of a control sequence.
            offset += self.consumeUntilGround(input[offset..]);
            if (offset >= input.len) return;
            offset += self.consumeAllEscapes(input[offset..]);

            // If we're in the ground state then we can use SIMD to process
            // input until we see an ESC (0x1B), since all other characters
            // up to that point are just UTF-8.
            while (self.parser.state == .ground and offset < input.len) {
                const res = simd.vt.utf8DecodeUntilControlSeq(input[offset..], cp_buf);
                for (cp_buf[0..res.decoded]) |cp| {
                    if (cp <= 0xF) {
                        self.execute(@intCast(cp));
                    } else {
                        self.print(@intCast(cp));
                    }
                }
                // Consume the bytes we just processed.
                offset += res.consumed;

                if (offset >= input.len) return;

                // If our offset is NOT an escape then we must have a
                // partial UTF-8 sequence. In that case, we pass it off
                // to the scalar parser.
                if (input[offset] != 0x1B) {
                    const rem = input[offset..];
                    for (rem) |c| self.nextUtf8(c);
                    return;
                }

                // Process control sequences until we run out.
                offset += self.consumeAllEscapes(input[offset..]);
            }
        }

        /// Parses back-to-back escape sequences until none are left.
        /// Returns the number of bytes consumed from the provided input.
        ///
        /// Expects input to start with 0x1B, use consumeUntilGround first
        /// if the stream may be in the middle of an escape sequence.
        inline fn consumeAllEscapes(self: *Self, input: []const u8) usize {
            var offset: usize = 0;
            while (input[offset] == 0x1B) {
                self.parser.state = .escape;
                self.parser.clear();
                offset += 1;
                offset += self.consumeUntilGround(input[offset..]);
                if (offset >= input.len) return input.len;
            }
            return offset;
        }

        /// Parses escape sequences until the parser reaches the ground state.
        /// Returns the number of bytes consumed from the provided input.
        inline fn consumeUntilGround(self: *Self, input: []const u8) usize {
            var offset: usize = 0;
            while (self.parser.state != .ground) {
                if (offset >= input.len) return input.len;
                self.nextNonUtf8(input[offset]);
                offset += 1;
            }
            return offset;
        }

        /// Like nextSlice but takes one byte and is necessarily a scalar
        /// operation that can't use SIMD. Prefer nextSlice if you can and
        /// try to get multiple bytes at once.
        pub inline fn next(self: *Self, c: u8) void {
            // The scalar path can be responsible for decoding UTF-8.
            if (self.parser.state == .ground) {
                self.nextUtf8(c);
                return;
            }

            self.nextNonUtf8(c);
        }

        /// Process the next byte and print as necessary.
        ///
        /// This assumes we're in the UTF-8 decoding state. If we may not
        /// be in the UTF-8 decoding state call nextSlice or next.
        inline fn nextUtf8(self: *Self, c: u8) void {
            assert(self.parser.state == .ground);

            const res = self.utf8decoder.next(c);
            const consumed = res[1];
            if (res[0]) |codepoint| {
                self.handleCodepoint(codepoint);
            }
            if (!consumed) {
                // We optimize for the scenario where the text being
                // printed in the terminal ISN'T full of ill-formed
                // UTF-8 sequences.
                @branchHint(.unlikely);

                const retry = self.utf8decoder.next(c);
                // It should be impossible for the decoder
                // to not consume the byte twice in a row.
                assert(retry[1] == true);
                if (retry[0]) |codepoint| {
                    self.handleCodepoint(codepoint);
                }
            }
        }

        /// To be called whenever the utf-8 decoder produces a codepoint.
        ///
        /// This function is abstracted this way to handle the case where
        /// the decoder emits a 0x1B after rejecting an ill-formed sequence.
        inline fn handleCodepoint(self: *Self, c: u21) void {
            // We need to increase the eval branch limit because a lot of
            // tests end up running almost completely at comptime due to
            // a chain of inline functions.
            @setEvalBranchQuota(100_000);

            // C0 control
            if (c <= 0xF) {
                @branchHint(.unlikely);
                self.execute(@intCast(c));
                return;
            }
            // ESC
            if (c == 0x1B) {
                self.parser.state = .escape;
                self.parser.clear();
                return;
            }
            self.print(@intCast(c));
        }

        /// Process the next character and call any callbacks if necessary.
        ///
        /// This assumes that we're not in the UTF-8 decoding state. If
        /// we may be in the UTF-8 decoding state call nextSlice or next.
        fn nextNonUtf8(self: *Self, c: u8) void {
            assert(self.parser.state != .ground);

            // Fast path for CSI entry.
            if (self.parser.state == .escape and c == '[') {
                self.parser.state = .csi_entry;
                return;
            }
            // Fast path for CSI params.
            if (self.parser.state == .csi_param) csi_param: {
                // csi_param is the most common parser state
                // other than ground by a fairly wide margin.
                //
                // ref: https://github.com/qwerasd205/asciinema-stats
                @branchHint(.likely);
                switch (c) {
                    // A C0 escape (yes, this is valid):
                    0x00...0x0F => self.execute(c),
                    // We ignore C0 escapes > 0xF since execute
                    // doesn't have processing for them anyway:
                    0x10...0x17, 0x19, 0x1C...0x1F => {},
                    // We don't currently have any handling for
                    // 0x18 or 0x1A, but they should still move
                    // the parser state to ground.
                    0x18, 0x1A => self.parser.state = .ground,
                    // A parameter digit:
                    '0'...'9' => if (self.parser.params_idx < Parser.MAX_PARAMS) {
                        self.parser.param_acc *|= 10;
                        self.parser.param_acc +|= c - '0';
                        // The parser's CSI param action uses param_acc_idx
                        // to decide if there's a final param that needs to
                        // be consumed or not, but it doesn't matter really
                        // what it is as long as it's not 0.
                        self.parser.param_acc_idx |= 1;
                    },
                    // A parameter separator:
                    ':', ';' => if (self.parser.params_idx < Parser.MAX_PARAMS) {
                        self.parser.params[self.parser.params_idx] = self.parser.param_acc;
                        if (c == ':') self.parser.params_sep.set(self.parser.params_idx);
                        self.parser.params_idx += 1;

                        self.parser.param_acc = 0;
                        self.parser.param_acc_idx = 0;
                    },
                    // Explicitly ignored:
                    0x7F => {},
                    // Defer to the state machine to
                    // handle any other characters:
                    else => break :csi_param,
                }
                return;
            }

            // We explicitly inline this call here for performance reasons.
            //
            // We do this rather than mark Parser.next as inline because doing
            // that causes weird behavior in some tests- I'm not sure if they
            // miscompile or it's just very counter-intuitive comptime stuff,
            // but regardless, this is the easy solution.
            const actions = @call(.always_inline, Parser.next, .{ &self.parser, c });

            for (actions) |action_opt| {
                const action = action_opt orelse continue;
                if (comptime debug) log.info("action: {f}", .{action});

                // A handler can expose this to get the raw action before
                // it is further parsed. If this returns `true` then we skip
                // processing ourselves.
                if (@hasDecl(T, "vtRaw")) {
                    const skip = self.handler.vtRaw(action) catch |err| err: {
                        log.warn("error handling action manually err={} action={f}", .{
                            err,
                            action,
                        });
                        // Always skip erroneous actions because we can't
                        // be sure...
                        break :err true;
                    };

                    if (skip) continue;
                }

                switch (action) {
                    .print => |p| self.print(p),
                    .execute => |code| self.execute(code),
                    .csi_dispatch => |csi_action| self.csiDispatch(csi_action),
                    .esc_dispatch => |esc| self.escDispatch(esc),
                    .osc_dispatch => |cmd| self.oscDispatch(cmd),
                    .dcs_hook => |dcs| self.handler.vt(.dcs_hook, dcs),
                    .dcs_put => |code| self.handler.vt(.dcs_put, code),
                    .dcs_unhook => self.handler.vt(.dcs_unhook, {}),
                    .apc_start => self.handler.vt(.apc_start, {}),
                    .apc_put => |code| self.handler.vt(.apc_put, code),
                    .apc_end => self.handler.vt(.apc_end, {}),
                }
            }
        }

        inline fn print(self: *Self, c: u21) void {
            self.handler.vt(.print, .{ .cp = c });
        }

        inline fn execute(self: *Self, c: u8) void {
            // If the character is > 0x7F, it's a C1 (8-bit) control,
            // which is strictly equivalent to `ESC` plus `c - 0x40`.
            if (c > 0x7F) {
                @branchHint(.unlikely);
                log.info("executing C1 0x{x} as ESC {c}", .{ c, c - 0x40 });
                self.escDispatch(.{
                    .intermediates = &.{},
                    .final = c - 0x40,
                });
                return;
            }

            const c0: ansi.C0 = @enumFromInt(c);
            if (comptime debug) log.info("execute: {f}", .{c0});
            switch (c0) {
                // We ignore SOH/STX: https://github.com/microsoft/terminal/issues/10786
                .NUL, .SOH, .STX => {},

                .ENQ => self.handler.vt(.enquiry, {}),
                .BEL => self.handler.vt(.bell, {}),
                .BS => self.handler.vt(.backspace, {}),
                .HT => self.handler.vt(.horizontal_tab, 1),
                .LF, .VT, .FF => self.handler.vt(.linefeed, {}),
                .CR => self.handler.vt(.carriage_return, {}),
                .SO => self.handler.vt(.invoke_charset, .{ .bank = .GL, .charset = .G1, .locking = false }),
                .SI => self.handler.vt(.invoke_charset, .{ .bank = .GL, .charset = .G0, .locking = false }),

                else => log.warn("invalid C0 character, ignoring: 0x{x}", .{c}),
            }
        }

        inline fn csiDispatch(self: *Self, input: Parser.Action.CSI) void {
            // The branch hints here are based on real world data
            // which indicates that the most common CSI finals are:
            //
            // 1. m
            // 2. H
            // 3. K
            // 4. A
            // 5. C
            // 6. X
            // 7. l
            // 8. h
            // 9. r
            //
            // Together, these 9 finals make up about 96% of all
            // CSI sequences encountered in real world scenarios.
            //
            // Additionally, within the prongs, unlikely branch
            // hints have been added to branches that deal with
            // invalid sequences/commands, this is in order to
            // optimize for the happy path where we're getting
            // valid data from the program we're running.
            //
            // ref: https://github.com/qwerasd205/asciinema-stats

            switch (input.final) {
                // CUU - Cursor Up
                'A', 'k' => {
                    @branchHint(.likely);
                    switch (input.intermediates.len) {
                        0 => self.handler.vt(.cursor_up, .{
                            .value = switch (input.params.len) {
                                0 => 1,
                                1 => input.params[0],
                                else => {
                                    @branchHint(.unlikely);
                                    log.warn("invalid cursor up command: {f}", .{input});
                                    return;
                                },
                            },
                        }),

                        else => log.warn(
                            "ignoring unimplemented CSI A with intermediates: {s}",
                            .{input.intermediates},
                        ),
                    }
                },

                // CUD - Cursor Down
                'B' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.cursor_down, .{
                        .value = switch (input.params.len) {
                            0 => 1,
                            1 => input.params[0],
                            else => {
                                @branchHint(.unlikely);
                                log.warn("invalid cursor down command: {f}", .{input});
                                return;
                            },
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI B with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // CUF - Cursor Right
                'C' => {
                    @branchHint(.likely);
                    switch (input.intermediates.len) {
                        0 => self.handler.vt(.cursor_right, .{
                            .value = switch (input.params.len) {
                                0 => 1,
                                1 => input.params[0],
                                else => {
                                    @branchHint(.unlikely);
                                    log.warn("invalid cursor right command: {f}", .{input});
                                    return;
                                },
                            },
                        }),

                        else => log.warn(
                            "ignoring unimplemented CSI C with intermediates: {s}",
                            .{input.intermediates},
                        ),
                    }
                },

                // CUB - Cursor Left
                'D', 'j' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.cursor_left, .{
                        .value = switch (input.params.len) {
                            0 => 1,
                            1 => input.params[0],
                            else => {
                                @branchHint(.unlikely);
                                log.warn("invalid cursor left command: {f}", .{input});
                                return;
                            },
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI D with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // CNL - Cursor Next Line
                'E' => switch (input.intermediates.len) {
                    0 => {
                        self.handler.vt(.cursor_down, .{
                            .value = switch (input.params.len) {
                                0 => 1,
                                1 => input.params[0],
                                else => {
                                    @branchHint(.unlikely);
                                    log.warn("invalid cursor up command: {f}", .{input});
                                    return;
                                },
                            },
                        });
                        self.handler.vt(.carriage_return, {});
                    },

                    else => log.warn(
                        "ignoring unimplemented CSI E with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // CPL - Cursor Previous Line
                'F' => switch (input.intermediates.len) {
                    0 => {
                        self.handler.vt(.cursor_up, .{
                            .value = switch (input.params.len) {
                                0 => 1,
                                1 => input.params[0],
                                else => {
                                    @branchHint(.unlikely);
                                    log.warn("invalid cursor down command: {f}", .{input});
                                    return;
                                },
                            },
                        });
                        self.handler.vt(.carriage_return, {});
                    },

                    else => log.warn(
                        "ignoring unimplemented CSI F with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // HPA - Cursor Horizontal Position Absolute
                // TODO: test
                'G', '`' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.cursor_col, .{
                        .value = switch (input.params.len) {
                            0 => 1,
                            1 => input.params[0],
                            else => {
                                @branchHint(.unlikely);
                                log.warn("invalid HPA command: {f}", .{input});
                                return;
                            },
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI G with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // CUP - Set Cursor Position.
                // TODO: test
                'H', 'f' => {
                    @branchHint(.likely);
                    switch (input.intermediates.len) {
                        0 => {
                            const pos: streampkg.Action.CursorPos = switch (input.params.len) {
                                0 => .{ .row = 1, .col = 1 },
                                1 => .{ .row = input.params[0], .col = 1 },
                                2 => .{ .row = input.params[0], .col = input.params[1] },
                                else => {
                                    @branchHint(.unlikely);
                                    log.warn("invalid CUP command: {f}", .{input});
                                    return;
                                },
                            };
                            self.handler.vt(.cursor_pos, pos);
                        },

                        else => log.warn(
                            "ignoring unimplemented CSI H with intermediates: {s}",
                            .{input.intermediates},
                        ),
                    }
                },

                // CHT - Cursor Horizontal Tabulation
                'I' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.horizontal_tab, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid horizontal tab command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI I with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // Erase Display
                'J' => {
                    const protected_: ?bool = switch (input.intermediates.len) {
                        0 => false,
                        1 => if (input.intermediates[0] == '?') true else null,
                        else => null,
                    };

                    const protected = protected_ orelse {
                        log.warn("invalid erase display command: {f}", .{input});
                        return;
                    };

                    const mode_: ?csi.EraseDisplay = switch (input.params.len) {
                        0 => .below,
                        1 => std.meta.intToEnum(csi.EraseDisplay, input.params[0]) catch null,
                        else => null,
                    };

                    const mode = mode_ orelse {
                        log.warn("invalid erase display command: {f}", .{input});
                        return;
                    };

                    switch (mode) {
                        .below => self.handler.vt(.erase_display_below, protected),
                        .above => self.handler.vt(.erase_display_above, protected),
                        .complete => self.handler.vt(.erase_display_complete, protected),
                        .scrollback => self.handler.vt(.erase_display_scrollback, protected),
                        .scroll_complete => self.handler.vt(.erase_display_scroll_complete, protected),
                    }
                },

                // Erase Line
                'K' => {
                    @branchHint(.likely);
                    const protected_: ?bool = switch (input.intermediates.len) {
                        0 => false,
                        1 => if (input.intermediates[0] == '?') true else null,
                        else => null,
                    };

                    const protected = protected_ orelse {
                        @branchHint(.unlikely);
                        log.warn("invalid erase line command: {f}", .{input});
                        return;
                    };

                    const mode_: ?csi.EraseLine = switch (input.params.len) {
                        0 => .right,
                        1 => if (input.params[0] < 3) @enumFromInt(input.params[0]) else null,
                        else => null,
                    };

                    const mode = mode_ orelse {
                        @branchHint(.unlikely);
                        log.warn("invalid erase line command: {f}", .{input});
                        return;
                    };

                    switch (mode) {
                        .right => self.handler.vt(.erase_line_right, protected),
                        .left => self.handler.vt(.erase_line_left, protected),
                        .complete => self.handler.vt(.erase_line_complete, protected),
                        .right_unless_pending_wrap => self.handler.vt(.erase_line_right_unless_pending_wrap, protected),
                        _ => {
                            @branchHint(.unlikely);
                            log.warn("invalid erase line mode: {}", .{mode});
                        },
                    }
                },

                // IL - Insert Lines
                // TODO: test
                'L' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.insert_lines, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid IL command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI L with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // DL - Delete Lines
                // TODO: test
                'M' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.delete_lines, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid DL command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI M with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // Delete Character (DCH)
                'P' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.delete_chars, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid delete characters command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI P with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // Scroll Up (SD)

                'S' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.scroll_up, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid scroll up command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI S with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // Scroll Down (SD)
                'T' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.scroll_down, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid scroll down command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI T with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // Cursor Tabulation Control
                'W' => switch (input.intermediates.len) {
                    0 => {
                        if (input.params.len == 0 or
                            (input.params.len == 1 and input.params[0] == 0))
                        {
                            self.handler.vt(.tab_set, {});
                            return;
                        }

                        switch (input.params.len) {
                            0 => unreachable,

                            1 => switch (input.params[0]) {
                                0 => unreachable,

                                2 => self.handler.vt(.tab_clear_current, {}),

                                5 => self.handler.vt(.tab_clear_all, {}),

                                else => {},
                            },

                            else => {},
                        }

                        log.warn("invalid cursor tabulation control: {f}", .{input});
                        return;
                    },

                    1 => if (input.intermediates[0] == '?' and
                        input.params.len == 1 and
                        input.params[0] == 5)
                    {
                        self.handler.vt(.tab_reset, {});
                    } else log.warn("invalid cursor tabulation control: {f}", .{input}),

                    else => log.warn(
                        "ignoring unimplemented CSI W with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // Erase Characters (ECH)
                'X' => {
                    @branchHint(.likely);
                    switch (input.intermediates.len) {
                        0 => self.handler.vt(.erase_chars, switch (input.params.len) {
                            0 => 1,
                            1 => input.params[0],
                            else => {
                                @branchHint(.unlikely);
                                log.warn("invalid erase characters command: {f}", .{input});
                                return;
                            },
                        }),

                        else => log.warn(
                            "ignoring unimplemented CSI X with intermediates: {s}",
                            .{input.intermediates},
                        ),
                    }
                },

                // CHT - Cursor Horizontal Tabulation Back
                'Z' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.horizontal_tab_back, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid horizontal tab back command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI Z with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // HPR - Cursor Horizontal Position Relative
                'a' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.cursor_col_relative, .{
                        .value = switch (input.params.len) {
                            0 => 1,
                            1 => input.params[0],
                            else => {
                                log.warn("invalid HPR command: {f}", .{input});
                                return;
                            },
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI a with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // Repeat Previous Char (REP)
                'b' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.print_repeat, switch (input.params.len) {
                        0 => 1,
                        1 => input.params[0],
                        else => {
                            log.warn("invalid print repeat command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI b with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // c - Device Attributes (DA1)
                'c' => {
                    const req: ?device_attributes.Req = switch (input.intermediates.len) {
                        0 => .primary,
                        1 => switch (input.intermediates[0]) {
                            '>' => .secondary,
                            '=' => .tertiary,
                            else => null,
                        },
                        else => null,
                    };

                    if (req) |r| {
                        self.handler.vt(.device_attributes, r);
                    } else {
                        log.warn("invalid device attributes command: {f}", .{input});
                        return;
                    }
                },

                // VPA - Cursor Vertical Position Absolute
                'd' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.cursor_row, .{
                        .value = switch (input.params.len) {
                            0 => 1,
                            1 => input.params[0],
                            else => {
                                log.warn("invalid VPA command: {f}", .{input});
                                return;
                            },
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI d with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // VPR - Cursor Vertical Position Relative
                'e' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.cursor_row_relative, .{
                        .value = switch (input.params.len) {
                            0 => 1,
                            1 => input.params[0],
                            else => {
                                log.warn("invalid VPR command: {f}", .{input});
                                return;
                            },
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI e with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // TBC - Tab Clear
                // TODO: test
                'g' => switch (input.intermediates.len) {
                    0 => {
                        const mode: csi.TabClear = switch (input.params.len) {
                            1 => std.meta.intToEnum(csi.TabClear, input.params[0]) catch {
                                log.warn("invalid tab clear mode: {}", .{input.params[0]});
                                return;
                            },
                            else => {
                                log.warn("invalid tab clear command: {f}", .{input});
                                return;
                            },
                        };
                        switch (mode) {
                            .current => self.handler.vt(.tab_clear_current, {}),
                            .all => self.handler.vt(.tab_clear_all, {}),
                            _ => log.warn("unknown tab clear mode: {}", .{mode}),
                        }
                    },

                    else => log.warn(
                        "ignoring unimplemented CSI g with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                // SM - Set Mode
                'h' => mode: {
                    @branchHint(.likely);
                    const ansi_mode = ansi: {
                        if (input.intermediates.len == 0) break :ansi true;
                        if (input.intermediates.len == 1 and
                            input.intermediates[0] == '?') break :ansi false;

                        log.warn("invalid set mode command: {f}", .{input});
                        break :mode;
                    };

                    for (input.params) |mode_int| {
                        if (modes.modeFromInt(mode_int, ansi_mode)) |mode| {
                            self.handler.vt(.set_mode, .{ .mode = mode });
                        } else {
                            log.warn("unimplemented mode: {}", .{mode_int});
                        }
                    }
                },

                // RM - Reset Mode
                'l' => mode: {
                    @branchHint(.likely);
                    const ansi_mode = ansi: {
                        if (input.intermediates.len == 0) break :ansi true;
                        if (input.intermediates.len == 1 and
                            input.intermediates[0] == '?') break :ansi false;

                        log.warn("invalid set mode command: {f}", .{input});
                        break :mode;
                    };

                    for (input.params) |mode_int| {
                        if (modes.modeFromInt(mode_int, ansi_mode)) |mode| {
                            self.handler.vt(.reset_mode, .{ .mode = mode });
                        } else {
                            log.warn("unimplemented mode: {}", .{mode_int});
                        }
                    }
                },

                // SGR - Select Graphic Rendition
                'm' => {
                    @branchHint(.likely);
                    switch (input.intermediates.len) {
                        0 => {
                            // This is the most common case.
                            @branchHint(.likely);
                            // log.info("parse SGR params={any}", .{input.params});
                            var p: sgr.Parser = .{
                                .params = input.params,
                                .params_sep = input.params_sep,
                            };
                            while (p.next()) |attr| {
                                // log.info("SGR attribute: {}", .{attr});
                                self.handler.vt(.set_attribute, attr);
                            }
                        },

                        1 => switch (input.intermediates[0]) {
                            '>' => blk: {
                                if (input.params.len == 0) {
                                    // Reset
                                    self.handler.vt(.modify_key_format, .legacy);
                                    break :blk;
                                }

                                var format: ansi.ModifyKeyFormat = switch (input.params[0]) {
                                    0 => .legacy,
                                    1 => .cursor_keys,
                                    2 => .function_keys,
                                    4 => .other_keys_none,
                                    else => {
                                        @branchHint(.unlikely);
                                        log.warn("invalid setModifyKeyFormat: {f}", .{input});
                                        break :blk;
                                    },
                                };

                                if (input.params.len > 2) {
                                    @branchHint(.unlikely);
                                    log.warn("invalid setModifyKeyFormat: {f}", .{input});
                                    break :blk;
                                }

                                if (input.params.len == 2) {
                                    switch (format) {
                                        // We don't support any of the subparams yet for these.
                                        .legacy => {},
                                        .cursor_keys => {},
                                        .function_keys => {},

                                        // We only support the numeric form.
                                        .other_keys_none => switch (input.params[1]) {
                                            2 => format = .other_keys_numeric,
                                            else => {},
                                        },
                                        .other_keys_numeric_except => {},
                                        .other_keys_numeric => {},
                                    }
                                }

                                self.handler.vt(.modify_key_format, format);
                            },

                            else => log.warn(
                                "unknown CSI m with intermediate: {}",
                                .{input.intermediates[0]},
                            ),
                        },

                        else => {
                            // Nothing, but I wanted a place to put this comment:
                            // there are others forms of CSI m that have intermediates.
                            // `vim --clean` uses `CSI ? 4 m` and I don't know what
                            // that means.
                            log.warn(
                                "ignoring unimplemented CSI m with intermediates: {s}",
                                .{input.intermediates},
                            );
                        },
                    }
                },

                // TODO: test
                'n' => {
                    // Handle deviceStatusReport first
                    if (input.intermediates.len == 0 or
                        input.intermediates[0] == '?')
                    {
                        if (input.params.len != 1) {
                            log.warn("invalid device status report command: {f}", .{input});
                            return;
                        }

                        const question = question: {
                            if (input.intermediates.len == 0) break :question false;
                            if (input.intermediates.len == 1 and
                                input.intermediates[0] == '?') break :question true;

                            log.warn("invalid set mode command: {f}", .{input});
                            return;
                        };

                        const req = device_status.reqFromInt(input.params[0], question) orelse {
                            log.warn("invalid device status report command: {f}", .{input});
                            return;
                        };

                        self.handler.vt(.device_status, .{ .request = req });
                        return;
                    }

                    // Handle other forms of CSI n
                    switch (input.intermediates.len) {
                        0 => unreachable, // handled above

                        1 => switch (input.intermediates[0]) {
                            '>' => {
                                // This isn't strictly correct. CSI > n has parameters that
                                // control what exactly is being disabled. However, we
                                // only support reverting back to modify other keys in
                                // numeric except format.
                                self.handler.vt(.modify_key_format, .other_keys_numeric_except);
                            },

                            else => log.warn(
                                "unknown CSI n with intermediate: {}",
                                .{input.intermediates[0]},
                            ),
                        },

                        else => log.warn(
                            "ignoring unimplemented CSI n with intermediates: {s}",
                            .{input.intermediates},
                        ),
                    }
                },

                // DECRQM - Request Mode
                'p' => switch (input.intermediates.len) {
                    2 => decrqm: {
                        const ansi_mode = ansi: {
                            switch (input.intermediates.len) {
                                1 => if (input.intermediates[0] == '$') break :ansi true,
                                2 => if (input.intermediates[0] == '?' and
                                    input.intermediates[1] == '$') break :ansi false,
                                else => {},
                            }

                            log.warn(
                                "ignoring unimplemented CSI p with intermediates: {s}",
                                .{input.intermediates},
                            );
                            break :decrqm;
                        };

                        if (input.params.len != 1) {
                            log.warn("invalid DECRQM command: {f}", .{input});
                            break :decrqm;
                        }

                        const mode_raw = input.params[0];
                        const mode = modes.modeFromInt(mode_raw, ansi_mode);
                        if (mode) |m| {
                            self.handler.vt(.request_mode, .{ .mode = m });
                        } else {
                            self.handler.vt(.request_mode_unknown, .{
                                .mode = mode_raw,
                                .ansi = ansi_mode,
                            });
                        }
                    },

                    else => log.warn(
                        "ignoring unimplemented CSI p with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                'q' => switch (input.intermediates.len) {
                    1 => switch (input.intermediates[0]) {
                        // DECSCUSR - Select Cursor Style
                        // TODO: test
                        ' ' => {
                            const style: ansi.CursorStyle = switch (input.params.len) {
                                0 => .default,
                                1 => switch (input.params[0]) {
                                    0 => .default,
                                    1 => .blinking_block,
                                    2 => .steady_block,
                                    3 => .blinking_underline,
                                    4 => .steady_underline,
                                    5 => .blinking_bar,
                                    6 => .steady_bar,
                                    else => {
                                        log.warn("invalid cursor style value: {}", .{input.params[0]});
                                        return;
                                    },
                                },
                                else => {
                                    log.warn("invalid set cursor style command: {f}", .{input});
                                    return;
                                },
                            };
                            self.handler.vt(.cursor_style, style);
                        },

                        // DECSCA
                        '"' => {
                            const mode_: ?ansi.ProtectedMode = switch (input.params.len) {
                                else => null,
                                0 => .off,
                                1 => switch (input.params[0]) {
                                    0, 2 => .off,
                                    1 => .dec,
                                    else => null,
                                },
                            };

                            const mode = mode_ orelse {
                                log.warn("invalid set protected mode command: {f}", .{input});
                                return;
                            };

                            switch (mode) {
                                .off => self.handler.vt(.protected_mode_off, {}),
                                .iso => self.handler.vt(.protected_mode_iso, {}),
                                .dec => self.handler.vt(.protected_mode_dec, {}),
                            }
                        },

                        // XTVERSION
                        '>' => self.handler.vt(.xtversion, {}),
                        else => {
                            log.warn(
                                "ignoring unimplemented CSI q with intermediates: {s}",
                                .{input.intermediates},
                            );
                        },
                    },

                    else => log.warn(
                        "ignoring unimplemented CSI p with intermediates: {s}",
                        .{input.intermediates},
                    ),
                },

                'r' => {
                    @branchHint(.likely);
                    switch (input.intermediates.len) {
                        // DECSTBM - Set Top and Bottom Margins
                        0 => switch (input.params.len) {
                            0 => self.handler.vt(.top_and_bottom_margin, .{ .top_left = 0, .bottom_right = 0 }),
                            1 => self.handler.vt(.top_and_bottom_margin, .{ .top_left = input.params[0], .bottom_right = 0 }),
                            2 => self.handler.vt(.top_and_bottom_margin, .{ .top_left = input.params[0], .bottom_right = input.params[1] }),
                            else => {
                                @branchHint(.unlikely);
                                log.warn("invalid DECSTBM command: {f}", .{input});
                            },
                        },

                        1 => switch (input.intermediates[0]) {
                            // Restore Mode
                            '?' => {
                                for (input.params) |mode_int| {
                                    if (modes.modeFromInt(mode_int, false)) |mode| {
                                        self.handler.vt(.restore_mode, .{ .mode = mode });
                                    } else {
                                        log.warn(
                                            "unimplemented restore mode: {}",
                                            .{mode_int},
                                        );
                                    }
                                }
                            },

                            else => log.warn(
                                "unknown CSI s with intermediate: {f}",
                                .{input},
                            ),
                        },

                        else => log.warn(
                            "ignoring unimplemented CSI s with intermediates: {f}",
                            .{input},
                        ),
                    }
                },

                's' => switch (input.intermediates.len) {
                    // DECSLRM
                    0 => switch (input.params.len) {
                        // CSI S is ambiguous with zero params so we defer
                        // to our handler to do the proper logic. If mode 69
                        // is set, then we should invoke DECSLRM, otherwise
                        // we should invoke SC.
                        0 => self.handler.vt(.left_and_right_margin_ambiguous, {}),
                        1 => self.handler.vt(.left_and_right_margin, .{ .top_left = input.params[0], .bottom_right = 0 }),
                        2 => self.handler.vt(.left_and_right_margin, .{ .top_left = input.params[0], .bottom_right = input.params[1] }),
                        else => log.warn("invalid DECSLRM command: {f}", .{input}),
                    },

                    1 => switch (input.intermediates[0]) {
                        '?' => {
                            for (input.params) |mode_int| {
                                if (modes.modeFromInt(mode_int, false)) |mode| {
                                    self.handler.vt(.save_mode, .{ .mode = mode });
                                } else {
                                    log.warn(
                                        "unimplemented save mode: {}",
                                        .{mode_int},
                                    );
                                }
                            }
                        },

                        // XTSHIFTESCAPE
                        '>' => capture: {
                            const capture = switch (input.params.len) {
                                0 => false,
                                1 => switch (input.params[0]) {
                                    0 => false,
                                    1 => true,
                                    else => {
                                        log.warn("invalid XTSHIFTESCAPE command: {f}", .{input});
                                        break :capture;
                                    },
                                },
                                else => {
                                    log.warn("invalid XTSHIFTESCAPE command: {f}", .{input});
                                    break :capture;
                                },
                            };

                            self.handler.vt(.mouse_shift_capture, capture);
                        },

                        else => log.warn(
                            "unknown CSI s with intermediate: {f}",
                            .{input},
                        ),
                    },

                    else => log.warn(
                        "ignoring unimplemented CSI s with intermediates: {f}",
                        .{input},
                    ),
                },

                // XTWINOPS
                't' => switch (input.intermediates.len) {
                    0 => {
                        if (input.params.len > 0) {
                            switch (input.params[0]) {
                                14 => if (input.params.len == 1) {
                                    // report the text area size in pixels
                                    self.handler.vt(.size_report, .csi_14_t);
                                } else log.warn(
                                    "ignoring CSI 14 t with extra parameters: {f}",
                                    .{input},
                                ),
                                16 => if (input.params.len == 1) {
                                    // report cell size in pixels
                                    self.handler.vt(.size_report, .csi_16_t);
                                } else log.warn(
                                    "ignoring CSI 16 t with extra parameters: {f}",
                                    .{input},
                                ),
                                18 => if (input.params.len == 1) {
                                    // report screen size in characters
                                    self.handler.vt(.size_report, .csi_18_t);
                                } else log.warn(
                                    "ignoring CSI 18 t with extra parameters: {f}",
                                    .{input},
                                ),
                                21 => if (input.params.len == 1) {
                                    // report window title
                                    self.handler.vt(.size_report, .csi_21_t);
                                } else log.warn(
                                    "ignoring CSI 21 t with extra parameters: {f}",
                                    .{input},
                                ),
                                inline 22, 23 => |number| if ((input.params.len == 2 or
                                    input.params.len == 3) and
                                    // we only support window title
                                    (input.params[1] == 0 or
                                        input.params[1] == 2))
                                {
                                    // push/pop title
                                    const index: u16 = if (input.params.len == 3)
                                        input.params[2]
                                    else
                                        0;
                                    switch (number) {
                                        22 => self.handler.vt(.title_push, index),
                                        23 => self.handler.vt(.title_pop, index),
                                        else => @compileError("unreachable"),
                                    }
                                } else log.warn(
                                    "ignoring CSI 22/23 t with extra parameters: {f}",
                                    .{input},
                                ),
                                else => log.warn(
                                    "ignoring CSI t with unimplemented parameter: {f}",
                                    .{input},
                                ),
                            }
                        } else log.err(
                            "ignoring CSI t with no parameters: {f}",
                            .{input},
                        );
                    },
                    else => log.warn(
                        "ignoring unimplemented CSI t with intermediates: {f}",
                        .{input},
                    ),
                },

                'u' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.restore_cursor, {}),

                    // Kitty keyboard protocol
                    1 => switch (input.intermediates[0]) {
                        '?' => self.handler.vt(.kitty_keyboard_query, {}),

                        '>' => push: {
                            const flags: u5 = if (input.params.len == 1)
                                std.math.cast(u5, input.params[0]) orelse {
                                    log.warn("invalid pushKittyKeyboard command: {f}", .{input});
                                    break :push;
                                }
                            else
                                0;

                            self.handler.vt(.kitty_keyboard_push, .{ .flags = @as(kitty.KeyFlags, @bitCast(flags)) });
                        },

                        '<' => {
                            const number: u16 = if (input.params.len == 1)
                                input.params[0]
                            else
                                1;

                            self.handler.vt(.kitty_keyboard_pop, number);
                        },

                        '=' => set: {
                            const flags: u5 = if (input.params.len >= 1)
                                std.math.cast(u5, input.params[0]) orelse {
                                    log.warn("invalid setKittyKeyboard command: {f}", .{input});
                                    break :set;
                                }
                            else
                                0;

                            const number: u16 = if (input.params.len >= 2)
                                input.params[1]
                            else
                                1;

                            const action_tag: streampkg.Action.Tag = switch (number) {
                                1 => .kitty_keyboard_set,
                                2 => .kitty_keyboard_set_or,
                                3 => .kitty_keyboard_set_not,
                                else => {
                                    log.warn("invalid setKittyKeyboard command: {f}", .{input});
                                    break :set;
                                },
                            };

                            const kitty_flags: streampkg.Action.KittyKeyboardFlags = .{ .flags = @as(kitty.KeyFlags, @bitCast(flags)) };
                            switch (action_tag) {
                                .kitty_keyboard_set => self.handler.vt(.kitty_keyboard_set, kitty_flags),
                                .kitty_keyboard_set_or => self.handler.vt(.kitty_keyboard_set_or, kitty_flags),
                                .kitty_keyboard_set_not => self.handler.vt(.kitty_keyboard_set_not, kitty_flags),
                                else => unreachable,
                            }
                        },

                        else => log.warn(
                            "unknown CSI s with intermediate: {f}",
                            .{input},
                        ),
                    },

                    else => log.warn(
                        "ignoring unimplemented CSI u: {f}",
                        .{input},
                    ),
                },

                // ICH - Insert Blanks
                '@' => switch (input.intermediates.len) {
                    0 => self.handler.vt(.insert_blanks, switch (input.params.len) {
                        0 => 1,
                        1 => @max(1, input.params[0]),
                        else => {
                            @branchHint(.unlikely);
                            log.warn("invalid ICH command: {f}", .{input});
                            return;
                        },
                    }),

                    else => log.warn(
                        "ignoring unimplemented CSI @: {f}",
                        .{input},
                    ),
                },

                // DECSASD - Select Active Status Display
                '}' => decsasd: {
                    // Verify we're getting a DECSASD command
                    if (input.intermediates.len != 1 or input.intermediates[0] != '$') {
                        log.warn("unimplemented CSI callback: {f}", .{input});
                        break :decsasd;
                    }
                    if (input.params.len != 1) {
                        log.warn("unimplemented CSI callback: {f}", .{input});
                        break :decsasd;
                    }

                    const display: ansi.StatusDisplay = switch (input.params[0]) {
                        0 => .main,
                        1 => .status_line,
                        else => {
                            log.warn("unimplemented CSI callback: {f}", .{input});
                            break :decsasd;
                        },
                    };

                    self.handler.vt(.active_status_display, display);
                },

                else => log.warn("unimplemented CSI action: {f}", .{input}),
            }
        }

        inline fn oscDispatch(self: *Self, cmd: osc.Command) void {
            // The branch hints here are based on real world data
            // which indicates that the most common OSC commands are:
            //
            // 1. hyperlink_end
            // 2. change_window_title
            // 3. change_window_icon
            // 4. hyperlink_start
            // 5. report_pwd
            // 6. color_operation
            // 7. semantic_prompt
            //
            // Together, these 7 commands make up about 96% of all
            // OSC commands encountered in real world scenarios.
            //
            // Additionally, within the prongs, unlikely branch
            // hints have been added to branches that deal with
            // invalid sequences/commands, this is in order to
            // optimize for the happy path where we're getting
            // valid data from the program we're running.
            //
            // ref: https://github.com/qwerasd205/asciinema-stats

            switch (cmd) {
                .semantic_prompt => |sp| {
                    @branchHint(.likely);
                    self.handler.vt(.semantic_prompt, sp);
                },

                .change_window_title => |title| {
                    @branchHint(.likely);
                    if (!std.unicode.utf8ValidateSlice(title)) {
                        @branchHint(.unlikely);
                        log.warn("change title request: invalid utf-8, ignoring request", .{});
                        return;
                    }

                    self.handler.vt(.window_title, .{ .title = title });
                },

                .change_window_icon => |icon| {
                    @branchHint(.likely);
                    log.info("OSC 1 (change icon) received and ignored icon={s}", .{icon});
                },

                .clipboard_contents => |clip| {
                    self.handler.vt(.clipboard_contents, .{
                        .kind = clip.kind,
                        .data = clip.data,
                    });
                },

                .report_pwd => |v| {
                    @branchHint(.likely);
                    self.handler.vt(.report_pwd, .{ .url = v.value });
                },

                .mouse_shape => |v| {
                    const shape = MouseShape.fromString(v.value) orelse {
                        @branchHint(.unlikely);
                        log.warn("unknown cursor shape: {s}", .{v.value});
                        return;
                    };

                    self.handler.vt(.mouse_shape, shape);
                },

                .color_operation => |v| {
                    @branchHint(.likely);
                    self.handler.vt(.color_operation, .{
                        .op = v.op,
                        .requests = v.requests,
                        .terminator = v.terminator,
                    });
                },

                .kitty_color_protocol => |v| {
                    self.handler.vt(.kitty_color_report, v);
                },

                .show_desktop_notification => |v| {
                    self.handler.vt(.show_desktop_notification, .{
                        .title = v.title,
                        .body = v.body,
                    });
                },

                .hyperlink_start => |v| {
                    @branchHint(.likely);
                    self.handler.vt(.start_hyperlink, .{
                        .uri = v.uri,
                        .id = v.id,
                    });
                },

                .hyperlink_end => {
                    @branchHint(.likely);
                    self.handler.vt(.end_hyperlink, {});
                },

                .conemu_progress_report => |v| {
                    self.handler.vt(.progress_report, v);
                },

                .conemu_sleep,
                .conemu_show_message_box,
                .conemu_change_tab_title,
                .conemu_wait_input,
                .conemu_guimacro,
                .conemu_comment,
                .conemu_xterm_emulation,
                .conemu_output_environment_variable,
                .conemu_run_process,
                .kitty_text_sizing,
                .kitty_clipboard_protocol,
                .context_signal,
                => {
                    log.debug("unimplemented OSC callback: {}", .{cmd});
                },

                .invalid => {
                    @branchHint(.cold);
                    // This is an invalid internal state, not an invalid OSC
                    // string being parsed. We shouldn't see this.
                    log.warn("invalid OSC, should never happen", .{});
                },
            }
        }

        inline fn configureCharset(
            self: *Self,
            intermediates: []const u8,
            set: charsets.Charset,
        ) void {
            if (intermediates.len != 1) {
                log.warn("invalid charset intermediate: {any}", .{intermediates});
                return;
            }

            const slot: charsets.Slots = switch (intermediates[0]) {
                // TODO: support slots '-', '.', '/'

                '(' => .G0,
                ')' => .G1,
                '*' => .G2,
                '+' => .G3,
                else => {
                    @branchHint(.unlikely);
                    log.warn("invalid charset intermediate: {any}", .{intermediates});
                    return;
                },
            };

            self.handler.vt(.configure_charset, .{
                .slot = slot,
                .charset = set,
            });
        }

        inline fn escDispatch(
            self: *Self,
            action: Parser.Action.ESC,
        ) void {
            // The branch hints here are based on real world data
            // which indicates that the most common ESC finals are:
            //
            // 1. B
            // 2. \
            // 3. 0
            // 4. M
            // 5. 8
            // 6. 7
            // 7. >
            // 8. =
            //
            // Together, these 8 finals make up nearly 99% of all
            // ESC sequences encountered in real world scenarios.
            //
            // Additionally, within the prongs, unlikely branch
            // hints have been added to branches that deal with
            // invalid sequences/commands, this is in order to
            // optimize for the happy path where we're getting
            // valid data from the program we're running.
            //
            // ref: https://github.com/qwerasd205/asciinema-stats

            switch (action.final) {
                // Charsets
                'B' => {
                    @branchHint(.likely);
                    self.configureCharset(action.intermediates, .ascii);
                },
                'A' => self.configureCharset(action.intermediates, .british),
                '0' => {
                    @branchHint(.likely);
                    self.configureCharset(action.intermediates, .dec_special);
                },

                // DECSC - Save Cursor
                '7' => {
                    @branchHint(.likely);
                    switch (action.intermediates.len) {
                        0 => self.handler.vt(.save_cursor, {}),
                        else => {
                            @branchHint(.unlikely);
                            log.warn("invalid command: {f}", .{action});
                            return;
                        },
                    }
                },

                '8' => blk: {
                    @branchHint(.likely);
                    switch (action.intermediates.len) {
                        // DECRC - Restore Cursor
                        0 => {
                            self.handler.vt(.restore_cursor, {});
                            break :blk {};
                        },

                        1 => switch (action.intermediates[0]) {
                            // DECALN - Fill Screen with E
                            '#' => {
                                self.handler.vt(.decaln, {});
                                break :blk {};
                            },

                            else => {},
                        },

                        else => {}, // fall through
                    }

                    log.warn("unimplemented ESC action: {f}", .{action});
                },

                // IND - Index
                'D' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.index, {}),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid index command: {f}", .{action});
                        return;
                    },
                },

                // NEL - Next Line
                'E' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.next_line, {}),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid next line command: {f}", .{action});
                        return;
                    },
                },

                // HTS - Horizontal Tab Set
                'H' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.tab_set, {}),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid tab set command: {f}", .{action});
                        return;
                    },
                },

                // RI - Reverse Index
                'M' => {
                    @branchHint(.likely);
                    switch (action.intermediates.len) {
                        0 => self.handler.vt(.reverse_index, {}),
                        else => {
                            @branchHint(.unlikely);
                            log.warn("invalid reverse index command: {f}", .{action});
                            return;
                        },
                    }
                },

                // SS2 - Single Shift 2
                'N' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.invoke_charset, .{
                        .bank = .GL,
                        .charset = .G2,
                        .locking = true,
                    }),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid single shift 2 command: {f}", .{action});
                        return;
                    },
                },

                // SS3 - Single Shift 3
                'O' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.invoke_charset, .{
                        .bank = .GL,
                        .charset = .G3,
                        .locking = true,
                    }),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid single shift 3 command: {f}", .{action});
                        return;
                    },
                },

                // SPA - Start of Guarded Area
                'V' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.protected_mode_iso, {}),
                    else => log.warn("unimplemented ESC callback: {f}", .{action}),
                },

                // EPA - End of Guarded Area
                'W' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.protected_mode_off, {}),
                    else => log.warn("unimplemented ESC callback: {f}", .{action}),
                },

                // DECID
                'Z' => if (action.intermediates.len == 0) {
                    self.handler.vt(.device_attributes, .primary);
                } else log.warn("unimplemented ESC callback: {f}", .{action}),

                // RIS - Full Reset
                'c' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.full_reset, {}),
                    else => {
                        log.warn("invalid full reset command: {f}", .{action});
                        return;
                    },
                },

                // LS2 - Locking Shift 2
                'n' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.invoke_charset, .{
                        .bank = .GL,
                        .charset = .G2,
                        .locking = false,
                    }),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid single shift 2 command: {f}", .{action});
                        return;
                    },
                },

                // LS3 - Locking Shift 3
                'o' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.invoke_charset, .{
                        .bank = .GL,
                        .charset = .G3,
                        .locking = false,
                    }),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid single shift 3 command: {f}", .{action});
                        return;
                    },
                },

                // LS1R - Locking Shift 1 Right
                '~' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.invoke_charset, .{
                        .bank = .GR,
                        .charset = .G1,
                        .locking = false,
                    }),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid locking shift 1 right command: {f}", .{action});
                        return;
                    },
                },

                // LS2R - Locking Shift 2 Right
                '}' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.invoke_charset, .{
                        .bank = .GR,
                        .charset = .G2,
                        .locking = false,
                    }),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid locking shift 2 right command: {f}", .{action});
                        return;
                    },
                },

                // LS3R - Locking Shift 3 Right
                '|' => switch (action.intermediates.len) {
                    0 => self.handler.vt(.invoke_charset, .{
                        .bank = .GR,
                        .charset = .G3,
                        .locking = false,
                    }),
                    else => {
                        @branchHint(.unlikely);
                        log.warn("invalid locking shift 3 right command: {f}", .{action});
                        return;
                    },
                },

                // Set application keypad mode
                '=' => {
                    @branchHint(.likely);
                    switch (action.intermediates.len) {
                        0 => self.handler.vt(.set_mode, .{ .mode = .keypad_keys }),
                        else => log.warn("unimplemented setMode: {f}", .{action}),
                    }
                },

                // Reset application keypad mode
                '>' => {
                    @branchHint(.likely);
                    switch (action.intermediates.len) {
                        0 => self.handler.vt(.reset_mode, .{ .mode = .keypad_keys }),
                        else => log.warn("unimplemented setMode: {f}", .{action}),
                    }
                },

                // Sets ST (string terminator). We don't have to do anything
                // because our parser always accepts ST.
                '\\' => {
                    @branchHint(.likely);
                },

                else => log.warn("unimplemented ESC action: {f}", .{action}),
            }
        }
    };
}

test Action {
    // Forces the C type to be reified when the target is C, ensuring
    // all our types are C ABI compatible.
    _ = Action.C;
}

test "stream: print" {
    const H = struct {
        c: ?u21 = 0,

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            switch (action) {
                .print => self.c = value.cp,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.next('x');
    try testing.expectEqual(@as(u21, 'x'), s.handler.c.?);
}

test "simd: print invalid utf-8" {
    const H = struct {
        c: ?u21 = 0,

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            switch (action) {
                .print => self.c = value.cp,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice(&.{0xFF});
    try testing.expectEqual(@as(u21, 0xFFFD), s.handler.c.?);
}

test "simd: complete incomplete utf-8" {
    const H = struct {
        c: ?u21 = null,

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            switch (action) {
                .print => self.c = value.cp,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice(&.{0xE0}); // 3 byte
    try testing.expect(s.handler.c == null);
    s.nextSlice(&.{0xA0}); // still incomplete
    try testing.expect(s.handler.c == null);
    s.nextSlice(&.{0x80});
    try testing.expectEqual(@as(u21, 0x800), s.handler.c.?);
}

test "stream: cursor right (CUF)" {
    const H = struct {
        amount: u16 = 0,

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            switch (action) {
                .cursor_right => self.amount = value.value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[C");
    try testing.expectEqual(@as(u16, 1), s.handler.amount);

    s.nextSlice("\x1B[5C");
    try testing.expectEqual(@as(u16, 5), s.handler.amount);

    s.handler.amount = 0;
    s.nextSlice("\x1B[5;4C");
    try testing.expectEqual(@as(u16, 0), s.handler.amount);

    s.handler.amount = 0;
    s.nextSlice("\x1b[?3C");
    try testing.expectEqual(@as(u16, 0), s.handler.amount);
}

test "stream: dec set mode (SM) and reset mode (RM)" {
    const H = struct {
        mode: modes.Mode = @as(modes.Mode, @enumFromInt(1)),

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            switch (action) {
                .set_mode => self.mode = value.mode,
                .reset_mode => self.mode = @as(modes.Mode, @enumFromInt(1)),
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[?6h");
    try testing.expectEqual(@as(modes.Mode, .origin), s.handler.mode);

    s.nextSlice("\x1B[?6l");
    try testing.expectEqual(@as(modes.Mode, @enumFromInt(1)), s.handler.mode);

    s.handler.mode = @as(modes.Mode, @enumFromInt(1));
    s.nextSlice("\x1B[6 h");
    try testing.expectEqual(@as(modes.Mode, @enumFromInt(1)), s.handler.mode);
}

test "stream: ansi set mode (SM) and reset mode (RM)" {
    const H = struct {
        mode: ?modes.Mode = null,

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            switch (action) {
                .set_mode => self.mode = value.mode,
                .reset_mode => self.mode = null,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[4h");
    try testing.expectEqual(@as(modes.Mode, .insert), s.handler.mode.?);

    s.nextSlice("\x1B[4l");
    try testing.expect(s.handler.mode == null);

    s.handler.mode = null;
    s.nextSlice("\x1B[>5h");
    try testing.expect(s.handler.mode == null);
}

test "stream: ansi set mode (SM) and reset mode (RM) with unknown value" {
    const H = struct {
        mode: ?modes.Mode = null,

        pub fn setMode(self: *@This(), mode: modes.Mode, v: bool) !void {
            self.mode = null;
            if (v) self.mode = mode;
        }

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            _ = self;
            _ = value;
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[6h");
    try testing.expect(s.handler.mode == null);

    s.nextSlice("\x1B[6l");
    try testing.expect(s.handler.mode == null);
}

test "stream: restore mode" {
    const H = struct {
        const Self = @This();
        called: bool = false,

        pub fn vt(
            self: *Self,
            comptime action: Stream(Self).Action.Tag,
            value: Stream(Self).Action.Value(action),
        ) void {
            _ = value;
            switch (action) {
                .top_and_bottom_margin => self.called = true,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    for ("\x1B[?42r") |c| s.next(c);
    try testing.expect(!s.handler.called);
}

test "stream: pop kitty keyboard with no params defaults to 1" {
    const H = struct {
        const Self = @This();
        n: u16 = 0,

        pub fn vt(
            self: *Self,
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .kitty_keyboard_pop => self.n = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    for ("\x1B[<u") |c| s.next(c);
    try testing.expectEqual(@as(u16, 1), s.handler.n);
}

test "stream: DECSCA" {
    const H = struct {
        const Self = @This();
        v: ?ansi.ProtectedMode = null,

        pub fn vt(
            self: *Self,
            comptime action: Stream(Self).Action.Tag,
            value: Stream(Self).Action.Value(action),
        ) void {
            _ = value;
            switch (action) {
                .protected_mode_off => self.v = .off,
                .protected_mode_iso => self.v = .iso,
                .protected_mode_dec => self.v = .dec,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    {
        for ("\x1B[\"q") |c| s.next(c);
        try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
    }
    {
        for ("\x1B[0\"q") |c| s.next(c);
        try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
    }
    {
        for ("\x1B[2\"q") |c| s.next(c);
        try testing.expectEqual(ansi.ProtectedMode.off, s.handler.v.?);
    }
    {
        for ("\x1B[1\"q") |c| s.next(c);
        try testing.expectEqual(ansi.ProtectedMode.dec, s.handler.v.?);
    }
}

test "stream: DECED, DECSED" {
    const H = struct {
        const Self = @This();
        mode: ?csi.EraseDisplay = null,
        protected: ?bool = null,

        pub fn vt(
            self: *Self,
            comptime action: anytype,
            value: anytype,
        ) void {
            switch (action) {
                .erase_display_below => {
                    self.mode = .below;
                    self.protected = value;
                },
                .erase_display_above => {
                    self.mode = .above;
                    self.protected = value;
                },
                .erase_display_complete => {
                    self.mode = .complete;
                    self.protected = value;
                },
                .erase_display_scrollback => {
                    self.mode = .scrollback;
                    self.protected = value;
                },
                .erase_display_scroll_complete => {
                    self.mode = .scroll_complete;
                    self.protected = value;
                },
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    {
        for ("\x1B[?J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }
    {
        for ("\x1B[?0J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }
    {
        for ("\x1B[?1J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.above, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }
    {
        for ("\x1B[?2J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.complete, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }
    {
        for ("\x1B[?3J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }

    {
        for ("\x1B[J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        for ("\x1B[0J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.below, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        for ("\x1B[1J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.above, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        for ("\x1B[2J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.complete, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        for ("\x1B[3J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        // Invalid and ignored by the handler
        for ("\x1B[>0J") |c| s.next(c);
        try testing.expectEqual(csi.EraseDisplay.scrollback, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
}

test "stream: DECEL, DECSEL" {
    const H = struct {
        const Self = @This();
        mode: ?csi.EraseLine = null,
        protected: ?bool = null,

        pub fn vt(
            self: *Self,
            comptime action: anytype,
            value: anytype,
        ) void {
            switch (action) {
                .erase_line_right => {
                    self.mode = .right;
                    self.protected = value;
                },
                .erase_line_left => {
                    self.mode = .left;
                    self.protected = value;
                },
                .erase_line_complete => {
                    self.mode = .complete;
                    self.protected = value;
                },
                .erase_line_right_unless_pending_wrap => {
                    self.mode = .right_unless_pending_wrap;
                    self.protected = value;
                },
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    {
        for ("\x1B[?K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }
    {
        for ("\x1B[?0K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }
    {
        for ("\x1B[?1K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.left, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }
    {
        for ("\x1B[?2K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?);
        try testing.expect(s.handler.protected.?);
    }

    {
        for ("\x1B[K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        for ("\x1B[0K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.right, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        for ("\x1B[1K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.left, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        for ("\x1B[2K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
    {
        // Invalid and ignored by the handler
        for ("\x1B[<1K") |c| s.next(c);
        try testing.expectEqual(csi.EraseLine.complete, s.handler.mode.?);
        try testing.expect(!s.handler.protected.?);
    }
}

test "stream: DECSCUSR" {
    const H = struct {
        style: ?ansi.CursorStyle = null,

        pub fn vt(
            self: *@This(),
            comptime action: Stream(@This()).Action.Tag,
            value: Stream(@This()).Action.Value(action),
        ) void {
            switch (action) {
                .cursor_style => self.style = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[ q");
    try testing.expect(s.handler.style.? == .default);

    s.nextSlice("\x1B[1 q");
    try testing.expect(s.handler.style.? == .blinking_block);

    // Invalid and ignored by the handler
    s.nextSlice("\x1B[?0 q");
    try testing.expect(s.handler.style.? == .blinking_block);
}

test "stream: DECSCUSR without space" {
    const H = struct {
        style: ?ansi.CursorStyle = null,

        pub fn vt(
            self: *@This(),
            comptime action: Stream(@This()).Action.Tag,
            value: Stream(@This()).Action.Value(action),
        ) void {
            switch (action) {
                .cursor_style => self.style = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[q");
    try testing.expect(s.handler.style == null);

    s.nextSlice("\x1B[1q");
    try testing.expect(s.handler.style == null);
}

test "stream: XTSHIFTESCAPE" {
    const H = struct {
        escape: ?bool = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .mouse_shift_capture => self.escape = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[>2s");
    try testing.expect(s.handler.escape == null);

    s.nextSlice("\x1B[>s");
    try testing.expect(s.handler.escape.? == false);

    s.nextSlice("\x1B[>0s");
    try testing.expect(s.handler.escape.? == false);

    s.nextSlice("\x1B[>1s");
    try testing.expect(s.handler.escape.? == true);

    // Invalid and ignored by the handler
    s.nextSlice("\x1B[1 s");
    try testing.expect(s.handler.escape.? == true);
}

test "stream: change window title with invalid utf-8" {
    const H = struct {
        seen: bool = false,

        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = value;
            switch (action) {
                .window_title => self.seen = true,
                else => {},
            }
        }
    };

    {
        var s: Stream(H) = .init(.{});
        s.nextSlice("\x1b]2;abc\x1b\\");
        try testing.expect(s.handler.seen);
    }

    {
        var s: Stream(H) = .init(.{});
        s.nextSlice("\x1b]2;abc\xc0\x1b\\");
        try testing.expect(!s.handler.seen);
    }
}

test "stream: insert characters" {
    const H = struct {
        const Self = @This();
        called: bool = false,

        pub fn vt(
            self: *Self,
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = value;
            switch (action) {
                .insert_blanks => self.called = true,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    for ("\x1B[42@") |c| s.next(c);
    try testing.expect(s.handler.called);

    s.handler.called = false;
    for ("\x1B[?42@") |c| s.next(c);
    try testing.expect(!s.handler.called);
}

test "stream: insert characters explicit zero clamps to 1" {
    const H = struct {
        const Self = @This();
        value: ?usize = null,

        pub fn vt(
            self: *Self,
            comptime action: anytype,
            value: anytype,
        ) void {
            switch (action) {
                .insert_blanks => self.value = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    for ("\x1B[0@") |c| s.next(c);
    try testing.expectEqual(@as(usize, 1), s.handler.value.?);
}

test "stream: SCOSC" {
    const H = struct {
        const Self = @This();
        called: bool = false,

        pub fn vt(
            self: *Self,
            comptime action: Stream(Self).Action.Tag,
            value: Stream(Self).Action.Value(action),
        ) void {
            _ = value;
            switch (action) {
                .left_and_right_margin => @panic("bad"),
                .left_and_right_margin_ambiguous => self.called = true,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    for ("\x1B[s") |c| s.next(c);
    try testing.expect(s.handler.called);
}

test "stream: SCORC" {
    const H = struct {
        const Self = @This();
        called: bool = false,

        pub fn vt(
            self: *Self,
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            _ = value;
            switch (action) {
                .restore_cursor => self.called = true,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    for ("\x1B[u") |c| s.next(c);
    try testing.expect(s.handler.called);
}

test "stream: too many csi params" {
    const H = struct {
        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = self;
            _ = value;
            switch (action) {
                .cursor_right => unreachable,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1;1C");
}

test "stream: csi param too long" {
    const H = struct {
        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = self;
            _ = action;
            _ = value;
        }
    };

    var s: Stream(H) = .init(.{});
    s.nextSlice("\x1B[1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111C");
}

test "stream: send report with CSI t" {
    const H = struct {
        style: ?csi.SizeReportStyle = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .size_report => self.style = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[14t");
    try testing.expectEqual(csi.SizeReportStyle.csi_14_t, s.handler.style);

    s.nextSlice("\x1b[16t");
    try testing.expectEqual(csi.SizeReportStyle.csi_16_t, s.handler.style);

    s.nextSlice("\x1b[18t");
    try testing.expectEqual(csi.SizeReportStyle.csi_18_t, s.handler.style);

    s.nextSlice("\x1b[21t");
    try testing.expectEqual(csi.SizeReportStyle.csi_21_t, s.handler.style);
}

test "stream: invalid CSI t" {
    const H = struct {
        style: ?csi.SizeReportStyle = null,

        pub fn sendSizeReport(self: *@This(), style: csi.SizeReportStyle) void {
            self.style = style;
        }

        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = self;
            _ = action;
            _ = value;
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[19t");
    try testing.expectEqual(null, s.handler.style);
}

test "stream: CSI t push title" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_push => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[22;0t");
    try testing.expectEqual(@as(u16, 0), s.handler.index.?);
}

test "stream: CSI t push title with explicit window" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_push => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[22;2t");
    try testing.expectEqual(@as(u16, 0), s.handler.index.?);
}

test "stream: CSI t push title with explicit icon" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_push => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[22;1t");
    try testing.expectEqual(null, s.handler.index);
}

test "stream: CSI t push title with index" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_push => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[22;0;5t");
    try testing.expectEqual(@as(u16, 5), s.handler.index.?);
}

test "stream: CSI t pop title" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_pop => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[23;0t");
    try testing.expectEqual(@as(u16, 0), s.handler.index.?);
}

test "stream: CSI t pop title with explicit window" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_pop => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[23;2t");
    try testing.expectEqual(@as(u16, 0), s.handler.index.?);
}

test "stream: CSI t pop title with explicit icon" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_pop => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[23;1t");
    try testing.expectEqual(null, s.handler.index);
}

test "stream: CSI t pop title with index" {
    const H = struct {
        index: ?u16 = null,

        pub fn vt(
            self: *@This(),
            comptime action: streampkg.Action.Tag,
            value: streampkg.Action.Value(action),
        ) void {
            switch (action) {
                .title_pop => self.index = value,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[23;0;5t");
    try testing.expectEqual(@as(u16, 5), s.handler.index.?);
}

test "stream CSI W clear tab stops" {
    const H = struct {
        action: ?Action.Key = null,

        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = value;
            self.action = action;
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[2W");
    try testing.expectEqual(Action.Key.tab_clear_current, s.handler.action.?);

    s.nextSlice("\x1b[5W");
    try testing.expectEqual(Action.Key.tab_clear_all, s.handler.action.?);
}

test "stream CSI W tab set" {
    const H = struct {
        action: ?Action.Key = null,

        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = value;
            self.action = action;
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[W");
    try testing.expectEqual(Action.Key.tab_set, s.handler.action.?);

    s.handler.action = null;
    s.nextSlice("\x1b[0W");
    try testing.expectEqual(Action.Key.tab_set, s.handler.action.?);

    s.handler.action = null;
    s.nextSlice("\x1b[>W");
    try testing.expect(s.handler.action == null);

    s.handler.action = null;
    s.nextSlice("\x1b[99W");
    try testing.expect(s.handler.action == null);
}

test "stream CSI ? W reset tab stops" {
    const H = struct {
        action: ?Action.Key = null,

        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            _ = value;
            self.action = action;
        }
    };

    var s: Stream(H) = .init(.{});

    s.nextSlice("\x1b[?2W");
    try testing.expect(s.handler.action == null);

    s.nextSlice("\x1b[?5W");
    try testing.expectEqual(Action.Key.tab_reset, s.handler.action.?);

    // Invalid and ignored by the handler
    s.handler.action = null;
    s.nextSlice("\x1b[?1;2;3W");
    try testing.expect(s.handler.action == null);
}

test "stream: SGR with 17+ parameters for underline color" {
    const H = struct {
        attrs: ?sgr.Attribute = null,
        called: bool = false,

        pub fn vt(
            self: *@This(),
            comptime action: anytype,
            value: anytype,
        ) void {
            switch (action) {
                .set_attribute => {
                    self.attrs = value;
                    self.called = true;
                },
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});

    // Kakoune-style SGR with underline color as 17th parameter
    // This tests the fix where param 17 was being dropped
    s.nextSlice("\x1b[4:3;38;2;51;51;51;48;2;170;170;170;58;2;255;97;136;0m");
    try testing.expect(s.handler.called);
}

test "stream: tab clear with overflowing param" {
    // Regression test for a fuzz crash: CSI with a parameter value that
    // saturates to 65535 (u16 max) causes @enumFromInt to panic when
    // converting to TabClear (enum(u8)).
    const H = struct {
        called: bool = false,

        pub fn vt(
            self: *@This(),
            comptime action: Action.Tag,
            value: Action.Value(action),
        ) void {
            _ = value;
            switch (action) {
                .tab_clear_current, .tab_clear_all => self.called = true,
                else => {},
            }
        }
    };

    var s: Stream(H) = .init(.{});
    // This is the exact input from the fuzz crash (minus the mode byte):
    // CSI with a huge numeric param that saturates to 65535, followed by 'g'.
    s.nextSlice("\x1b[388888888888888888888888888888888888g\x1b[0m");
}
