defmodule Microwaveprop.Radio.EnrichmentStatus do
  @moduledoc """
  State machine for contact enrichment lifecycle.

  States:
    :pending     → initial, no work started
    :queued      → Oban job inserted
    :processing  → worker actively running
    :complete    → data exists in DB
    :failed      → worker exhausted retries
    :unavailable → data cannot be fetched (missing pos, archive gone, etc.)

  Valid transitions:
    pending     → queued, unavailable
    queued      → processing, failed
    processing  → complete, failed
    failed      → queued (retry)
    unavailable → queued (retry with new data source)
    complete    → pending (reset for re-enrichment)
  """

  @type status :: :pending | :queued | :processing | :complete | :failed | :unavailable
  @type field_name :: :hrrr_status | :weather_status | :terrain_status | :iemre_status

  @states [:pending, :queued, :processing, :complete, :failed, :unavailable]
  @fields [:hrrr_status, :weather_status, :terrain_status, :iemre_status]

  @transitions %{
    pending: [:queued, :unavailable],
    queued: [:processing, :failed],
    processing: [:complete, :failed],
    failed: [:queued],
    unavailable: [:queued],
    complete: [:pending]
  }

  @spec states() :: [status()]
  def states, do: @states

  @spec fields() :: [field_name()]
  def fields, do: @fields

  @doc "Returns true if transitioning from `from` to `to` is valid."
  @spec valid_transition?(status(), status()) :: boolean()
  def valid_transition?(from, to) do
    to in Map.get(@transitions, from, [])
  end

  @doc "Validates and applies a status transition on a changeset."
  @spec transition(Ecto.Changeset.t(), field_name(), status()) :: Ecto.Changeset.t()
  def transition(changeset, field, new_status) when field in @fields and new_status in @states do
    current = Ecto.Changeset.get_field(changeset, field)

    if valid_transition?(current, new_status) do
      Ecto.Changeset.put_change(changeset, field, new_status)
    else
      Ecto.Changeset.add_error(changeset, field, "invalid transition from #{current} to #{new_status}")
    end
  end

  @doc "Force-sets status without transition validation (for migrations, resets)."
  @spec force_status(Ecto.Changeset.t(), field_name(), status()) :: Ecto.Changeset.t()
  def force_status(changeset, field, status) when field in @fields and status in @states do
    Ecto.Changeset.put_change(changeset, field, status)
  end
end
