import { ByteArrayInput, ByteArrayOutput, Option, UUID } from "@opendaw/lib-std";
import { BoxGraph, PointerField } from "@opendaw/lib-box";
import { BoxIO } from "@opendaw/studio-boxes";
export var ClipboardUtils;
(function (ClipboardUtils) {
    ClipboardUtils.extractMetadata = (data) => {
        const input = new ByteArrayInput(data);
        const metadataLength = input.readInt();
        const metadataBytes = new Int8Array(metadataLength);
        input.readBytes(metadataBytes);
        return metadataBytes.buffer;
    };
    ClipboardUtils.serializeBoxes = (boxes, metadata = new ArrayBuffer(0)) => {
        const uniqueBoxes = UUID.newSet(box => box.address.uuid);
        boxes.forEach(box => uniqueBoxes.add(box));
        const typeCounts = new Map();
        uniqueBoxes.forEach(box => typeCounts.set(box.name, (typeCounts.get(box.name) ?? 0) + 1));
        const types = [...typeCounts.entries()].map(([type, count]) => `${type}: ${count}`).join(", ");
        const clipboardGraph = new BoxGraph(Option.wrap(BoxIO.create));
        clipboardGraph.beginTransaction();
        uniqueBoxes.forEach(sourceBox => {
            const input = new ByteArrayInput(sourceBox.toArrayBuffer());
            clipboardGraph.createBox(sourceBox.name, sourceBox.address.uuid, box => box.read(input));
        });
        clipboardGraph.endTransaction();
        const graphData = clipboardGraph.toArrayBuffer();
        const output = ByteArrayOutput.create();
        output.writeInt(metadata.byteLength);
        output.writeBytes(new Int8Array(metadata));
        output.writeInt(graphData.byteLength);
        output.writeBytes(new Int8Array(graphData));
        const arrayBuffer = output.toArrayBuffer();
        console.debug(`Clipboard copy (${arrayBuffer.byteLength >> 10}kB): ${types}`);
        return arrayBuffer;
    };
    ClipboardUtils.deserializeBoxes = (data, targetGraph, options) => {
        const input = new ByteArrayInput(data);
        input.skip(input.readInt());
        const graphDataLength = input.readInt();
        const graphData = new Int8Array(graphDataLength);
        input.readBytes(graphData);
        const clipboardGraph = new BoxGraph(Option.wrap(BoxIO.create));
        clipboardGraph.fromArrayBuffer(graphData.buffer, false);
        const skippedExternalUuids = UUID.newSet(uuid => uuid);
        clipboardGraph.boxes().forEach(box => {
            if (box.resource === "preserved" && targetGraph.findBox(box.address.uuid).nonEmpty()) {
                skippedExternalUuids.add(box.address.uuid);
            }
        });
        const shouldSkipBox = (box) => {
            if (options.excludeBox?.(box)) {
                return true;
            }
            if (box.resource === "preserved" && skippedExternalUuids.hasKey(box.address.uuid)) {
                return true;
            }
            for (const [, targetAddress] of box.outgoingEdges()) {
                if (skippedExternalUuids.hasKey(targetAddress.uuid) && !targetAddress.isBox()) {
                    return true;
                }
            }
            return false;
        };
        const sourceBoxes = clipboardGraph.boxes().filter(box => !shouldSkipBox(box));
        const typeCounts = new Map();
        sourceBoxes.forEach(box => typeCounts.set(box.name, (typeCounts.get(box.name) ?? 0) + 1));
        console.debug("Clipboard paste:", [...typeCounts.entries()].map(([type, count]) => `${type}: ${count}`).join(", "));
        const uuidMap = UUID.newSet(({ source }) => source);
        sourceBoxes.forEach(box => {
            const isExternal = box.resource === "preserved";
            uuidMap.add({ source: box.address.uuid, target: isExternal ? box.address.uuid : UUID.generate() });
        });
        skippedExternalUuids.forEach(uuid => uuidMap.add({ source: uuid, target: uuid }));
        const result = [];
        PointerField.decodeWith({
            map: (pointer, address) => {
                const remappedInternal = address.flatMap(addr => uuidMap.opt(addr.uuid).map(({ target }) => addr.moveTo(target)));
                if (remappedInternal.nonEmpty()) {
                    return remappedInternal;
                }
                return options.mapPointer(pointer, address);
            }
        }, () => sourceBoxes.forEach(sourceBox => {
            const inputStream = new ByteArrayInput(sourceBox.toArrayBuffer());
            const targetUuid = uuidMap.get(sourceBox.address.uuid).target;
            targetGraph.createBox(sourceBox.name, targetUuid, box => {
                box.read(inputStream);
                options.modifyBox?.(box);
                result.push(box);
            });
        }));
        return result;
    };
})(ClipboardUtils || (ClipboardUtils = {}));
