import { ByteArrayInput, ByteArrayOutput, Option } from "@opendaw/lib-std";
import { Pointers } from "@opendaw/studio-enums";
import { ValueEventBox } from "@opendaw/studio-boxes";
import { ValueEventBoxAdapter } from "@opendaw/studio-adapters";
import { ClipboardUtils } from "../ClipboardUtils";
export var ValuesClipboard;
(function (ValuesClipboard) {
    ValuesClipboard.createHandler = ({ getEnabled, getPosition, setPosition, editing, selection, collection, targetAddress, boxGraph, boxAdapters }) => {
        const copyValues = () => {
            const selected = selection.selected();
            if (selected.length === 0) {
                return Option.None;
            }
            const sorted = selected.slice().sort((a, b) => {
                const positionDiff = a.position - b.position;
                return positionDiff !== 0 ? positionDiff : a.index - b.index;
            });
            const min = sorted[0].position;
            const max = sorted[sorted.length - 1].position;
            const eventBoxes = sorted.map(adapter => adapter.box);
            const dependencies = eventBoxes.flatMap(box => Array.from(boxGraph.dependenciesOf(box, {
                alwaysFollowMandatory: true,
                excludeBox: dep => dep.ephemeral
            }).boxes));
            const allBoxes = [...eventBoxes, ...dependencies];
            const data = ClipboardUtils.serializeBoxes(allBoxes, encodeMetadata(min, max));
            setPosition(max);
            return Option.wrap({ type: "values", data });
        };
        return {
            canCopy: () => getEnabled() && selection.nonEmpty(),
            canCut: () => getEnabled() && selection.nonEmpty(),
            canPaste: (entry) => getEnabled() && entry.type === "values",
            copy: copyValues,
            cut: () => {
                const result = copyValues();
                result.ifSome(() => editing.modify(() => selection.selected().forEach(adapter => adapter.box.delete())));
                return result;
            },
            paste: (entry) => {
                if (entry.type !== "values" || !getEnabled()) {
                    return;
                }
                const position = getPosition();
                const { min, max } = decodeMetadata(ClipboardUtils.extractMetadata(entry.data));
                const positionOffset = Math.max(0, position) - min;
                const pastedMin = min + positionOffset;
                const pastedMax = max + positionOffset;
                editing.modify(() => {
                    selection.deselectAll();
                    const existingAdapters = [];
                    for (const adapter of collection.events.asArray()) {
                        if (pastedMin < adapter.position && adapter.position < pastedMax) {
                            adapter.box.delete();
                        }
                        else {
                            existingAdapters.push(adapter);
                        }
                    }
                    const boxes = ClipboardUtils.deserializeBoxes(entry.data, boxGraph, {
                        mapPointer: pointer => pointer.pointerType === Pointers.ValueEvents
                            ? Option.wrap(targetAddress)
                            : Option.None,
                        modifyBox: box => {
                            if (box instanceof ValueEventBox) {
                                box.position.setValue(box.position.getValue() + positionOffset);
                            }
                        }
                    });
                    const valueEventBoxes = boxes.filter((box) => box instanceof ValueEventBox);
                    const pastedAdapters = valueEventBoxes.map(box => boxAdapters.adapterFor(box, ValueEventBoxAdapter));
                    const deleted = resolveIndexConflicts(existingAdapters, pastedAdapters, pastedMin, pastedMax);
                    selection.select(...pastedAdapters.filter(adapter => !deleted.has(adapter)));
                    setPosition(pastedMax);
                });
            }
        };
    };
    const encodeMetadata = (min, max) => {
        const output = ByteArrayOutput.create();
        output.writeFloat(min);
        output.writeFloat(max);
        return output.toArrayBuffer();
    };
    const decodeMetadata = (buffer) => {
        const input = new ByteArrayInput(buffer);
        return { min: input.readFloat(), max: input.readFloat() };
    };
    // At pastedMin: push all pasted after existing → keep first (existing, index 0) and last (pasted, index 1)
    // At pastedMax: unshift all pasted before existing → keep first (pasted, index 0) and last (existing, index 1)
    // Delete middle events at each boundary. Returns the set of deleted pasted adapters.
    const resolveIndexConflicts = (existingAdapters, pastedAdapters, pastedMin, pastedMax) => {
        const deleted = new Set();
        const sortedPasted = pastedAdapters.slice().sort((a, b) => {
            const positionDiff = a.position - b.position;
            return positionDiff !== 0 ? positionDiff : a.index - b.index;
        });
        if (sortedPasted.length === 0) {
            return deleted;
        }
        const existingAtMin = existingAdapters.filter(adapter => adapter.position === pastedMin);
        const existingAtMax = existingAdapters.filter(adapter => adapter.position === pastedMax);
        const pastedAtMin = sortedPasted.filter(adapter => adapter.position === pastedMin);
        const pastedAtMax = sortedPasted.filter(adapter => adapter.position === pastedMax);
        // At pastedMin: existing first, then all pasted pushed after
        const combinedAtMin = [...existingAtMin, ...pastedAtMin];
        if (combinedAtMin.length > 1) {
            for (let i = 1; i < combinedAtMin.length - 1; i++) {
                const adapter = combinedAtMin[i];
                adapter.box.delete();
                if (pastedAtMin.includes(adapter)) {
                    deleted.add(adapter);
                }
            }
            combinedAtMin[0].box.index.setValue(0);
            combinedAtMin[combinedAtMin.length - 1].box.index.setValue(1);
        }
        // At pastedMax: all pasted first (unshifted), then existing after
        if (pastedMax !== pastedMin) {
            const combinedAtMax = [...pastedAtMax, ...existingAtMax];
            if (combinedAtMax.length > 1) {
                for (let i = 1; i < combinedAtMax.length - 1; i++) {
                    const adapter = combinedAtMax[i];
                    adapter.box.delete();
                    if (pastedAtMax.includes(adapter)) {
                        deleted.add(adapter);
                    }
                }
                combinedAtMax[0].box.index.setValue(0);
                combinedAtMax[combinedAtMax.length - 1].box.index.setValue(1);
            }
        }
        return deleted;
    };
})(ValuesClipboard || (ValuesClipboard = {}));
