const std = @import("std");
const std_math = std.math;
const Document = @import("../../models/Document.zig");
const pipeline = @import("pipeline.zig");
const line = @import("line.zig");
const DrawContext = pipeline.DrawContext;
const Color = @import("dvui").Color;
const basic_models = @import("../../models/basic_models.zig");
const Point2_f = basic_models.Point2_f;

const Object = Document.Object;
const default_stroke: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 255 };
const default_fill: Color.PMA = .{ .r = 0, .g = 0, .b = 0, .a = 0 };
const default_thickness: f32 = 2.0;

/// Эллипс с центром (0,0) и полуосями radii. Обводка — полоса расстояния до контура (чёткая линия, не круги).
/// arc_percent: 100 — полный эллипс, иначе одна дуга; обход в коде от (0,ry) по квадрантам (визуально может казаться от низа против часовой из‑за экранной Y).
/// Отрисовка в отдельный буфер и один composite, чтобы при alpha<255 пиксели не накладывались несколько раз.
pub fn draw(ctx: *DrawContext, obj: *const Object, allocator: std.mem.Allocator) !void {
    const radii = obj.getProperty(.radii) orelse return;
    const rx = radii.x;
    const ry = radii.y;
    if (rx <= 0 or ry <= 0) return;

    const stroke = if (obj.getProperty(.stroke_rgba)) |stroke_rgba| pipeline.rgbaToPma(stroke_rgba) else default_stroke;
    const thickness = if (obj.getProperty(.thickness)) |thickness| thickness else default_thickness;
    const arc_percent = std_math.clamp(if (obj.getProperty(.arc_percent)) |arc_percent| arc_percent else 100.0, 0.0, 100.0);
    const closed = obj.getProperty(.closed) orelse true;
    const filled = obj.getProperty(.filled) orelse false;
    const fill_color = if (obj.getProperty(.fill_rgba)) |fill_rgba| pipeline.rgbaToPma(fill_rgba) else default_fill;
    const do_fill = filled and (closed or arc_percent >= 100.0);

    const t = &ctx.transform;
    const min_r = @min(rx, ry);
    const half_norm = thickness / (2.0 * min_r);
    const inner = @max(0.0, 1.0 - half_norm);
    const outer = 1.0 + half_norm;
    const d_inner_sq = inner * inner;
    const d_outer_sq = outer * outer;

    const margin = 1.0 + half_norm;
    const corners = [_]Point2_f{
        .{ .x = -rx * margin, .y = -ry * margin },
        .{ .x = rx * margin, .y = -ry * margin },
        .{ .x = rx * margin, .y = ry * margin },
        .{ .x = -rx * margin, .y = ry * margin },
    };
    var min_bx: f32 = std_math.inf(f32);
    var min_by: f32 = std_math.inf(f32);
    var max_bx: f32 = -std_math.inf(f32);
    var max_by: f32 = -std_math.inf(f32);
    for (corners) |c| {
        const w = ctx.localToWorld(c.x, c.y);
        const b = ctx.worldToBufferF(w.x, w.y);
        min_bx = @min(min_bx, b.x);
        min_by = @min(min_by, b.y);
        max_bx = @max(max_bx, b.x);
        max_by = @max(max_by, b.y);
    }
    const buf_w: i32 = @intCast(ctx.buf_width);
    const buf_h: i32 = @intCast(ctx.buf_height);
    const x0: i32 = @max(0, @as(i32, @intFromFloat(std_math.floor(min_bx))));
    const y0: i32 = @max(0, @as(i32, @intFromFloat(std_math.floor(min_by))));
    const x1: i32 = @min(buf_w, @as(i32, @intFromFloat(std_math.ceil(max_bx))) + 1);
    const y1: i32 = @min(buf_h, @as(i32, @intFromFloat(std_math.ceil(max_by))) + 1);

    const buffer = try allocator.alloc(Color.PMA, ctx.buf_width * ctx.buf_height);
    @memset(buffer, .{ .r = 0, .g = 0, .b = 0, .a = 0 });
    defer allocator.free(buffer);

    var stroke_ctx = ctx.*;
    stroke_ctx.pixels = buffer;
    stroke_ctx.replace_mode = true;

    if (do_fill) {
        stroke_ctx.startFill(allocator) catch return;
    }

    const inv_rx = 1.0 / rx;
    const inv_ry = 1.0 / ry;
    const arc_len = 2.0 * std_math.pi * arc_percent / 100.0;

    var by: i32 = y0;
    while (by < y1) : (by += 1) {
        const buf_y = @as(f32, @floatFromInt(by)) + 0.5;
        var bx: i32 = x0;
        while (bx < x1) : (bx += 1) {
            const buf_x = @as(f32, @floatFromInt(bx)) + 0.5;
            const w = stroke_ctx.bufferToWorld(buf_x, buf_y);
            const loc = stroke_ctx.worldToLocal(w.x, w.y);
            const nx = loc.x * inv_rx;
            const ny = loc.y * inv_ry;
            const d = nx * nx + ny * ny;
            if (d < d_inner_sq or d > d_outer_sq) continue;
            if (arc_percent < 100.0) {
                var diff = std_math.pi / 2.0 - std_math.atan2(ny, nx);
                if (diff < 0) diff += 2.0 * std_math.pi;
                if (diff > arc_len) continue;
            }
            stroke_ctx.blendPixelAtBuffer(bx, by, stroke);
        }
    }

    if (closed and arc_percent < 100.0) {
        const end_x = rx * std_math.sin(arc_len);
        const end_y = ry * std_math.cos(arc_len);
        line.drawLine(&stroke_ctx, 0, 0, 0, ry, stroke, thickness, false);
        line.drawLine(&stroke_ctx, 0, 0, end_x, end_y, stroke, thickness, false);
    }

    if (do_fill) {
        stroke_ctx.stopFill(allocator, fill_color);
    }

    ctx.compositeDrawerContext(&stroke_ctx, t.opacity);
}
