-- The following code is slightly adapted from
-- https://github.com/MathIsFun0/Cryptid/blob/main/lib/misprintize.lua

local base_values = {}

local big_num_whitelist = {
    j_egg = true,
    j_wee = true,
    j_flash = true,
    j_ramen = true,
    j_glass = true,
    j_caino = true,
    j_cry_m = true,
    j_runner = true,
    j_square = true,
    j_castle = true,
    j_yorick = true,
    j_madness = true,
    j_vampire = true,
    j_obelisk = true,
    j_popcorn = true,
    j_red_card = true,
    j_hologram = true,
    j_trousers = true,
    j_campfire = true,
    j_cry_whip = true,
    j_ice_cream = true,
    j_lucky_cat = true,
    j_throwback = true,
    j_cry_hugem = true,
    j_cry_pickle = true,
    j_cry_cursor = true,
    j_cry_primus = true,
    j_cry_mprime = true,
    j_green_joker = true,
    j_turtle_bean = true,
    j_cry_wee_fib = true,
    j_cry_jimball = true,
    j_ride_the_bus = true,
    j_hit_the_road = true,
    j_cry_dropshot = true,
    j_cry_fspinner = true,
    j_cry_mondrian = true,
    j_constellation = true,
    j_cry_crustulum = true,
    j_cry_spaceglobe = true,
    j_cry_exponentia = true,
    j_cry_chili_pepper = true,
    j_cry_eternalflame = true,
    j_cry_stella_mortis = true,
    j_cry_krustytheclown = true,
    j_cry_antennastoheaven = true,
}

local function check(v, is_big)
    if is_big then
        if not v or type(v) == "number" and (v ~= v or v > 1e300 or v < -1e300) then
            v = 1e300
        end

        if type(v) == "table" then
            return v
        end

        if v > 1e100 or v < -1e100 then
            return to_big(v)
        end
    end

    return not v or type(v) == "number" and (v ~= v or v > 1e300 or v < -1e300) and 1e300 or v
end

local function deep_copy(obj, seen)
    if type(obj) ~= "table" then
        return obj
    end

    if seen and seen[obj] then
        return seen[obj]
    end

    local s = seen or {}
    local res = setmetatable({}, getmetatable(obj))
    s[obj] = res

    for k, v in pairs(obj) do
        res[deep_copy(k, s)] = deep_copy(v, s)
    end

    return res
end

local function format(number, str)
    return math.abs(to_big(number)) >= to_big(1e300) and number
        or tonumber(str:format((Big and to_number(to_big(number)) or number)))
end

local function is_card_big(joker)
    local center = joker.config and joker.config.center

    if not center then
        return false
    end

    return big_num_whitelist[center.key or "Nope!"]
end

local function log_random(seed, min, max)
    math.randomseed(seed)
    local lmin = math.log(min, 2.718281828459045)
    local lmax = math.log(max, 2.718281828459045)
    local poll = math.random() * (lmax - lmin) + lmin
    return math.exp(poll)
end

function Jane.misprintize_val(val, override, big)

    return is_number(val) and check(
        format(
            val * log_random(
                pseudoseed("cry_misprint" .. G.GAME.round_resets.ante),
                override and override.min or G.GAME.modifiers.cry_misprint_min,
                override and override.max or G.GAME.modifiers.cry_misprint_max
            ),
            "%.2g"
        ),
        big
    ) or val
end

