///|
fn device_name(component : String) -> Bool {
  let base = component.split(".").collect()[0].to_upper().to_owned()
  if ["CON", "PRN", "AUX", "NUL", "CONIN$", "CONOUT$"].contains(base) {
    return true
  }
  if base.length() == 4 && (base.has_prefix("COM") || base.has_prefix("LPT")) {
    return base[3] >= '1' && base[3] <= '9'
  }
  false
}

///|
/// Advisory destination portability checks, distinct from BagIt validity.
pub fn lint_portability(view : BagView) -> Array[Diagnostic] {
  let diagnostics : Array[Diagnostic] = []
  let paths = view.directories.copy()
  paths.append(view.files.map(f => f.path))
  let seen : Map[String, Bool] = Map([])
  let folded : Map[String, String] = Map([])
  for path in paths {
    if seen.contains(path) {
      continue
    }
    seen[path] = true
    let key = path[:].to_lower().to_owned()
    if folded.get(key) is Some(other) && other != path {
      issue(
        diagnostics,
        "portable_case_collision",
        path,
        "case-insensitive destination collides with " + other,
        warning=true,
      )
    } else {
      folded[key] = path
    }
    if path.length() >= 260 {
      issue(
        diagnostics,
        "portable_long_path",
        path,
        "legacy Windows path limit may be exceeded after adding the destination root",
        warning=true,
      )
    }
    for component in path.split("/") {
      let component = component.to_owned()
      if device_name(component) {
        issue(
          diagnostics,
          "portable_device_name",
          path,
          "Windows reserved component: " + component,
          warning=true,
        )
      }
      if component.has_suffix(".") || component.has_suffix(" ") {
        issue(
          diagnostics,
          "portable_trailing_character",
          path,
          "trailing dot/space may be stripped by a destination filesystem",
          warning=true,
        )
      }
      if component
        .iter()
        .any(c => c < '\u0020' || "<>\"|?*".contains(c.to_string())) {
        issue(
          diagnostics,
          "portable_character",
          path,
          "component contains a control or Windows-reserved character",
          warning=true,
        )
      }
      if component.length() > 255 {
        issue(
          diagnostics,
          "portable_long_component",
          path,
          "component exceeds 255 UTF-16 units; byte-limited filesystems may have a lower limit",
          warning=true,
        )
      }
    }
  }
  diagnostics.sort_by((a, b) => {
    let order = a.path.compare(b.path)
    if order == 0 {
      a.code.compare(b.code)
    } else {
      order
    }
  })
  diagnostics
}