const std = @import("std");

const HASH_SEED: u64 = 6065983110;
const HASH_SALT: []const u8 = "ZnNLSRbY12DpPeMaPooKhOsxk7Qq325a2KF8EoIIeOaEz";

fn SpreadResult(comptime Base: type, comptime Additional: type) type {
    comptime {
        // const type_info = @typeInfo(Base);
        // if (@Type(type_info) != std.builtin.Type.Struct) {
        //     @compileError("Cannot have anything but struct but got: " ++ @typeName(Base));
        // }
        if (@typeInfo(Base) != .Struct) {
            @compileError("Provided non struct to struct concat: " ++ @typeName(Base));
        }
        if (@typeInfo(Additional) != .Struct) {
            @compileError("Provided non struct to struct concat: " ++ @typeName(Additional));
        }
        // _ = std.fmt.comptimePrint("Passed in base: {} {}", .{ Base, type_info });
    }
    var fields = @typeInfo(Base).Struct.fields;

    const additional_fields = @typeInfo(Additional).Struct.fields;
    @setEvalBranchQuota(additional_fields.len * fields.len * 10);
    for (additional_fields) |field| {
        if (@hasField(Base, field.name)) continue;
        fields = fields ++ &[_]std.builtin.Type.StructField{field};
    }

    return @Type(.{ .Struct = .{
        .is_tuple = false,
        .layout = .Auto,
        .backing_integer = null,
        .decls = &.{},
        .fields = fields,
    } });
}

pub fn structConcatFields(
    base: anytype,
    additional: anytype,
) SpreadResult(@TypeOf(base), @TypeOf(additional)) {
    // comptime {
    //     if (@typeInfo(@TypeOf(base)) != .Struct or @typeInfo(@TypeOf(additional)) != .Struct) {
    //         @compileError("Provided non struct to struct concat");
    //     }
    // }
    const Base = @TypeOf(base);
    const Additional = @TypeOf(additional);
    var result: SpreadResult(Base, Additional) = undefined;
    inline for (@typeInfo(Base).Struct.fields) |field| {
        @field(result, field.name) = @field(base, field.name);
    }
    inline for (@typeInfo(Additional).Struct.fields) |field| {
        @field(result, field.name) = @field(additional, field.name);
    }
    return result;
}

fn RemovedResult(comptime Base: type, comptime indicesToRemove: []const u8) type {
    var fields = @typeInfo(Base).Struct.fields;

    const remainingFieldsLen = fields.len - indicesToRemove.len;
    _ = remainingFieldsLen;
    var remainingFields: ?[]const std.builtin.Type.StructField = null;
    @setEvalBranchQuota(fields.len * 10);
    for (fields, 0..) |field, i| {
        var remove: bool = false;
        for (indicesToRemove) |r_index| {
            if (r_index == i) {
                remove = true;
            }
            if (remove) continue;
        }
        if (!remove) {
            if (remainingFields == null) {
                remainingFields = &[_]std.builtin.Type.StructField{field};
            } else {
                remainingFields = remainingFields.? ++ &[_]std.builtin.Type.StructField{field};
            }
        }
    }
    if (remainingFields) |f| {
        return @Type(.{ .Struct = .{
            .is_tuple = false,
            .layout = .Auto,
            .backing_integer = null,
            .decls = &.{},
            .fields = f,
        } });
    }
    return @Type(.{ .Struct = .{
        .is_tuple = false,
        .layout = .Auto,
        .backing_integer = null,
        .decls = &.{},
        .fields = &[_]std.builtin.Type.StructField{},
    } });
}

pub fn removeStructFields(
    base: anytype,
    comptime indicesToRemove: []const u8,
) RemovedResult(@TypeOf(base), indicesToRemove) {
    const Base = @TypeOf(base);
    var result: RemovedResult(Base, indicesToRemove) = undefined;
    inline for (@typeInfo(@TypeOf(result)).Struct.fields) |field| {
        @field(result, field.name) = @field(base, field.name);
    }
    return result;
}

pub fn generateRandomString(allocator: std.mem.Allocator) ![]const u8 {
    const chars: []const u8 = "ABCDEFGHIJKJMNOPQRSTUVWXYZ1234567890";
    var xoshiro = std.rand.DefaultPrng.init(@bitCast(u64, std.time.milliTimestamp()));
    const rng = xoshiro.random();

    var code: []u8 = try allocator.alloc(u8, 5);

    for (0..code.len) |i| {
        const char_index = rng.uintLessThan(u8, chars.len + 1) % chars.len;
        code[i] = chars[char_index];
    }

    std.log.info("Generated family code: {s}", .{code});

    return code;
}

pub fn hashPassword(allocator: std.mem.Allocator, password: []const u8) !u32 {
    const salted_password = try std.mem.concat(allocator, u8, &[_][]const u8{ password, HASH_SALT });
    const password_hash = @truncate(u32, std.hash.Wyhash.hash(HASH_SEED, salted_password));
    return password_hash;
}

test {
    // const vote = .{ .id = 0, .createdAt = "DATE" };
    // const data = structConcatFields(vote, .{ .id2 = vote.id });
    // std.log.err("\n{any}\n", .{data});

    // const user = .{ .id = 0, .createdAt = 2, .other = 3, .key = 4 };
    // const date = removeStructFields(user, &[_]u8{4});
    // std.debug.print("\n{any}\n", .{date});

    var gpa = std.testing.allocator_instance;
    // _ = gpa;
    var allocator = gpa.allocator();
    // _ = allocator;

    // const code = try generateRandomString(allocator);
    const hash = try hashPassword(allocator, "password");
    std.debug.print("\nGot {}\n", .{hash});
}
