import { Arrays, isDefined, Progress, tryCatch, UUID } from "@opendaw/lib-std";
import { AudioData, estimateBpm } from "@opendaw/lib-dsp";
import { Promises } from "@opendaw/lib-runtime";
import { SamplePeaks } from "@opendaw/lib-fusion";
import { AudioFileBox } from "@opendaw/studio-boxes";
import { AssetService } from "../AssetService";
import { FilePickerAcceptTypes } from "../FilePickerAcceptTypes";
import { WavFile } from "@opendaw/lib-dsp";
import { Workers } from "../Workers";
import { SampleStorage } from "./SampleStorage";
import { OpenSampleAPI } from "./OpenSampleAPI";
export class SampleService extends AssetService {
    audioContext;
    namePlural = "Samples";
    nameSingular = "Sample";
    boxType = AudioFileBox;
    filePickerOptions = FilePickerAcceptTypes.WavFiles;
    constructor(audioContext) {
        super();
        this.audioContext = audioContext;
    }
    async importRecording(audioData, bpm, name = "Recording") {
        const arrayBuffer = WavFile.encodeFloats({
            frames: audioData.frames.slice(),
            numberOfFrames: audioData.numberOfFrames,
            numberOfChannels: audioData.numberOfChannels,
            sampleRate: audioData.sampleRate
        });
        return this.importFile({ name, bpm, arrayBuffer, origin: "recording" });
    }
    async importFile({ uuid, name, bpm, arrayBuffer, progressHandler = Progress.Empty, origin = "import" }, transformMeta) {
        console.debug(`importSample '${name}' (${arrayBuffer.byteLength >> 10}kb)`);
        uuid ??= await UUID.sha256(arrayBuffer);
        const audioData = await this.#decodeAudio(arrayBuffer);
        const duration = audioData.numberOfFrames / audioData.sampleRate;
        const shifts = SamplePeaks.findBestFit(audioData.numberOfFrames);
        const peaks = await Workers.Peak.generateAsync(progressHandler, shifts, audioData.frames, audioData.numberOfFrames, audioData.numberOfChannels);
        const meta = {
            bpm: bpm ?? estimateBpm(duration),
            name: name ?? "Unnnamed",
            duration,
            sample_rate: audioData.sampleRate,
            origin
        };
        if (isDefined(transformMeta)) {
            await transformMeta(meta, audioData);
        }
        const sample = { uuid: UUID.toString(uuid), ...meta };
        await SampleStorage.get().save({ uuid, audio: audioData, peaks, meta });
        this.notifier.notify([sample, audioData]);
        return sample;
    }
    async collectAllFiles() {
        const stock = await OpenSampleAPI.get().all();
        const local = await SampleStorage.get().list();
        return Arrays.merge(stock, local, (sample, { uuid }) => sample.uuid === uuid);
    }
    async #decodeAudio(arrayBuffer) {
        const wavResult = tryCatch(() => WavFile.decodeFloats(arrayBuffer));
        if (wavResult.status === "success") {
            return wavResult.value;
        }
        console.debug("decoding with web-api-api (fallback)");
        const { status, value: audioBuffer } = await Promises.tryCatch(this.audioContext.decodeAudioData(arrayBuffer));
        if (status === "rejected") {
            return Promise.reject();
        }
        const audioData = AudioData.create(audioBuffer.sampleRate, audioBuffer.length, audioBuffer.numberOfChannels);
        for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
            audioData.frames[channel].set(audioBuffer.getChannelData(channel));
        }
        return audioData;
    }
}
