BucketsProvider.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Provider;
using System.Text;
using System.Text.Json;

namespace Buckets.Provider
{
    public class BucketObjectInfo
    {
        public string LogicalPath { get; set; }
        public string PhysicalPath { get; set; }
        public string Key { get; set; }
        public string Bucket { get; set; }
        public string Format { get; set; }
        public bool Compressed { get; set; }
        public long SizeBytes { get; set; }
        public DateTime Modified { get; set; }
        public object Content { get; set; }
    }

    public class BucketItemInfo
    {
        public string Type
        {
            get
            {
                switch (ItemKind)
                {
                    case "bucket": return "b--";
                    case "object": return "--o";
                    default: return IsContainer ? "b--" : "--o";
                }
            }
        }
        public DateTime LastWriteTime { get; set; }
        public DateTime CreationTime { get; set; }
        public string Size
        {
            get
            {
                if (IsContainer)
                {
                    return FormatSize(SizeBytes);
                }
                return SizeBytes > 0 ? FormatSize(SizeBytes) : "--";
            }
        }
        public string Name { get; set; }

        // Internal use only
        internal bool IsContainer { get; set; }
        internal string ItemKind { get; set; } // "bucket", "object"
        internal int ItemCount { get; set; }
        internal string Format { get; set; }
        internal long SizeBytes { get; set; }
        internal string PhysicalPath { get; set; }

        private static string FormatSize(long bytes)
        {
            if (bytes == 0) return "0 B";
            string[] units = { "B", "KB", "MB", "GB", "TB" };
            int unit = 0;
            double size = bytes;
            while (size >= 1024 && unit < units.Length - 1)
            {
                size /= 1024;
                unit++;
            }
            return (int)Math.Round(size) + " " + units[unit];
        }
    }

    [CmdletProvider("Buckets", ProviderCapabilities.ShouldProcess)]
    public class BucketsProvider : NavigationCmdletProvider, IContentCmdletProvider
    {
        private static readonly char[] InvalidChars = { '/', ':', '*', '?', '"', '<', '>', '|', '[', ']' };
        private static readonly byte[] GZipMagic = { 0x1F, 0x8B };
        private static readonly char ProviderSep = Path.DirectorySeparatorChar;
        private static readonly char Sep = Path.DirectorySeparatorChar;

        // Static cache: drive name -> physical root path
        private static readonly Dictionary<string, string> DriveRoots = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

        #region Drive Support

        protected override PSDriveInfo NewDrive(PSDriveInfo drive)
        {
            if (drive == null || string.IsNullOrEmpty(drive.Root))
            {
                WriteError(new ErrorRecord(
                    new ArgumentNullException("drive", "Drive root cannot be null or empty."),
                    "NullDrive", ErrorCategory.InvalidArgument, drive));
                return null;
            }

            string root = drive.Root;
            if (!Path.IsPathRooted(root))
            {
                var cwd = SessionState.Path.CurrentFileSystemLocation;
                if (cwd != null)
                {
                    root = Path.Combine(cwd.Path, root);
                }
            }
            root = Path.GetFullPath(root);

            if (!Directory.Exists(root))
            {
                Directory.CreateDirectory(root);
            }

            // Return PSDriveInfo with drive name as logical root
            var newDrive = new PSDriveInfo(drive.Name, this.ProviderInfo, drive.Name + ":" + ProviderSep, drive.Description, drive.Credential);
            DriveRoots[drive.Name] = root;
            SessionState.PSVariable.Set("__buckets_physical_root_" + drive.Name, root);
            return newDrive;
        }

        private string GetPhysicalRoot()
        {
            string driveName = PSDriveInfo.Name;

            // Check static cache first (works across all scopes)
            if (DriveRoots.TryGetValue(driveName, out string cachedRoot))
            {
                return cachedRoot;
            }

            // Fall back to session variable
            string varName = "__buckets_physical_root_" + driveName;
            var variable = SessionState.PSVariable.Get(varName);
            if (variable?.Value is string sessionRoot)
            {
                DriveRoots[driveName] = sessionRoot;
                return sessionRoot;
            }

            // Last resort: drive root (shouldn't happen)
            return PSDriveInfo.Root;
        }

        protected override Collection<PSDriveInfo> InitializeDefaultDrives()
        {
            return new Collection<PSDriveInfo>();
        }

        protected override PSDriveInfo RemoveDrive(PSDriveInfo drive)
        {
            return null;
        }

        #endregion

        #region Path Resolution

        /// <summary>
        /// Convert a provider-logical path to the actual filesystem path.
        /// Supports nested buckets: projects/myproject/object resolves to
        /// projects/myproject/object.dat or projects/myproject/object.json.
        /// </summary>
        private string ToPhysicalPath(string path)
        {
            string root = GetPhysicalRoot();

            if (string.IsNullOrEmpty(path))
            {
                return Directory.Exists(root) ? root : null;
            }

            // Strip provider qualification: Buckets::...
            int colonColon = path.IndexOf("::", StringComparison.Ordinal);
            if (colonColon >= 0)
            {
                path = path.Substring(colonColon + 2);
            }

            // Strip drive prefix: buckets: or Buckets:
            string driveName = PSDriveInfo.Name + ":";
            if (path.StartsWith(driveName, StringComparison.OrdinalIgnoreCase))
            {
                path = path.Substring(driveName.Length);
            }

            // Strip leading separators
            path = path.TrimStart(Sep, '/', '\\');

            if (string.IsNullOrEmpty(path))
            {
                return Directory.Exists(root) ? root : null;
            }

            // Check if it's a filesystem-absolute path
            if (Path.IsPathRooted(path))
            {
                string normalized = Path.GetFullPath(path);
                if (Directory.Exists(normalized)) return normalized;
                if (File.Exists(normalized)) return normalized;
                string datPath = normalized + ".dat";
                if (File.Exists(datPath)) return datPath;
                string jsonPath = normalized + ".json";
                if (File.Exists(jsonPath)) return jsonPath;
                return null;
            }

            // Normalize separators and split into parts
            path = path.Replace('/', Sep).Replace('\\', Sep);
            string[] rawParts = path.Split(new[] { Sep }, StringSplitOptions.RemoveEmptyEntries);

            // Resolve .. segments (clamp to root)
            var parts = new System.Collections.Generic.List<string>();
            foreach (var part in rawParts)
            {
                if (part == ".") continue;
                if (part == "..") continue;
                parts.Add(part);
            }

            if (parts.Count == 0)
            {
                return Directory.Exists(root) ? root : null;
            }

            // Build the physical path for a prefix of parts
            string BuildPath(int count)
            {
                string p = root;
                for (int i = 0; i < count; i++) p = Path.Combine(p, parts[i]);
                return p;
            }

            // Strategy: walk from longest bucket prefix to shortest.
            // For "a/b/c/d", try:
            // 1. a/b/c is bucket? -> a/b/c/d.dat, a/b/c/d.json
            // 2. a/b is bucket? -> a/b/c/d.dat, a/b/c/d.json
            // 3. a is bucket? -> a/b/c/d.dat, a/b/c/d.json
            // 4. Fall back to direct path

            for (int bucketLen = parts.Count - 1; bucketLen >= 1; bucketLen--)
            {
                string bucketDir = BuildPath(bucketLen);
                if (!Directory.Exists(bucketDir)) continue;

                // Only treat as bucket if it has .dat/.json files, or it's the first segment
                // (first segment is always a potential bucket for backward compatibility)
                bool looksLikeBucket = bucketLen == 1 || IsBucketDirectory(bucketDir);
                if (!looksLikeBucket) continue;

                // Remaining parts after bucket prefix
                int remainCount = parts.Count - bucketLen;
                if (remainCount == 0)
                {
                    // Exact bucket path
                    return bucketDir;
                }

                // Try direct object files: bucket/<last part>.dat/.json (only for 1 remaining part)
                if (remainCount == 1)
                {
                    string objPath = Path.Combine(bucketDir, parts[bucketLen]);
                    if (File.Exists(objPath)) return objPath;
                    if (File.Exists(objPath + ".dat")) return objPath + ".dat";
                    if (File.Exists(objPath + ".json")) return objPath + ".json";
                }

                // Try direct directory path
                string directPath = BuildPath(parts.Count);
                if (Directory.Exists(directPath)) return directPath;
                if (File.Exists(directPath)) return directPath;
            }

            // Last resort: direct path from root
            string finalPath = BuildPath(parts.Count);
            if (Directory.Exists(finalPath)) return finalPath;
            if (File.Exists(finalPath)) return finalPath;

            return null;
        }

