///|
pub(all) struct CslName {
  mut literal : String
  mut family : String
  mut given : String
  mut suffix : String
  mut dropping_particle : String
  mut non_dropping_particle : String
} derive(Debug, Eq)

///|
pub extend CslName with Eq::{not_equal, equal}

///|
pub extend CslName with @debug.Debug::{to_repr}

///|
pub fn CslName::new(family : String, given~ : String = "") -> CslName {
  {
    literal: "",
    family,
    given,
    suffix: "",
    dropping_particle: "",
    non_dropping_particle: "",
  }
}

///|
pub(all) struct CslDate {
  mut year : Int32
  mut month : Int32
  mut day : Int32
  mut end_year : Int32
  mut end_month : Int32
  mut end_day : Int32
  mut circa : Bool
  mut literal : String
} derive(Debug, Eq)

///|
pub extend CslDate with Eq::{not_equal, equal}

///|
pub extend CslDate with @debug.Debug::{to_repr}

///|
pub fn CslDate::new(
  year : Int32,
  month~ : Int32 = 0,
  day~ : Int32 = 0,
) -> CslDate {
  {
    year,
    month,
    day,
    end_year: 0,
    end_month: 0,
    end_day: 0,
    circa: false,
    literal: "",
  }
}

///|
pub(all) struct Item {
  mut id : String
  mut title : String
  mut container_title : String
  mut publisher : String
  mut publisher_place : String
  mut volume : String
  mut issue : String
  mut page : String
  mut edition : String
  mut url : String
  mut doi : String
  mut item_type : String
  mut abstract_text : String
  names : Map[String, Array[CslName]]
  dates : Map[String, CslDate]
  strings : Map[String, String]
  numbers : Map[String, Int32]
  booleans : Map[String, Bool]
} derive(Debug)

///|
pub extend Item with @debug.Debug::{to_repr}

///|
pub fn Item::new(id : String) -> Item {
  {
    id,
    title: "",
    container_title: "",
    publisher: "",
    publisher_place: "",
    volume: "",
    issue: "",
    page: "",
    edition: "",
    url: "",
    doi: "",
    item_type: "article-journal",
    abstract_text: "",
    names: Map([]),
    dates: Map([]),
    strings: Map([]),
    numbers: Map([]),
    booleans: Map([]),
  }
}

///|
pub fn Item::set_string(self : Item, variable : String, value : String) -> Item {
  match variable {
    "title" => self.title = value
    "container-title" => self.container_title = value
    "publisher" => self.publisher = value
    "publisher-place" => self.publisher_place = value
    "volume" => self.volume = value
    "issue" => self.issue = value
    "page" => self.page = value
    "edition" => self.edition = value
    "URL" => self.url = value
    "DOI" => self.doi = value
    "abstract" => self.abstract_text = value
    _ => self.strings[variable] = value
  }
  self
}

///|
pub fn Item::set_name(
  self : Item,
  variable : String,
  names : Array[CslName],
) -> Item {
  self.names[variable] = names
  self
}

///|
pub fn Item::set_date(self : Item, variable : String, date : CslDate) -> Item {
  self.dates[variable] = date
  self
}

///|
pub fn Item::set_number(self : Item, variable : String, value : Int32) -> Item {
  self.numbers[variable] = value
  self
}

///|
pub fn Item::set_bool(self : Item, variable : String, value : Bool) -> Item {
  self.booleans[variable] = value
  self
}

///|
pub fn Item::string(self : Item, variable : String) -> String {
  match variable {
    "title" => self.title
    "container-title" => self.container_title
    "publisher" => self.publisher
    "publisher-place" => self.publisher_place
    "volume" => self.volume
    "issue" => self.issue
    "page" => self.page
    "edition" => self.edition
    "URL" => self.url
    "DOI" => self.doi
    "abstract" => self.abstract_text
    _ => self.strings.get(variable).unwrap_or("")
  }
}

///|
pub fn Item::name(self : Item, variable : String) -> Array[CslName] {
  self.names.get(variable).unwrap_or([])
}

///|
pub fn Item::date(self : Item, variable : String) -> CslDate? {
  self.dates.get(variable)
}

///|
pub fn Item::has(self : Item, variable : String) -> Bool {
  !self.string(variable).is_empty() ||
  !self.name(variable).is_empty() ||
  self.dates.contains(variable) ||
  self.numbers.contains(variable)
}

///|
pub fn Item::number(self : Item, variable : String) -> Int32? {
  self.numbers.get(variable)
}

///|
pub(all) struct Cite {
  mut id : String
  mut locator : String
  mut label : String
  mut prefix : String
  mut suffix : String
  mut suppress_author : Bool
} derive(Debug, Eq)

///|
pub extend Cite with Eq::{not_equal, equal}

///|
pub extend Cite with @debug.Debug::{to_repr}

///|
pub fn Cite::new(id : String) -> Cite {
  {
    id,
    locator: "",
    label: "page",
    prefix: "",
    suffix: "",
    suppress_author: false,
  }
}

///|
fn first_family(item : Item, variable : String) -> String {
  match item.names.get(variable) {
    Some(xs) =>
      match xs.get(0) {
        Some(n) => n.family
        None => ""
      }
    None => ""
  }
}