declare namespace BDV {
  interface State {
    hub: EventHandler<StateEvents>;

    /**** models ****/
    readonly models: StateModel[];
    readonly modelsMap: Map<number, StateModel>;
    loadModels(modelIds: number[]): Promise<StateModel[]>;
    unloadModels(modelIds: number[]): void;
    getStoreyFromAbsoluteElevation(model: StateModel, elevation: number): StateStorey;

    /**** objects ****/
    readonly objects: StateObject[];
    readonly objectsIds: number[];
    readonly objectsUuids: string[];
    readonly objectsMap: Map<number, StateObject>;
    readonly uuidsMap: { get(uuid: string): StateObject[]; };
    getObject(id: number): StateObject;
    getObjectsByUuids(uuids: string[]): StateObject[];
    getObjectsOfType(type: string): StateObject[];
    getObjectsWithTheSameTypeAs(ids: number[]): StateObject[];
    getTypesOf(ids: number[]): string[];

    readonly visibleObjects: StateObject[];
    readonly visibleObjectsIds: number[];
    readonly visibleObjectsUuids: string[];
    showObjects(ids: number[], options?: any): void;
    showObjectsByUuids(uuids: string[], options?: any): void;

    readonly unvisibleObjects: StateObject[];
    readonly unvisibleObjectsIds: number[];
    readonly unvisibleObjectsUuids: string[];
    hideObjects(ids: number[], options?: any): void;
    hideObjectsByUuids(uuids: string[], options?: any): void;

    readonly pickableObjects: StateObject[];
    readonly pickableObjectsIds: number[];
    readonly pickableObjectsUuids: string[];
    setObjectsPickable(ids: number[], options?: any): void;
    setObjectsPickableByUuids(uuids: string[], options?: any): void;

    readonly unpickableObjects: StateObject[];
    readonly unpickableObjectsIds: number[];
    readonly unpickableObjectsUuids: string[];
    setObjectsUnpickable(ids: number[], options?: any): void;
    setObjectsUnpickableByUuids(uuids: string[], options?: any): void;

    readonly selectedObjects: StateObject[];
    readonly selectedObjectsIds: number[];
    readonly selectedObjectsUuids: string[];
    selectObjects(ids: number[], options?: any): void;
    selectObjectsByUuids(uuids: string[], options?: any): void;

    readonly deselectedObjects: StateObject[];
    readonly deselectedObjectsIds: number[];
    readonly deselectedObjectsUuids: string[];
    deselectObjects(ids: number[], options?: any): void;
    deselectObjectsByUuids(uuids: string[], options?: any): void;

    readonly highlightedObjects: StateObject[];
    readonly highlightedObjectsIds: number[];
    readonly highlightedObjectsUuids: string[];
    highlightObjects(ids: number[], options?: any): void;
    highlightObjectsByUuids(uuids: string[], options?: any): void;

    readonly unhighlightedObjects: StateObject[];
    readonly unhighlightedObjectsIds: number[];
    readonly unhighlightedObjectsUuids: string[];
    unhighlightObjects(ids: number[], options?: any): void;
    unhighlightObjectsByUuids(uuids: string[], options?: any): void;

    readonly xrayedObjects: StateObject[];
    readonly xrayedObjectsIds: number[];
    readonly xrayedObjectsUuids: string[];
    xrayObjects(ids: number[], options?: any): void;
    xrayObjectsByUuids(uuids: string[], options?: any): void;

    readonly unxrayedObjects: StateObject[];
    readonly unxrayedObjectsIds: number[];
    readonly unxrayedObjectsUuids: string[];
    unxrayObjects(ids: number[], options?: any): void;
    unxrayObjectsByUuids(uuids: string[], options?: any): void;

    readonly colorizedObjects: StateObject[];
    readonly colorizedObjectsIds: number[];
    readonly colorizedObjectsUuids: string[];
    colorizeObjects(ids: number[], color?: string, options?: any): void;
    colorizeObjectsByUuids(uuids: string[], color?: string, options?: any): void;

