/*!
 * Copyright (c) 2026 Vertex Software LLC. All rights reserved.
 */
import { Uuid } from "@vertexvis/scene-tree-protos/core/protos/uuid_pb";
import { Pager } from "@vertexvis/scene-view-protos/core/protos/paging_pb";
import { CreateSceneViewAnnotationSetRequest, DeleteSceneViewAnnotationSetRequest, ListSceneAnnotationsRequest, ListSceneViewAnnotationSetsRequest, } from "@vertexvis/scene-view-protos/sceneview/protos/scene_view_api_pb";
import { EventDispatcher } from "@vertexvis/utils";
import { Async } from "@vertexvis/utils";
import { delay } from "../../testing/utils";
import { createMetadata, requestPaged, requestUnary, } from "../grpc";
import { fromPbAnnotationOrThrow, fromPbAnnotationSetOrThrow, } from "../mappers/annotation";
import { toUuid2l } from "../mappers/uuid";
/**
 * The controller for managing the annotations of a scene and scene view.
 */
export class AnnotationController {
    constructor(client, jwtProvider, deviceIdProvider) {
        this.client = client;
        this.jwtProvider = jwtProvider;
        this.deviceIdProvider = deviceIdProvider;
        /**
         * A dispatcher that emits an event whenever the state of the annotations has
         * changed. The annotation state includes the annotation sets that are active
         * and the annotations that belong to each set.
         */
        this.onStateChange = new EventDispatcher();
    }
    /**
     * Activates an annotation set for the current session. This method will emit
     * an event on `onStateChange` when the annotation set has been added, and
     * return a promise the resolves with the new annotation state.
     *
     * @param id The ID of the annotation set.
     * @returns A promise that resolves with the new annotation state.
     */
    async addAnnotationSet(id) {
        await requestUnary(async (handler) => {
            const deviceId = this.deviceIdProvider();
            const meta = await createMetadata(this.jwtProvider, deviceId);
            const req = new CreateSceneViewAnnotationSetRequest();
            const setId = new Uuid();
            setId.setHex(id);
            req.setSceneAnnotationSetId(setId);
            this.client.createSceneViewAnnotationSet(req, meta, handler);
        });
        return this.fetch();
    }
    /**
     * Stops the automatic retrieval of annotation state.
     */
    disconnect() {
        if (this.connection != null) {
            this.connection.dispose();
            this.connection = undefined;
        }
    }
    /**
     * Starts the automatic retrieval of annotation state. When a new annotation
     * state is retrieved, an event will be emitted on `onStateChange`.
     *
     * @param pollingIntervalInMs The interval to poll for a new annotation state.
     */
    connect(pollingIntervalInMs = 10000) {
        this.disconnect();
        const controller = this.pollAnnotationState(pollingIntervalInMs);
        this.connection = { dispose: () => controller.abort() };
    }
    /**
     * Performs a manual fetch of the annotation state. This method will emit an
     * event on `onStateChange` when the annotation set has been added, and return
     * a promise the resolves with the new annotation state.
     *
     * @param opts Options to configure the fetch behavior. If an abort signal is
     *   given, then this will not emit a change event if the signal is aborted.
     * @returns A promise that resolves with the new annotation state.
     */
    async fetch(opts = {}) {
        const sets = await this.fetchAnnotationSetsAsArray();
        const annotations = await this.fetchAnnotationsAsArray(sets);
        const state = (await Promise.all(annotations)).reduce((state, [set, annotations]) => ({
            annotations: { ...state.annotations, [set.id]: annotations },
        }), { annotations: {} });
        if (opts.signal == null || !opts.signal.aborted) {
            this.updateState(state);
        }
        return state;
    }
    /**
     * Deactivates an annotation set for the current session. This method will
     * emit an event on `onStateChange` when the annotation set has been added,
     * and return a promise the resolves with the new annotation state.
     *
     * @param id The ID of the annotation set.
     * @returns A promise that resolves with the new annotation state.
     */
    async removeAnnotationSet(id) {
        await requestUnary(async (handler) => {
            const deviceId = this.deviceIdProvider();
            const meta = await createMetadata(this.jwtProvider, deviceId);
            const req = new DeleteSceneViewAnnotationSetRequest();
            const setId = new Uuid();
            setId.setHex(id);
            req.setSceneAnnotationSetId(setId);
            this.client.deleteSceneViewAnnotationSet(req, meta, handler);
        });
        return this.fetch();
    }
    pollAnnotationState(pollingIntervalInMs) {
        const controller = new AbortController();
        const poll = async (delayMs) => {
            await this.fetch({ signal: controller.signal });
            if (!controller.signal.aborted) {
                await delay(delayMs);
                await poll(delayMs);
            }
        };
        Async.abort(controller.signal, poll(pollingIntervalInMs));
        return controller;
    }
    fetchAnnotationSets() {
        return requestPaged((cursor) => async (handler) => {
            const deviceId = this.deviceIdProvider();
            const meta = await createMetadata(this.jwtProvider, deviceId);
            const req = new ListSceneViewAnnotationSetsRequest();
            if (cursor != null) {
                const page = new Pager();
                page.setCursor(cursor);
                page.setLimit(100);
                req.setPage(page);
            }
            this.client.listSceneViewAnnotationSets(req, meta, handler);
        }, (res) => { var _a; return (_a = res.toObject().nextCursor) === null || _a === void 0 ? void 0 : _a.next; });
    }
    async fetchAnnotationSetsAsArray() {
        return (await Async.asArray(this.fetchAnnotationSets()))
            .flatMap((res) => res.getSceneAnnotationSetsList())
            .map((set) => fromPbAnnotationSetOrThrow(set));
    }
    fetchAnnotations(setId) {
        return requestPaged((cursor) => async (handler) => {
            const deviceId = this.deviceIdProvider();
            const meta = await createMetadata(this.jwtProvider, deviceId);
            const req = new ListSceneAnnotationsRequest();
            const setId2l = toUuid2l(setId);
            req.setSceneAnnotationSetId(setId2l);
            if (cursor != null) {
                const page = new Pager();
                page.setCursor(cursor);
                page.setLimit(100);
                req.setPage(page);
            }
            this.client.listSceneAnnotations(req, meta, handler);
        }, (res) => { var _a; return (_a = res.toObject().nextCursor) === null || _a === void 0 ? void 0 : _a.next; });
    }
    fetchAnnotationsAsArray(sets) {
        const annotations = sets.map(async (set) => {
            const annotations = (await Async.asArray(this.fetchAnnotations(set.id)))
                .flatMap((ann) => ann.getSceneAnnotationsList())
                .map((ann) => fromPbAnnotationOrThrow(ann));
            return [set, annotations];
        });
        return Promise.all(annotations);
    }
    updateState(state) {
        this.state = state;
        this.onStateChange.emit(state);
    }
}
//# sourceMappingURL=controller.js.map
