import { ArmClient, BaseClient, CameraClient, GantryClient, GenericComponentClient, GripperClient, } from '@viamrobotics/sdk';
import { createResourceClient, createResourceQuery, useResourceNames, } from '@viamrobotics/svelte-sdk';
import {} from 'koota';
import { getContext, setContext, untrack } from 'svelte';
import { Color, Matrix4 } from 'three';
import { resourceColors } from '../color';
import { RefetchRates } from '../components/overlay/RefreshRate.svelte';
import { hierarchy, traits, useWorld } from '../ecs';
import { updateGeometryTrait } from '../ecs/traits';
import { createPose, poseToMatrix } from '../transform';
import { useEnvironment } from './useEnvironment.svelte';
import { useLogs } from './useLogs.svelte';
import { useResourceByName } from './useResourceByName.svelte';
import { RefreshRates, useSettings } from './useSettings.svelte';
const key = Symbol('geometries-context');
const colorUtil = new Color();
const tempMatrix = new Matrix4();
export const provideGeometries = (partID) => {
    const environment = useEnvironment();
    const resources = useResourceByName();
    const world = useWorld();
    const logs = useLogs();
    const arms = useResourceNames(partID, 'arm');
    const bases = useResourceNames(partID, 'base');
    const cameras = useResourceNames(partID, 'camera');
    const grippers = useResourceNames(partID, 'gripper');
    const gantries = useResourceNames(partID, 'gantry');
    const generics = useResourceNames(partID, 'generic');
    const settings = useSettings();
    const { refreshRates } = $derived(settings.current);
    const armClients = $derived(arms.current.map((arm) => createResourceClient(ArmClient, partID, () => arm.name)));
    const baseClients = $derived(bases.current.map((base) => createResourceClient(BaseClient, partID, () => base.name)));
    const gripperClients = $derived(grippers.current.map((gripper) => createResourceClient(GripperClient, partID, () => gripper.name)));
    const cameraClients = $derived(cameras.current.map((camera) => createResourceClient(CameraClient, partID, () => camera.name)));
    const gantryClients = $derived(gantries.current.map((gantry) => createResourceClient(GantryClient, partID, () => gantry.name)));
    const genericClients = $derived(generics.current
        .filter((generic) => generic.type === 'component')
        .map((generic) => createResourceClient(GenericComponentClient, partID, () => generic.name)));
    const interval = $derived(refreshRates[RefreshRates.poses]);
    const options = $derived({
        enabled: interval !== RefetchRates.OFF && environment.current.viewerMode === 'monitor',
        refetchInterval: interval === RefetchRates.MANUAL ? false : interval,
    });
    const armQueries = $derived(armClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
    const baseQueries = $derived(baseClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
    const gripperQueries = $derived(gripperClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
    const cameraQueries = $derived(cameraClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
    const gantryQueries = $derived(gantryClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
    const genericQueries = $derived(genericClients.map((client) => [client.current?.name, createResourceQuery(client, 'getGeometries', () => options)]));
    const queries = $derived([
        ...armQueries,
        ...baseQueries,
        ...gripperQueries,
        ...cameraQueries,
        ...gantryQueries,
        ...genericQueries,
    ]);
    $effect(() => {
        if (interval === RefetchRates.FPS_30 || interval === RefetchRates.FPS_60) {
            return logs.add(`Fetching geometries every ${interval}ms...`);
        }
        for (const [name, query] of queries) {
            untrack(() => {
                $effect(() => {
                    if (query.isFetching) {
                        logs.add(`Fetching geometries for ${name}...`);
                    }
                    else if (query.error) {
                        logs.add(`Error fetching geometries from ${name}: ${query.error.message}`, 'error');
                    }
                });
            });
        }
    });
    const entities = new Map();
    const queryEntityKeys = new Map();
    $effect(() => {
        const activeQueryKeys = new Set();
        const currentPartID = partID();
        for (const [name, query] of queries) {
            if (!name) {
                continue;
            }
            const queryKey = `${currentPartID}:${name}`;
            activeQueryKeys.add(queryKey);
            $effect(() => {
                const nextKeys = new Set();
                const resourceName = resources.current[name];
                const subtype = resourceName?.subtype;
                if (query.data) {
                    let index = 0;
                    for (const geometry of query.data) {
                        index += 1;
                        const label = geometry.label || `${name} geometry ${index}`;
                        const entityKey = `${currentPartID}:${name}:${label}`;
                        nextKeys.add(entityKey);
                        const center = createPose(geometry.center);
                        const existing = entities.get(entityKey);
                        if (existing) {
                            hierarchy.setParent(existing, name);
                            poseToMatrix(center, tempMatrix);
                            const matrix = existing.get(traits.Matrix);
                            if (matrix && !matrix.equals(tempMatrix)) {
                                matrix.copy(tempMatrix);
                                existing.changed(traits.Matrix);
                            }
                            updateGeometryTrait(existing, geometry);
                            continue;
                        }
                        const entityTraits = [
                            ...hierarchy.parentTraits(name),
                            traits.Name(label),
                            traits.Matrix(poseToMatrix(center, new Matrix4())),
                            traits.GeometriesAPI,
                            traits.Geometry(geometry),
                        ];
                        if (subtype) {
                            entityTraits.push(traits.Color(subtype ? colorUtil.set(resourceColors[subtype]) : undefined));
                        }
                        const entity = world.spawn(...entityTraits);
                        entities.set(entityKey, entity);
                    }
                }
                const prevKeys = queryEntityKeys.get(queryKey) ?? new Set();
                // Remove entities no longer present for this specific query
                for (const key of prevKeys) {
                    if (!nextKeys.has(key)) {
                        entities.get(key)?.destroy();
                        entities.delete(key);
                    }
                }
                queryEntityKeys.set(queryKey, nextKeys);
            });
        }
        // Clean up owners whose queries disappeared entirely
        for (const [queryKey, keys] of queryEntityKeys) {
            if (!activeQueryKeys.has(queryKey)) {
                for (const key of keys) {
                    const entity = entities.get(key);
                    if (entity && world.has(entity)) {
                        entity.destroy();
                    }
                    entities.delete(key);
                }
                queryEntityKeys.delete(queryKey);
            }
        }
    });
    // Clear all entities on unmount
    $effect(() => {
        return () => {
            for (const [, entity] of entities) {
                entity?.destroy();
            }
            entities.clear();
        };
    });
    setContext(key, {
        refetch() {
            for (const [, query] of queries) {
                query.refetch();
            }
        },
    });
};
export const useGeometries = () => {
    return getContext(key);
};