        /// <summary>
        /// Convert a physical filesystem path to a provider-logical path.
        /// Works with nested buckets at any depth.
        /// </summary>
        private string ToLogicalPath(string physicalPath)
        {
            string root = GetPhysicalRoot();
            if (!physicalPath.StartsWith(root, StringComparison.OrdinalIgnoreCase))
            {
                return physicalPath;
            }

            string relative = physicalPath.Substring(root.Length).TrimStart(Sep);

            // Strip known extensions
            string lower = relative.ToLowerInvariant();
            if (lower.EndsWith(".dat"))
            {
                relative = relative.Substring(0, relative.Length - 4);
            }
            else if (lower.EndsWith(".json"))
            {
                relative = relative.Substring(0, relative.Length - 5);
            }

            return relative;
        }

        /// <summary>
        /// Extract the full bucket path (relative to root) from a physical path.
        /// Works with nested buckets at any depth.
        /// </summary>
        private string GetBucketNameFromPhysical(string physicalPath, string root)
        {
            string bucketPhysical = FindBucketAncestor(physicalPath, root);
            if (bucketPhysical != null)
            {
                return bucketPhysical.Substring(root.Length).TrimStart(Sep);
            }
            // Fallback: just first segment
            string relative = physicalPath.Substring(root.Length).TrimStart(Sep);
            string[] parts = relative.Split(new[] { Sep }, StringSplitOptions.RemoveEmptyEntries);
            return parts.Length > 0 ? parts[0] : "";
        }

        #endregion

        #region Serialization

        private object DeserializeFile(string physicalPath, out string format, out bool compressed)
        {
            format = "Binary";
            compressed = false;
            byte[] bytes = File.ReadAllBytes(physicalPath);
            string ext = Path.GetExtension(physicalPath).ToLowerInvariant();

            if (ext == ".dat")
            {
                if (bytes.Length >= 2 && bytes[0] == GZipMagic[0] && bytes[1] == GZipMagic[1])
                {
                    compressed = true;
                    using (var ms = new MemoryStream(bytes))
                    using (var gzip = new GZipStream(ms, CompressionMode.Decompress))
                    using (var reader = new StreamReader(gzip, Encoding.Unicode))
                    {
                        string gzXml = reader.ReadToEnd();
                        return PSSerializer.Deserialize(gzXml);
                    }
                }

                string xml = Encoding.UTF8.GetString(bytes);
                return PSSerializer.Deserialize(xml);
            }
            else if (ext == ".json")
            {
                format = "JSON";
                string json = Encoding.UTF8.GetString(bytes);
                if (json.StartsWith("\ufeff")) json = json.Substring(1);
                return PSSerializer.Deserialize(json);
            }

            throw new InvalidOperationException($"Unsupported format: {ext}");
        }

        private void SerializeFile(object value, string physicalPath, string format)
        {
            string dir = Path.GetDirectoryName(physicalPath);
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }

            if (format.Equals("json", StringComparison.OrdinalIgnoreCase))
            {
                TrySerializeJson(value, physicalPath);
            }
            else
            {
                string xml = PSSerializer.Serialize(value);
                File.WriteAllBytes(physicalPath, Encoding.UTF8.GetBytes(xml));
            }
        }

        internal object DeserializeFileInternal(string physicalPath, out string format, out bool compressed)
        {
            return DeserializeFile(physicalPath, out format, out compressed);
        }

        internal void SerializeFileInternal(object value, string physicalPath, string format)
        {
            SerializeFile(value, physicalPath, format);
        }

        private void TrySerializeJson(object value, string physicalPath)
        {
            try
            {
                object serializable = ConvertToSerializable(value);
                var options = new JsonSerializerOptions
                {
                    WriteIndented = true,
                    Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
                };
                string json = JsonSerializer.Serialize(serializable, options);
                File.WriteAllText(physicalPath, json, Encoding.UTF8);
            }
            catch (Exception ex)
            {
                string datPath = Path.ChangeExtension(physicalPath, ".dat");
                WriteWarning($"JSON serialization failed: {ex.Message}. Falling back to binary ({datPath}).");
                string xml = PSSerializer.Serialize(value);
                File.WriteAllBytes(datPath, Encoding.Unicode.GetBytes(xml));
            }
        }

        private object ConvertToSerializable(object value)
        {
            if (value is IDictionary dict)
            {
                var result = new Dictionary<string, object>();
                foreach (DictionaryEntry entry in dict)
                {
                    result[entry.Key?.ToString() ?? ""] = ConvertToSerializable(entry.Value);
                }
                return result;
            }

            var pso = value as PSObject;
            if (pso != null)
            {
                var result = new Dictionary<string, object>();
                foreach (var prop in pso.Properties.Where(p => p.IsGettable))
                {
                    result[prop.Name] = ConvertToSerializable(prop.Value);
                }
                return result;
            }

            if (value is IEnumerable enumerable && value is not string)
            {
                var list = new List<object>();
                foreach (var item in enumerable)
                {
                    list.Add(ConvertToSerializable(item));
                }
                return list;
            }

            return value;
        }

        #endregion

        #region Helpers

        private string SanitizeKey(string key)
        {
            string result = key;
            foreach (var ch in InvalidChars)
            {
                result = result.Replace(ch.ToString(), "_");
            }
            return result;
        }

