#pragma once

// fs card arduino implementation.

// IWYU pragma: begin_keep
#include <SPI.h>
// IWYU pragma: end_keep
#ifdef USE_SDFAT
// IWYU pragma: begin_keep
#include <SdFat.h>
// IWYU pragma: end_keep
#else
// IWYU pragma: begin_keep
#include <SD.h>
// IWYU pragma: end_keep
#endif


#include "fl/stl/memory.h"
#include "fl/system/file_system.h"
#include "fl/stl/type_traits.h"
#include "fl/stl/noexcept.h"

namespace fl {

// Forward declare the smart pointer type for FsArduino
class FsArduino;
FASTLED_SHARED_PTR(FsArduino);

#ifdef USE_SDFAT
class SdFatFileHandle : public filebuf {
private:
    SdFile _file;
    string _path;

public:
    SdFatFileHandle(SdFile file, const char* path) : _file(fl::move(file)), _path(path) {}
    ~SdFatFileHandle() override {
        if (_file.isOpen()) {
            _file.close();
        }
    }

    bool is_open() const FL_NOEXCEPT override {
        return _file.isOpen();
    }
    bool available() const FL_NOEXCEPT override {
        auto f = const_cast<SdFatFileHandle*>(this);
        return f->_file.available() > 0;
    }
    fl::size_t size() const FL_NOEXCEPT override {
        return _file.fileSize();
    }
    fl::size_t read(char *dst, fl::size_t bytesToRead) FL_NOEXCEPT override {
        return _file.read(reinterpret_cast<u8*>(dst), bytesToRead); // ok reinterpret cast
    }
    using filebuf::read;
    fl::size_t write(const char *data, fl::size_t count) FL_NOEXCEPT override {
        (void)data; (void)count;
        return 0;
    }
    fl::size_t tell() FL_NOEXCEPT override {
        return _file.curPosition();
    }
    const char* path() const FL_NOEXCEPT override {
        return _path.c_str();
    }
    bool seek(fl::size_t pos, fl::seek_dir dir) FL_NOEXCEPT override {
        if (dir == fl::seek_dir::beg) return _file.seekSet(pos);
        if (dir == fl::seek_dir::cur) return _file.seekCur(pos);
        return _file.seekEnd(pos);
    }
    using filebuf::seek;
    void close() FL_NOEXCEPT override {
        if (_file.isOpen()) {
            _file.close();
        }
    }
    bool is_eof() const FL_NOEXCEPT override {
        auto f = const_cast<SdFatFileHandle*>(this);
        return f->_file.available() <= 0;
    }
    bool has_error() const override { return false; }
    void clear_error() override {}
    int error_code() const override { return 0; }
    const char* error_message() const override { return "No error"; }
};
#else
class SDFileHandle : public filebuf {
private:
    File _file;
    string _path;

public:
    SDFileHandle(File file, const char* path) : _file(file), _path(path) {}
    ~SDFileHandle() override {
        if (_file) {
            _file.close();
        }
    }

    bool is_open() const FL_NOEXCEPT override {
        auto f = const_cast<File&>(_file);
        return f;
    }
    bool available() const FL_NOEXCEPT override {
        auto f = const_cast<File&>(_file);
        return f.available() > 0;
    }
    fl::size_t size() const FL_NOEXCEPT override {
        auto f = const_cast<File&>(_file);
        return f.size();
    }
    fl::size_t read(char *dst, fl::size_t bytesToRead) FL_NOEXCEPT override {
        return _file.read(reinterpret_cast<u8*>(dst), bytesToRead); // ok reinterpret cast
    }
    using filebuf::read;
    fl::size_t write(const char *data, fl::size_t count) FL_NOEXCEPT override {
        (void)data; (void)count;
        return 0;
    }
    fl::size_t tell() FL_NOEXCEPT override {
        auto f = const_cast<File&>(_file);
        return f.position();
    }
    const char* path() const FL_NOEXCEPT override {
        return _path.c_str();
    }
    bool seek(fl::size_t pos, fl::seek_dir dir) FL_NOEXCEPT override {
        if (dir == fl::seek_dir::beg) return _file.seek(pos);
        // Arduino SD doesn't support cur/end natively
        if (dir == fl::seek_dir::cur) {
            auto f = const_cast<File&>(_file);
            return _file.seek(f.position() + pos);
        }
        auto f = const_cast<File&>(_file);
        return _file.seek(f.size() + pos);
    }
    using filebuf::seek;
    void close() FL_NOEXCEPT override {
        if (_file) {
            _file.close();
        }
    }
    bool is_eof() const FL_NOEXCEPT override {
        auto f = const_cast<File&>(_file);
        return f.available() <= 0;
    }
    bool has_error() const override { return false; }
    void clear_error() override {}
    int error_code() const override { return 0; }
    const char* error_message() const override { return "No error"; }
};
#endif

class FsArduino : public FsImpl {
private:
    int _cs_pin;
#ifdef USE_SDFAT
    SdFat _sd;
#endif

public:
    FsArduino(int cs_pin) : _cs_pin(cs_pin) {}

    bool begin() FL_NOEXCEPT override {
#ifdef USE_SDFAT
        // Use the CS pin provided in constructor
        return _sd.begin(_cs_pin, SPI_HALF_SPEED);
#else
        return SD.begin(_cs_pin);
#endif
    }

    void end() FL_NOEXCEPT override {
        // SD library doesn't have an end() method, but we can ensure files are closed
        // Note: This is a limitation of the Arduino SD library
    }

    filebuf_ptr openRead(const char *name) FL_NOEXCEPT override {
#ifdef USE_SDFAT
        SdFile file;
        // Open file for reading
        if (!file.open(name, O_READ)) {
            return filebuf_ptr();
        }
        return fl::make_shared<SdFatFileHandle>(fl::move(file), name);
#else
        File file = SD.open(name, FILE_READ);
        if (!file) {
            return filebuf_ptr();
        }
        return fl::make_shared<SDFileHandle>(fl::move(file), name);
#endif
    }

};

// Implementation of the factory function to create SD card filesystem
inline FsImplPtr make_sdcard_filesystem(int cs_pin) FL_NOEXCEPT {
    return fl::make_shared<FsArduino>(cs_pin);
}

} // namespace fl
