/**
 * Render hooks for authored office screens.
 *
 * Screen faces remain batched in `Furnishings`; this layer adds one thin overlay
 * only for each interactive monitor/display. Each overlay has an independent
 * material, so replacing one screen never splits every other furniture batch.
 * It consumes an already-authorized caller-owned `VideoTexture`, never a source
 * locator, URL, stream, or identity record.
 */

import * as THREE from "three";
import type { Plan } from "../interiors/plan.ts";

const SCREEN_KINDS = new Set(["tera:screen.monitor", "tera:screen.wall-display"]);
const PLACEHOLDER_COLOR = 0x101820;

export interface MediaSurfaceDescriptor {
  screenId: string;
  officeId: string;
  levelId: string;
  roomId: string | null;
  kind: string;
  bound: boolean;
}

/** Minimal output of server authorization; source locators never enter this layer. */
export interface MediaSurfaceGrant {
  canView: boolean;
  optedIn: boolean;
}

export interface OfficeMediaPresentation {
  group: THREE.Group;
  list(): MediaSurfaceDescriptor[];
  bind(screenId: string, grant: MediaSurfaceGrant, texture: THREE.VideoTexture): boolean;
  clear(screenId: string): boolean;
  dispose(): void;
}

interface Surface {
  descriptor: MediaSurfaceDescriptor;
  mesh: THREE.Mesh;
  material: THREE.MeshBasicMaterial;
  /** Caller-owned. Kept only while bound and never disposed here. */
  texture: THREE.VideoTexture | null;
}

export function createOfficeMediaPresentation(
  plan: Plan,
  furnishings: THREE.Object3D,
): OfficeMediaPresentation {
  const group = new THREE.Group();
  group.name = "office-media-surfaces";
  const surfaces = new Map<string, Surface>();
  let disposed = false;

  furnishings.traverse((object) => {
    if (!(object instanceof THREE.InstancedMesh)) return;
    const separator = object.name.indexOf(":screenDisplay");
    if (separator < 0) return;
    const kind = object.name.slice(0, separator);
    if (!SCREEN_KINDS.has(kind)) return;
    const ids = object.userData.props as string[] | undefined;
    if (!ids || ids.length !== object.count) return;

    for (let index = 0; index < object.count; index += 1) {
      const screenId = ids[index];
      if (!screenId || surfaces.has(screenId)) continue;
      const prop = plan.prop(screenId);
      if (!prop) continue;
      const level = plan.level(prop.levelId);
      if (!level) continue;

      const material = placeholderMaterial(screenId);
      const mesh = new THREE.Mesh(object.geometry, material);
      mesh.name = `media-surface:${screenId}`;
      mesh.matrixAutoUpdate = false;
      object.getMatrixAt(index, mesh.matrix);
      mesh.matrixWorldNeedsUpdate = true;
      mesh.castShadow = false;
      mesh.receiveShadow = false;
      mesh.renderOrder = 1;
      const roomId = plan.roomAt(prop.levelId, prop.position)?.id ?? null;
      const descriptor: MediaSurfaceDescriptor = {
        screenId,
        officeId: plan.office.id,
        levelId: prop.levelId,
        roomId,
        kind,
        bound: false,
      };
      mesh.userData.mediaSurface = { ...descriptor };
      group.add(mesh);
      surfaces.set(screenId, { descriptor, mesh, material, texture: null });
    }
  });

  function clearSurface(surface: Surface): void {
    if (surface.material.map !== null) {
      surface.material.map = null;
      surface.material.color.setHex(PLACEHOLDER_COLOR);
      surface.material.needsUpdate = true;
    }
    surface.texture = null;
    surface.descriptor.bound = false;
    surface.mesh.userData.mediaSurface = { ...surface.descriptor };
  }

  return {
    group,
    list() {
      return [...surfaces.values()].map(({ descriptor }) => ({ ...descriptor }));
    },
    bind(screenId, grant, texture) {
      if (disposed) return false;
      const surface = surfaces.get(screenId);
      if (!surface) return false;
      if (!grant.canView || !grant.optedIn) {
        clearSurface(surface);
        return false;
      }
      surface.texture = texture;
      surface.material.map = texture;
      surface.material.color.setHex(0xffffff);
      surface.material.needsUpdate = true;
      surface.descriptor.bound = true;
      surface.mesh.userData.mediaSurface = { ...surface.descriptor };
      return true;
    },
    clear(screenId) {
      if (disposed) return false;
      const surface = surfaces.get(screenId);
      if (!surface) return false;
      clearSurface(surface);
      return true;
    },
    dispose() {
      if (disposed) return;
      disposed = true;
      for (const surface of surfaces.values()) {
        clearSurface(surface);
        // Geometry belongs to the original furniture batch; the small overlay
        // owns only its cloned material.
        surface.material.dispose();
        surface.mesh.removeFromParent();
      }
      surfaces.clear();
      group.clear();
    },
  };
}

function placeholderMaterial(screenId: string): THREE.MeshBasicMaterial {
  return new THREE.MeshBasicMaterial({
    name: `media-placeholder:${screenId}`,
    color: PLACEHOLDER_COLOR,
    toneMapped: false,
    polygonOffset: true,
    polygonOffsetFactor: -1,
    polygonOffsetUnits: -1,
  });
}

