///|
/// The iTunes podcast namespace.
pub const ITUNES_NAMESPACE : String = "http://www.itunes.com/dtds/podcast-1.0.dtd"

///|
fn eq_ignore_ascii_case(a : String, b : String) -> Bool {
  if a.length() != b.length() {
    return false
  }
  for i in 0..= 97 && ca <= 122 { ca - 32 } else { ca }
    let lb = if cb >= 97 && cb <= 122 { cb - 32 } else { cb }
    if la != lb {
      return false
    }
  }
  true
}

///|
fn is_itunes_namespace(ns : String) -> Bool {
  eq_ignore_ascii_case(ns, ITUNES_NAMESPACE)
}

///|
/// An iTunes category with an optional nested subcategory.
pub(all) struct ITunesCategory {
  /// The name of the category.
  text : String
  /// An optional subcategory.
  subcategory : ITunesCategory?
} derive(Debug, Eq)

///|
/// Contact information for the owner of a podcast.
pub(all) struct ITunesOwner {
  /// The name of the owner.
  name : String?
  /// The email of the owner.
  email : String?
} derive(Debug, Eq)

///|
/// iTunes metadata extracted from a channel.
pub(all) struct ITunesChannelExtension {
  /// The author of the podcast.
  author : String?
  /// `"Yes"` hides the podcast from the iTunes Store.
  block : String?
  /// The iTunes categories of the podcast.
  categories : Array[ITunesCategory]
  /// The artwork URL of the podcast.
  image : String?
  /// Whether the podcast contains explicit content.
  explicit : String?
  /// Whether the podcast is complete.
  complete : String?
  /// The new URL where the podcast is located.
  new_feed_url : String?
  /// The contact information of the podcast owner.
  owner : ITunesOwner?
  /// A short description of the podcast.
  subtitle : String?
  /// A long description of the podcast.
  summary : String?
  /// Comma-separated keywords.
  keywords : String?
  /// The type of podcast, e.g. `"episodic"` or `"serial"`.
  itunes_type : String?
} derive(Debug, Eq)

///|
/// Build a channel extension from parsed generic extensions,
/// mirroring `ITunesChannelExtension::from_map`.
pub fn ITunesChannelExtension::from_map(
  map : Map[String, Array[Extension]],
) -> ITunesChannelExtension {
  fn value(key : String) -> String? {
    match map.get(key) {
      Some(list) => if list.length() > 0 { list[0].value } else { None }
      None => None
    }
  }

  let categories : Array[ITunesCategory] = []
  if map.get("category") is Some(list) {
    for ext in list {
      categories.push(itunes_category_from(ext))
    }
  }
  let owner : ITunesOwner? = match map.get("owner") {
    Some(list) =>
      if list.length() > 0 {
        let ext = list[0]
        Some({
          name: child_value(ext, "name"),
          email: child_value(ext, "email"),
        })
      } else {
        None
      }
    None => None
  }
  {
    author: value("author"),
    block: value("block"),
    categories,
    image: attr_of(map, "image", "href"),
    explicit: value("explicit"),
    complete: value("complete"),
    new_feed_url: value("new-feed-url"),
    owner,
    subtitle: value("subtitle"),
    summary: value("summary"),
    keywords: value("keywords"),
    itunes_type: value("type"),
  }
}

///|
/// iTunes metadata extracted from an item.
pub(all) struct ITunesItemExtension {
  /// The author of the episode.
  author : String?
  /// Whether the episode is blocked from the iTunes Store.
  block : String?
  /// The artwork URL of the episode.
  image : String?
  /// The duration in HH:MM:SS, MM:SS or M:SS form.
  duration : String?
  /// Whether the episode contains explicit content.
  explicit : String?
  /// Whether the episode has closed captioning.
  closed_captioned : String?
  /// Overrides default sorting order.
  order : String?
  /// A short description.
  subtitle : String?
  /// A long description.
  summary : String?
  /// Comma-separated keywords.
  keywords : String?
  /// The episode number.
  episode : String?
  /// The season number.
  season : String?
  /// Whether the episode is a full or trailer, e.g. `"full"`.
  episode_type : String?
} derive(Debug, Eq)

///|
/// Build an item extension from parsed generic extensions,
/// mirroring `ITunesItemExtension::from_map`.
pub fn ITunesItemExtension::from_map(
  map : Map[String, Array[Extension]],
) -> ITunesItemExtension {
  fn value(key : String) -> String? {
    match map.get(key) {
      Some(list) => if list.length() > 0 { list[0].value } else { None }
      None => None
    }
  }

  {
    author: value("author"),
    block: value("block"),
    image: attr_of(map, "image", "href"),
    duration: value("duration"),
    explicit: value("explicit"),
    closed_captioned: value("isClosedCaptioned"),
    order: value("order"),
    subtitle: value("subtitle"),
    summary: value("summary"),
    keywords: value("keywords"),
    episode: value("episode"),
    season: value("season"),
    episode_type: value("episodeType"),
  }
}

///|
fn attr_of(
  map : Map[String, Array[Extension]],
  key : String,
  attr : String,
) -> String? {
  match map.get(key) {
    Some(list) => if list.length() > 0 { list[0].attrs.get(attr) } else { None }
    None => None
  }
}

///|
fn child_value(ext : Extension, name : String) -> String? {
  match ext.children.get(name) {
    Some(list) => if list.length() > 0 { list[0].value } else { None }
    None => None
  }
}

///|
fn itunes_category_from(ext : Extension) -> ITunesCategory {
  // Mirrors the crate: the name comes from the `text` attribute and the
  // nested category from a child element named `category`.
  let text = ext.attrs.get("text").unwrap_or("")
  let subcategory : ITunesCategory? = match ext.children.get("category") {
    Some(list) =>
      if list.length() > 0 {
        let child = list[0]
        Some({ text: child.attrs.get("text").unwrap_or(""), subcategory: None })
      } else {
        None
      }
    None => None
  }
  { text, subcategory }
}