const std = @import("std");
const vaxis = @import("../main.zig");

const Allocator = std.mem.Allocator;

const vxfw = @import("vxfw.zig");

pub const BorderLabel = struct {
    text: []const u8,
    alignment: enum {
        top_left,
        top_center,
        top_right,
        bottom_left,
        bottom_center,
        bottom_right,
    },
};

const Border = @This();

child: vxfw.Widget,
style: vaxis.Style = .{},
labels: []const BorderLabel = &[_]BorderLabel{},

pub fn widget(self: *const Border) vxfw.Widget {
    return .{
        .userdata = @constCast(self),
        .drawFn = typeErasedDrawFn,
    };
}

fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
    const self: *const Border = @ptrCast(@alignCast(ptr));
    return self.draw(ctx);
}

/// If Border has a bounded maximum size, it will shrink the maximum size to account for the border
/// before drawing the child. If the size is unbounded, border will draw the child and then itself
/// around the child's size
pub fn draw(self: *const Border, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
    const max_width: ?u16 = if (ctx.max.width) |width| width -| 2 else null;
    const max_height: ?u16 = if (ctx.max.height) |height| height -| 2 else null;

    const child_ctx = ctx.withConstraints(ctx.min, .{
        .width = max_width,
        .height = max_height,
    });
    const child = try self.child.draw(child_ctx);

    const children = try ctx.arena.alloc(vxfw.SubSurface, 1);
    children[0] = .{
        .origin = .{ .col = 1, .row = 1 },
        .z_index = 0,
        .surface = child,
    };

    const size: vxfw.Size = .{ .width = child.size.width + 2, .height = child.size.height + 2 };

    var surf = try vxfw.Surface.initWithChildren(ctx.arena, self.widget(), size, children);

    // Draw the border
    const right_edge = size.width -| 1;
    const bottom_edge = size.height -| 1;
    surf.writeCell(0, 0, .{ .char = .{ .grapheme = "╭", .width = 1 }, .style = self.style });
    surf.writeCell(right_edge, 0, .{ .char = .{ .grapheme = "╮", .width = 1 }, .style = self.style });
    surf.writeCell(right_edge, bottom_edge, .{ .char = .{ .grapheme = "╯", .width = 1 }, .style = self.style });
    surf.writeCell(0, bottom_edge, .{ .char = .{ .grapheme = "╰", .width = 1 }, .style = self.style });

    var col: u16 = 1;
    while (col < right_edge) : (col += 1) {
        surf.writeCell(col, 0, .{ .char = .{ .grapheme = "─", .width = 1 }, .style = self.style });
        surf.writeCell(col, bottom_edge, .{ .char = .{ .grapheme = "─", .width = 1 }, .style = self.style });
    }

    var row: u16 = 1;
    while (row < bottom_edge) : (row += 1) {
        surf.writeCell(0, row, .{ .char = .{ .grapheme = "│", .width = 1 }, .style = self.style });
        surf.writeCell(right_edge, row, .{ .char = .{ .grapheme = "│", .width = 1 }, .style = self.style });
    }

    // Add border labels
    for (self.labels) |label| {
        const text_len: u16 = @intCast(ctx.stringWidth(label.text));
        if (text_len == 0) continue;

        const text_row: u16 = switch (label.alignment) {
            .top_left, .top_center, .top_right => 0,
            .bottom_left, .bottom_center, .bottom_right => bottom_edge,
        };

        var text_col: u16 = switch (label.alignment) {
            .top_left, .bottom_left => 1,
            .top_center, .bottom_center => @max((size.width - text_len) / 2, 1),
            .top_right, .bottom_right => @max(size.width - 1 - text_len, 1),
        };

        var iter = ctx.graphemeIterator(label.text);
        while (iter.next()) |grapheme| {
            const text = grapheme.bytes(label.text);
            const width: u16 = @intCast(ctx.stringWidth(text));
            surf.writeCell(text_col, text_row, .{
                .char = .{ .grapheme = text, .width = @intCast(width) },
                .style = self.style,
            });
            text_col += width;
        }
    }

    return surf;
}

test Border {
    const Text = @import("Text.zig");
    // Will be height=1, width=3
    const text: Text = .{ .text = "abc" };

    const border: Border = .{ .child = text.widget() };

    var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
    defer arena.deinit();
    vxfw.DrawContext.init(.unicode);

    // Border will draw itself tightly around the child
    const ctx: vxfw.DrawContext = .{
        .arena = arena.allocator(),
        .min = .{},
        .max = .{ .width = 10, .height = 10 },
        .cell_size = .{ .width = 10, .height = 20 },
    };

    const surface = try border.draw(ctx);
    // Border should be the size of Text + 2
    try std.testing.expectEqual(5, surface.size.width);
    try std.testing.expectEqual(3, surface.size.height);
    // Border has 1 child
    try std.testing.expectEqual(1, surface.children.len);
    const child = surface.children[0];
    // The child is 1x3
    try std.testing.expectEqual(3, child.surface.size.width);
    try std.testing.expectEqual(1, child.surface.size.height);
}

test "refAllDecls" {
    std.testing.refAllDecls(@This());
}
