lib/storage/adapters/ecto/parsing.ex

defmodule Sorcery.Storage.Adapters.Ecto.Parsing do
  use Norm
  alias Sorcery.Specs.Primative, as: T
  alias Sorcery.Utils.Maps

  # This is probably the entrypoint you are looking for
  @contract get_ordered_inserts(T.src()) :: coll_of({T.tk(), T.id(), T.entity()})
  def get_ordered_inserts(src) do
    src = mv_changes_inserts(src)
    li = list_inserts_deps(src)
    ids = inserts_id_order(li)

    Enum.map(ids, fn id -> 
      {tk, _, _} = Enum.find(li, fn {_, sorcery_id, _} -> sorcery_id == id end)
      entity = Map.get(src.inserts[tk], id)
      {tk, id, entity}
    end)
    
  end
  

  # Moves entities from changes_db into :inserts.
  @contract mv_changes_inserts(spec(is_struct(Sorcery.Src))) :: spec(is_struct(Sorcery.Src))
  def mv_changes_inserts(src) do
    Enum.reduce(src.changes_db, src, fn {tk, table}, src_acc ->
      Enum.reduce(table, src_acc, fn 
        {"$sorcery:" <> _ = id, entity}, src_acc ->
          src_acc
          |> Maps.put_in_p([:inserts, tk, id], entity)
          |> Maps.delete_in([:changes_db, tk, id])
        {_id, _entity}, src_acc -> src_acc
      end)
    end)
  end

  
  @contract list_inserts_deps(spec(is_struct(Sorcery.Src))) :: coll_of({spec(is_atom), spec(is_binary), coll_of(spec(is_binary()))})
  def list_inserts_deps(src) do
    Enum.reduce(src.inserts, [], fn {tk, table}, src_acc ->
      Enum.reduce(table, src_acc, fn {id, entity}, src_acc ->
        deps = Enum.reduce(entity, [], fn 
          {_, "$sorcery:" <> _ = dep}, acc -> [dep | acc]
          _, acc -> acc
        end)
        [{tk, id, deps} | src_acc]
      end)
    end)
  end

  
  @contract inserts_id_order(coll_of({spec(is_atom), spec(is_binary), coll_of(spec(is_binary()))})) :: coll_of(spec(is_binary()))
  def inserts_id_order(curr) do
    inserts_id_order(curr, [], [])
  end
  def inserts_id_order([], [], acc), do: Enum.reverse(acc)
  def inserts_id_order([], pending, acc), do: inserts_id_order(pending, [], acc)
  def inserts_id_order([hd | tl], pending, acc) do
    case hd do
      {_tk, id, []} -> inserts_id_order(tl, pending, [id | acc])
      {tk, id, deps} ->
        new_deps = Enum.filter(deps, fn k -> k not in acc end)
        inserts_id_order(tl, [{tk, id, new_deps} | pending], acc)
    end
  end

  
end