        /// <summary>
        /// Determine if a physical directory is a bucket (has .dat/.json files).
        /// Works at any depth, enabling nested buckets.
        /// </summary>
        private bool IsBucketDirectory(string physicalDir)
        {
            if (!Directory.Exists(physicalDir)) return false;
            var di = new DirectoryInfo(physicalDir);
            return di.GetFiles("*.dat").Length > 0 || di.GetFiles("*.json").Length > 0;
        }

        /// <summary>
        /// Determine if we're at the drive root (not inside any bucket).
        /// </summary>
        private bool IsDriveRoot(string physicalDir, string root)
        {
            return string.Equals(Path.GetFullPath(physicalDir), Path.GetFullPath(root), StringComparison.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Find the nearest bucket ancestor directory for a physical path.
        /// Walks up from the given directory toward root, returning the first
        /// directory that is a bucket (has .dat/.json files).
        /// Returns null if no bucket ancestor found.
        /// </summary>
        private string FindBucketAncestor(string physicalPath, string root)
        {
            // Start with parent directory if path is a file
            string current = File.Exists(physicalPath) ? Path.GetDirectoryName(physicalPath) : physicalPath;
            while (!string.IsNullOrEmpty(current) && current.StartsWith(root, StringComparison.OrdinalIgnoreCase))
            {
                if (IsBucketDirectory(current)) return current;
                string parent = Path.GetDirectoryName(current);
                if (string.IsNullOrEmpty(parent) || parent == current) break;
                current = parent;
            }
            return null;
        }

        /// <summary>
        /// Count nested bucket directories recursively.
        /// </summary>
        private int CountNestedBuckets(string directory, string root)
        {
            int count = 0;
            var di = new DirectoryInfo(directory);
            foreach (var subDir in di.GetDirectories())
            {
                if (subDir.Name == ".buckets") continue;
                if (IsBucketDirectory(subDir.FullName)) count++;
                count += CountNestedBuckets(subDir.FullName, root);
            }
            return count;
        }

        /// <summary>
        /// Recursively calculate total size of .dat and .json files in a directory tree.
        /// </summary>
        private long GetDirectorySize(string directory)
        {
            long total = 0;
            try
            {
                var di = new DirectoryInfo(directory);
                foreach (var file in di.GetFiles("*.dat")) total += file.Length;
                foreach (var file in di.GetFiles("*.json")) total += file.Length;
                foreach (var subDir in di.GetDirectories().Where(d => d.Name != ".buckets"))
                {
                    total += GetDirectorySize(subDir.FullName);
                }
            }
            catch { }
            return total;
        }

        private string GetBucketName(string logicalPath)
        {
            string normalized = ToLogicalPath(logicalPath).TrimStart(Sep, '/', '\\');
            int sep = normalized.IndexOf(Sep);
            return sep < 0 ? normalized : normalized.Substring(0, sep);
        }

        private string GetKeyName(string logicalPath)
        {
            string normalized = ToLogicalPath(logicalPath).TrimStart(Sep, '/', '\\');
            int sep = normalized.LastIndexOf(Sep);
            return sep < 0 ? normalized : normalized.Substring(sep + 1);
        }

        #endregion

        #region Core Provider Methods

        protected override bool IsValidPath(string path)
        {
            return true;
        }

        protected override bool ItemExists(string path)
        {
            return ToPhysicalPath(path) != null;
        }

        protected override bool IsItemContainer(string path)
        {
            string physical = ToPhysicalPath(path);
            return physical != null && Directory.Exists(physical);
        }

        protected override void GetItem(string path)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null)
            {
                WriteError(new ErrorRecord(
                    new ItemNotFoundException($"Item not found: {path}"),
                    "ItemNotFound", ErrorCategory.ObjectNotFound, path));
                return;
            }

            if (Directory.Exists(physical))
            {
                var di = new DirectoryInfo(physical);
                var files = di.GetFiles("*.dat").Concat(di.GetFiles("*.json"));
                var dirs = di.GetDirectories().Where(d => d.Name != ".buckets");
                int count = files.Count() + dirs.Count();

                var info = new BucketItemInfo
                {
                    Name = di.Name,
                    IsContainer = true,
                    ItemKind = "bucket",
                    ItemCount = count,
                    Format = "",
                    SizeBytes = GetDirectorySize(physical),
                    PhysicalPath = physical,
                    LastWriteTime = di.LastWriteTime,
                    CreationTime = di.CreationTime
                };

                WriteItemObject(info, ToLogicalPath(physical), true);
            }
            else
            {
                try
                {
                    object content = DeserializeFile(physical, out string format, out bool compressed);
                    var fi = new FileInfo(physical);
                    var info = new BucketObjectInfo
                    {
                        LogicalPath = ToLogicalPath(physical),
                        PhysicalPath = physical,
                        Key = GetKeyName(path),
                        Bucket = GetBucketName(path),
                        Format = format,
                        Compressed = compressed,
                        SizeBytes = fi.Length,
                        Modified = fi.LastWriteTime,
                        Content = content
                    };

                    WriteItemObject(info, ToLogicalPath(physical), false);
                }
                catch (Exception ex)
                {
                    WriteError(new ErrorRecord(
                        new RuntimeException($"Failed to deserialize: {ex.Message}"),
                        "DeserializeFailed", ErrorCategory.ReadError, path));
                }
            }
        }

