#pragma once
#include "buffer.h"
#include "randomaccessaudioreadable.h"
#include <emscripten/val.h>
#include <memory>
#include <optional>
#include <vector>

struct FfmpegState;

struct FfmpegDataSource {
  virtual ~FfmpegDataSource() = default;
  virtual size_t size() = 0;
  virtual size_t read(size_t offset, size_t count, uint8_t *out) = 0;
  virtual bool isCompleted() const = 0;
};

EMSCRIPTEN_DECLARE_VAL_TYPE(FfmpegAudioBufferPromise);
EMSCRIPTEN_DECLARE_VAL_TYPE(FfmpegByteArrayType);
EMSCRIPTEN_DECLARE_VAL_TYPE(FfmpegReadCallbackType);
EMSCRIPTEN_DECLARE_VAL_TYPE(FfmpegCloseCallbackType);

class FfmpegAudioBuffer final : public RandomAccessAudioReadable {
  int token;
  int nextFrameOffset;
  float streamReadOffset;
  bool createdOnMainThread;
  std::shared_ptr<FfmpegDataSource> data;
  std::unique_ptr<FfmpegState> ffmpegState;
  emscripten::val completedBlankPromise;

  void reset();

  friend class FfmpegLibrary;

public:
  FfmpegAudioBuffer(int token, float streamReadOffset = 0.0f);
  virtual ~FfmpegAudioBuffer();

  static std::shared_ptr<FfmpegAudioBuffer>
  createFromUrl(const std::string &url,
                const std::optional<std::string> &uuid = std::nullopt);
  static std::shared_ptr<FfmpegAudioBuffer>
  createFromUrlStreaming(const std::string &url,
                         const std::optional<std::string> &uuid = std::nullopt,
                         std::optional<float> streamReadOffset = std::nullopt);
  static std::shared_ptr<FfmpegAudioBuffer>
  createFromUuid(const std::string &uuid);
  static std::shared_ptr<FfmpegAudioBuffer>
  createFromByteArray(const FfmpegByteArrayType &source,
                      const std::optional<std::string> &uuid = std::nullopt);
  static std::shared_ptr<FfmpegAudioBuffer>
  createFromByteVector(const std::vector<uint8_t> &source);
  static std::shared_ptr<FfmpegAudioBuffer>
  createFromReadCallback(size_t size, FfmpegReadCallbackType readCallback,
                         FfmpegCloseCallbackType closeCallback);
  static bool addUuidAlias(const std::string &existingUuid,
                          const std::string &newUuid);

  void setData(const std::shared_ptr<FfmpegDataSource> &data);
  void resolvePromise(const std::shared_ptr<FfmpegAudioBuffer> &buffer);

  FfmpegAudioBufferPromise getCompletedPromise() const;

  virtual void read(int frameOffset,
                    std::shared_ptr<BufferF32> output) override {
    auto outputAsSlice = output->slice(0, output->getFrameCount());
    read(frameOffset, outputAsSlice);
  }

  virtual void read(int frameOffset, BufferF32 output) override;

  virtual int getSampleRate() const override;
  virtual int getChannelCount() const override;
  virtual int getFrameCount() const override;

  int getNextFrameOffset() const { return nextFrameOffset; }
  bool isCompleted() const;
  float getAvailableDuration() const;

  std::shared_ptr<RandomAccessAudioReadable> clone() const override {
    return createClone();
  }

  std::shared_ptr<FfmpegAudioBuffer> createClone() const;
};
