export interface ModelConfig {
  /** Parent model ID; TRS is relative to the parent. Missing parents defer display. */
  parent?: string | null;
  id?: string;
  name?: string;
  url: string;
  position?: [number, number, number];
  /** Euler XYZ degrees. */
  rotation?: [number, number, number];
  scale?: number | [number, number, number];
  normalize?: boolean;
  /** Opt in to selection and dragging on a horizontal plane; default false. */
  isMovable?: boolean;
  /** Hide this model and descendants when rendering, preserving interaction; default false. */
  invisible?: boolean;
  /** Non-empty event name enables clicks, independently of isMovable. */
  clickEvent?: string;
}

export type ModelPosition = [number, number, number];
export interface ModelTransform {
  /** Omitted for scene-root models. */
  parent?: string;
  id: string;
  /** Current parent-local position, matching ModelConfig.position. */
  position: ModelPosition;
  /** Euler XYZ degrees, regardless of the object's internal Euler order. */
  rotation: [number, number, number];
  scale: [number, number, number];
}
export type ModelEventReason = 'pointer' | 'release' | 'multitouch' | 'touchcancel' |
  'blur' | 'resize' | 'low-angle' | 'modelremoved' | 'scenechange' | 'selectioncleared' | 'dispose' | 'parentchange';

export type ModelEvent = {
  type: 'click';
  modelId: string;
  event: string;
} | {
  type: 'selectionchange';
  modelId: string | null;
  previousModelId: string | null;
  reason: ModelEventReason;
} | {
  type: 'dragstart' | 'drag' | 'dragend' | 'dragcancel' | 'dragblocked';
  modelId: string;
  /** Parent-local coordinates, matching ModelConfig.position. */
  startPosition: ModelPosition;
  position: ModelPosition;
  reason?: ModelEventReason;
  message?: string;
};

export interface ModelTouchEvent {
  touches: ArrayLike<{ clientX: number; clientY: number; identifier?: number }>;
  changedTouches?: ArrayLike<{ clientX: number; clientY: number; identifier?: number }>;
  pointerType?: string;
}

export interface ModelWheelEvent {
  /** Negative zooms in; positive zooms out. */
  deltaY: number;
  /** 0: pixels (default), 1: lines, 2: pages. */
  deltaMode?: 0 | 1 | 2;
  ctrlKey?: boolean;
}

export interface DecalOptions {
  projectionMode?: 'cylindrical' | 'projector' | 'orthographic';
  mode?: 'cylindrical' | 'projector' | 'orthographic';
  angle?: number;
  height?: number;
  scaleX?: number;
  scaleY?: number;
  arcWidth?: number;
  heightRange?: number;
  opacity?: number;
  position?: [number, number, number];
  direction?: [number, number, number];
  targetPoint?: [number, number, number];
  rotation?: [number, number, number];
  roll?: number;
  rollDeg?: number;
  up?: [number, number, number];
  width?: number;
  projectorPosition?: [number, number, number];
  projectorDirection?: [number, number, number];
  projectorTarget?: [number, number, number];
  projectorRotation?: [number, number, number];
  projectorRoll?: number;
  projectorRollDeg?: number;
  projectorUp?: [number, number, number];
  projectorWidth?: number;
  projectorHeight?: number;
  projectorNormalThreshold?: number;
  normalThreshold?: number;
}

export interface SceneProjectorDecalConfig extends Omit<DecalOptions, 'target'> {
  target?: string;
  url?: string;
  debug?: boolean;
  debugColor?: number | string;
  debugLength?: number;
}

export interface SceneButtonUiConfig {
  type: 'button';
  id?: string;
  /** Normalized screen-space position, origin at the canvas bottom-left corner. Usually 0..1. */
  position?: [number, number];
  /** Normalized button width and height relative to the canvas. Usually 0..1. */
  scale?: [number, number];
  text?: string;
  img?: string;
  alpha?: number;
  event?: string;
  backgroundColor?: string;
  borderColor?: string;
  color?: string;
  fontSize?: number;
}

