/* @jsx React.createElement */
// ─────────────────────────────────────────────────────────────────────
// METASPEAK HDRI Environment Map Manager
// ─────────────────────────────────────────────────────────────────────
// HDRI = High Dynamic Range Image, used for image-based lighting (IBL).
// When applied as scene.environment, every PBR material in the scene
// (avatars, custom GLBs) automatically gets:
//   - realistic reflections from the HDR sky/space
//   - color-bleed from the surrounding environment
//   - softer, more cinematic shading than analytical lights alone
//
// HDRI can also be the visible background (sky) when desired. The
// background can be blurred independently of the lighting (so reflections
// stay sharp while the sky becomes a soft gradient — common cinematic look).
//
// Source workflow:
//   .hdr file → RGBELoader → DataTexture (linear HDR colors)
//                 │
//                 ├─→ PMREMGenerator.fromEquirectangular(hdr)
//                 │     └─→ scene.environment   (used by all PBR mats)
//                 │
//                 └─→ scene.background          (optional skybox display)
//
// Built-in presets bundled as URLs (loaded on demand). User uploads
// override the preset selection. Cached to IDB so refresh is instant.
//
// Public API:
//   const m = new HDRIManager(renderer, scene)
//   await m.loadFromURL(url)          // for presets
//   await m.loadFromBuffer(buf, name)  // for uploaded files
//   m.setIntensity(v)                  // env map energy (0..3)
//   m.setBackgroundVisible(true)       // toggle skybox display
//   m.setBackgroundBlur(0.4)           // 0 = sharp, 1 = max blur
//   m.unload()                         // remove env map entirely