        protected override void GetChildItems(string path, bool recurse)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null || !Directory.Exists(physical))
            {
                return;
            }

            uint currentDepth = 0;
            EnumerateDirectory(physical, recurse, null, ref currentDepth);
        }

        protected override void GetChildItems(string path, bool recurse, uint depth)
        {
            GetChildItemsInternal(path, recurse, depth);
        }

        private void GetChildItemsInternal(string path, bool recurse, uint? depthLimit = null)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null || !Directory.Exists(physical))
            {
                return;
            }

            uint currentDepth = 0;
            EnumerateDirectory(physical, recurse, depthLimit, ref currentDepth);
        }

        private void EnumerateDirectory(string directory, bool recurse, uint? depthLimit, ref uint currentDepth)
        {
            if (depthLimit.HasValue && currentDepth >= depthLimit.Value) return;

            var di = new DirectoryInfo(directory);
            string root = GetPhysicalRoot();
            bool isDriveRoot = IsDriveRoot(directory, root);
            bool isBucket = IsBucketDirectory(directory);

            if (isDriveRoot)
            {
                // At drive root: show bucket directories (b--)
                foreach (var bucketDir in di.GetDirectories().Where(d => d.Name != ".buckets").OrderBy(d => d.Name))
                {
                    int count = bucketDir.GetFiles("*.dat").Length + bucketDir.GetFiles("*.json").Length;

                    WriteItemObject(new BucketItemInfo
                    {
                        Name = bucketDir.Name,
                        IsContainer = true,
                        ItemKind = "bucket",
                        ItemCount = count,
                        Format = "",
                        SizeBytes = GetDirectorySize(bucketDir.FullName),
                        PhysicalPath = bucketDir.FullName,
                        LastWriteTime = bucketDir.LastWriteTime,
                        CreationTime = bucketDir.CreationTime
                    }, bucketDir.Name, true);

                    if (recurse)
                    {
                        currentDepth++;
                        EnumerateDirectory(bucketDir.FullName, recurse, depthLimit, ref currentDepth);
                        currentDepth--;
                    }
                }
            }
            else if (isBucket)
            {
                // At a bucket (any depth): show files and nested bucket subdirectories

                // Regular files (--o)
                foreach (var file in di.GetFiles("*.dat").Concat(di.GetFiles("*.json")).OrderBy(f => f.Name))
                {
                    string ext = Path.GetExtension(file.Name).ToLowerInvariant();
                    string logical = ToLogicalPath(file.FullName);

                    WriteItemObject(new BucketItemInfo
                    {
                        Name = Path.GetFileNameWithoutExtension(file.Name),
                        IsContainer = false,
                        ItemKind = "object",
                        ItemCount = 0,
                        Format = ext == ".json" ? "JSON" : "Binary",
                        SizeBytes = file.Length,
                        PhysicalPath = file.FullName,
                        LastWriteTime = file.LastWriteTime,
                        CreationTime = file.CreationTime
                    }, logical, false);
                }

                // Nested bucket subdirectories (b--)
                foreach (var subDir in di.GetDirectories().Where(d => d.Name != ".buckets").OrderBy(d => d.Name))
                {
                    if (IsBucketDirectory(subDir.FullName))
                    {
                        int subCount = subDir.GetFiles("*.dat").Length + subDir.GetFiles("*.json").Length;

                        string logical = subDir.FullName.Substring(root.Length).TrimStart(Sep).Replace(Sep, ProviderSep);

                        WriteItemObject(new BucketItemInfo
                        {
                            Name = subDir.Name,
                            IsContainer = true,
                            ItemKind = "bucket",
                            ItemCount = subCount,
                            Format = "",
                            SizeBytes = GetDirectorySize(subDir.FullName),
                            PhysicalPath = subDir.FullName,
                            LastWriteTime = subDir.LastWriteTime,
                            CreationTime = subDir.CreationTime
                        }, logical, true);

                        if (recurse)
                        {
                            currentDepth++;
                            EnumerateDirectory(subDir.FullName, recurse, depthLimit, ref currentDepth);
                            currentDepth--;
                        }
                    }
                }
            }
            else
            {
                // General subdirectory (not a bucket): show subdirectories as potential buckets
                foreach (var subDir in di.GetDirectories().OrderBy(d => d.Name))
                {
                    if (subDir.Name == ".buckets") continue;

                    bool subIsBucket = IsBucketDirectory(subDir.FullName);
                    var files = subDir.GetFiles("*.dat").Concat(subDir.GetFiles("*.json"));
                    int subCount = files.Count();

                    string logical = ToLogicalPath(subDir.FullName);

                    WriteItemObject(new BucketItemInfo
                    {
                        Name = subDir.Name,
                        IsContainer = true,
                        ItemKind = subIsBucket ? "bucket" : "bucket",
                        ItemCount = subCount,
                        Format = "",
                        SizeBytes = GetDirectorySize(subDir.FullName),
                        PhysicalPath = subDir.FullName,
                        LastWriteTime = subDir.LastWriteTime,
                        CreationTime = subDir.CreationTime
                    }, logical, true);

                    if (recurse)
                    {
                        currentDepth++;
                        EnumerateDirectory(subDir.FullName, recurse, depthLimit, ref currentDepth);
                        currentDepth--;
                    }
                }

                // Files
                foreach (var file in di.GetFiles("*.dat").Concat(di.GetFiles("*.json")).OrderBy(f => f.Name))
                {
                    string ext = Path.GetExtension(file.Name).ToLowerInvariant();
                    string logical = ToLogicalPath(file.FullName);

                    WriteItemObject(new BucketItemInfo
                    {
                        Name = Path.GetFileNameWithoutExtension(file.Name),
                        IsContainer = false,
                        ItemKind = "object",
                        ItemCount = 0,
                        Format = ext == ".json" ? "JSON" : "Binary",
                        SizeBytes = file.Length,
                        PhysicalPath = file.FullName,
                        LastWriteTime = file.LastWriteTime,
                        CreationTime = file.CreationTime
                    }, logical, false);
                }
            }
        }

        protected override void GetChildNames(string path, ReturnContainers returnContainers)
        {
            string physical = ToPhysicalPath(path);
            
            // Fallback: if path doesn't resolve, use the physical root directly
            if (physical == null || !Directory.Exists(physical))
            {
                physical = GetPhysicalRoot();
                if (!Directory.Exists(physical)) return;
            }

            var di = new DirectoryInfo(physical);
            string root = GetPhysicalRoot();
            bool isDriveRoot = IsDriveRoot(physical, root);
            bool isBucket = IsBucketDirectory(physical);

            if (isDriveRoot)
            {
                foreach (var bucketDir in di.GetDirectories().Where(d => d.Name != ".buckets").OrderBy(d => d.Name))
                {
                    string logicalPath = PSDriveInfo.Name + ":" + ProviderSep + bucketDir.Name;
                    WriteItemObject(bucketDir.Name, logicalPath, true);
                }
            }
            else if (isBucket)
            {
                // Build the parent path from the physical directory
                string bucketRelative = physical.Substring(root.Length).TrimStart(Sep);
                string parentPath = PSDriveInfo.Name + ":" + ProviderSep + bucketRelative.Replace(Sep, ProviderSep);

                foreach (var file in di.GetFiles("*.dat").Concat(di.GetFiles("*.json")).OrderBy(f => f.Name))
                {
                    string name = Path.GetFileNameWithoutExtension(file.Name);
                    WriteItemObject(name, parentPath + ProviderSep + name, false);
                }

                // Nested bucket subdirectories
                foreach (var subDir in di.GetDirectories().Where(d => d.Name != ".buckets").OrderBy(d => d.Name))
                {
                    if (IsBucketDirectory(subDir.FullName))
                    {
                        string subLogical = subDir.FullName.Substring(root.Length).TrimStart(Sep).Replace(Sep, ProviderSep);
                        WriteItemObject(subDir.Name, PSDriveInfo.Name + ":" + ProviderSep + subLogical, true);
                    }
                }
            }
            else
            {
                string relativePhysical = physical.Substring(root.Length).TrimStart(Sep, '/', '\\');
                string relativeLogical = relativePhysical.Replace('/', ProviderSep).Replace('\\', ProviderSep);
                string prefix = PSDriveInfo.Name + ":" + ProviderSep + relativeLogical;

                foreach (var subDir in di.GetDirectories().Where(d => d.Name != ".buckets").OrderBy(d => d.Name))
                {
                    WriteItemObject(subDir.Name, prefix + ProviderSep + subDir.Name, true);
                }

                foreach (var file in di.GetFiles("*.dat").Concat(di.GetFiles("*.json")).OrderBy(f => f.Name))
                {
                    string name = Path.GetFileNameWithoutExtension(file.Name);
                    WriteItemObject(name, prefix + ProviderSep + name, false);
                }
            }
        }

        protected override bool HasChildItems(string path)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null || !Directory.Exists(physical)) return false;

            var di = new DirectoryInfo(physical);
            string root = GetPhysicalRoot();
            bool isDriveRoot = IsDriveRoot(physical, root);
            bool isBucket = IsBucketDirectory(physical);

            if (isDriveRoot)
            {
                return di.GetDirectories().Any(d => d.Name != ".buckets");
            }

            if (isBucket)
            {
                if (di.GetFiles("*.dat").Length > 0 || di.GetFiles("*.json").Length > 0) return true;
                return di.GetDirectories().Any(d => d.Name != ".buckets");
            }

            return di.GetFiles("*.dat").Length > 0 ||
                   di.GetFiles("*.json").Length > 0 ||
                   di.GetDirectories().Length > 0;
        }

        #endregion

        #region Navigation

        protected override string GetChildName(string path)
        {
            if (string.IsNullOrEmpty(path)) return "";

            string physRoot = GetPhysicalRoot();
            if (path.StartsWith(physRoot, StringComparison.OrdinalIgnoreCase))
            {
                path = ToLogicalPath(path);
            }

            string cleaned = path;
            int colonColon = cleaned.IndexOf("::", StringComparison.Ordinal);
            if (colonColon >= 0) cleaned = cleaned.Substring(colonColon + 2);
            string driveName = PSDriveInfo.Name + ":";
            if (cleaned.StartsWith(driveName, StringComparison.OrdinalIgnoreCase))
            {
                cleaned = cleaned.Substring(driveName.Length);
            }
            cleaned = cleaned.TrimStart(ProviderSep, '/', '\\');

            if (string.IsNullOrEmpty(cleaned)) return "";

            int sep = cleaned.LastIndexOf(ProviderSep);
            if (sep < 0) sep = cleaned.LastIndexOf('/');
            if (sep < 0) sep = cleaned.LastIndexOf('\\');
            return sep < 0 ? cleaned : cleaned.Substring(sep + 1);
        }

        protected override string MakePath(string parent, string child)
        {
            if (string.IsNullOrEmpty(parent)) return child ?? "";
            if (string.IsNullOrEmpty(child)) return parent;

            string driveName = PSDriveInfo.Name + ":";
            string physRoot = GetPhysicalRoot();

            // Convert physical paths to logical first
            if (parent.StartsWith(physRoot, StringComparison.OrdinalIgnoreCase))
            {
                parent = ToLogicalPath(parent);
                parent = driveName + ProviderSep + parent;
            }

            string normalized = parent.TrimEnd(ProviderSep, '/', '\\');
            if (child == ".") return normalized;
            if (child == "..")
            {
                if (normalized.EndsWith(driveName, StringComparison.OrdinalIgnoreCase) && normalized.Length == driveName.Length)
                {
                    return normalized;
                }
                int lastSep = normalized.LastIndexOf(ProviderSep);
                if (lastSep < 0) lastSep = normalized.LastIndexOf('/');
                if (lastSep < 0) return normalized;
                normalized = normalized.Substring(0, lastSep);
                if (string.IsNullOrEmpty(normalized)) return driveName;
                return normalized;
            }

            // Clamp ".." parent to drive root
            if (normalized == "..")
            {
                return driveName + ProviderSep + child;
            }

            // If child is already fully qualified, return it as-is
            if (child.StartsWith(driveName, StringComparison.OrdinalIgnoreCase))
            {
                return child;
            }

            if (normalized.EndsWith(":", StringComparison.OrdinalIgnoreCase))
            {
                return normalized + ProviderSep + child;
            }

            return normalized + ProviderSep + child;
        }

        protected override string GetParentPath(string path, string root)
        {
            string driveName = PSDriveInfo.Name + ":";
            string physRoot = GetPhysicalRoot();

            // Convert physical paths to logical first
            if (path.StartsWith(physRoot, StringComparison.OrdinalIgnoreCase))
            {
                path = ToLogicalPath(path);
                path = driveName + ProviderSep + path;
            }

            // Normalize separators to forward slash (consistent across platforms)
            char sep = '/';
            string normalized = path.Replace('\\', sep);

            // Check if the path is just a separator or empty (drive root)
            string trimmedNorm = normalized.TrimEnd(sep).TrimStart(sep);
            if (string.IsNullOrEmpty(trimmedNorm))
            {
                // At drive root - return the root path as-is
                string rootPath = root ?? "";
                if (!string.IsNullOrEmpty(rootPath) && !rootPath.EndsWith(sep.ToString()))
                {
                    rootPath = rootPath + sep;
                }
                return rootPath.Replace('\\', sep);
            }

            // Ensure root has trailing separator
            string rootPath2 = root ?? "";
            if (!string.IsNullOrEmpty(rootPath2) && !rootPath2.EndsWith(sep.ToString()))
            {
                rootPath2 = rootPath2 + sep;
            }
            rootPath2 = rootPath2.Replace('\\', sep);

            // Check if path equals root
            string trimmedNormalized = normalized.TrimEnd(sep);
            string trimmedRoot = rootPath2.TrimEnd(sep);
            if (string.Equals(trimmedNormalized, trimmedRoot, StringComparison.OrdinalIgnoreCase))
            {
                return rootPath2;
            }

            // Find the last separator and return everything before it
            int lastIndex = trimmedNormalized.LastIndexOf(sep);

            if (lastIndex != -1)
            {
                if (lastIndex == 0) ++lastIndex;
                return trimmedNormalized.Substring(0, lastIndex);
            }

            return rootPath2;
        }

        protected override string NormalizeRelativePath(string path, string basePath)
        {
            if (string.IsNullOrEmpty(path)) return basePath ?? "";

            string drivePrefix = PSDriveInfo.Name + ":";
            string root = GetPhysicalRoot();

            // Handle physical filesystem paths - convert to logical
            if (path.StartsWith(root, StringComparison.OrdinalIgnoreCase))
            {
                string logical = ToLogicalPath(path);
                path = drivePrefix + ProviderSep + logical;
            }

            // Handle provider-qualified paths like "buckets:/../something" or "buckets:\something"
            string cleaned = path;
            int colonColon = cleaned.IndexOf("::", StringComparison.Ordinal);
            if (colonColon >= 0) cleaned = cleaned.Substring(colonColon + 2);

            // Strip drive prefix
            if (cleaned.StartsWith(drivePrefix, StringComparison.OrdinalIgnoreCase))
            {
                cleaned = cleaned.Substring(drivePrefix.Length);
            }
            cleaned = cleaned.TrimStart(ProviderSep, '/', '\\');

            // Also normalize basePath to get its relative portion
            string cleanedBase = basePath ?? "";
            int baseColonColon = cleanedBase.IndexOf("::", StringComparison.Ordinal);
            if (baseColonColon >= 0) cleanedBase = cleanedBase.Substring(baseColonColon + 2);
            if (cleanedBase.StartsWith(drivePrefix, StringComparison.OrdinalIgnoreCase))
            {
                cleanedBase = cleanedBase.Substring(drivePrefix.Length);
            }
            cleanedBase = cleanedBase.TrimStart(ProviderSep, '/', '\\');

            // Strip basePath from path to get the truly relative portion
            if (!string.IsNullOrEmpty(cleanedBase) && cleaned.StartsWith(cleanedBase, StringComparison.OrdinalIgnoreCase))
            {
                string afterBase = cleaned.Substring(cleanedBase.Length);
                cleaned = afterBase.TrimStart(ProviderSep, '/', '\\');
            }

            // Remove . and .. segments (clamp to root)
            string[] parts = cleaned.Split(new[] { ProviderSep, '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
            var stack = new System.Collections.Generic.List<string>();
            foreach (var part in parts)
            {
                if (part == "." || part == "..") continue;
                stack.Add(part);
            }

            return string.Join(ProviderSep.ToString(), stack);
        }

        #endregion

        #region Write Operations

        protected override void NewItem(string path, string itemTypeName, object newItemValue)
        {
            if (string.IsNullOrEmpty(path))
            {
                WriteError(new ErrorRecord(new ArgumentException("Path cannot be empty."),
                    "EmptyPath", ErrorCategory.InvalidArgument, path));
                return;
            }

            if (!ShouldProcess(path, "New Item")) return;

            string physical = ToPhysicalPath(path);
            bool wantContainer = string.Equals(itemTypeName, "directory", StringComparison.OrdinalIgnoreCase);

            if (physical == null)
            {
                string root = GetPhysicalRoot();
                string logical = path;

                int colonColon = logical.IndexOf("::", StringComparison.Ordinal);
                if (colonColon >= 0) logical = logical.Substring(colonColon + 2);

                string driveName = PSDriveInfo.Name + ":";
                if (logical.StartsWith(driveName, StringComparison.OrdinalIgnoreCase))
                {
                    logical = logical.Substring(driveName.Length);
                }
                logical = logical.TrimStart(Sep, '/', '\\', ' ');
                if (!string.IsNullOrEmpty(logical))
                {
                    logical = logical.Replace('/', Sep).Replace('\\', Sep);
                    physical = Path.Combine(root, logical);
                }
            }

            if (wantContainer)
            {
                if (!Directory.Exists(physical))
                {
                    Directory.CreateDirectory(physical);
                }
                WriteItemObject(new BucketItemInfo
                {
                    Name = GetChildName(path),
                    IsContainer = true,
                    ItemKind = "bucket",
                    ItemCount = 0,
                    Format = "",
                    SizeBytes = 0,
                    PhysicalPath = physical,
                    LastWriteTime = DateTime.Now,
                    CreationTime = DateTime.Now
                }, ToLogicalPath(physical), true);
            }
            else
            {
                string format = "binary";
                if (!string.IsNullOrEmpty(itemTypeName) && itemTypeName.Equals("json", StringComparison.OrdinalIgnoreCase))
                {
                    format = "json";
                }

                string ext = format == "json" ? ".json" : ".dat";
                string dir = Path.GetDirectoryName(physical);
                string key = Path.GetFileName(physical);
                string sanitizedKey = SanitizeKey(key);

                if (string.IsNullOrEmpty(sanitizedKey))
                {
                    WriteError(new ErrorRecord(new ArgumentException("Key is empty after sanitization."),
                        "EmptyKey", ErrorCategory.InvalidArgument, path));
                    return;
                }

                physical = Path.Combine(dir, sanitizedKey + ext);

                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }

                if (File.Exists(physical))
                {
                    WriteError(new ErrorRecord(new IOException($"Item already exists: {ToLogicalPath(physical)}"),
                        "ItemAlreadyExists", ErrorCategory.ResourceExists, path));
                    return;
                }

                SerializeFile(newItemValue, physical, format);
                var fi = new FileInfo(physical);
                WriteItemObject(new BucketItemInfo
                {
                    Name = sanitizedKey,
                    IsContainer = false,
                    ItemKind = "object",
                    ItemCount = 0,
                    Format = format == "json" ? "JSON" : "Binary",
                    SizeBytes = fi.Length,
                    PhysicalPath = physical,
                    LastWriteTime = fi.LastWriteTime,
                    CreationTime = fi.CreationTime
                }, ToLogicalPath(physical), false);
            }
        }

        protected override void SetItem(string path, object value)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null)
            {
                WriteError(new ErrorRecord(new ItemNotFoundException($"Item not found: {path}"),
                    "ItemNotFound", ErrorCategory.ObjectNotFound, path));
                return;
            }

            if (Directory.Exists(physical))
            {
                WriteError(new ErrorRecord(new InvalidOperationException("Cannot set value on a container."),
                    "ContainerSetError", ErrorCategory.InvalidOperation, path));
                return;
            }

            try
            {
                DeserializeFile(physical, out string format, out _);
                object existing = DeserializeFile(physical, out _, out _);
                object merged = MergeObjects(existing, value);
                SerializeFile(merged, physical, format);

                object updated = DeserializeFile(physical, out _, out _);
                var fi = new FileInfo(physical);
                WriteItemObject(new BucketObjectInfo
                {
                    LogicalPath = ToLogicalPath(physical),
                    PhysicalPath = physical,
                    Key = GetKeyName(path),
                    Bucket = GetBucketName(path),
                    Format = format,
                    Compressed = false,
                    SizeBytes = fi.Length,
                    Modified = fi.LastWriteTime,
                    Content = updated
                }, ToLogicalPath(physical), false);
            }
            catch (Exception ex)
            {
                WriteError(new ErrorRecord(new RuntimeException($"Failed to set item: {ex.Message}"),
                    "SetItemFailed", ErrorCategory.WriteError, path));
            }
        }

        private object MergeObjects(object existing, object newValue)
        {
            var existingDict = ToDictionary(existing);

            if (newValue is IDictionary newDict)
            {
                foreach (DictionaryEntry entry in newDict)
                {
                    existingDict[entry.Key?.ToString() ?? ""] = entry.Value;
                }
                return existingDict;
            }

            var psoNew = newValue as PSObject;
            if (psoNew != null)
            {
                foreach (var prop in psoNew.Properties.Where(p => p.IsGettable))
                {
                    existingDict[prop.Name] = prop.Value;
                }
                return existingDict;
            }

            return newValue;
        }

        private Dictionary<string, object> ToDictionary(object value)
        {
            var result = new Dictionary<string, object>();

            if (value is IDictionary dict)
            {
                foreach (DictionaryEntry entry in dict)
                {
                    result[entry.Key?.ToString() ?? ""] = entry.Value;
                }
                return result;
            }

            var pso = value as PSObject;
            if (pso != null)
            {
                if (pso.BaseObject is IDictionary baseDict)
                {
                    foreach (DictionaryEntry entry in baseDict)
                    {
                        result[entry.Key?.ToString() ?? ""] = entry.Value;
                    }
                    return result;
                }

                var skip = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
                {
                    "Equals", "GetHashCode", "GetType", "ToString",
                    "pstypenames", "psadapted", "psbase", "psextended", "psobject"
                };
                foreach (var prop in pso.Properties.Where(p => p.IsGettable && !skip.Contains(p.Name)))
                {
                    result[prop.Name] = prop.Value;
                }

                return result;
            }

            return result;
        }

        protected override void RemoveItem(string path, bool recurse)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null)
            {
                WriteError(new ErrorRecord(new ItemNotFoundException($"Item not found: {path}"),
                    "ItemNotFound", ErrorCategory.ObjectNotFound, path));
                return;
            }

            if (!ShouldProcess(path, "Remove Item")) return;

            if (File.Exists(physical))
            {
                File.Delete(physical);
            }
            else if (Directory.Exists(physical))
            {
                if (!IsSafeBucketDirectory(physical))
                {
                    WriteWarning($"Bucket '{path}' contains non-bucket files. Refusing to remove.");
                    return;
                }
                Directory.Delete(physical, true);
            }
        }

        private bool IsSafeBucketDirectory(string path)
        {
            var di = new DirectoryInfo(path);
            foreach (var file in di.GetFiles())
            {
                string ext = file.Extension.ToLowerInvariant();
                if (ext != ".dat" && ext != ".json") return false;
            }
            foreach (var subDir in di.GetDirectories())
            {
                if (subDir.Name == ".buckets") continue;
                if (!IsBucketDirectory(subDir.FullName))
                {
                    // Subdirectory that is not a bucket itself
                    return false;
                }
                // If it is a bucket subdirectory, that's OK - nested buckets are safe
            }
            return true;
        }

        protected override void MoveItem(string path, string destination)
        {
            string srcPhysical = ToPhysicalPath(path);
            if (srcPhysical == null)
            {
                WriteError(new ErrorRecord(new ItemNotFoundException($"Source not found: {path}"),
                    "SourceNotFound", ErrorCategory.ObjectNotFound, path));
                return;
            }

            string destPhysical = ToPhysicalPath(destination);
            if (destPhysical != null && Directory.Exists(destPhysical))
            {
                destPhysical = Path.Combine(destPhysical, Path.GetFileName(srcPhysical));
            }
            else if (destPhysical == null)
            {
                string root = GetPhysicalRoot();
                string logical = destination;
                int colonColon = logical.IndexOf("::", StringComparison.Ordinal);
                if (colonColon >= 0) logical = logical.Substring(colonColon + 2);
                string driveName = PSDriveInfo.Name + ":";
                if (logical.StartsWith(driveName, StringComparison.OrdinalIgnoreCase))
                {
                    logical = logical.Substring(driveName.Length);
                }
                logical = logical.TrimStart(Sep, '/', '\\', ' ');
                if (!string.IsNullOrEmpty(logical))
                {
                    logical = logical.Replace('/', Sep).Replace('\\', Sep);
                    destPhysical = Path.Combine(root, logical);
                }

                if (File.Exists(srcPhysical))
                {
                    string ext = Path.GetExtension(srcPhysical);
                    if (string.IsNullOrEmpty(Path.GetExtension(destPhysical)))
                    {
                        destPhysical = Path.ChangeExtension(destPhysical, ext);
                    }
                }
            }

            if (!ShouldProcess($"{path} -> {destination}", "Move Item")) return;

            string destDir = Path.GetDirectoryName(destPhysical);
            if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);

            if (File.Exists(srcPhysical)) File.Move(srcPhysical, destPhysical);
            else if (Directory.Exists(srcPhysical)) Directory.Move(srcPhysical, destPhysical);
        }

        protected override void CopyItem(string path, string destination, bool recurse)
        {
            string srcPhysical = ToPhysicalPath(path);
            if (srcPhysical == null)
            {
                WriteError(new ErrorRecord(new ItemNotFoundException($"Source not found: {path}"),
                    "SourceNotFound", ErrorCategory.ObjectNotFound, path));
                return;
            }

            string destPhysical = ToPhysicalPath(destination);
            if (destPhysical != null && Directory.Exists(destPhysical))
            {
                destPhysical = Path.Combine(destPhysical, Path.GetFileName(srcPhysical));
            }
            else if (destPhysical == null)
            {
                string root = GetPhysicalRoot();
                string logical = destination;
                int colonColon = logical.IndexOf("::", StringComparison.Ordinal);
                if (colonColon >= 0) logical = logical.Substring(colonColon + 2);
                string driveName = PSDriveInfo.Name + ":";
                if (logical.StartsWith(driveName, StringComparison.OrdinalIgnoreCase))
                {
                    logical = logical.Substring(driveName.Length);
                }
                logical = logical.TrimStart(Sep, '/', '\\', ' ');
                if (!string.IsNullOrEmpty(logical))
                {
                    logical = logical.Replace('/', Sep).Replace('\\', Sep);
                    destPhysical = Path.Combine(root, logical);
                }

                if (File.Exists(srcPhysical))
                {
                    string ext = Path.GetExtension(srcPhysical);
                    if (string.IsNullOrEmpty(Path.GetExtension(destPhysical)))
                    {
                        destPhysical = Path.ChangeExtension(destPhysical, ext);
                    }
                }
            }

            if (!ShouldProcess($"{path} -> {destination}", "Copy Item")) return;

            if (File.Exists(srcPhysical))
            {
                string destDir = Path.GetDirectoryName(destPhysical);
                if (!Directory.Exists(destDir)) Directory.CreateDirectory(destDir);
                File.Copy(srcPhysical, destPhysical, true);
            }
            else if (Directory.Exists(srcPhysical))
            {
                CopyDirectory(srcPhysical, destPhysical, recurse);
            }
        }

        private void CopyDirectory(string source, string destination, bool recurse)
        {
            if (!Directory.Exists(destination)) Directory.CreateDirectory(destination);
            var di = new DirectoryInfo(source);
            foreach (var file in di.GetFiles()) file.CopyTo(Path.Combine(destination, file.Name), true);
            if (recurse)
            {
                foreach (var subDir in di.GetDirectories())
                {
                    CopyDirectory(subDir.FullName, Path.Combine(destination, subDir.Name), recurse);
                }
            }
        }

        protected override void RenameItem(string path, string newName)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null)
            {
                WriteError(new ErrorRecord(new ItemNotFoundException($"Item not found: {path}"),
                    "ItemNotFound", ErrorCategory.ObjectNotFound, path));
                return;
            }

            string sanitized = SanitizeKey(newName);
            if (string.IsNullOrEmpty(sanitized))
            {
                WriteError(new ErrorRecord(new ArgumentException("New name is empty after sanitization."),
                    "EmptyName", ErrorCategory.InvalidArgument, newName));
                return;
            }

            if (!ShouldProcess($"{path} -> {newName}", "Rename Item")) return;

            string parent = Path.GetDirectoryName(physical);
            string newNameWithExt = File.Exists(physical) ? sanitized + Path.GetExtension(physical) : sanitized;
            string newPhysical = Path.Combine(parent, newNameWithExt);

            if (File.Exists(newPhysical) || Directory.Exists(newPhysical))
            {
                WriteError(new ErrorRecord(new IOException($"Target already exists: {newNameWithExt}"),
                    "TargetExists", ErrorCategory.ResourceExists, newName));
                return;
            }

            File.Move(physical, newPhysical);
        }

        #endregion

        #region Content Cmdlet Provider

        public IContentReader GetContentReader(string path)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null || !File.Exists(physical))
            {
                WriteError(new ErrorRecord(new ItemNotFoundException($"Object not found: {path}"),
                    "ItemNotFound", ErrorCategory.ObjectNotFound, path));
                return null;
            }

            if (Directory.Exists(physical))
            {
                WriteError(new ErrorRecord(new InvalidOperationException("Get-Content cannot be used on a bucket directory. Use Get-Item to retrieve objects."),
                    "NotAnObject", ErrorCategory.InvalidType, path));
                return null;
            }

            return new BucketContentReader(physical, this);
        }

        public IContentWriter GetContentWriter(string path)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null)
            {
                physical = ResolveNewObjectPath(path);
                if (physical == null)
                {
                    WriteError(new ErrorRecord(new ItemNotFoundException($"Cannot resolve path: {path}"),
                        "PathNotFound", ErrorCategory.ObjectNotFound, path));
                    return null;
                }
            }

            if (Directory.Exists(physical))
            {
                WriteError(new ErrorRecord(new InvalidOperationException("Set-Content cannot be used on a bucket directory."),
                    "NotAnObject", ErrorCategory.InvalidType, path));
                return null;
            }

            return new BucketContentWriter(physical, this);
        }

        public void ClearContent(string path)
        {
            string physical = ToPhysicalPath(path);
            if (physical == null || !File.Exists(physical))
            {
                WriteError(new ErrorRecord(new ItemNotFoundException($"Object not found: {path}"),
                    "ItemNotFound", ErrorCategory.ObjectNotFound, path));
                return;
            }

            if (ShouldProcess($"Clear content of {path}", "Clear-Content"))
            {
                File.Delete(physical);
            }
        }

        public object GetContentReaderDynamicParameters(string path) => null;
        public object GetContentWriterDynamicParameters(string path) => null;
        public object ClearContentDynamicParameters(string path) => null;

        private string ResolveNewObjectPath(string path)
        {
            string root = GetPhysicalRoot();

            string driveName = PSDriveInfo.Name + ":";
            if (path.StartsWith(driveName, StringComparison.OrdinalIgnoreCase))
            {
                path = path.Substring(driveName.Length);
            }

            path = path.TrimStart(Sep, '/', '\\');
            if (string.IsNullOrEmpty(path)) return null;

            string normalized = path.Replace('/', Sep).Replace('\\', Sep);
            string[] parts = normalized.Split(new[] { Sep }, StringSplitOptions.RemoveEmptyEntries);

            // Last part is the key, rest is the bucket path
            if (parts.Length < 2) return null;

            string bucketPath = root;
            for (int i = 0; i < parts.Length - 1; i++)
            {
                bucketPath = Path.Combine(bucketPath, parts[i]);
            }

            string key = parts[parts.Length - 1];
            string sanitized = SanitizeKey(key);
            if (string.IsNullOrEmpty(sanitized)) return null;

            if (!Directory.Exists(bucketPath))
            {
                Directory.CreateDirectory(bucketPath);
            }

            // Default to .dat if no extension specified
            var jsonCount = new DirectoryInfo(bucketPath).GetFiles("*.json").Length;
            var datCount = new DirectoryInfo(bucketPath).GetFiles("*.dat").Length;
            string ext = jsonCount > datCount ? ".json" : ".dat";
            sanitized = sanitized + ext;

            return Path.Combine(bucketPath, sanitized);
        }

        #endregion
    }

    public class BucketContentReader : IContentReader
    {
        private readonly string _physicalPath;
        private readonly BucketsProvider _provider;
        private bool _read;

        public BucketContentReader(string physicalPath, BucketsProvider provider)
        {
            _physicalPath = physicalPath;
            _provider = provider;
            _read = false;
        }

        public IList Read(long readCount)
        {
            if (_read) return new object[0];
            _read = true;

            try
            {
                object content = _provider.DeserializeFileInternal(_physicalPath, out _, out _);
                return new object[] { content };
            }
            catch (Exception ex)
            {
                _provider.WriteError(new ErrorRecord(
                    new RuntimeException($"Failed to read content: {ex.Message}"),
                    "ReadError", ErrorCategory.ReadError, _physicalPath));
                return new object[0];
            }
        }

        public void Seek(long offset, SeekOrigin origin)
        {
            // Object storage doesn't support seeking
        }

        public void Close()
        {
        }

        public void Dispose()
        {
        }
    }

    public class BucketContentWriter : IContentWriter
    {
        private readonly string _physicalPath;
        private readonly BucketsProvider _provider;
        private readonly System.Collections.Generic.List<object> _buffer;

        public BucketContentWriter(string physicalPath, BucketsProvider provider)
        {
            _physicalPath = physicalPath;
            _provider = provider;
            _buffer = new System.Collections.Generic.List<object>();
        }

        public IList Write(IList items)
        {
            if (items != null)
            {
                foreach (var item in items)
                {
                    _buffer.Add(item);
                }
            }
            return new object[0];
        }

        public void Seek(long offset, SeekOrigin origin)
        {
            // Object storage doesn't support seeking
        }

        public void Close()
        {
            if (_buffer.Count == 0) return;

            try
            {
                object value = _buffer.Count == 1 ? _buffer[0] : _buffer.ToArray();
                string ext = Path.GetExtension(_physicalPath).ToLowerInvariant();
                string format = ext == ".json" ? "json" : "binary";

                string dir = Path.GetDirectoryName(_physicalPath);
                if (!Directory.Exists(dir))
                {
                    Directory.CreateDirectory(dir);
                }

                _provider.SerializeFileInternal(value, _physicalPath, format);
            }
            catch (Exception ex)
            {
                _provider.WriteError(new ErrorRecord(
                    new RuntimeException($"Failed to write content: {ex.Message}"),
                    "WriteError", ErrorCategory.WriteError, _physicalPath));
            }
        }

        public void Dispose()
        {
            Close();
        }
    }
}