app/cnabex/cnab240/services/decode.ex

defmodule ExCnab.Cnab240.Services.Decode do
  @moduledoc """
  Service to read and generate info about the CNAB 240 file.
  """

  alias ExCnab.Cnab240.Services.Details
  alias ExCnab.Cnab240.Templates.FileHeader
  alias ExCnab.Cnab240.Templates.Footer

  alias ExCnab.Cnab240.Services.GetFileInfo

  @item_type %{
    "0" => :file_header,
    "1" => :chunk_header,
    "2" => :chunk_first_register,
    "3" => :details,
    "5" => :chunk_footer,
    "9" => :file_footer
  }

  @spec run(String.t(), Map.t()) :: {:ok, Map.t()} | {:error, Any.t()}
  def run(file, attrs) do
    map =
      file
      |> File.read!()
      |> String.split("\r\n")
      |> classify_by_type()

    {:ok, filename_info} =
      file
      |> Path.basename()
      |> GetFileInfo.run(attrs)

    {:ok, file_header} = FileHeader.generate(map.file_header, attrs)
    {:ok, details} = Details.run(map.chunks, attrs)
    {:ok, footer} = Footer.generate(map.file_footer, attrs)

    {:ok,
     %{
       cnab240: %{
         arquivo_header: file_header,
         detalhes: details,
         trailer: footer
       },
       informacoes_extras: filename_info
     }}
  end

  defp classify_by_type(array) do
    file_header = Enum.at(array, 0)

    file_footer = Enum.at(array, -2)

    shorted_array =
      array
      |> Enum.drop(1)
      |> Enum.drop(-2)

    chunks =
      shorted_array
      |> get_footers_index()
      |> build_details_object(shorted_array)

    %{file_header: file_header, file_footer: file_footer, chunks: chunks}
  end

  defp verify_type(raw_string) do
    type = String.slice(raw_string, 7..7)

    @item_type[type]
  end

  defp get_footers_index(shorted_array) do
    Enum.reduce(shorted_array, [], fn item, acc ->
      case verify_type(item) do
        :chunk_footer ->
          index = Enum.find_index(shorted_array, &(&1 == item))

          acc ++ [index]

        _ ->
          acc
      end
    end)
  end

  defp build_details_object(list_footer_index, shorted_array) do
    {_, details} =
      Enum.reduce(list_footer_index, {shorted_array, %{chunks: []}}, fn index,
                                                                        {list_acc, map_acc} ->
        footer = Enum.at(shorted_array, index)

        footer_index =
          list_acc
          |> get_footers_index()
          |> hd()

        {[header | details], shorted_array} = Enum.split(list_acc, footer_index + 1)

        details = Enum.drop(details, -1) |> Enum.drop(1)
        key_id = Enum.find_index(list_footer_index, &(&1 == index)) + 1

        new_map = %{
          "chunk_register_#{key_id}": %{
            header: header,
            detail: details,
            footer: footer
          }
        }

        {shorted_array, Map.update(map_acc, :chunks, [new_map], &(&1 ++ [new_map]))}
      end)

    details
  end
end