import { getContext, setContext } from 'svelte';
import { useConfigFrames } from '../../hooks/useConfigFrames.svelte';
import { useFragmentInfo } from '../../hooks/useFragmentInfo.svelte';
import { useFrames } from '../../hooks/useFrames.svelte';
import { usePartConfig } from '../../hooks/usePartConfig.svelte';
import { createPoseFromFrame, poseToEulerDegrees } from '../../transform';
import { resolveFragmentCurrentFrames, validateProposedFrameDeltas, } from './frameDeltaAdapter';
const key = Symbol('scene-builder-context');
export const provideSceneBuilder = (onInfer) => {
    const partConfig = usePartConfig();
    const fragmentInfo = useFragmentInfo();
    const frames = useFrames();
    const configFrames = useConfigFrames();
    const fragmentFrames = $derived(resolveFragmentCurrentFrames(Object.keys(fragmentInfo.current), fragmentInfo.current, frames.current ?? [], configFrames.current ?? {}));
    let uiState = $state('idle');
    let deltas = $state([]);
    let explanation = $state('');
    let errorMessage = $state('');
    // Re-derived whenever deltas or the current config changes (e.g. from a drag).
    // confirm() therefore always applies the LLM's intent against the latest config —
    // drag changes to unspecified axes are preserved, not overwritten.
    const validation = $derived.by(() => deltas.length > 0
        ? validateProposedFrameDeltas(deltas, partConfig.current, fragmentFrames)
        : { prepared: [], errors: [] });
    const updateErrors = $derived(validation.errors);
    const diffGroups = $derived(validation.prepared.flatMap((u) => {
        const changes = [];
        const roundMm = (v) => `${Math.round(v * 100) / 100}mm`;
        const roundDeg = (v) => `${Math.round(v * 100) / 100}°`;
        if (u.parent !== u.previousParent) {
            changes.push({ field: 'parent', oldValue: u.previousParent, newValue: u.parent });
        }
        if (u.pose.x !== u.previousPose.x) {
            changes.push({
                field: 'translation.x',
                oldValue: roundMm(u.previousPose.x),
                newValue: roundMm(u.pose.x),
            });
        }
        if (u.pose.y !== u.previousPose.y) {
            changes.push({
                field: 'translation.y',
                oldValue: roundMm(u.previousPose.y),
                newValue: roundMm(u.pose.y),
            });
        }
        if (u.pose.z !== u.previousPose.z) {
            changes.push({
                field: 'translation.z',
                oldValue: roundMm(u.previousPose.z),
                newValue: roundMm(u.pose.z),
            });
        }
        if (u.pose.oX !== u.previousPose.oX ||
            u.pose.oY !== u.previousPose.oY ||
            u.pose.oZ !== u.previousPose.oZ ||
            u.pose.theta !== u.previousPose.theta) {
            const prev = poseToEulerDegrees(u.previousPose);
            const next = poseToEulerDegrees(u.pose);
            for (const axis of ['yaw', 'pitch', 'roll']) {
                if (Math.abs(next[axis] - prev[axis]) > 0.01) {
                    changes.push({
                        field: axis,
                        oldValue: roundDeg(prev[axis]),
                        newValue: roundDeg(next[axis]),
                    });
                }
            }
        }
        return changes.length > 0
            ? [{ componentName: u.componentName, explanation: u.explanation, changes }]
            : [];
    }));
    const clear = () => {
        deltas = [];
        explanation = '';
        errorMessage = '';
    };
    setContext(key, {
        get uiState() {
            return uiState;
        },
        get updateErrors() {
            return updateErrors;
        },
        get explanation() {
            return explanation;
        },
        get errorMessage() {
            return errorMessage;
        },
        get diffGroups() {
            return diffGroups;
        },
        async submit(prompt) {
            uiState = 'loading';
            const partComponents = partConfig.current.components
                .filter((c) => c.frame !== undefined)
                .map(({ name, frame }) => {
                const pose = createPoseFromFrame(frame);
                const orientation = poseToEulerDegrees(pose);
                return {
                    name,
                    frame: {
                        parent: frame.parent,
                        translation: frame.translation,
                        orientation,
                    },
                };
            });
            // Fragment components never appear in partConfig.components, so there's
            // no name overlap with partComponents.
            const fragmentComponents = Object.entries(fragmentFrames)
                .filter(([, current]) => current.frame !== undefined)
                .map(([name, current]) => {
                const pose = createPoseFromFrame(current.frame);
                const orientation = poseToEulerDegrees(pose);
                return {
                    name,
                    frame: {
                        parent: current.frame.parent,
                        translation: current.frame.translation,
                        orientation,
                    },
                };
            });
            const components = [...partComponents, ...fragmentComponents];
            try {
                const data = await onInfer(prompt.trim(), components);
                deltas = data.updates;
                explanation = data.explanation;
                uiState = 'diff';
            }
            catch (error) {
                errorMessage = error instanceof Error ? error.message : String(error);
                uiState = 'error';
            }
        },
        confirm() {
            for (const update of validation.prepared) {
                partConfig.updateFrame(update.componentName, update.parent, update.pose, update.geometry);
            }
            clear();
            uiState = 'idle';
        },
        cancel() {
            clear();
            uiState = 'idle';
        },
        resetError() {
            errorMessage = '';
            uiState = 'idle';
        },
    });
};
export const useSceneBuilder = () => {
    return getContext(key);
};