function Jane.misprintize_tbl(name, ref_tbl, ref_value, clear, override, stack, big)
    if name and ref_tbl and ref_value then
        local tbl = deep_copy(ref_tbl[ref_value])

        for k, v in pairs(tbl) do
            if (type(tbl[k]) ~= "table") or is_number(tbl[k]) then
                if is_number(tbl[k]) and not
                    (k == "id") and not
                    (k == "colour") and not
                    (k == "suit_nominal") and not
                    (k == "base_nominal") and not
                    (k == "face_nominal") and not
                    (k == "qty") and not
                    (k == "x_mult" and v == 1 and not tbl.override_x_mult_check) and not
                    (k == "selected_d6_face") then
                    if not base_values[name] then
                        base_values[name] = {}
                    end

                    if not base_values[name][k] then
                        base_values[name][k] = tbl[k]
                    end

                    tbl[k] = check(
                        clear and base_values[name][k]
                            or format(
                                (stack and tbl[k] or base_values[name][k])
                                    * log_random(
                                        pseudoseed("cry_misprint" .. G.GAME.round_resets.ante),
                                        override and override.min or G.GAME.modifiers.cry_misprint_min,
                                        override and override.max or G.GAME.modifiers.cry_misprint_max
                                    ),
                                "%.2g"
                            ),
                        big
                    )
                end
            else
                for _k, _ in pairs(tbl[k]) do
                    if is_number(tbl[k][_k]) and not
                    (_k == "id") and not
                    (k == "perish_tally") and not
                    (k == "colour") and not
                    (_k == "suit_nominal") and not
                    (_k == "base_nominal") and not
                    (_k == "face_nominal") and not
                    (_k == "qty") and not
                    (k == "x_mult" and v == 1 and not tbl[k].override_x_mult_check) and not
                    (_k == "selected_d6_face") then
                        if not base_values[name] then
                            base_values[name] = {}
                        end

                        if not base_values[name][k] then
                            base_values[name][k] = {}
                        end

                        if not base_values[name][k][_k] then
                            base_values[name][k][_k] = tbl[k][_k]
                        end

                        tbl[k][_k] = check(
                            clear and base_values[name][k][_k]
                                or format(
                                    (stack and tbl[k][_k] or base_values[name][k][_k]) *
                                        log_random(
                                            pseudoseed("cry_misprint" .. G.GAME.round_resets.ante),
                                            override and override.min or G.GAME.modifiers.cry_misprint_min,
                                            override and override.max or G.GAME.modifiers.cry_misprint_max
                                        ),
                                    "%.2g"
                                ),
                            big
                        )
                    end
                end
            end
        end

        ref_tbl[ref_value] = tbl
    end
end

function Jane.misprintize(card, override, force_reset, stack)
    local function no(self, m, no_no)
        if no_no then
            if self.infinifusion then
                for i = 1, #self.infinifusion do
                    if G.P_CENTERS[self.infinifusion[i].key][m] or
                        (G.GAME and G.GAME[m] and G.GAME[m][self.infinifusion[i].key]) then
                        return true
                    end
                end

                return false
            end

            if not self.config then
                return G.P_CENTERS[self.key][m] or (G.GAME and G.GAME[m] and G.GAME[m][self.key])
            end

            return self.config.center[m] or (G.GAME and G.GAME[m] and G.GAME[m][self.config.center_key]) or false
        end

        return no(self, "no_" .. m, true)
    end

    if no(card, "immutable", true) then
        force_reset = true
    end

    if card.infinifusion then
        if card.config.center == card.infinifusion_center or card.config.center.key == "j_infus_fused" then
            ---@diagnostic disable-next-line: undefined-global
            calculate_infinifusion(card, nil, function(i)
                Jane.misprintize(card, override, force_reset, stack)
            end)
        end
    end
    if (not force_reset or G.GAME.modifiers.cry_jkr_misprint_mod) and
        (G.GAME.modifiers.cry_misprint_min or override or card.ability.set == "Joker") and not
        stack or not
        no(card, "immutable", true)	then
        if card.ability.name == "Ace Aequilibrium" then
            return
        end

        if G.GAME.modifiers.cry_jkr_misprint_mod and card.ability.set == "Joker" then
            if not override then
                override = {}
            end

            override.min = override.min or G.GAME.modifiers.cry_misprint_min or 1
            override.max = override.max or G.GAME.modifiers.cry_misprint_max or 1
            override.min = override.min * G.GAME.modifiers.cry_jkr_misprint_mod
            override.max = override.max * G.GAME.modifiers.cry_jkr_misprint_mod
        end

        if G.GAME.modifiers.cry_misprint_min or override and override.min then
            Jane.misprintize_tbl(
                card.config.center_key,
                card,
                "ability",
                nil,
                override,
                stack,
                is_card_big(card)
            )

            if card.base then
                Jane.misprintize_tbl(
                    card.config.card_key,
                    card,
                    "base",
                    nil,
                    override,
                    stack,
                    is_card_big(card)
                )
            end
        end

        if G.GAME.modifiers.cry_misprint_min then
            card.misprint_cost_fac = 1 /
                log_random(
                    pseudoseed("cry_misprint" .. G.GAME.round_resets.ante),
                    override and override.min or G.GAME.modifiers.cry_misprint_min,
                    override and override.max or G.GAME.modifiers.cry_misprint_max
                )

            card:set_cost()
        end
    else
        Jane.misprintize_tbl(card.config.center_key, card, "ability", true, nil, nil, is_card_big(card))
    end

    if card.ability.consumeable then
        for k, _ in pairs(card.ability.consumeable) do
            card.ability.consumeable[k] = deep_copy(card.ability[k])
        end
    end
end
