const std = @import("std");
const Document = @import("models/Document.zig");
const Object = Document.Object;
const shape = @import("models/shape/shape.zig");
const basic_models = @import("models/basic_models.zig");
const Size_f = basic_models.Size_f;
const Point2_f = basic_models.Point2_f;
const Scale2_f = basic_models.Scale2_f;
const Radii_f = basic_models.Radii_f;

fn randFloat(rng: std.Random, min: f32, max: f32) f32 {
    return min + (max - min) * rng.float(f32);
}

fn randRgba(rng: std.Random) u32 {
    const r = rng.int(u8);
    const g = rng.int(u8);
    const b = rng.int(u8);
    const a: u8 = @intCast(rng.intRangeLessThan(usize, 128, 256));
    return (@as(u32, r) << 24) | (@as(u32, g) << 16) | (@as(u32, b) << 8) | a;
}

fn randomShapeKind(rng: std.Random) Object.ShapeKind {
    const shapes_implemented = [_]Object.ShapeKind{ .line, .ellipse, .broken };
    return shapes_implemented[rng.intRangeLessThan(usize, 0, shapes_implemented.len)];
}

fn randomizeObjectProperties(allocator: std.mem.Allocator, doc_size: *const Size_f, obj: *Object, rng: std.Random) !void {
    const margin: f32 = 8;
    const max_x = @max(0, doc_size.w - margin);
    const max_y = @max(0, doc_size.h - margin);

    try obj.setProperty(allocator, .{ .data = .{
        .position = .{
            .x = randFloat(rng, margin, if (max_x > margin) max_x else margin),
            .y = randFloat(rng, margin, if (max_y > margin) max_y else margin),
        },
    } });
    try obj.setProperty(allocator, .{ .data = .{ .angle = randFloat(rng, 0, 2 * std.math.pi) } });
    try obj.setProperty(allocator, .{ .data = .{
        .scale = .{
            .scale_x = randFloat(rng, 0.25, 2.0),
            .scale_y = randFloat(rng, 0.25, 2.0),
        },
    } });
    try obj.setProperty(allocator, .{ .data = .{ .visible = true } });
    try obj.setProperty(allocator, .{ .data = .{ .opacity = randFloat(rng, 0.3, 1.0) } });
    try obj.setProperty(allocator, .{ .data = .{ .locked = rng.boolean() } });

    const stroke = randRgba(rng);
    try obj.setProperty(allocator, .{ .data = .{ .stroke_rgba = stroke } });
    obj.setProperty(allocator, .{ .data = .{ .fill_rgba = randRgba(rng) } }) catch {};
    const thickness = randFloat(rng, max_x * 0.001, max_x * 0.01);
    try obj.setProperty(allocator, .{ .data = .{ .thickness = thickness } });

    switch (obj.shape) {
        .line => {
            const len = randFloat(rng, 20, @min(doc_size.w, doc_size.h) * 0.5);
            const angle = randFloat(rng, 0, 2 * std.math.pi);
            try obj.setProperty(allocator, .{ .data = .{
                .end_point = .{
                    .x = std.math.cos(angle) * len,
                    .y = std.math.sin(angle) * len,
                },
            } });
        },
        .ellipse => {
            const max_r = @min(120, @min(doc_size.w / 4, doc_size.h / 4));
            try obj.setProperty(allocator, .{ .data = .{
                .radii = .{
                    .x = randFloat(rng, 8, @max(8, max_r)),
                    .y = randFloat(rng, 8, @max(8, max_r)),
                },
            } });
        },
        .broken => {
            var list = std.ArrayList(Point2_f).empty;
            defer list.deinit(allocator);
            const n = rng.intRangeLessThan(usize, 2, 9);
            var x: f32 = 0;
            var y: f32 = 0;
            for (0..n) |_| {
                try list.append(allocator, .{ .x = x, .y = y });
                x += randFloat(rng, -40, 80);
                y += randFloat(rng, -30, 60);
            }
            const slice = try allocator.dupe(Point2_f, list.items);
            errdefer allocator.free(slice);
            try obj.setProperty(allocator, .{ .data = .{ .points = slice } });
        },
    }
}

/// Создаёт в документе случайные фигуры (line, ellipse, broken).
pub fn addRandomShapes(doc: *Document, allocator: std.mem.Allocator, rng: std.Random) !void {
    const max_total: usize = 80;
    var total_count: usize = 0;

    const n_root = rng.intRangeLessThan(usize, 6, 15);
    for (0..n_root) |_| {
        if (total_count >= max_total) break;
        var obj = try shape.createObject(allocator, randomShapeKind(rng));
        defer obj.deinit(allocator);
        try randomizeObjectProperties(allocator, &doc.size, &obj, rng);
        try doc.addObject(allocator, obj);
        total_count += 1;
    }

    var stack = std.ArrayList(*Object).empty;
    defer stack.deinit(allocator);
    for (doc.objects.items) |*obj| {
        try stack.append(allocator, obj);
    }
    while (stack.pop()) |obj| {
        if (total_count >= max_total) continue;
        const n_children = rng.intRangeLessThan(usize, 0, 2);
        const base_len = obj.children.items.len;
        for (0..n_children) |_| {
            if (total_count >= max_total) break;
            var child = try shape.createObject(allocator, randomShapeKind(rng));
            defer child.deinit(allocator);
            try randomizeObjectProperties(allocator, &doc.size, &child, rng);
            try obj.addChild(allocator, child, &doc.next_object_id);
            total_count += 1;
        }
        for (obj.children.items[base_len..]) |*child| {
            try stack.append(allocator, child);
        }
    }
}