(function() {

// ── Built-in presets ───────────────────────────────────────────────
// These are CC0 / public-domain HDRIs from Poly Haven. We hotlink the
// 1k versions (small file size, ~1MB each) since the kiosk doesn't need
// 4k resolution for IBL — the lighting effect is the same.
const HDRI_PRESETS = {
  'studio_soft':   {
    label: 'Studio Soft',
    url: 'https://cdn.jsdelivr.net/gh/pmndrs/drei-assets/hdri/studio_small_03_1k.hdr',
    desc: 'Neutral studio lighting, soft white. Good default for avatars.',
  },
  'sunset_outdoor': {
    label: 'Sunset Outdoor',
    url: 'https://cdn.jsdelivr.net/gh/pmndrs/drei-assets/hdri/venice_sunset_1k.hdr',
    desc: 'Warm golden hour, dramatic shadows.',
  },
  'sky_overcast': {
    label: 'Sky Overcast',
    url: 'https://cdn.jsdelivr.net/gh/pmndrs/drei-assets/hdri/kloofendal_48d_partly_cloudy_puresky_1k.hdr',
    desc: 'Diffuse blue-grey sky, even lighting.',
  },
  'night_city': {
    label: 'Night City',
    url: 'https://cdn.jsdelivr.net/gh/pmndrs/drei-assets/hdri/dancing_hall_1k.hdr',
    desc: 'Dim warm interior, magic-hour mood.',
  },
  'tropical_beach': {
    label: 'Tropical Beach',
    url: 'https://cdn.jsdelivr.net/gh/pmndrs/drei-assets/hdri/lebombo_1k.hdr',
    desc: 'Bright sun, blue sky — fits Malaysian beach themes.',
  },
};

class HDRIManager {
  constructor(renderer, scene) {
    this.renderer = renderer;
    this.scene = scene;
    this.loader = window.RGBELoader ? new window.RGBELoader() : null;
    this.pmrem = new THREE.PMREMGenerator(renderer);
    this.pmrem.compileEquirectangularShader();

    this.envMap = null;            // PMREM-processed map → scene.environment
    this.rawHDR = null;             // raw equirect texture → scene.background
    this.fileName = null;
    this.sourceKind = null;          // 'preset' | 'upload' | null

    // Display settings (live-tuned via setters)
    this.intensity = 1.0;
    this.backgroundVisible = false;
    this.backgroundBlur = 0.4;

    // Cache renderer state we override so we can restore on unload
    this._origToneMappingExposure = renderer.toneMappingExposure;
  }

  /**
   * Load HDR from a URL (CDN / preset / blob).
   * @returns {Promise<void>}
   */
  async loadFromURL(url, fileName = url.split('/').pop()) {
    if (!this.loader) throw new Error('RGBELoader unavailable');
    return new Promise((resolve, reject) => {
      this.loader.load(
        url,
        (hdrTexture) => {
          try {
            this._installTexture(hdrTexture, fileName);
            resolve();
          } catch (e) { reject(e); }
        },
        undefined,
        (err) => reject(err)
      );
    });
  }

  /**
   * Load HDR from an ArrayBuffer (uploaded file).
   * RGBELoader has a `parse()` helper that takes the raw bytes and returns
   * a DataTexture directly — same shape as load() callback.
   */
  async loadFromBuffer(arrayBuffer, fileName = 'environment.hdr') {
    if (!this.loader) throw new Error('RGBELoader unavailable');
    // RGBELoader.parse expects ArrayBuffer; returns object with data + dims
    const result = this.loader.parse(arrayBuffer);
    if (!result || !result.data) throw new Error('HDR parse failed');
    // Reconstruct a DataTexture matching what load() produces
    const tex = new THREE.DataTexture(
      result.data, result.width, result.height,
      result.format, result.type
    );
    tex.mapping = THREE.EquirectangularReflectionMapping;
    tex.colorSpace = THREE.LinearSRGBColorSpace;
    tex.needsUpdate = true;
    this._installTexture(tex, fileName);
  }

  _installTexture(hdrTexture, fileName) {
    // Always set equirect mapping so PMREMGenerator handles correctly
    hdrTexture.mapping = THREE.EquirectangularReflectionMapping;

    // Dispose previous if any
    this._disposeCurrent();

    this.rawHDR = hdrTexture;
    this.fileName = fileName;

    // Generate PMREM (prefiltered mipmap radiance environment map).
    // This is the version Three.js uses for material reflections — much
    // faster + better quality than using the raw equirect at runtime.
    const pmremRT = this.pmrem.fromEquirectangular(hdrTexture);
    this.envMap = pmremRT.texture;

    // Apply to scene
    this._apply();
    console.log(`[HDRI] loaded "${fileName}" — applied as scene.environment`);
  }

  /**
   * Apply current display settings to the scene.
   */
  _apply() {
    if (!this.envMap) return;
    this.scene.environment = this.envMap;
    this.scene.environmentIntensity = this.intensity;

    if (this.backgroundVisible && this.rawHDR) {
      this.scene.background = this.rawHDR;
      // Three.js r155+ supports backgroundBlurriness for skybox softening
      if ('backgroundBlurriness' in this.scene) {
        this.scene.backgroundBlurriness = this.backgroundBlur;
      }
      this.scene.backgroundIntensity = this.intensity;
    } else {
      // Restore null so the scene's CSS bg shows through
      this.scene.background = null;
    }
  }

  setIntensity(v) {
    this.intensity = Math.max(0, Math.min(3, v));
    this._apply();
  }

  setBackgroundVisible(visible) {
    this.backgroundVisible = !!visible;
    this._apply();
  }

  setBackgroundBlur(blur) {
    this.backgroundBlur = Math.max(0, Math.min(1, blur));
    this._apply();
  }

  isLoaded() { return !!this.envMap; }

  unload() {
    this._disposeCurrent();
    this.scene.environment = null;
    this.scene.background = null;
    this.fileName = null;
    this.sourceKind = null;
  }

  _disposeCurrent() {
    if (this.envMap) {
      this.envMap.dispose?.();
      this.envMap = null;
    }
    if (this.rawHDR) {
      this.rawHDR.dispose?.();
      this.rawHDR = null;
    }
  }

  /** Cleanup PMREM generator on full app teardown. */
  dispose() {
    this.unload();
    this.pmrem?.dispose?.();
  }
}

window.MetaspeakHDRI = { HDRIManager, HDRI_PRESETS };

})();
