import { Notifier, Option, panic, Terminable, Terminator, UUID } from "@opendaw/lib-std";
import { AudioData } from "@opendaw/lib-dsp";
import { mergeChunkPlanes, RingBuffer } from "@opendaw/studio-adapters";
import { RenderQuantum } from "./RenderQuantum";
import { PeaksWriter } from "./PeaksWriter";
export class RecordingWorklet extends AudioWorkletNode {
    #terminator = new Terminator();
    uuid = UUID.generate();
    #output;
    #notifier;
    #reader;
    #peakWriter;
    #data = Option.None;
    #peaks = Option.None;
    #isRecording = true;
    #limitSamples = Number.POSITIVE_INFINITY;
    #state = { type: "record" };
    #onSaved = Option.None;
    #sampleService = Option.None;
    #bpm = Option.None;
    constructor(context, config) {
        super(context, "recording-processor", {
            numberOfInputs: 1,
            channelCount: config.numberOfChannels,
            channelCountMode: "explicit",
            processorOptions: config
        });
        this.#peakWriter = new PeaksWriter(config.numberOfChannels);
        this.#peaks = Option.wrap(this.#peakWriter);
        this.#output = [];
        this.#notifier = new Notifier();
        this.#reader = RingBuffer.reader(config, array => {
            if (this.#isRecording) {
                this.#output.push(array);
                this.#peakWriter.append(array);
                if (this.numberOfFrames >= this.#limitSamples) {
                    this.#finalize().catch(error => console.warn(error));
                }
            }
        });
    }
    own(terminable) { return this.#terminator.own(terminable); }
    limit(count) {
        this.#limitSamples = count;
        if (this.numberOfFrames >= this.#limitSamples) {
            this.#finalize().catch(error => console.warn(error));
        }
    }
    set onSaved(callback) { this.#onSaved = Option.wrap(callback); }
    set bpm(value) { this.#bpm = Option.wrap(value); }
    set sampleService(service) { this.#sampleService = Option.wrap(service); }
    setFillLength(value) { this.#peakWriter.numFrames = value; }
    get numberOfFrames() { return this.#output.length * RenderQuantum; }
    get data() { return this.#data; }
    get peaks() { return this.#peaks.isEmpty() ? Option.wrap(this.#peakWriter) : this.#peaks; }
    get state() { return this.#state; }
    invalidate() { }
    subscribe(observer) {
        if (this.#state.type === "loaded") {
            observer(this.#state);
            return Terminable.Empty;
        }
        return this.#notifier.subscribe(observer);
    }
    terminate() {
        this.#reader.stop();
        this.#isRecording = false;
        this.#terminator.terminate();
    }
    toString() { return `{RecordingWorklet}`; }
    async #finalize() {
        this.#isRecording = false;
        this.#reader.stop();
        if (this.#output.length === 0) {
            return panic("No recording data available");
        }
        const totalSamples = this.#limitSamples;
        const mergedFrames = mergeChunkPlanes(this.#output, RenderQuantum, this.#output.length * RenderQuantum)
            .map(frame => frame.slice(-totalSamples));
        const audioData = AudioData.create(this.context.sampleRate, totalSamples, this.channelCount);
        mergedFrames.forEach((frame, index) => audioData.frames[index].set(frame));
        this.#data = Option.wrap(audioData);
        const sample = await this.#sampleService
            .unwrap("SampleService not set")
            .importRecording(audioData, this.#bpm.unwrapOrElse(120));
        this.#onSaved.ifSome(callback => callback(UUID.parse(sample.uuid)));
        this.#setState({ type: "loaded" });
        this.terminate();
    }
    #setState(value) {
        this.#state = value;
        this.#notifier.notify(this.#state);
    }
}
