///|
/// Resolution policy for duplicate series while merging snapshots.
pub(all) enum ConflictPolicy {
  Reject
  KeepFirst
  KeepLast
} derive(Eq)

///|
/// Failure returned by document merging.
pub(all) struct MergeError {
  family : String
  sample : String?
  message : String
} derive(Eq)

///|
/// Render a compact merge failure.
pub fn MergeError::to_string(self : MergeError) -> String {
  let sample = match self.sample {
    Some(name) => ", sample \{name}"
    None => ""
  }
  "family \{self.family}\{sample}: \{self.message}"
}

///|
fn merge_error(
  family : String,
  message : String,
  sample? : String,
) -> Result[MetricFamily, MergeError] {
  Err({ family, sample, message })
}

///|
fn merge_help(
  family : String,
  left : String?,
  right : String?,
) -> Result[String?, MergeError] {
  match (left, right) {
    (None, value) | (value, None) => Ok(value)
    (Some(left), Some(right)) =>
      if left == right {
        Ok(Some(left))
      } else {
        Err({ family, sample: None, message: "conflicting HELP text" })
      }
  }
}

///|
fn merge_unit(
  family : String,
  left : String?,
  right : String?,
) -> Result[String?, MergeError] {
  match (left, right) {
    (None, value) | (value, None) => Ok(value)
    (Some(left), Some(right)) =>
      if left == right {
        Ok(Some(left))
      } else {
        Err({
          family,
          sample: None,
          message: "conflicting UNIT values '\{left}' and '\{right}'",
        })
      }
  }
}

///|
fn merge_metric_type(
  family : String,
  left : MetricType,
  right : MetricType,
) -> Result[MetricType, MergeError] {
  if left == right {
    Ok(left)
  } else if left == Unknown {
    Ok(right)
  } else if right == Unknown {
    Ok(left)
  } else {
    Err({
      family,
      sample: None,
      message: "conflicting TYPE values '\{left.to_keyword()}' and '\{right.to_keyword()}'",
    })
  }
}

///|
fn merge_family(
  left : MetricFamily,
  right : MetricFamily,
  policy : ConflictPolicy,
) -> Result[MetricFamily, MergeError] {
  let metric_type = match
    merge_metric_type(left.name, left.metric_type, right.metric_type) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let help = match merge_help(left.name, left.help, right.help) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let unit = match merge_unit(left.name, left.unit, right.unit) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let samples = left.samples.copy()
  for sample in right.samples {
    let index = find_series_index(samples, sample.name, sample.labels)
    if index < 0 {
      samples.push(sample)
    } else {
      match policy {
        KeepFirst => ()
        KeepLast => samples[index] = sample
        Reject =>
          return merge_error(
            left.name,
            "duplicate series exists in more than one document",
            sample=sample.name,
          )
      }
    }
  }
  Ok({ name: left.name, help, metric_type, unit, samples })
}

///|
fn copy_document(document : Document) -> Document {
  let families : Array[MetricFamily] = []
  for family in document.families {
    families.push({ ..family, samples: family.samples.copy() })
  }
  { families, has_eof: document.has_eof }
}

///|
/// Merge two parsed snapshots.
///
/// Metadata for a shared family must be compatible. A concrete TYPE wins over
/// `unknown`; conflicting concrete types, HELP strings, or UNIT values fail.
/// `policy` only resolves duplicate series with the same name and label set.
pub fn merge(
  left : Document,
  right : Document,
  policy? : ConflictPolicy = Reject,
) -> Result[Document, MergeError] {
  let result = copy_document(left)
  for incoming in right.families {
    let index = find_family_index(result.families, incoming.name)
    if index < 0 {
      result.families.push({ ..incoming, samples: incoming.samples.copy() })
    } else {
      result.families[index] = match
        merge_family(result.families[index], incoming, policy) {
        Ok(family) => family
        Err(error) => return Err(error)
      }
    }
  }
  Ok({ ..result, has_eof: left.has_eof && right.has_eof })
}

///|
/// Merge any number of documents from left to right.
pub fn merge_all(
  documents : Array[Document],
  policy? : ConflictPolicy = Reject,
) -> Result[Document, MergeError] {
  if documents.is_empty() {
    return Ok(Document::new())
  }
  let mut result = copy_document(documents[0])
  for index in 1.. document
      Err(error) => return Err(error)
    }
  }
  Ok(result)
}

///|
fn contains_name(names : Array[String], name : String) -> Bool {
  for candidate in names {
    if candidate == name {
      return true
    }
  }
  false
}

///|
/// Copy only named metric families into a new document.
pub fn Document::select_families(
  self : Document,
  names : Array[String],
) -> Document {
  let families : Array[MetricFamily] = []
  for family in self.families {
    if contains_name(names, family.name) {
      families.push({ ..family, samples: family.samples.copy() })
    }
  }
  { families, has_eof: self.has_eof }
}

///|
fn sample_has_labels(sample : Sample, required : Array[Label]) -> Bool {
  for expected in required {
    let mut found = false
    for actual in sample.labels {
      if actual == expected {
        found = true
        break
      }
    }
    if !found {
      return false
    }
  }
  true
}

///|
/// Copy a document while retaining only samples that contain all requested
/// label pairs. Empty families are omitted.
pub fn Document::select_labels(
  self : Document,
  required : Array[Label],
) -> Document {
  let families : Array[MetricFamily] = []
  for family in self.families {
    let samples : Array[Sample] = []
    for sample in family.samples {
      if sample_has_labels(sample, required) {
        samples.push(sample)
      }
    }
    if !samples.is_empty() {
      families.push({ ..family, samples, })
    }
  }
  { families, has_eof: self.has_eof }
}