///|
/// What a path denotes.
///
/// `Unknown` is not a placeholder for "we did not look": it is what a filesystem
/// answers for a socket, a fifo or a device node, which a store can see and
/// cannot serve.
pub(all) enum EntryMode {
  File
  Dir
  Unknown
} derive(Eq, Debug)

///|
/// What a store knows about one path.
///
/// Everything but `mode` is optional, because backends genuinely differ: a
/// filesystem has no content type and no etag, an object store has both. A
/// caller that needs one must check the backend's `Capability` first, or
/// tolerate `None`.
pub(all) struct Metadata {
  mode : EntryMode
  /// Bytes. Zero for a directory.
  content_length : Int64
  content_type : String?
  etag : String?
  /// Unix epoch milliseconds where the backend has a clock. Monotone within one
  /// store, and NOT comparable across stores: `MemoryStore` counts writes, it
  /// does not read a clock.
  last_modified : Int64?
} derive(Eq, Debug)

///|
pub fn Metadata::file(
  content_length : Int64,
  content_type? : String,
  etag? : String,
  last_modified? : Int64,
) -> Metadata {
  { mode: File, content_length, content_type, etag, last_modified, }
}

///|
pub fn Metadata::dir(last_modified? : Int64) -> Metadata {
  {
    mode: Dir,
    content_length: 0L,
    content_type: None,
    etag: None,
    last_modified,
  }
}

///|
pub fn Metadata::is_dir(self : Metadata) -> Bool {
  self.mode is Dir
}

///|
pub fn Metadata::is_file(self : Metadata) -> Bool {
  self.mode is File
}

///|
/// One result of `list`: a path and what is known about it.
///
/// `path` is relative to the store root and is the exact string `read` and
/// `stat` will accept. A directory entry's path ends in `/`; a file's does not.
/// That invariant is what lets a caller recurse without a second `stat`.
pub(all) struct Entry {
  path : String
  metadata : Metadata
} derive(Eq, Debug)

///|
/// The last segment. Keeps the trailing `/` for a directory.
pub fn Entry::name(self : Entry) -> String {
  basename(self.path)
}

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

///|
/// What a backend can do.
///
/// Declared rather than discovered, and checked by `Operator` before the call
/// reaches the store, so an unsupported option fails with the operation's own
/// name instead of somewhere inside a backend.
///
/// The conformance suite reads this to decide which checks to run, and asserts
/// the inverse too: every `false` flag must produce `Unsupported`. A backend
/// cannot under-declare its way out of a check.
pub(all) struct Capability {
  read : Bool
  read_with_range : Bool
  write : Bool
  write_can_append : Bool
  write_with_content_type : Bool
  write_with_if_not_exists : Bool
  stat : Bool
  list : Bool
  list_with_recursive : Bool
  list_with_limit : Bool
  list_with_start_after : Bool
  delete : Bool
  delete_with_recursive : Bool
  create_dir : Bool
  copy : Bool
  rename : Bool
  /// True when a second operator over the same root -- in another process,
  /// another tab -- sees these writes. False for `MemoryStore`.
  shared : Bool
} derive(Eq, Debug)

///|
/// Everything off.
///
/// Build a real one with struct update, so adding a field to `Capability` does
/// not silently turn it on everywhere:
/// `{ ..Capability::none(), read: true, write: true }`.
pub fn Capability::none() -> Capability {
  {
    read: false,
    read_with_range: false,
    write: false,
    write_can_append: false,
    write_with_content_type: false,
    write_with_if_not_exists: false,
    stat: false,
    list: false,
    list_with_recursive: false,
    list_with_limit: false,
    list_with_start_after: false,
    delete: false,
    delete_with_recursive: false,
    create_dir: false,
    copy: false,
    rename: false,
    shared: false,
  }
}

///|
/// Who a store is.
///
/// Cheap and pure: `Operator::new` calls `Store::info` once at construction and
/// caches the answer, so it must not do I/O.
pub(all) struct StoreInfo {
  /// "memory", "fs", "indexeddb".
  scheme : String
  /// Where the store is anchored. Display only; never concatenated with a store
  /// path by anything outside the backend that produced it.
  root : String
  /// Bucket, database or namespace. Empty when the backend has none.
  name : String
  capability : Capability
} derive(Eq, Debug)