// Member - represents a file or directory in a ZIP archive

///|
/// Re-export dependencies from other packages
pub using @file {type File}

///|
pub using @types {type Fpath}

///|
pub using @types {type FileMode}

///|
pub using @types {type Ptime}

///|
pub let dos_epoch : Ptime = @types.dos_epoch

///|
pub using @types {fpath_ensure_unix}

///|
pub using @types {fpath_ensure_directoryness}

///|
pub using @types {format_file_mode}

///|
pub using @types {ptime_format}

///|
/// Archive Member - represents a file or directory in a ZIP archive
pub(all) enum MemberKind {
  Dir // Directory entry
  File(File) // File entry with compressed data
}

///|
/// Maximum number of members in a ZIP archive (non-ZIP64)
pub let max_member_count : Int = 65535

///|
/// Maximum path length in ZIP archives
pub let max_path_length : Int = 65535

///|
/// Archive member (file or directory)
pub struct Member {
  path : Fpath
  kind : MemberKind
  mode : FileMode
  mtime : Ptime
}

///|
/// Create a new archive member
#as_free_fn
/// Create a new Member, normalizing path, enforcing length, defaulting mode & mtime.
pub fn Member::make(
  path : Fpath,
  kind : MemberKind,
  mode? : FileMode,
  mtime? : Ptime,
) -> Member raise {
  // Ensure Unix-style path
  let normalized_path = fpath_ensure_unix(path)

  // Ensure directories end with /
  let final_path = match kind {
    Dir => fpath_ensure_directoryness(normalized_path)
    File(_) => normalized_path
  }

  // Check path length
  if final_path.length() > max_path_length {
    fail(
      "Path length \{final_path.length()} exceeds maximum \{max_path_length}",
    )
  }
  // Set default mode
  let file_mode = mode.unwrap_or(
    match kind {
      Dir => FileMode(0o755)
      File(_) => FileMode(0o644)
    },
  )

  // Set default mtime (truncate to dos_epoch if before)
  let modification_time = mtime.unwrap_or(dos_epoch)
  let final_mtime = if modification_time < dos_epoch {
    dos_epoch
  } else {
    modification_time
  }
  { path: final_path, kind, mode: file_mode, mtime: final_mtime, }
}

///|
/// Get the path of a member
pub fn Member::path(self : Member) -> Fpath {
  self.path
}

///|
/// Get the kind of a member
pub fn Member::kind(self : Member) -> MemberKind {
  self.kind
}

///|
/// Get the file mode of a member
pub fn Member::mode(self : Member) -> FileMode {
  self.mode
}

///|
/// Get the modification time of a member
pub fn Member::mtime(self : Member) -> Ptime {
  self.mtime
}

///|
/// Check if member is a directory
pub fn Member::is_dir(self : Member) -> Bool {
  match self.kind {
    Dir => true
    File(_) => false
  }
}

///|
/// Check if member is a file
pub fn Member::is_file(self : Member) -> Bool {
  match self.kind {
    Dir => false
    File(_) => true
  }
}

///|
/// Format member info (like ls -l)
/// Short ls -l style listing line.
pub fn Member::format(self : Member) -> String {
  let is_dir_char = match self.kind {
    Dir => "d"
    File(_) => "-"
  }
  let mode_str = format_file_mode(self.mode)
  let size = match self.kind {
    Dir => 0
    File(f) => f.decompressed_size
  }
  let time_str = ptime_format(self.mtime)

  // Format size with padding (8 characters)
  let size_str = size.to_string()
  let spaces_needed = 8 - size_str.length()
  let mut padding = ""
  for i = 0; i < spaces_needed; i = i + 1 {
    padding = padding + " "
  }
  let padded_size = padding + size_str
  is_dir_char +
  mode_str +
  " " +
  padded_size +
  " " +
  time_str +
  " " +
  self.path.to_string()
}

///|
/// Format member info with detailed compression information
/// Extended listing with compression method & ratio (if file).
pub fn Member::format_long(self : Member) -> String {
  let basic = self.format()
  match self.kind {
    Dir => basic
    File(f) => {
      let compression_str = f.compression.to_string()
      let ratio = if f.decompressed_size == 0 {
        "0%"
      } else {
        let pct = f.compressed_size * 100 / f.decompressed_size
        pct.to_string() + "%"
      }
      basic + " [" + compression_str + ", " + ratio + " compressed]"
    }
  }
}