import { GrooveShuffleBox } from "@opendaw/studio-boxes";
import { asInstanceOf, UUID } from "@opendaw/lib-std";
import { migrateAudioClipBox, migrateAudioFileBox, migrateAudioRegionBox, migrateAudioUnitBox, migrateDelayDeviceBox, migrateMIDIOutputDeviceBox, migrateNeuralAmpDeviceBox, migrateRevampDeviceBox, migrateTimelineBox, migrateValueEventBox, migrateValueEventCollection, migrateVaporisateurDeviceBox, migrateZeitgeistDeviceBox } from "./migration";
export class ProjectMigration {
    static async migrate(env, { boxGraph, mandatoryBoxes }) {
        const { rootBox, timelineBox: { bpm } } = mandatoryBoxes;
        console.debug("migrate project from", rootBox.created.getValue());
        if (rootBox.groove.targetAddress.isEmpty()) {
            console.debug("Migrate to global GrooveShuffleBox");
            boxGraph.beginTransaction();
            rootBox.groove.refer(GrooveShuffleBox.create(boxGraph, UUID.generate()));
            boxGraph.endTransaction();
        }
        const globalShuffle = asInstanceOf(rootBox.groove.targetVertex.unwrap(), GrooveShuffleBox).label;
        if (globalShuffle.getValue() !== "Groove Shuffle") {
            boxGraph.beginTransaction();
            globalShuffle.setValue("Groove Shuffle");
            boxGraph.endTransaction();
        }
        const loadAudioData = (uuid) => {
            const { promise, resolve, reject } = Promise.withResolvers();
            const loader = env.sampleManager.getOrCreate(uuid);
            let subscription;
            subscription = loader.subscribe(state => {
                if (state.type === "loaded") {
                    queueMicrotask(() => subscription.terminate());
                    resolve(loader.data.unwrap("State mismatch"));
                }
                else if (state.type === "error") {
                    queueMicrotask(() => subscription.terminate());
                    reject(new Error(state.reason));
                }
            });
            return promise;
        };
        const orphans = boxGraph.findOrphans(rootBox);
        if (orphans.length > 0) {
            console.debug("Migrate remove orphaned boxes: ", orphans.length);
            boxGraph.beginTransaction();
            orphans.forEach(orphan => orphan.delete());
            boxGraph.endTransaction();
        }
        const grooveTarget = rootBox.groove.targetVertex.unwrap();
        const outputMidiDevices = rootBox.outputMidiDevices;
        const bpmValue = bpm.getValue();
        // 1st pass (2nd pass might rely on those changes)
        for (const box of boxGraph.boxes()) {
            await box.accept({
                visitAudioFileBox: (box) => migrateAudioFileBox(boxGraph, box, loadAudioData),
                visitNeuralAmpDeviceBox: (box) => migrateNeuralAmpDeviceBox(boxGraph, box)
            });
        }
        // 2nd pass. We need to run on a copy, because we might add more boxes during the migration
        boxGraph.boxes().slice().forEach(box => box.accept({
            visitAudioRegionBox: (box) => migrateAudioRegionBox(boxGraph, box, bpmValue),
            visitAudioClipBox: (box) => migrateAudioClipBox(boxGraph, box),
            visitTimelineBox: (box) => migrateTimelineBox(boxGraph, box),
            visitMIDIOutputDeviceBox: (box) => migrateMIDIOutputDeviceBox(boxGraph, box, outputMidiDevices),
            visitZeitgeistDeviceBox: (box) => migrateZeitgeistDeviceBox(boxGraph, box, grooveTarget),
            visitValueEventBox: (box) => migrateValueEventBox(boxGraph, box),
            visitAudioUnitBox: (box) => migrateAudioUnitBox(boxGraph, box),
            visitRevampDeviceBox: (box) => migrateRevampDeviceBox(boxGraph, box),
            visitVaporisateurDeviceBox: (box) => migrateVaporisateurDeviceBox(boxGraph, box),
            visitValueEventCollectionBox: (box) => migrateValueEventCollection(boxGraph, box),
            visitDelayDeviceBox: (box) => migrateDelayDeviceBox(boxGraph, box)
        }));
    }
}
