import type { SceneGraph } from '@pascal-app/core/clone-scene-graph' import type { AnyNode, AnyNodeId, AnyNodeType } from '@pascal-app/core/schema' import type { ActiveSceneMeta, Patch, SceneBridge, ValidationResult } from '../bridge/scene-bridge' import type { SceneEvent, SceneEventAppendOptions, SceneEventListOptions, SceneListOptions, SceneMeta, SceneMutateOptions, SceneSaveOptions, SceneStore, SceneWithGraph, } from '../storage/types' export type CreateSceneOperationsOptions = { bridge?: SceneBridge store?: SceneStore } export interface SceneOperations { readonly hasBridge: boolean readonly hasStore: boolean readonly hasSceneEvents: boolean readonly canAppendSceneEvents: boolean readonly canListSceneEvents: boolean readonly storeBackend: SceneStore['backend'] | null setActiveScene(meta: ActiveSceneMeta): void getActiveScene(): ActiveSceneMeta | null clearActiveScene(): void loadDefault(): void setScene(nodes: Record, rootNodeIds: AnyNodeId[]): void exportJSON(): SceneGraph & { collections: Record } exportSceneGraph(): SceneGraph loadJSON(json: string | SceneGraph): void getNode(id: AnyNodeId): AnyNode | null getNodes(): Record getRootNodeIds(): AnyNodeId[] getChildren(parentId: AnyNodeId): AnyNode[] getAncestry(id: AnyNodeId): AnyNode[] findNodes(filter: { type?: AnyNodeType parentId?: AnyNodeId | null levelId?: AnyNodeId }): AnyNode[] resolveLevelId(id: AnyNodeId): AnyNodeId | null createNode(node: AnyNode, parentId?: AnyNodeId): AnyNodeId updateNode(id: AnyNodeId, data: Partial): void deleteNode(id: AnyNodeId, cascade?: boolean): string[] applyPatch(patches: Patch[]): { appliedOps: number deletedIds: AnyNodeId[] createdIds: AnyNodeId[] } undo(steps?: number): number redo(steps?: number): number validateScene(): ValidationResult flushDirty(): string[] getHistory(): { pastCount: number; futureCount: number } clearHistory(): void saveScene(options: SceneSaveOptions): Promise loadStoredScene(id: string): Promise listScenes(options?: SceneListOptions): Promise deleteStoredScene(id: string, options?: SceneMutateOptions): Promise renameStoredScene(id: string, newName: string, options?: SceneMutateOptions): Promise appendSceneEvent(options: SceneEventAppendOptions): Promise listSceneEvents(id: string, options?: SceneEventListOptions): Promise } export function createSceneOperations(options: CreateSceneOperationsOptions): SceneOperations { return new SceneOperationsFacade(options) } class SceneOperationsFacade implements SceneOperations { readonly #bridge?: SceneBridge readonly #store?: SceneStore constructor(options: CreateSceneOperationsOptions) { this.#bridge = options.bridge this.#store = options.store } get hasBridge(): boolean { return this.#bridge !== undefined } get hasStore(): boolean { return this.#store !== undefined } get hasSceneEvents(): boolean { return this.canAppendSceneEvents && this.canListSceneEvents } get canAppendSceneEvents(): boolean { return typeof this.#store?.appendSceneEvent === 'function' } get canListSceneEvents(): boolean { return typeof this.#store?.listSceneEvents === 'function' } get storeBackend(): SceneStore['backend'] | null { return this.#store?.backend ?? null } setActiveScene(meta: ActiveSceneMeta): void { this.requireBridge().setActiveScene(meta) } getActiveScene(): ActiveSceneMeta | null { return this.requireBridge().getActiveScene() } clearActiveScene(): void { this.requireBridge().clearActiveScene() } loadDefault(): void { this.requireBridge().loadDefault() } setScene(nodes: Record, rootNodeIds: AnyNodeId[]): void { this.requireBridge().setScene(nodes, rootNodeIds) } exportJSON(): SceneGraph & { collections: Record } { return this.requireBridge().exportJSON() } exportSceneGraph(): SceneGraph { const exported = this.exportJSON() return { nodes: exported.nodes, rootNodeIds: exported.rootNodeIds, collections: exported.collections as SceneGraph['collections'], } } loadJSON(json: string | SceneGraph): void { this.requireBridge().loadJSON(json) } getNode(id: AnyNodeId): AnyNode | null { return this.requireBridge().getNode(id) } getNodes(): Record { return this.requireBridge().getNodes() } getRootNodeIds(): AnyNodeId[] { return this.requireBridge().getRootNodeIds() } getChildren(parentId: AnyNodeId): AnyNode[] { return this.requireBridge().getChildren(parentId) } getAncestry(id: AnyNodeId): AnyNode[] { return this.requireBridge().getAncestry(id) } findNodes(filter: { type?: AnyNodeType parentId?: AnyNodeId | null levelId?: AnyNodeId }): AnyNode[] { return this.requireBridge().findNodes(filter) } resolveLevelId(id: AnyNodeId): AnyNodeId | null { return this.requireBridge().resolveLevelId(id) } createNode(node: AnyNode, parentId?: AnyNodeId): AnyNodeId { return this.requireBridge().createNode(node, parentId) } updateNode(id: AnyNodeId, data: Partial): void { this.requireBridge().updateNode(id, data) } deleteNode(id: AnyNodeId, cascade?: boolean): string[] { return this.requireBridge().deleteNode(id, cascade) } applyPatch(patches: Patch[]): { appliedOps: number deletedIds: AnyNodeId[] createdIds: AnyNodeId[] } { return this.requireBridge().applyPatch(patches) } undo(steps?: number): number { return this.requireBridge().undo(steps) } redo(steps?: number): number { return this.requireBridge().redo(steps) } validateScene(): ValidationResult { return this.requireBridge().validateScene() } flushDirty(): string[] { return this.requireBridge().flushDirty() } getHistory(): { pastCount: number; futureCount: number } { return this.requireBridge().getHistory() } clearHistory(): void { this.requireBridge().clearHistory() } async saveScene(options: SceneSaveOptions): Promise { return this.requireStore().save(options) } async loadStoredScene(id: string): Promise { return this.requireStore().load(id) } async listScenes(options?: SceneListOptions): Promise { return this.requireStore().list(options) } async deleteStoredScene(id: string, options?: SceneMutateOptions): Promise { return this.requireStore().delete(id, options) } async renameStoredScene( id: string, newName: string, options?: SceneMutateOptions, ): Promise { return this.requireStore().rename(id, newName, options) } async appendSceneEvent(options: SceneEventAppendOptions): Promise { const append = this.requireStore().appendSceneEvent if (!append) return null return append(options) } async listSceneEvents(id: string, options?: SceneEventListOptions): Promise { const list = this.requireStore().listSceneEvents if (!list) { throw new Error('scene_events_unavailable') } return list(id, options) } private requireBridge(): SceneBridge { if (!this.#bridge) { throw new Error('scene_bridge_unavailable') } return this.#bridge } private requireStore(): SceneStore { if (!this.#store) { throw new Error('scene_store_unavailable') } return this.#store } }