const std = @import("std");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");

const Allocator = std.mem.Allocator;
const DynamicString = std.ArrayListUnmanaged(u8);

const termbox = interop.termbox;

const Text = @This();

allocator: Allocator,
buffer: *TerminalBuffer,
text: DynamicString,
end: usize,
cursor: usize,
visible_start: usize,
visible_length: usize,
x: usize,
y: usize,
masked: bool,
maybe_mask: ?u32,

pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u32) Text {
    const text: DynamicString = .empty;

    return .{
        .allocator = allocator,
        .buffer = buffer,
        .text = text,
        .end = 0,
        .cursor = 0,
        .visible_start = 0,
        .visible_length = 0,
        .x = 0,
        .y = 0,
        .masked = masked,
        .maybe_mask = maybe_mask,
    };
}

pub fn deinit(self: *Text) void {
    self.text.deinit(self.allocator);
}

pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void {
    self.x = x;
    self.y = y;
    self.visible_length = visible_length;
}

pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void {
    if (maybe_event) |event| blk: {
        if (event.type != termbox.TB_EVENT_KEY) break :blk;

        switch (event.key) {
            termbox.TB_KEY_ARROW_LEFT => self.goLeft(),
            termbox.TB_KEY_ARROW_RIGHT => self.goRight(),
            termbox.TB_KEY_DELETE => self.delete(),
            termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => {
                if (insert_mode) {
                    self.backspace();
                } else {
                    self.goLeft();
                }
            },
            termbox.TB_KEY_SPACE => try self.write(' '),
            else => {
                if (event.ch > 31 and event.ch < 127) {
                    if (insert_mode) {
                        try self.write(@intCast(event.ch));
                    } else {
                        switch (event.ch) {
                            'h' => self.goLeft(),
                            'l' => self.goRight(),
                            else => {},
                        }
                    }
                }
            },
        }
    }

    if (self.masked and self.maybe_mask == null) {
        _ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y));
        return;
    }

    _ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
}

pub fn draw(self: Text) void {
    if (self.masked) {
        if (self.maybe_mask) |mask| {
            const length = @min(self.text.items.len, self.visible_length - 1);
            if (length == 0) return;

            self.buffer.drawCharMultiple(mask, self.x, self.y, length);
        }
        return;
    }

    const length = @min(self.text.items.len, self.visible_length);
    if (length == 0) return;

    const visible_slice = vs: {
        if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) {
            break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)];
        } else {
            break :vs self.text.items[self.visible_start..];
        }
    };

    self.buffer.drawLabel(visible_slice, self.x, self.y);
}

pub fn clear(self: *Text) void {
    self.text.clearRetainingCapacity();
    self.end = 0;
    self.cursor = 0;
    self.visible_start = 0;
}

fn goLeft(self: *Text) void {
    if (self.cursor == 0) return;
    if (self.visible_start > 0) self.visible_start -= 1;

    self.cursor -= 1;
}

fn goRight(self: *Text) void {
    if (self.cursor >= self.end) return;
    if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1;

    self.cursor += 1;
}

fn delete(self: *Text) void {
    if (self.cursor >= self.end) return;

    _ = self.text.orderedRemove(self.cursor);

    self.end -= 1;
}

fn backspace(self: *Text) void {
    if (self.cursor == 0) return;

    self.goLeft();
    self.delete();
}

fn write(self: *Text, char: u8) !void {
    if (char == 0) return;

    try self.text.insert(self.allocator, self.cursor, char);

    self.end += 1;
    self.goRight();
}
