defmodule CobblemonUi.CobblemonFS.Pokemon do @moduledoc """ Normalizes raw NBT Pokémon compound data into a structured map. """ @stat_keys ~w(hp attack defence special_attack special_defence speed) @stat_display_keys %{ "hp" => :hp, "attack" => :attack, "defence" => :defense, "special_attack" => :special_attack, "special_defence" => :special_defense, "speed" => :speed } @doc """ Normalizes a raw NBT compound map representing a single Pokémon into the standardized format. Returns `nil` if the input is `nil` (empty slot). """ @spec normalize(map() | nil) :: map() | nil def normalize(nil), do: nil def normalize(raw) when is_map(raw) do %{ species: strip_namespace(get_string(raw, "Species")), level: get_int(raw, "Level"), form: get_string(raw, "FormId", "default"), shiny: get_boolean(raw, "Shiny"), nature: strip_namespace(get_string(raw, "Nature")), gender: downcase(get_string(raw, "Gender")), experience: get_int(raw, "Experience"), friendship: get_int(raw, "Friendship"), ability: extract_ability(Map.get(raw, "Ability")), ivs: normalize_stats(nested_get(raw, ["IVs", "Base"])), evs: normalize_stats(Map.get(raw, "EVs")), moves: normalize_moves(Map.get(raw, "MoveSet")) } end defp normalize_stats(nil), do: nil defp normalize_stats(stats) when is_map(stats) do Map.new(@stat_keys, fn key -> # Stats may be keyed with "cobblemon:" namespace prefix val = Map.get(stats, "cobblemon:#{key}", Map.get(stats, key, 0)) display_key = Map.get(@stat_display_keys, key, String.to_atom(key)) {display_key, val} end) end defp normalize_moves(nil), do: [] defp normalize_moves(moves) when is_list(moves) do Enum.map(moves, fn move when is_map(move) -> get_string(move, "MoveName") || get_string(move, "id") move when is_binary(move) -> move _ -> nil end) |> Enum.reject(&is_nil/1) end defp normalize_moves(_), do: [] defp get_string(map, key, default \\ nil) do case Map.get(map, key) do val when is_binary(val) -> val _ -> default end end defp get_int(map, key, default \\ nil) do case Map.get(map, key) do val when is_integer(val) -> val _ -> default end end defp get_boolean(map, key) do case Map.get(map, key) do 1 -> true 0 -> false true -> true false -> false _ -> false end end defp strip_namespace(nil), do: nil defp strip_namespace(val) when is_binary(val) do case String.split(val, ":", parts: 2) do [_ns, name] -> name _ -> val end end defp downcase(nil), do: nil defp downcase(val) when is_binary(val), do: String.downcase(val) defp extract_ability(nil), do: nil defp extract_ability(%{"AbilityName" => name}) when is_binary(name), do: name defp extract_ability(val) when is_binary(val), do: val defp extract_ability(_), do: nil defp nested_get(map, []), do: map defp nested_get(nil, _keys), do: nil defp nested_get(map, [key | rest]) when is_map(map), do: nested_get(Map.get(map, key), rest) defp nested_get(_map, _keys), do: nil end