defmodule Packmatic.Encoder.Journaling do
  @moduledoc false
  alias Packmatic.Encoder.EncodingState
  alias Packmatic.Encoder.JournalingState
  alias Packmatic.Encoder.Field
  alias Packmatic.Encoder.Event
  import :erlang, only: [iolist_size: 1]

  defp cont(state), do: {:cont, state}
  defp data(state, data), do: {:cont, data, state}
  defp done(_, data), do: {:done, data}

  @spec journaling_start(EncodingState.t()) ::
          {:cont, JournalingState.t()}

  @spec journaling_next(JournalingState.t()) ::
          {:cont, iodata(), JournalingState.t()}
          | {:done, iodata()}

  def journaling_start(state) do
    cont(%JournalingState{
      stream_id: state.stream_id,
      remaining: state.encoded,
      offset: state.bytes_emitted,
      on_event: state.on_event
    })
  end

  def journaling_next(%{current: nil, remaining: [{entry, {:ok, info}} | rest]} = state) do
    journaling_next(%{state | current: {entry, info}, remaining: rest})
  end

  def journaling_next(%{current: nil, remaining: [{_, {:error, _}} | rest]} = state) do
    journaling_next(%{state | remaining: rest})
  end

  def journaling_next(%{current: {entry, info}} = state) do
    data = Field.encode_central_file_header(entry, info)

    state
    |> Map.put(:current, nil)
    |> Map.update!(:entries_emitted, &(&1 + 1))
    |> Map.update!(:bytes_emitted, &(&1 + iolist_size(data)))
    |> data(data)
  end

  def journaling_next(%{remaining: []} = state) do
    data = Field.encode_central_directory_end(state)

    state
    |> Map.update!(:bytes_emitted, &(&1 + iolist_size(data)))
    |> Event.emit_stream_ended(:done)
    |> done(data)
  end
end