    /**** annotations ****/
    readonly annotations: StateAnnotation[];
    addAnnotation(annotation: StateAnnotation, options?: any): StateAnnotation;
    removeAnnotation(annotation: StateAnnotation, options?: any): boolean;
    clearAnnotations(): void;
  }

  type StateEvents = {
    // models events
    "models-loaded": {
      models: StateModel[];
    };
    "models-unloaded": {
      models: StateModel[];
    };
    // objects events
    "objects-added": {
      objects: StateObject[];
    };
    "objects-removed": {
      objects: StateObject[];
    };
    "objects-shown": {
      objects: StateObject[];
      options?: any;
    };
    "objects-hidden": {
      objects: StateObject[];
      options?: any;
    };
    "objects-pickable": {
      objects: StateObject[];
      options?: any;
    };
    "objects-unpickable": {
      objects: StateObject[];
      options?: any;
    };
    "objects-selected": {
      objects: StateObject[];
      options?: any;
    };
    "objects-deselected": {
      objects: StateObject[];
      options?: any;
    };
    "objects-highlighted": {
      objects: StateObject[];
      options?: any;
    };
    "objects-unhighlighted": {
      objects: StateObject[];
      options?: any;
    };
    "objects-xrayed": {
      objects: StateObject[];
      options?: any;
    };
    "objects-unxrayed": {
      objects: StateObject[];
      options?: any;
    };
    "objects-colorized": {
      objects: StateObject[];
      color: number;
      options?: any;
    };
    // annotations events
    "annotation-added": {
      annotation: StateAnnotation;
      options?: any;
    };
    "annotation-updated": {
      annotation: StateAnnotation;
      options?: any;
    };
    "annotation-removed": {
      annotation: StateAnnotation;
      options?: any;
    };
  };

  interface StateModel extends ApiModel {
    structure: Object;
    uuids: Map<string, StateObject>;
    objects: StateObject[];
    storeys: StateStorey[];
  }

  interface StateObject extends ApiObject {
    id: number;
    model: StateModel;

    // Object state
    visible: boolean;
    pickable: boolean;
    selected: boolean;
    highlighted: boolean;
    xrayed: boolean;
    color: string;

    // Advanced getters
    readonly descendants: StateObject[];
    readonly ancestors: StateObject[];
    readonly site: StateObject;
    readonly building: StateObject;
    readonly storey: StateObject;
    readonly layout: StateObject;
    readonly space: StateObject;
    getFirstAncestorWithType(type: string): StateObject;
  }

  interface StateStorey {
    uuid: string;
    name: string;
    model: StateModel;
    plans: StatePlan[];
    object: StateObject;
    uuids: Set<string>;
    elevation: number;
    absoluteElevation: number;
    topElevation: number;
    absoluteTopElevation: number;

    key: string;
    selected: boolean;
  }

  interface StatePlan extends StatePositionning {
    model: StateModel;
    storey: StateStorey;
    index: number;
    key: string;
  }

  interface StatePositionning {
    translation_x: number;
    translation_y: number;
    rotate_z: number;
    scale: number;
    opacity: number;
    plan?: ApiPlan;
  }

  interface StateAnnotation {
    id: number;
    draggable: boolean;
    x: number;
    y: number;
    z: number;
    component: any;
    props?: any;
  }

  // ---

  type Events = {
    [eventName: string]: Object;
  };

  interface EventHandler<T extends Events> {
    on<N extends keyof T>(
      eventName: N,
      callback: (arg: T[N]) => void,
      options?: any
    ): number;
    once<N extends keyof T>(
      eventName: N,
      callback: (arg: T[N]) => void,
      options?: any
    ): number;
    off(subscriptionId: number): void;
    emit<N extends keyof T>(eventName: N, payload: T[N]): void;
    clear(): void;
  }
}
