#pragma once

#include <Arduino.h>
#include <SD.h>
#include <SPI.h>
#include <Wire.h>
#include <XPowersLib.h>
#include "tbeam_supreme_adapter.h"

namespace tbeam {

enum class SdState : uint8_t {
  UNKNOWN = 0,
  ABSENT,
  MOUNTED
};

enum class SdEvent : uint8_t {
  NO_CARD = 0,
  CARD_MOUNTED,
  CARD_REMOVED
};

using SdEventCallback = void (*)(SdEvent event, const char* message);

struct StorageConfig {
  const char* logDir = "/logs";
  bool enableSdRailCycle = true;
  bool enablePinDumps = false;
  bool recoveryRailCycleOnFullScan = true;
  uint32_t recoveryRailOffMs = 250;
  uint32_t recoveryRailOnSettleMs = 700;
  uint32_t startupWarmupMs = 1500;
  uint32_t pollIntervalAbsentMs = 1000;
  uint32_t pollIntervalMountedMs = 2000;
  uint32_t fullScanIntervalMs = 10000;
  uint32_t defaultFlushIntervalMs = 2000;
  uint8_t votesToPresent = 2;
  uint8_t votesToAbsent = 5;
};

class TBeamStorage {
 public:
  explicit TBeamStorage(Print& diagnostic = Serial);

  bool begin(const StorageConfig& config = StorageConfig{}, SdEventCallback callback = nullptr);
  void update();

  bool ready() const { return state_ == SdState::MOUNTED; }
  bool mounted() const { return ready(); }
  SdState state() const { return state_; }
  const char* lastError() const { return lastError_; }
  const char* logDir() const { return config_.logDir; }

  bool consumeMountedEvent();
  bool consumeRemovedEvent();
  bool forceRemount();

  bool ensureDirRecursive(const char* path);
  bool makeUniqueLogPath(const char* prefix, const char* extension, char* out, size_t outSize);
  bool openLog(const char* path);
  bool isLogOpen() const { return (bool)logFile_; }
  const char* currentLogPath() const { return currentLogPath_; }
  size_t write(const uint8_t* data, size_t len);
  size_t print(const char* text);
  size_t println(const char* text);
  bool flush();
  void closeLog();

  void listFiles(Print& out, const char* path = "/", uint8_t depth = 4);
  bool removeFile(const char* path);

 private:
  void logf(const char* fmt, ...);
  void notify(SdEvent event, const char* message);
  void setError(const char* message);
  void clearError();
  void forceSpiDeselected();
  void dumpSdPins(const char* tag);
  bool initPmuForSdPower();
  void cycleSdRail(uint32_t offMs, uint32_t onSettleMs);
  bool tryMountWithBus(SPIClass& bus, const char* busName, uint32_t hz, bool verbose);
  bool mountPreferred(bool verbose);
  bool mountCardFullScan();
  bool verifyMountedCard();
  void setStateMounted();
  void setStateAbsent();
  void listFilesRecursive(Print& out, File& dir, const char* parentPath, uint8_t depth);
  bool normalizePath(const char* input, char* out, size_t outSize) const;

  Print& diagnostic_;
  StorageConfig config_{};
  SdEventCallback callback_ = nullptr;

  SPIClass sdSpiH_{HSPI};
  SPIClass sdSpiF_{FSPI};
  SPIClass* sdSpi_ = nullptr;
  XPowersLibInterface* pmu_ = nullptr;

  SdState state_ = SdState::UNKNOWN;
  uint8_t presentVotes_ = 0;
  uint8_t absentVotes_ = 0;
  uint32_t lastPollMs_ = 0;
  uint32_t lastFullScanMs_ = 0;
  uint32_t logSeq_ = 0;
  bool mountedEventPending_ = false;
  bool removedEventPending_ = false;

  File logFile_;
  char currentLogPath_[128] = {};
  char lastError_[160] = {};
};

}  // namespace tbeam
