defmodule MusicBrainz.Release do
  @moduledoc false

  @enforce_keys [
    :id,
    :title,
    :disambiguation,
    :packaging,
    :artists,
    :date,
    :barcode,
    :catalog_number,
    :country,
    :media
  ]
  defstruct [
    :id,
    :title,
    :disambiguation,
    :packaging,
    :artists,
    :date,
    :barcode,
    :catalog_number,
    :country,
    :media
  ]

  @type t :: %__MODULE__{
          id: String.t(),
          title: String.t(),
          disambiguation: String.t() | nil,
          packaging: String.t() | nil,
          artists: [Artist.t()],
          date: String.t() | nil,
          barcode: String.t() | nil,
          catalog_number: String.t(),
          country: String.t() | nil,
          media: [Medium.t()]
        }

  defmodule Artist do
    @moduledoc false

    @enforce_keys [:id, :name, :sort_name]
    defstruct [:id, :name, :sort_name]

    @type t :: %__MODULE__{
            id: String.t(),
            name: String.t(),
            sort_name: String.t()
          }
  end

  defmodule Medium do
    @moduledoc false

    @enforce_keys [:title, :format, :number, :track_count, :tracks]
    defstruct [:title, :format, :number, :track_count, :tracks]

    @type t :: %__MODULE__{
            title: String.t() | nil,
            format: String.t() | nil,
            number: non_neg_integer(),
            track_count: non_neg_integer(),
            tracks: [MusicBrainz.Release.Track.t()]
          }
  end

  defmodule Track do
    @moduledoc false

    @enforce_keys [:id, :title, :artists, :length, :number, :position]
    defstruct [:id, :title, :artists, :length, :number, :position]

    @type t :: %__MODULE__{
            id: String.t(),
            title: String.t(),
            artists: [MusicBrainz.Release.Artist.t()],
            length: non_neg_integer() | nil,
            number: String.t(),
            position: non_neg_integer()
          }
  end

  @spec media_count(t()) :: non_neg_integer()
  def media_count(release) do
    Enum.count(release.media)
  end

  @spec get_medium(t(), non_neg_integer()) :: Medium.t() | nil
  def get_medium(release, medium_number) do
    Enum.find(release.media, fn m -> m.number == medium_number end)
  end

  @spec medium_duration(Medium.t()) :: non_neg_integer()
  def medium_duration(medium) do
    Enum.sum_by(medium.tracks, fn track -> track.length || 0 end)
  end

  @spec medium_tracks(t(), non_neg_integer()) :: [Track.t()]
  def medium_tracks(release, medium_number) do
    case Enum.find(release.media, fn m -> m.number == medium_number end) do
      nil -> []
      medium -> medium.tracks
    end
  end

  @spec release_duration(t()) :: non_neg_integer()
  def release_duration(release) do
    Enum.sum_by(release.media, fn medium -> medium_duration(medium) end)
  end

  @spec tracks(t()) :: [Track.t()]
  def tracks(release) do
    Enum.flat_map(release.media, fn medium -> medium.tracks end)
  end

  @spec from_api_response(map()) :: t()
  def from_api_response(r) do
    %__MODULE__{
      id: r["id"],
      title: r["title"],
      disambiguation: r["disambiguation"],
      packaging: r["packaging"],
      artists: parse_artists(r["artist-credit"] || []),
      date: r["date"],
      barcode: r["barcode"],
      catalog_number: parse_catalog_number(r["label-info"] || []),
      country: r["country"],
      media: parse_media(r["media"] || [])
    }
  end

  @spec thumb_url(t()) :: String.t()
  def thumb_url(release) do
    "https://coverartarchive.org/release/#{release.id}/front-250"
  end

  defp parse_media(media) do
    Enum.map(media, fn m ->
      %Medium{
        title: m["title"],
        format: m["format"],
        number: m["position"],
        track_count: m["track-count"],
        tracks: parse_tracks(m["tracks"] || [])
      }
    end)
  end

  defp parse_tracks(tracks) do
    Enum.map(tracks, fn t ->
      %Track{
        id: t["id"],
        title: t["title"],
        artists: parse_artists(t["recording"]["artist-credit"]),
        length: t["length"],
        number: t["number"],
        position: t["position"]
      }
    end)
  end

  defp parse_artists(artists) do
    Enum.map(artists, fn a ->
      %Artist{
        id: a["artist"]["id"],
        name: a["artist"]["name"],
        sort_name: a["artist"]["sort-name"]
      }
    end)
  end

  defp parse_catalog_number(label_infos) do
    Enum.map_join(label_infos, ", ", fn li ->
      li["catalog-number"]
    end)
  end
end
