defmodule CobblemonUi.CobblemonFS.PCStore do @moduledoc """ Parses a player's PC storage `.dat` file. PC layout: Root -> boxes -> box0..boxN -> slot0..slotN """ alias CobblemonUi.CobblemonFS.{NBT, Pokemon} @doc """ Reads and parses a PC storage `.dat` file at the given path. Returns `{:ok, [%{box: integer, pokemon: list}]}` or `{:error, reason}`. """ @spec parse(String.t()) :: {:ok, list(map())} | {:error, term()} def parse(path) do with {:ok, data} <- File.read(path), {:ok, {_name, root}} <- NBT.decode(data) do {:ok, normalize_boxes(root)} else {:error, :enoent} -> {:error, :not_found} {:error, reason} -> {:error, {:corrupt_data, reason}} end end defp normalize_boxes(root) when is_map(root) do root |> Enum.filter(fn {key, val} -> String.match?(key, ~r/^Box\d+$/) and is_map(val) and map_size(val) > 1 end) |> Enum.sort_by(fn {key, _} -> extract_index(key) end) |> Enum.map(fn {key, box_data} -> %{ box: extract_index(key), pokemon: normalize_box_slots(box_data) } end) end defp normalize_boxes(_), do: [] defp normalize_box_slots(box) when is_map(box) do box |> Enum.filter(fn {key, _} -> String.match?(key, ~r/^Slot\d+$/) end) |> Enum.sort_by(fn {key, _} -> extract_index(key) end) |> Enum.map(fn {_key, slot_data} -> Pokemon.normalize(slot_data) end) end defp normalize_box_slots(_), do: [] defp extract_index(key) do case Integer.parse(String.replace(key, ~r/[^\d]/, "")) do {n, _} -> n :error -> 0 end end end