import { Pose, Struct } from '@viamrobotics/sdk';
import { createAppMutation, createAppQuery } from '@viamrobotics/svelte-sdk';
import { getContext, setContext } from 'svelte';
import { createFrame } from '../frame';
import { createPoseFromFrame } from '../transform';
const key = Symbol('part-config-context');
export const providePartConfig = (partID, params) => {
    const props = $derived(params());
    const config = $derived(props ? useEmbeddedPartConfig(props) : useStandalonePartConfig(partID));
    const getCurrent = () => {
        return (config.current.toJson?.() ?? { components: [] });
    };
    const current = $derived(getCurrent());
    const createFragmentFrame = (fragmentId, componentName) => {
        const newConfig = getCurrent();
        newConfig.fragment_mods ??= [];
        let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
        if (fragmentMod === undefined) {
            fragmentMod = {
                fragment_id: fragmentId,
                mods: [],
            };
            newConfig.fragment_mods.push(fragmentMod);
        }
        const modSetPath = `components.${componentName}.frame`;
        const frame = {
            ['$set']: {
                [modSetPath]: createFrame(),
            },
        };
        fragmentMod.mods.push(frame);
        config.set(newConfig);
    };
    const createPartFrame = (componentName) => {
        const newConfig = getCurrent();
        const component = newConfig?.components?.find((comp) => comp.name === componentName);
        if (component) {
            component.frame = createFrame();
        }
        config.set(newConfig);
    };
    const updateFragmentFrame = (fragmentId, componentName, referenceFrame, framePosition, frameGeometry) => {
        const newConfig = getCurrent();
        newConfig.fragment_mods ??= [];
        let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
        if (fragmentMod === undefined) {
            fragmentMod = {
                fragment_id: fragmentId,
                mods: [],
            };
            newConfig.fragment_mods.push(fragmentMod);
        }
        const modSetPath = `components.${componentName}.frame`;
        const frame = {
            ['$set']: {
                [modSetPath]: {
                    translation: {
                        x: framePosition.x,
                        y: framePosition.y,
                        z: framePosition.z,
                    },
                    parent: referenceFrame,
                    orientation: {
                        type: 'ov_degrees',
                        value: {
                            x: framePosition.oX,
                            y: framePosition.oY,
                            z: framePosition.oZ,
                            th: framePosition.theta,
                        },
                    },
                    geometry: frameGeometry && frameGeometry.type !== 'none' ? { ...frameGeometry } : undefined,
                },
            },
        };
        if (frameGeometry === undefined || frameGeometry.type === 'none') {
            delete frame['$set'][modSetPath].geometry;
        }
        const existingFrameIndex = fragmentMod.mods.findLastIndex(
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        (mod) => mod?.['$set']?.[modSetPath] !== undefined);
        if (existingFrameIndex === -1) {
            fragmentMod.mods.push(frame);
        }
        else {
            const existingGeometry = fragmentMod.mods[existingFrameIndex]['$set']?.[modSetPath].geometry;
            if (existingGeometry && !frameGeometry) {
                frame['$set'][modSetPath].geometry = existingGeometry;
            }
            fragmentMod.mods[existingFrameIndex] = frame;
        }
        config.set(newConfig);
    };
    const updatePartFrame = (componentName, referenceFrame, pose, geometry) => {
        const newConfig = getCurrent();
        const component = newConfig.components?.find(({ name }) => name === componentName);
        console.log('hi', newConfig, componentName);
        if (!component) {
            return;
        }
        if (component.frame) {
            const currentPose = createPoseFromFrame(component.frame);
            component.frame.parent = referenceFrame;
            component.frame.translation = {
                x: pose.x ?? currentPose.x,
                y: pose.y ?? currentPose.y,
                z: pose.z ?? currentPose.z,
            };
            component.frame.orientation.type = 'ov_degrees';
            component.frame.orientation.value = {
                x: pose.oX ?? currentPose.oX,
                y: pose.oY ?? currentPose.oY,
                z: pose.oZ ?? currentPose.oZ,
                th: pose.theta ?? currentPose.theta,
            };
            if (geometry) {
                if (geometry.type === 'none') {
                    delete component.frame.geometry;
                }
                else {
                    component.frame.geometry = { ...geometry };
                }
            }
        }
        config.set(newConfig);
    };
    const deletePartFrame = (componentName) => {
        const newConfig = getCurrent();
        const component = newConfig?.components?.find(({ name }) => name === componentName);
        if (component) {
            delete component.frame;
            config.set(newConfig);
        }
    };
    const deleteFragmentFrame = (fragmentId, componentName) => {
        const newConfig = getCurrent();
        newConfig.fragment_mods ??= [];
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        let fragmentMod = newConfig.fragment_mods.find((mod) => mod.fragment_id === fragmentId);
        if (fragmentMod === undefined) {
            fragmentMod = {
                fragment_id: fragmentId,
                mods: [],
            };
            newConfig.fragment_mods.push(fragmentMod);
        }
        const modUnSetPath = `components.${componentName}.frame`;
        fragmentMod.mods.push({
            ['$unset']: {
                [modUnSetPath]: '',
            },
        });
        config.set(newConfig);
    };
    setContext(key, {
        get current() {
            return current;
        },
        get componentNameToFragmentId() {
            return config.componentNameToFragmentId;
        },
        get isDirty() {
            return config.isDirty;
        },
        get hasEditPermissions() {
            return config.hasEditPermissions;
        },
        updateFrame: (componentName, referenceFrame, framePosition, frameGeometry) => {
            const fragmentId = config.componentNameToFragmentId[componentName];
            if (fragmentId === undefined) {
                updatePartFrame(componentName, referenceFrame, framePosition, frameGeometry);
            }
            else {
                updateFragmentFrame(fragmentId, componentName, referenceFrame, framePosition, frameGeometry);
            }
        },
        deleteFrame: (componentName) => {
            const fragmentId = config.componentNameToFragmentId[componentName];
            if (fragmentId === undefined) {
                deletePartFrame(componentName);
            }
            else {
                deleteFragmentFrame(fragmentId, componentName);
            }
        },
        createFrame: (componentName) => {
            const fragmentId = config.componentNameToFragmentId[componentName];
            if (fragmentId === undefined) {
                createPartFrame(componentName);
            }
            else {
                createFragmentFrame(fragmentId, componentName);
            }
        },
        save: () => config.save?.(),
        discardChanges: () => config.discardChanges?.(),
    });
};
export const usePartConfig = () => {
    return getContext(key);
};
const useEmbeddedPartConfig = (props) => {
    return {
        hasEditPermissions: true,
        get isDirty() {
            return props.isDirty;
        },
        get current() {
            return props.current ?? new Struct();
        },
        get componentNameToFragmentId() {
            return props.componentToFragId;
        },
        set(config) {
            const struct = Struct.fromJson(config);
            return props.setLocalPartConfig(struct);
        },
    };
};
const useStandalonePartConfig = (partID) => {
    const partQuery = createAppQuery('getRobotPart', () => [partID()], {
        refetchInterval: false,
    });
    const partName = $derived(partQuery.data?.part?.name);
    const configJSON = $derived.by(() => {
        if (!partQuery.data?.configJson) {
            return undefined;
        }
        try {
            return JSON.parse(partQuery.data.configJson);
        }
        catch {
            return undefined;
        }
    });
    let networkPartConfig = $derived(configJSON ? Struct.fromJson(configJSON) : undefined);
    let current = $state.raw();
    let isDirty = $state(false);
    const hasEditPermissions = $derived(networkPartConfig !== undefined);
    const fragmentQueries = $derived((configJSON?.fragments ?? []).map((fragmentId) => {
        const id = typeof fragmentId === 'string' ? fragmentId : fragmentId.id;
        return createAppQuery('getFragment', () => [id], { refetchInterval: false });
    }));
    const componentNameToFragmentId = $derived.by(() => {
        const results = {};
        for (const query of fragmentQueries) {
            if (!query.data) {
                continue;
            }
            const fragmentId = query.data.id;
            const components = query.data?.fragment?.fields['components']?.kind;
            if (components?.case === 'listValue') {
                for (const component of components.value.values) {
                    if (component.kind.case === 'structValue') {
                        const componentName = component.kind.value.fields['name']?.kind;
                        if (componentName.case === 'stringValue') {
                            results[componentName.value] = fragmentId;
                        }
                    }
                }
            }
        }
        return results;
    });
    $effect.pre(() => {
        if (!networkPartConfig) {
            // no config returned here indicates this api key has no permission to update config
            return;
        }
        current = networkPartConfig;
    });
    const updateRobotPartMutation = createAppMutation('updateRobotPart');
    return {
        get current() {
            return current ?? new Struct();
        },
        get isDirty() {
            return isDirty;
        },
        get hasEditPermissions() {
            return hasEditPermissions;
        },
        get componentNameToFragmentId() {
            return componentNameToFragmentId;
        },
        set(config) {
            current = Struct.fromJson(config);
            isDirty = true;
        },
        async save() {
            if (!current || !partName) {
                return;
            }
            networkPartConfig = current;
            await updateRobotPartMutation.mutateAsync([partID(), partName, current]);
            isDirty = false;
        },
        discardChanges() {
            current = networkPartConfig;
            isDirty = false;
        },
    };
};
