/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (C) 2025 Raspberry Pi Ltd
 */

#ifndef FILE_OPERATIONS_MACOS_H_
#define FILE_OPERATIONS_MACOS_H_

#include "../file_operations.h"
#include <dispatch/dispatch.h>
#include <atomic>
#include <chrono>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <map>

namespace rpi_imager {

// macOS implementation using POSIX file operations with optional GCD async I/O
class MacOSFileOperations : public FileOperations {
 public:
  MacOSFileOperations();
  ~MacOSFileOperations() override;

  // Non-copyable, non-movable (due to dispatch resources)
  MacOSFileOperations(const MacOSFileOperations&) = delete;
  MacOSFileOperations& operator=(const MacOSFileOperations&) = delete;
  MacOSFileOperations(MacOSFileOperations&&) = delete;
  MacOSFileOperations& operator=(MacOSFileOperations&&) = delete;

  FileError OpenDevice(const std::string& path) override;
  FileError CreateTestFile(const std::string& path, std::uint64_t size) override;
  FileError WriteAtOffset(
      std::uint64_t offset,
      const std::uint8_t* data,
      std::size_t size) override;
  FileError GetSize(std::uint64_t& size) override;
  FileError Close() override;
  bool IsOpen() const override;

  // Streaming I/O operations
  FileError WriteSequential(const std::uint8_t* data, std::size_t size) override;
  FileError ReadSequential(std::uint8_t* data, std::size_t size, std::size_t& bytes_read) override;
  
  // File positioning
  FileError Seek(std::uint64_t position) override;
  std::uint64_t Tell() const override;
  
  // Sync operations
  FileError ForceSync() override;
  FileError Flush() override;
  
  // Sequential read optimization
  void PrepareForSequentialRead(std::uint64_t offset, std::uint64_t length) override;
  
  // Handle access
  int GetHandle() const override;

  // Get the last errno error code
  int GetLastErrorCode() const override;

  // Check if direct I/O is enabled
  bool IsDirectIOEnabled() const override { return using_direct_io_; }
  
  // Enable or disable direct I/O
  FileError SetDirectIOEnabled(bool enabled) override;
  
  // Get direct I/O attempt details (macOS: F_NOCACHE always attempted)
  DirectIOInfo GetDirectIOInfo() const override { 
    DirectIOInfo info;
    info.attempted = true;
    info.succeeded = using_direct_io_;
    info.currently_enabled = using_direct_io_;
    return info;
  }
  
  // ============= Async I/O API (macOS: using GCD dispatch_io) =============
  bool SetAsyncQueueDepth(int depth) override;
  int GetAsyncQueueDepth() const override { return async_queue_depth_; }
  bool IsAsyncIOSupported() const override { return true; }
  FileError AsyncWriteSequential(const std::uint8_t* data, std::size_t size, 
                                  AsyncWriteCallback callback = nullptr) override;
  int GetPendingWriteCount() const override { return pending_writes_.load(); }
  void PollAsyncCompletions() override;
  FileError WaitForPendingWrites() override;
  void CancelAsyncIO() override;
  std::vector<PendingWriteInfo> GetPendingWritesSorted() const override;
  void ReduceQueueDepthForRecovery(int newDepth) override;
  // GetAsyncIOStats() inherited from FileOperations base class

 private:
  int fd_;
  std::string current_path_;
  int last_error_code_;
  bool using_direct_io_;  // Track if we're using F_NOCACHE
  
  // Async I/O state
  int async_queue_depth_;
  std::atomic<int> pending_writes_;
  std::atomic<bool> cancelled_;
  std::atomic<FileError> first_async_error_;
  dispatch_queue_t async_queue_;
  dispatch_semaphore_t queue_semaphore_;  // Limits in-flight writes
  std::mutex completion_mutex_;
  std::condition_variable completion_cv_;
  std::uint64_t async_write_offset_;  // Current write position for async
  
  // Tracking for sync fallback: map write_id -> pending write info
  struct PendingAsyncWrite {
    std::uint64_t offset;
    const std::uint8_t* data;
    std::size_t size;
    AsyncWriteCallback callback;
    std::chrono::steady_clock::time_point submit_time;
  };
  std::uint64_t next_write_id_;
  mutable std::mutex pending_writes_mutex_;
  std::map<std::uint64_t, PendingAsyncWrite> pending_writes_map_;

  // ---- Alignment fix-up for /dev/rdisk* I/O ----------------------------
  //
  // macOS raw block devices (/dev/rdisk*) reject pwrite()/read()/pread()
  // with EINVAL when the length isn't a multiple of the logical block
  // size. libarchive's zstd decompressor in particular emits chunks of
  // arbitrary length; without intervention the write fails mid-stream
  // ("Error writing to device", typically partway through a .img.zst).
  //
  // For writes we coalesce unaligned residue into a per-instance tail
  // buffer so emitted pwrites are always aligned; the trailing partial
  // block is committed via read-modify-write on Flush/ForceSync/Close.
  // For reads we round the syscall length up to the next block, then
  // only return the originally-requested bytes to the caller.
  std::uint32_t logical_block_size_ = 512;
  mutable std::mutex tail_mutex_;
  std::vector<std::uint8_t> tail_bytes_;   // 0..(BS-1) bytes
  std::uint64_t tail_offset_ = 0;          // device offset where tail belongs

  // Aligned write: takes the alignment lock, prepends any contiguous
  // tail to (data,size), pwrites the block-aligned portion at offset,
  // stashes the new residue. Returns the logical bytes committed
  // (== size on success, regardless of how it was decomposed) or -1
  // with errno on the underlying pwrite failure.
  ssize_t PwriteAligned(const std::uint8_t* data, std::size_t size,
                          std::uint64_t offset);
  // Flush any pending tail via read-modify-write. Called from Flush /
  // ForceSync / Close. 0 on success, -1 with errno on I/O failure.
  int FlushAlignTail();

  FileError OpenInternal(const char* path, int flags, mode_t mode = 0);
  
  // Helper to determine if path is a block device
  static bool IsBlockDevicePath(const std::string& path);
  
  // Enable direct I/O mode using F_NOCACHE
  bool EnableDirectIO();
  
  // Initialize async I/O resources
  void InitAsyncIO();
  
  // Cleanup async I/O resources
  void CleanupAsyncIO();
  
  // Attempt sync fallback when async I/O stalls
  FileError AttemptSyncFallback() override;
  bool DrainAndSwitchToSync(int timeoutSeconds) override;
  
  // Note: write_latency_stats_ is inherited from FileOperations base class
};

} // namespace rpi_imager

#endif // FILE_OPERATIONS_MACOS_H_ 