export interface SceneSliderUiConfig {
  type: 'slider' | 'slide';
  id?: string;
  /** Normalized screen-space position, origin at the canvas bottom-left corner. Usually 0..1. */
  position?: [number, number];
  /** Normalized slider width and height relative to the canvas. Usually 0..1. */
  scale?: [number, number];
  text?: string;
  min?: number;
  max?: number;
  value?: number;
  step?: number;
  alpha?: number;
  event?: string;
  /** Built-in target such as 'decal.scaleX' or 'decal.scaleY'. */
  target?: string;
  trackColor?: string;
  fillColor?: string;
  thumbColor?: string;
  thumbBorderColor?: string;
  color?: string;
  fontSize?: number;
}

export interface SceneLabelUiConfig {
  type: 'label';
  /** Unique id used by setUiLabelText(id, text). */
  id: string;
  /** Normalized screen-space position, origin at the canvas bottom-left corner. Usually 0..1. */
  position?: [number, number];
  /** Normalized label width and height relative to the canvas. Usually 0..1. */
  scale?: [number, number];
  text?: string;
  alpha?: number;
  backgroundColor?: string;
  borderColor?: string;
  borderWidth?: number;
  radius?: number;
  padding?: number;
  color?: string;
  fontSize?: number;
  align?: 'left' | 'center' | 'right';
}

export type SceneUiConfig = SceneButtonUiConfig | SceneSliderUiConfig | SceneLabelUiConfig;

export type CameraControlMode = 'orbit' | 'pan';

export interface SceneCameraConfig {
  /** Single-finger / left-mouse drag on empty space. Defaults to orbit. */
  controlMode?: CameraControlMode;
  position?: [number, number, number];
  lookAt?: [number, number, number];
  fov?: number;
  near?: number;
  far?: number;
  /** Spherical angles in radians; radius in world units. */
  orbit?: { theta?: number; phi?: number; radius?: number };
  [key: string]: unknown;
}

export interface SceneEnvironmentConfig {
  url?: string;
  showSkybox?: boolean;
  /** Show the horizontal reference grid while dragging. Defaults to true. */
  showDragGrid?: boolean;
  /** Show the selected model's bounds. Defaults to true. */
  showBounds?: boolean;
  [key: string]: unknown;
}

export interface SceneConfig {
  renderer?: Record<string, unknown>;
  environment?: SceneEnvironmentConfig;
  camera?: SceneCameraConfig;
  lights?: Array<Record<string, unknown>>;
  models?: ModelConfig[];
  ui?: SceneUiConfig[];
  decal?: DecalOptions & {
    target?: string;
    url?: string;
    decalAngleDeg?: number;
    decalHeight?: number;
  };
  decal_projector?: SceneProjectorDecalConfig;
  decalProjector?: SceneProjectorDecalConfig;
}

export interface PlatformAdapter {
  getWindowInfo(): { pixelRatio?: number; windowWidth?: number; windowHeight?: number };
  getPixelRatio(): number;
  getContext(canvas: unknown, type: string): unknown;
  requestJson(url: string): Promise<unknown>;
  fetchFile(url: string, options?: Record<string, unknown>): Promise<string>;
  fetchArrayBuffer(url: string, options?: Record<string, unknown>): Promise<ArrayBuffer>;
  readFileBase64?(path: string): Promise<string>;
  createImage(canvas?: unknown): unknown;
  createCanvas?(width: number, height: number): unknown;
  requestAnimationFrame(canvas: unknown, callback: FrameRequestCallback): number;
  cancelAnimationFrame(canvas: unknown, id: number): void;
}

