/**
 * LANE-RENDER — the relic. `snapshot.relics` (v6): optical fossils buried in the crust.
 *
 * The brief is a Mario hidden pipe: findable by a player who wanders and looks, invisible
 * to one who doesn't. So an UNFOUND relic is only a faint static-patch decal that breathes
 * — no marker, no minimap ping, no arrow. Its discoverability budget is curiosity. The
 * restraint is the feature: if you can see it from across the map it isn't hidden.
 *
 * FOUND (excavated) → the patch becomes a small monument: a fossil slab on a plinth,
 * lifting out of the ground, lit. §1: the optical soundtrack fossils *sing* when
 * excavated — the singing is LANE-SCREEN's audio band; this is the thing it sings from.
 */
import * as THREE from 'three';
import type { RelicState } from '../contracts';
import { disposeObject } from './coords';

const SHIMMER_VERT = /* glsl */ `
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }
`;

const SHIMMER_FRAG = /* glsl */ `
  precision highp float;
  uniform float uTime;
  varying vec2 vUv;

  float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1,311.7)))*43758.5453); }

  void main() {
    vec2 p = vUv * 2.0 - 1.0;
    float rr = dot(p, p);
    if (rr > 1.0) discard;
    // static crawl: a hidden VHS dropout, quantised so it flickers rather than smears.
    vec2 g = floor(vUv * 22.0) + floor(vec2(uTime * 9.0));
    float s = hash(g);
    // breathe the whole patch so a still eye catches motion, but faintly.
    float breathe = 0.5 + 0.5 * sin(uTime * 1.3);
    float edge = 1.0 - smoothstep(0.4, 1.0, rr);
    float a = s * edge * (0.05 + 0.06 * breathe);   // deliberately tiny
    gl_FragColor = vec4(vec3(0.7, 0.75, 0.85) * s, a);
  }
`;

interface Rec {
  found: boolean;
  obj: THREE.Object3D;
}

const PLANE = new THREE.PlaneGeometry(1, 1);

export class RelicLayer {
  readonly group = new THREE.Group();
  private live = new Map<number, Rec>();
  private seen = new Set<number>();
  private shimmerUniforms = { uTime: { value: 0 } };
  private shimmerMat: THREE.ShaderMaterial;

  constructor() {
    this.group.name = 'relics';
    this.shimmerMat = new THREE.ShaderMaterial({
      uniforms: this.shimmerUniforms,
      vertexShader: SHIMMER_VERT,
      fragmentShader: SHIMMER_FRAG,
      transparent: true,
      depthWrite: false,
    });
  }

  sync(relics: ReadonlyArray<RelicState> | undefined, timeSec: number): void {
    this.shimmerUniforms.uTime.value = timeSec;
    this.seen.clear();

    for (const r of relics ?? []) {
      this.seen.add(r.id);
      let rec = this.live.get(r.id);
      if (rec && rec.found !== r.found) {
        this.drop(r.id, rec); // excavated: swap shimmer → monument
        rec = undefined;
      }
      if (!rec) {
        const obj = r.found ? this.buildMonument() : this.buildShimmer();
        obj.position.set(r.pos.x + 0.5, 0, r.pos.y + 0.5);
        this.group.add(obj);
        rec = { found: r.found, obj };
        this.live.set(r.id, rec);
      }
      if (r.found) {
        // gentle lift + rotate so the excavated fossil reads as an exhibit
        rec.obj.rotation.y = timeSec * 0.5;
        rec.obj.position.y = 0.05 + Math.sin(timeSec * 1.5) * 0.03;
      }
    }

    for (const [id, rec] of this.live) if (!this.seen.has(id)) this.drop(id, rec);
  }

  private buildShimmer(): THREE.Object3D {
    const m = new THREE.Mesh(PLANE, this.shimmerMat);
    m.rotation.x = -Math.PI / 2;
    m.scale.set(2.4, 2.4, 1);
    m.position.y = 0.02;
    m.renderOrder = 2;
    return m;
  }

  private buildMonument(): THREE.Object3D {
    const g = new THREE.Group();
    const plinth = new THREE.Mesh(
      new THREE.CylinderGeometry(0.5, 0.6, 0.4, 12),
      new THREE.MeshStandardMaterial({ color: 0x14121d, roughness: 0.9, emissive: 0x1a2740, emissiveIntensity: 0.4 }),
    );
    plinth.position.y = 0.2;
    plinth.castShadow = plinth.receiveShadow = true;
    // The fossil: a wavy photographic strip that "sings" — an emissive slab, gamut-lit.
    const strip = new THREE.Mesh(
      new THREE.BoxGeometry(0.18, 0.8, 0.5),
      new THREE.MeshStandardMaterial({
        color: 0xffe0b0, emissive: 0xffcaa0, emissiveIntensity: 1.3,
        roughness: 0.3, metalness: 0.4,
      }),
    );
    strip.position.y = 0.8;
    strip.castShadow = true;
    g.add(plinth, strip);
    g.userData.owns = true;
    return g;
  }

  private drop(id: number, rec: Rec): void {
    this.group.remove(rec.obj);
    if (rec.obj.userData.owns) disposeObject(rec.obj); // monuments own their geo/mats
    // shimmers share PLANE + shimmerMat — nothing to dispose
    this.live.delete(id);
  }
}
