///|
pub fn manifest_item(
  tracking_no : String,
  zone : String,
  parcel : ParcelDim,
  declared_value : Money,
) -> ManifestItem {
  { tracking_no, zone, parcel, declared_value }
}

///|
fn manifest_zone_known(zone : String) -> Bool {
  zone == "local" ||
  zone == "regional" ||
  zone == "remote" ||
  zone == "international"
}

///|
fn manifest_issue(
  item : ManifestItem,
  kind : ManifestIssueKind,
  detail : String,
) -> ManifestIssue {
  { tracking_no: item.tracking_no, kind, detail }
}

///|
fn validate_item(
  item : ManifestItem,
  seen : Map[String, Bool],
) -> Array[ManifestIssue] {
  let issues : Array[ManifestIssue] = []
  if item.tracking_no.trim().length() == 0 {
    issues.push(
      manifest_issue(item, MissingTracking, "tracking number is empty"),
    )
  } else if seen.contains(item.tracking_no) {
    issues.push(
      manifest_issue(item, DuplicateTracking, "tracking number is repeated"),
    )
  }
  if item.parcel.weight_g <= 0 {
    issues.push(manifest_issue(item, InvalidWeight, "weight must be positive"))
  }
  if item.declared_value.cents < 0 {
    issues.push(
      manifest_issue(
        item,
        InvalidDeclaredValue,
        "declared value must not be negative",
      ),
    )
  }
  if !manifest_zone_known(item.zone) {
    issues.push(manifest_issue(item, UnknownZone, "zone is not registered"))
  }
  issues
}

///|
pub fn validate_manifest(items : Array[ManifestItem]) -> ManifestValidation {
  let seen : Map[String, Bool] = Map([])
  let issues : Array[ManifestIssue] = []
  let mut accepted = 0
  for item in items {
    let item_issues = validate_item(item, seen)
    for issue in item_issues {
      issues.push(issue)
    }
    if item.tracking_no.trim().length() > 0 && !seen.contains(item.tracking_no) {
      seen[item.tracking_no] = true
    }
    if item_issues.length() == 0 {
      accepted += 1
    }
  }
  { valid: issues.length() == 0, issues, accepted }
}

///|
pub fn ManifestValidation::issue_count(self : ManifestValidation) -> Int {
  self.issues.length()
}

///|
pub fn ManifestValidation::is_empty(self : ManifestValidation) -> Bool {
  self.issues.length() == 0
}

///|
pub fn ManifestValidation::has_kind(
  self : ManifestValidation,
  kind : ManifestIssueKind,
) -> Bool {
  for issue in self.issues {
    if issue.kind == kind {
      return true
    }
  }
  false
}