///|
/// Parse a Windows path string into WinPath
/// Format: "C:\path\to\file.ext" or "C:\path\to\"
pub fn WinPath::parse(s : StringView) -> WinPath raise {
  if s.length() < 2 {
    raise Failure("Invalid Windows path: too short")
  }

  // Extract drive letter
  let drive = s.get_char(0).unwrap()
  if s.get_char(1).unwrap() != ':' {
    raise Failure("Invalid Windows path: missing colon after drive letter")
  }

  // Handle the remaining path after "C:"
  let remaining = if s.length() > 2 { s[2:].to_string() } else { "" }

  // If remaining starts with \, remove it
  let path_part = if remaining.has_prefix("\\") && remaining.length() > 1 {
    remaining[1:].to_string()
  } else if remaining == "\\" {
    "" // Just the root directory
  } else if remaining.is_empty() {
    "" // Just drive letter like "C:"
  } else {
    remaining // Relative path like "C:file.txt"
  }

  // Split by backslash
  let parts = if path_part.is_empty() {
    []
  } else {
    split_path(path_part, '\\')
  }

  // Determine if last part is a file
  let (dir_parts, file_opt) = if parts.is_empty() {
    ([], None)
  } else {
    let last_part = parts[parts.length() - 1]
    if has_file_extension(last_part) || is_likely_filename(last_part) {
      // Last part is likely a file
      let dir_parts_array = Array::new()
      for i = 0; i < parts.length() - 1; i = i + 1 {
        dir_parts_array.push(parts[i])
      }
      let (base, ext) = split_filename(last_part)
      let file = File::{
        base: WindowsPathComponent::from(base),
        extension: ext,
      }
      (dir_parts_array, Some(file))
    } else {
      // All parts are directories
      (parts, None)
    }
  }

  // Create directory components
  let dir_components = dir_parts.map(WindowsPathComponent::from)
  let directory = Directory(FixedArray::from_array(dir_components))
  match file_opt {
    Some(file) => WinPath::from(disk=drive, directory~, file~)
    None => WinPath::from(disk=drive, directory~)
  }
}

///|
/// 
/// `disk` should be valid drive letter (A-Z, a-z)
///
pub fn WinPath::from(
  disk~ : Char,
  directory~ : WinDirectory,
  file? : WinFile,
) -> WinPath raise {
  // Check precondition: disk must be a valid drive letter
  if not(is_valid_drive_letter(disk)) {
    raise Failure("Invalid drive letter: \{disk}. Must be A-Z or a-z")
  }
  WinPath::{ disk, directory, file }
}

///|
/// Check if a character is a valid Windows drive letter (A-Z, a-z)
fn is_valid_drive_letter(c : Char) -> Bool {
  (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
}

///|
test "WinPath::from with valid drive letters" {
  let dir = Directory([])

  // Test valid uppercase drive letters
  let path_a = WinPath::from(disk='A', directory=dir)
  inspect(path_a.disk, content="A")
  let path_z = WinPath::from(disk='Z', directory=dir)
  inspect(path_z.disk, content="Z")

  // Test valid lowercase drive letters
  let path_a_lower = WinPath::from(disk='a', directory=dir)
  inspect(path_a_lower.disk, content="a")
  let path_z_lower = WinPath::from(disk='z', directory=dir)
  inspect(path_z_lower.disk, content="z")
}

///|
test "WinPath::from with invalid drive letters" {
  let dir = Directory([])

  // Test invalid characters - they should all return Err
  let invalid_chars = ['1', '@', '[', '`', '{', ' ']
  for char in invalid_chars {
    let result = try? WinPath::from(disk=char, directory=dir)
    match result {
      Ok(_) => fail("Expected error for invalid drive letter: \{char}")
      Err(_) => () // Expected behavior
    }
  }
}

///|
test "WinPath::from error messages" {
  let dir = Directory([])

  // Test that error messages are descriptive
  let result = try? WinPath::from(disk='1', directory=dir)
  match result {
    Ok(_) => fail("Expected error for invalid drive letter")
    Err(error) => {
      let error_msg = error.to_string()
      inspect(error_msg.contains("Invalid drive letter"), content="true")
      inspect(error_msg.contains("1"), content="true")
    }
  }
}

///|
pub impl Show for WinPath with output(self, logger) {
  // Format: C:\path\to\file.ext or C:\path\to\ (if no file)
  logger.write_char(self.disk)
  logger.write_string(":\\")

  // Write directory components separated by backslashes
  for i = 0; i < self.directory.length(); i = i + 1 {
    if i > 0 {
      logger.write_char('\\')
    }
    self.directory[i].output(logger)
  }

  // Add file if present
  match self.file {
    Some(file) => {
      if self.directory.length() > 0 {
        logger.write_char('\\')
      }
      file.base.output(logger)
      if file.extension.length() > 0 {
        logger.write_char('.')
        logger.write_string(file.extension)
      }
    }
    None => ()
  }
}

///|
test "WinPath Show implementation" {
  // Test empty directory with just drive letter
  let empty_dir = Directory([])
  let path1 = WinPath::from(disk='C', directory=empty_dir)
  inspect(path1.to_string(), content="C:\\")

  // Test with directory components
  let dir_components = ["Users", "John", "Documents"].map(fn(s) {
    WindowsPathComponent(s)
  })
  let dir = Directory(FixedArray::from_array(dir_components))
  let path2 = WinPath::from(disk='D', directory=dir)
  inspect(path2.to_string(), content="D:\\Users\\John\\Documents")

  // Test with file (no extension)
  let file_base = WindowsPathComponent("readme")
  let file = File::{ base: file_base, extension: "" }
  let path3 = WinPath::from(disk='E', directory=dir, file~)
  inspect(path3.to_string(), content="E:\\Users\\John\\Documents\\readme")

  // Test with file and extension
  let file_with_ext = File::{ base: file_base, extension: "txt" }
  let path4 = WinPath::from(disk='F', directory=dir, file=file_with_ext)
  inspect(path4.to_string(), content="F:\\Users\\John\\Documents\\readme.txt")

  // Test file in root directory
  let path5 = WinPath::from(disk='G', directory=empty_dir, file=file_with_ext)
  inspect(path5.to_string(), content="G:\\readme.txt")
}

///|
test "WinPath::parse functionality" {
  // Test basic Windows path with drive letter
  let path1 = WinPath::parse("C:")
  inspect(path1.disk, content="C")
  inspect(path1.directory.length(), content="0")
  inspect(path1.file, content="None")

  // Test Windows path with directory
  let path2 = WinPath::parse("D:\\Users\\John")
  inspect(path2.disk, content="D")
  inspect(path2.directory.length(), content="2")
  inspect(path2.directory[0].0, content="Users")
  inspect(path2.directory[1].0, content="John")
  inspect(path2.file, content="None")

  // Test Windows path with file
  let path3 = WinPath::parse("E:\\Documents\\readme.txt")
  inspect(path3.disk, content="E")
  inspect(path3.directory.length(), content="1")
  inspect(path3.directory[0].0, content="Documents")
  match path3.file {
    Some(file) => {
      inspect(file.base.0, content="readme")
      inspect(file.extension, content="txt")
    }
    None => fail("Expected file")
  }

  // Test Windows path with file at root
  let path4 = WinPath::parse("F:\\config.json")
  inspect(path4.disk, content="F")
  inspect(path4.directory.length(), content="0")
  match path4.file {
    Some(file) => {
      inspect(file.base.0, content="config")
      inspect(file.extension, content="json")
    }
    None => fail("Expected file")
  }
}