export class ModelRenderer {
  constructor(canvas: unknown, options?: {
    platform?: PlatformAdapter;
    bgColor?: string;
    width?: number;
    height?: number;
    renderMode?: 'on-demand' | 'continuous';
    onUiEvent?: (detail: { type: string; event: string; text?: string; x?: number; y?: number }) => void;
    onModelEvent?: (detail: ModelEvent) => void;
  });
  setUiEventHandler(handler?: (detail: { type: string; event: string; text?: string; x?: number; y?: number }) => void): void;
  setUiLabelText(id: string, text: string): boolean;
  resize(width: number, height: number, rect?: { left?: number; top?: number }): void;
  loadModel(url: string | ModelConfig, options?: Partial<ModelConfig>): Promise<unknown>;
  switchModel(url: string | ModelConfig, options?: Partial<ModelConfig>): Promise<unknown>;
  loadScene(config?: SceneConfig | string): Promise<void>;
  clearModel(target: string | { id?: string; url?: string }): number;
  clearAllModels(): number;
  /** Preserve local TRS; null detaches to the scene root. */
  setParent(modelId: string, parentId: string | null): void;
  SetParent(modelId: string, parentId: string | null): void;
  getSelectedModelId(): string | null;
  /** JSON-encoded ModelTransform[] for all loaded models, including waiting children; '[]' when empty. */
  getModelTransforms(): string;
  clearSelection(): void;
  getCameraControlMode(): CameraControlMode;
  setCameraControlMode(mode: CameraControlMode): void;
  onTouchStart(event: ModelTouchEvent): void;
  onTouchMove(event: ModelTouchEvent): void;
  onTouchEnd(event: ModelTouchEvent): void;
  onTouchCancel(event?: Partial<ModelTouchEvent>, reason?: ModelEventReason): void;
  /** Returns whether the camera distance changed; both camera modes support wheel zoom. */
  onWheel(event: ModelWheelEvent): boolean;
  applyDecal(url: string, options?: DecalOptions): Promise<void>;
  updateDecal(options: DecalOptions): void;
  updateDecal(angle: number, height: number, scaleX?: number, scaleY?: number): void;
  updateProjectorDecal(options?: DecalOptions): void;
  removeDecal(): void;
  dispose(): void;
}

export class WechatPlatformAdapter implements PlatformAdapter {
  constructor(options?: { wx?: unknown; canvas?: unknown });
}

export class WebPlatformAdapter implements PlatformAdapter {
  constructor(options?: { window?: Window; document?: Document });
}

export class WebModelViewer {
  constructor(options: {
    container: string | HTMLElement;
    canvas?: HTMLCanvasElement;
    bgColor?: string;
    width?: number;
    height?: number;
    renderMode?: 'on-demand' | 'continuous';
    onUiEvent?: (detail: { type: string; event: string; text?: string; x?: number; y?: number }) => void;
    onModelEvent?: (detail: ModelEvent) => void;
  });
  loadModel(url: string | ModelConfig, options?: Partial<ModelConfig>): Promise<unknown>;
  switchModel(url: string | ModelConfig, options?: Partial<ModelConfig>): Promise<unknown>;
  loadScene(config?: SceneConfig | string): Promise<void>;
  clearModel(target: string | { id?: string; url?: string }): number;
  clearAllModels(): number;
  /** Preserve local TRS; null detaches to the scene root. */
  setParent(modelId: string, parentId: string | null): void;
  SetParent(modelId: string, parentId: string | null): void;
  getSelectedModelId(): string | null;
  /** JSON-encoded ModelTransform[] for all loaded models, including waiting children; '[]' when empty. */
  getModelTransforms(): string;
  clearSelection(): void;
  getCameraControlMode(): CameraControlMode;
  setCameraControlMode(mode: CameraControlMode): void;
  applyDecal(url: string, options?: DecalOptions): Promise<void>;
  updateDecal(options: DecalOptions): void;
  updateDecal(angle: number, height: number, scaleX?: number, scaleY?: number): void;
  updateProjectorDecal(options?: DecalOptions): void;
  removeDecal(): void;
  setUiLabelText(id: string, text: string): boolean;
  resize(): void;
  dispose(): void;
}

export function createWebModelViewer(options: ConstructorParameters<typeof WebModelViewer>[0]): WebModelViewer;

export const THREE: unknown;
export const DEFAULT_SCENE_CONFIG: SceneConfig;
