/**
 * Ephemeral webcam-face texture binding behind explicit consent.
 *
 * This adapter has no acquisition path: no `getUserMedia`, URL, fetch, canvas,
 * recorder, upload, or storage API. A caller that already owns a stream and a
 * video element may bind them only while the existing consent controller says
 * active. The adapter owns the resulting Three.js texture and nothing else.
 */

import * as THREE from "three";
import type { WebcamFaceConsentController } from "./webcamConsent.ts";

export type WebcamFaceTextureStatus = "off" | "active" | "disposed";

export interface WebcamFaceTextureState {
  status: WebcamFaceTextureStatus;
  active: boolean;
  /** UI must show this whenever live webcam pixels are available to the scene. */
  indicatorVisible: boolean;
  consentRevision: number;
  readonly ephemeral: true;
  readonly persistence: "none";
  readonly recording: false;
  readonly uploading: false;
}

export interface WebcamFaceTextureBinding {
  /** Caller-owned and never stopped. */
  stream: MediaStream;
  /** Caller-owned and never removed, played, paused, or disposed. */
  video: HTMLVideoElement;
}

export interface WebcamFaceTextureAdapter {
  state(): WebcamFaceTextureState;
  /**
   * Creates one adapter-owned `VideoTexture`. The caller must already have
   * attached the stream to the video and started playback after user consent.
   */
  bind(binding: WebcamFaceTextureBinding): THREE.VideoTexture;
  /** Returns null and clears immediately when consent is no longer active. */
  texture(): THREE.VideoTexture | null;
  /** Explicitly reconcile a consent transition and return the new state. */
  sync(): WebcamFaceTextureState;
  clear(): WebcamFaceTextureState;
  dispose(): void;
}

export interface WebcamFaceTextureOptions {
  consent: WebcamFaceConsentController;
  /** Active-indicator hook; fires only when the visible state changes. */
  onIndicatorChange?: (state: WebcamFaceTextureState) => void;
}

export function createWebcamFaceTexture(options: WebcamFaceTextureOptions): WebcamFaceTextureAdapter {
  if (!options.consent || typeof options.consent.state !== "function") {
    throw new RangeError("webcam face texture: consent controller is required");
  }

  let texture: THREE.VideoTexture | null = null;
  let disposed = false;
  let lastIndicator = false;

  function snapshot(): WebcamFaceTextureState {
    const consent = options.consent.state();
    const active = !disposed && texture !== null && consent.status === "active" && consent.consentGranted;
    return {
      status: disposed ? "disposed" : active ? "active" : "off",
      active,
      indicatorVisible: active,
      consentRevision: consent.revision,
      ephemeral: true,
      persistence: "none",
      recording: false,
      uploading: false,
    };
  }

  function announce(): WebcamFaceTextureState {
    const state = snapshot();
    if (state.indicatorVisible !== lastIndicator) {
      lastIndicator = state.indicatorVisible;
      options.onIndicatorChange?.({ ...state });
    }
    return state;
  }

  function release(): void {
    if (!texture) return;
    // `VideoTexture.dispose()` releases only renderer resources. It does not
    // stop tracks or dispose/mutate the caller's video element.
    texture.dispose();
    texture = null;
  }

  function reconcile(): WebcamFaceTextureState {
    const consent = options.consent.state();
    if (texture && (consent.status !== "active" || !consent.consentGranted)) release();
    return announce();
  }

  return {
    state: reconcile,
    bind(binding) {
      if (disposed) throw new Error("webcam face texture: adapter is disposed");
      const consent = options.consent.state();
      if (consent.status !== "active" || !consent.consentGranted) {
        throw new Error("webcam face texture: explicit active consent is required");
      }
      validateBinding(binding);
      release();
      texture = new THREE.VideoTexture(binding.video);
      texture.name = "ephemeral-webcam-face";
      texture.colorSpace = THREE.SRGBColorSpace;
      texture.generateMipmaps = false;
      announce();
      return texture;
    },
    texture() {
      reconcile();
      return texture;
    },
    sync: reconcile,
    clear() {
      if (!disposed) release();
      return announce();
    },
    dispose() {
      if (disposed) return;
      release();
      disposed = true;
      announce();
    },
  };
}

function validateBinding(binding: WebcamFaceTextureBinding): void {
  if (!binding || typeof binding !== "object") throw new TypeError("webcam face texture: binding is required");
  const stream = binding.stream as MediaStream | undefined;
  const video = binding.video as HTMLVideoElement | undefined;
  if (!stream || typeof stream.getVideoTracks !== "function") {
    throw new TypeError("webcam face texture: caller-owned MediaStream is required");
  }
  const tracks = stream.getVideoTracks();
  if (tracks.length === 0) throw new TypeError("webcam face texture: stream has no video track");
  if (!video || typeof video !== "object" || !("srcObject" in video)) {
    throw new TypeError("webcam face texture: caller-owned HTMLVideoElement is required");
  }
  if (video.srcObject !== stream) {
    throw new TypeError("webcam face texture: caller must attach the supplied stream to the video first");
  }
}

