const std = @import("std");

const Cell = @import("../Cell.zig");
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Position = @import("../Position.zig");

pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
    return struct {
        const Allocator = std.mem.Allocator;
        const ItemList = std.ArrayListUnmanaged(ItemType);
        const DrawItemFn = *const fn (*Self, ItemType, usize, usize, usize) void;
        const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;

        const Self = @This();

        allocator: Allocator,
        buffer: *TerminalBuffer,
        list: ItemList,
        current: usize,
        width: usize,
        component_pos: Position,
        children_pos: Position,
        text_in_center: bool,
        fg: u32,
        bg: u32,
        cursor: usize,
        draw_item_fn: DrawItemFn,
        change_item_fn: ?ChangeItemFn,
        change_item_arg: ?ChangeItemType,
        keybinds: TerminalBuffer.KeybindMap,

        pub fn init(
            allocator: Allocator,
            io: std.Io,
            buffer: *TerminalBuffer,
            draw_item_fn: DrawItemFn,
            change_item_fn: ?ChangeItemFn,
            change_item_arg: ?ChangeItemType,
            width: usize,
            text_in_center: bool,
            fg: u32,
            bg: u32,
        ) !*Self {
            var self = try allocator.create(Self);
            self.* = .{
                .allocator = allocator,
                .buffer = buffer,
                .list = .empty,
                .current = 0,
                .width = width,
                .component_pos = TerminalBuffer.START_POSITION,
                .children_pos = TerminalBuffer.START_POSITION,
                .text_in_center = text_in_center,
                .fg = fg,
                .bg = bg,
                .cursor = 0,
                .draw_item_fn = draw_item_fn,
                .change_item_fn = change_item_fn,
                .change_item_arg = change_item_arg,
                .keybinds = .init(allocator),
            };

            try buffer.registerKeybind(io, &self.keybinds, "Left", &goLeft, self);
            try buffer.registerKeybind(io, &self.keybinds, "Ctrl+H", &goLeft, self);
            try buffer.registerKeybind(io, &self.keybinds, "Right", &goRight, self);
            try buffer.registerKeybind(io, &self.keybinds, "Ctrl+L", &goRight, self);

            return self;
        }

        pub fn deinit(self: *Self) void {
            self.list.deinit(self.allocator);
            self.keybinds.deinit();
            self.allocator.destroy(self);
        }

        pub fn positionX(self: *Self, original_pos: Position) void {
            self.component_pos = original_pos;
            self.cursor = self.component_pos.x + 2;
            self.children_pos = original_pos.addX(self.width);
        }

        pub fn positionY(self: *Self, original_pos: Position) void {
            self.component_pos = original_pos;
            self.cursor = self.component_pos.x + 2;
            self.children_pos = original_pos.addY(1);
        }

        pub fn positionXY(self: *Self, original_pos: Position) void {
            self.component_pos = original_pos;
            self.cursor = self.component_pos.x + 2;
            self.children_pos = Position.init(
                self.width,
                1,
            ).add(original_pos);
        }

        pub fn childrenPosition(self: Self) Position {
            return self.children_pos;
        }

        pub fn addItem(self: *Self, item: ItemType) !void {
            try self.list.append(self.allocator, item);
            self.current = self.list.items.len - 1;
        }

        pub fn handle(self: *Self, _: ?keyboard.Key) void {
            TerminalBuffer.setCursor(
                self.component_pos.x + self.cursor + 2,
                self.component_pos.y,
            );
        }

        pub fn draw(self: *Self) void {
            if (self.list.items.len == 0) return;
            if (self.width < 2) return;

            var left_arrow = Cell.init('<', self.fg, self.bg);
            var right_arrow = Cell.init('>', self.fg, self.bg);

            left_arrow.put(self.component_pos.x, self.component_pos.y);
            right_arrow.put(
                self.component_pos.x + self.width - 1,
                self.component_pos.y,
            );

            const current_item = self.list.items[self.current];
            const x = self.component_pos.x + 2;
            const y = self.component_pos.y;
            const width = self.width - 2;

            @call(
                .auto,
                self.draw_item_fn,
                .{ self, current_item, x, y, width },
            );
        }

        fn goLeft(ptr: *anyopaque) !bool {
            var self: *Self = @ptrCast(@alignCast(ptr));

            self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;

            if (self.change_item_fn) |change_item_fn| {
                @call(
                    .auto,
                    change_item_fn,
                    .{ self.list.items[self.current], self.change_item_arg },
                );
            }

            return false;
        }

        fn goRight(ptr: *anyopaque) !bool {
            var self: *Self = @ptrCast(@alignCast(ptr));

            self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;

            if (self.change_item_fn) |change_item_fn| {
                @call(
                    .auto,
                    change_item_fn,
                    .{ self.list.items[self.current], self.change_item_arg },
                );
            }

            return false;
        }
    };
}
