const std = @import("std");
const json = std.json;
const Allocator = std.mem.Allocator;

pub const Character = struct {
    rank: []const u8,
};
pub const Lightcone = struct {
    rank: []const u8,
};

pub fn fetchDataFromApi(allocator: Allocator, url: []const u8) ![]u8 {
    const child = try std.process.Child.run(.{
        .allocator = allocator,
        .argv = &[_][]const u8{ "curl", "-s", url },
        .max_output_bytes = 10 * 1024 * 1024,
    });
    return child.stdout;
}

pub fn fetchDataMap(
    comptime T: type,
    allocator: Allocator,
    json_data: []const u8,
    filterFn: *const fn (u32) bool,
) !std.AutoHashMap(u32, T) {
    var map = std.AutoHashMap(u32, T).init(allocator);
    errdefer map.deinit();
    var parsed = try json.parseFromSlice(std.json.Value, allocator, json_data, .{});
    defer parsed.deinit();
    const root = parsed.value.object;
    var iter = root.iterator();
    while (iter.next()) |entry| {
        const id = std.fmt.parseInt(u32, entry.key_ptr.*, 10) catch continue;
        const obj = entry.value_ptr.*.object;
        if (!filterFn(id)) continue;
        const rank_value = obj.get("rank") orelse continue;
        const rank_str = if (rank_value == .string) rank_value.string else continue;
        try map.put(id, T{
            .rank = rank_str,
        });
    }
    return map;
}

fn filterCharacterID(id: u32) bool {
    return id <= 8001 and id != 1224;
}

fn filterLightconeID(id: u32) bool {
    return id >= 20000;
}

pub fn fetchAvatarID(allocator: Allocator) !std.AutoHashMap(u32, Character) {
    const json_data = try fetchDataFromApi(allocator, "https://api.hakush.in/hsr/data/character.json");
    defer allocator.free(json_data);
    return fetchDataMap(Character, allocator, json_data, filterCharacterID);
}

pub fn fetchLightconeID(allocator: Allocator) !std.AutoHashMap(u32, Lightcone) {
    const json_data = try fetchDataFromApi(allocator, "https://api.hakush.in/hsr/data/lightcone.json");
    defer allocator.free(json_data);
    return fetchDataMap(Lightcone, allocator, json_data, filterLightconeID);
}

pub fn AvatarList(allocator: Allocator, rank_filter: ?[]const u8) !std.ArrayList(u32) {
    return filterIDList(Character, allocator, try fetchAvatarID(allocator), rank_filter);
}
pub fn LightconeList(allocator: Allocator, rank_filter: ?[]const u8) !std.ArrayList(u32) {
    return filterIDList(Lightcone, allocator, try fetchLightconeID(allocator), rank_filter);
}

fn filterIDList(comptime T: type, allocator: Allocator, map: std.AutoHashMap(u32, T), rank_filter: ?[]const u8) !std.ArrayList(u32) {
    var id_list = std.ArrayList(u32).init(allocator);
    errdefer id_list.deinit();
    var iter = map.iterator();
    while (iter.next()) |entry| {
        const id = entry.key_ptr.*;
        const item = entry.value_ptr.*;
        if (rank_filter) |rank| {
            if (!std.mem.eql(u8, item.rank, rank)) continue;
        }
        try id_list.append(id);
    }
    return id_list;
}
