#pragma once
#include "analytics.h"
#include "audioclip.h"
#include "biquad.h"
#include "concurrent/bag.h"
#include "limiter.h"
#include "meter.h"
#include "mipmapworker.h"
#include "timeline.h"
#include "track.h"
#include <emscripten/val.h>
#include <emscripten/webaudio.h>

EMSCRIPTEN_DECLARE_VAL_TYPE(DSPContextPromise);
EMSCRIPTEN_DECLARE_VAL_TYPE(AudioContextValType);

struct TimelineDestroyRequest {
  TimelineDestroyRequest(std::shared_ptr<Timeline> timeline)
      : timeline(std::move(timeline)), next(nullptr) {}
  std::shared_ptr<Timeline> timeline;
  TimelineDestroyRequest *next;
};

struct LoopStateGuard {
  std::shared_ptr<Timeline> tl;
  bool prev;
  explicit LoopStateGuard(const std::shared_ptr<Timeline> &t)
      : tl(t), prev(t ? t->isLoopEnabled() : false) {
    if (tl) {
      tl->setLoopEnabled(false);
    }
  }
  ~LoopStateGuard() {
    if (tl) {
      tl->setLoopEnabled(prev);
    }
  }
};

class Bouncer final {
private:
  std::shared_ptr<BufferF32> outputBuffer;
  std::shared_ptr<Timeline> timeline;
  LoopStateGuard loopGuard;
  double end;

public:
  Bouncer(const std::shared_ptr<Timeline> &timeline, double start, double end,
          int channelCount, int bufferSize);
  std::shared_ptr<BufferF32> bounceNext();
};

class SumOnlyBouncer final {
private:
  std::shared_ptr<BufferF32> mixdownBuffer, outputBuffer;
  std::shared_ptr<Limiter> limiter;
  size_t framesProduced;
  size_t tailFramesProduced;

public:
  SumOnlyBouncer(int bufferSize, int channelCount, int sampleRate);

  void sumIntoMixdownBuffer(const std::shared_ptr<BufferF32> &buffer);

  std::shared_ptr<BufferF32> bounceNext(bool inputFinished);
};

class DSPContext final {
private:
  struct ContinueDSPContextCreate {
    std::string name;
    float vuAlpha;
    int channelCount;
    std::shared_ptr<AnalyticsObserver> analyticsObserver;
    emscripten::val resolve;
    emscripten::val reject;
  };

  struct DSPContextRoot {
    std::shared_ptr<DSPContext> instance;
  };

  static int audioContextSampleRate;
  static EMSCRIPTEN_WEBAUDIO_T audioContext;
  static bool asyncInitialized;

  static void AudioThreadInitialized(EMSCRIPTEN_WEBAUDIO_T audioContext,
                                     bool success, void *userData);

  static void CreateProcessorAsync(ContinueDSPContextCreate *continuation);

  static void AudioWorkletProcessorCreated(EMSCRIPTEN_WEBAUDIO_T audioContext,
                                           bool success, void *userData);

  static bool RunWorklet(int numInputs, const AudioSampleFrame *inputs,
                         int numOutputs, AudioSampleFrame *outputs,
                         int numParams, const AudioParamFrame *params,
                         void *userData);

  bool run(int numInputs, const AudioSampleFrame *inputs, int numOutputs,
           AudioSampleFrame *outputs);

  TimelineDestroyRequest *sync_newPlayingTimeline;
  TimelineDestroyRequest *rt_playingTimeline;
  IntrusiveBag<TimelineDestroyRequest> timelineDestroyRequests;

  void resolvePendingTimelineSwap();
  void flushDestroyRequests();

  const float vuAlpha;
  const int channelCount;
  const int sampleRate;
  const bool isOffline;

  std::shared_ptr<Limiter> limiter;
  std::shared_ptr<TimelineSharedState> timelineSharedState;

public:
  DSPContext(float vuAlpha, int channelCount, int sampleRate, bool isOffline,
             bool enableLimiter,
             const std::shared_ptr<AnalyticsObserver> &analyticsObserver);
  ~DSPContext();

  static DSPContextPromise
  create(const std::string &name, float vuAlpha,
         const std::shared_ptr<AnalyticsObserver> &analyticsObserver,
         std::optional<int> channelCount);
  static DSPContextPromise createOffline(const std::string &name, float vuAlpha,
                                         int channelCount, int sampleRate,
                                         bool enableLimiter);

  std::shared_ptr<Timeline> createTimeline(
      const std::shared_ptr<time_transform::TimelineTempoMap> &timing,
      const std::vector<std::shared_ptr<Track>> &tracks);
  std::shared_ptr<Track>
  createTrack(float gain, float pan, bool muted,
              const std::shared_ptr<Meter> &meter,
              const std::shared_ptr<FilterChain> &filterChain,
              const std::vector<std::shared_ptr<AudioClip>> &clips);
  std::shared_ptr<AudioClip> createAudioClip(
      std::shared_ptr<RandomAccessAudioReadable> underlyingReadable,
      std::shared_ptr<time_transform::WarpMap<double>> warpMap, float gain,
      double timelineStartBeats, double timelineEndBeats, double loopStartBeats,
      double loopEndBeats, double readStartBeats, double fadeInBeats,
      double fadeInExponent, double fadeOutBeats, double fadeOutExponent,
      float transposition, double warpedContentBps, bool warpEnabled,
      const std::string &clipId);
  std::shared_ptr<Meter> createMeter();
  std::shared_ptr<FilterChain> createFilterChain(int filterCount);

  void swapLiveTimeline(const std::shared_ptr<Timeline> &newTimeline);

  std::shared_ptr<Bouncer> createBouncer(double start, double end,
                                         int bufferSize);
  std::shared_ptr<SumOnlyBouncer> createSumOnlyBouncer(int bufferSize);

  AudioContextValType getAudioContext();

  std::vector<std::vector<std::shared_ptr<AudioClip>>>
  assignClipsToGroups(const std::vector<std::shared_ptr<AudioClip>> &allClips);

  const std::shared_ptr<Meter> meter;
  const std::shared_ptr<AnalyticsObserver> analyticsObserver;
  const std::shared_ptr<MipMapWorker> mipMapWorker;

  void setLimiterParams(float lookaheadSeconds, float attackSeconds,
                        float releaseSeconds, float preGainDb, bool bypass,
                        float stereoLink);
};
