///|
/// Kind of an entry observed during a directory scan.
pub(all) enum EntryKind {
  Regular
  Directory
  Symlink
  Other
} derive(Eq, Debug, ToJson, FromJson)

///|
/// One immutable observation from a directory scan.
/// Paths are stored relative to the scan root with `/` separators.
pub(all) struct Entry {
  path : String
  kind : EntryKind
  size : Int64
  modified_at : Int64
} derive(Eq, Debug, ToJson, FromJson)

///|
pub fn Entry::new(
  path : String,
  kind : EntryKind,
  size : Int64,
  modified_at : Int64,
) -> Entry {
  Entry::{ path: normalize_relative_path(path), kind, size, modified_at, }
}

///|
pub fn Entry::is_file(self : Entry) -> Bool {
  self.kind == Regular
}

///|
pub fn Entry::is_dir(self : Entry) -> Bool {
  self.kind == Directory
}

///|
pub fn Entry::extension(self : Entry) -> String {
  extension_of(self.path)
}

///|
pub fn Entry::name(self : Entry) -> String {
  basename(self.path)
}

///|
pub fn Entry::parent(self : Entry) -> String {
  parent_path(self.path)
}

///|
pub(all) struct ScanError {
  path : String
  operation : String
  message : String
} derive(Eq, Debug, ToJson, FromJson)

///|
pub(all) struct ScanOptions {
  include_hidden : Bool
  follow_symlinks : Bool
  max_depth : Int
  max_entries : Int
  max_concurrency : Int
  ignore_patterns : Array[String]
} derive(Eq, Debug, ToJson, FromJson)

///|
pub fn ScanOptions::default() -> ScanOptions {
  ScanOptions::{
    include_hidden: true,
    follow_symlinks: false,
    max_depth: 64,
    max_entries: 1000000,
    max_concurrency: 16,
    ignore_patterns: [],
  }
}

///|
pub fn ScanOptions::with_hidden(
  self : ScanOptions,
  enabled : Bool,
) -> ScanOptions {
  { ..self, include_hidden: enabled, }
}

///|
pub fn ScanOptions::with_max_depth(
  self : ScanOptions,
  depth : Int,
) -> ScanOptions {
  { ..self, max_depth: if depth < 0 { 0 } else { depth }, }
}

///|
pub fn ScanOptions::with_max_entries(
  self : ScanOptions,
  count : Int,
) -> ScanOptions {
  { ..self, max_entries: if count < 1 { 1 } else { count }, }
}

///|
pub(all) struct Snapshot {
  format_version : Int
  root : String
  created_at : Int64
  entries : Array[Entry]
  errors : Array[ScanError]
} derive(Eq, Debug, ToJson, FromJson)

///|
pub fn Snapshot::new(
  root : String,
  created_at : Int64,
  entries : Array[Entry],
  errors? : Array[ScanError] = [],
) -> Snapshot {
  Snapshot::{ format_version: 1, root, created_at, entries, errors, }
}

///|
pub fn Snapshot::file_count(self : Snapshot) -> Int {
  let mut count = 0
  for entry in self.entries {
    if entry.is_file() {
      count += 1
    }
  }
  count
}

///|
pub fn Snapshot::directory_count(self : Snapshot) -> Int {
  let mut count = 0
  for entry in self.entries {
    if entry.is_dir() {
      count += 1
    }
  }
  count
}

///|
pub fn Snapshot::total_size(self : Snapshot) -> Int64 {
  let mut total = 0L
  for entry in self.entries {
    if entry.is_file() {
      total += entry.size
    }
  }
  total
}

///|
pub fn Snapshot::find(self : Snapshot, path : String) -> Entry? {
  let wanted = normalize_relative_path(path)
  for entry in self.entries {
    if entry.path == wanted {
      return Some(entry)
    }
  }
  None
}