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

const Color = dvui.Color;
const Event = dvui.Event;
const Options = dvui.Options;
const Point = dvui.Point;
const Rect = dvui.Rect;
const RectScale = dvui.RectScale;
const Size = dvui.Size;
const Widget = dvui.Widget;
const WidgetData = dvui.WidgetData;

const ButtonWidget = @This();

pub var defaults: Options = .{
    .name = "Button",
    .role = .button,
    .margin = Rect.all(4),
    .corners = .default,
    .padding = Rect.all(6),
    .background = true,
    .style = .control,
};

pub const InitOptions = struct {
    draw_focus: bool = true,

    /// Blends color_text into color_fill to visually suggest the button action
    /// is disabled.  This does not by itself disable the button.  It's
    /// recommended to provide some user feedback as to why the action can't be
    /// taken.  Suggestions:
    /// * tooltip on the button
    /// * show a toast
    /// * dynamically swap the button for a label, or add a label next to the button
    grayed: bool = false,

    /// True if you are going to be processing events to drag the button around
    /// (like TreeWidget).  See `dvui.clicked`.
    touch_drag: bool = false,
};

wd: WidgetData,
init_options: InitOptions,
hover: bool = false,
focus: bool = false,
click: bool = false,

/// It's expected to call this when `self` is `undefined`
pub fn init(self: *ButtonWidget, src: std.builtin.SourceLocation, init_options: InitOptions, opts: Options) void {
    var options = defaults.override(opts);
    if (init_options.grayed) {
        options.color_text = dvui.Color.average(options.color(.text), options.color(.fill));
    }
    self.* = .{
        .wd = .init(src, .{}, options),
        .init_options = init_options,
    };
    self.data().register();
    dvui.parentSet(self.widget());

    dvui.tabIndexSet(self.data().id, self.data().options.tab_index, self.data().rectScale().r);

    if (self.data().accesskit_node()) |ak_node| {
        dvui.AccessKit.nodeAddAction(ak_node, dvui.AccessKit.Action.focus);
        dvui.AccessKit.nodeAddAction(ak_node, dvui.AccessKit.Action.click);
    }
}

pub fn matchEvent(self: *ButtonWidget, e: *Event) bool {
    return dvui.eventMatchSimple(e, self.data());
}

pub fn processEvents(self: *ButtonWidget) void {
    self.click = dvui.clicked(self.data(), .{ .hovered = &self.hover, .touch_drag = self.init_options.touch_drag });
}

pub fn processHover(self: *ButtonWidget) void {
    for (dvui.events()) |*e| {
        if (!dvui.eventMatchSimple(e, self.data())) continue;

        if (e.evt == .mouse and e.evt.mouse.action == .position) {
            self.hover = true;
        }
    }
}

pub fn drawBackground(self: *ButtonWidget) void {
    const style_opt = self.style();
    var npask: Options.NinepatchAsk = .fill;
    if (dvui.captured(self.data().id)) {
        npask = .press;
    } else if (self.hover) {
        npask = .hover;
    }
    self.data().borderAndBackground(.{
        .fill_color = style_opt.color_fill,
        .ninepatch = self.data().options.ninepatch(npask),
    });
}

pub fn drawFocus(self: *ButtonWidget) void {
    if (self.init_options.draw_focus and self.focused()) {
        self.data().focusBorder();
    }
}

/// Returns an `Options` struct with color/style overrides for the hover and press state
pub fn style(self: *ButtonWidget) Options {
    var opts = self.data().options.styleOnly();
    if (dvui.captured(self.data().id)) {
        opts.color_fill = self.data().options.color(.fill_press);
        opts.color_text = self.data().options.color(.text_press);
    } else if (self.hover) {
        opts.color_fill = self.data().options.color(.fill_hover);
        opts.color_text = self.data().options.color(.text_hover);
    }
    return opts;
}

pub fn focused(self: *ButtonWidget) bool {
    return self.data().id == dvui.focusedWidgetId();
}

pub fn hovered(self: *ButtonWidget) bool {
    return self.hover;
}

pub fn pressed(self: *ButtonWidget) bool {
    return dvui.captured(self.data().id);
}

pub fn clicked(self: *ButtonWidget) bool {
    return self.click;
}

pub fn widget(self: *ButtonWidget) Widget {
    return Widget.init(self, data, rectFor, screenRectScale, minSizeForChild);
}

pub fn data(self: *ButtonWidget) *WidgetData {
    return self.wd.validate();
}

pub fn rectFor(self: *ButtonWidget, id: dvui.Id, min_size: Size, e: Options.Expand, g: Options.Gravity) Rect {
    _ = id;
    return dvui.placeIn(self.data().contentRect().justSize(), min_size, e, g);
}

pub fn screenRectScale(self: *ButtonWidget, rect: Rect) RectScale {
    return self.data().contentRectScale().rectToRectScale(rect);
}

pub fn minSizeForChild(self: *ButtonWidget, s: Size) void {
    self.data().minSizeMax(self.data().options.padSize(s));
}

pub fn deinit(self: *ButtonWidget) void {
    defer if (dvui.widgetIsAllocated(self)) dvui.widgetFree(self);
    defer self.* = undefined;
    self.data().minSizeSetAndRefresh();
    self.data().minSizeReportToParent();
    dvui.parentReset(self.data().id, self.data().parent);
}

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