/**
 * Post-Processing Pipeline Example
 *
 * Demonstrates TSL post-processing:
 * - Bloom effect
 * - Custom vignette
 * - Color grading
 * - Effect chaining
 *
 * Based on Three.js webgpu_postprocessing examples (MIT License)
 * https://github.com/mrdoob/three.js
 */

import * as THREE from 'three/webgpu';
import {
  Fn,
  float,
  vec2,
  vec3,
  vec4,
  color,
  uniform,
  pass,
  screenUV,
  screenSize,
  time,
  oscSine,
  mix,
  smoothstep,
  texture,
  grayscale,
  saturation
} from 'three/tsl';
import { bloom } from 'three/addons/tsl/display/BloomNode.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

let camera, scene, renderer, controls;
let postProcessing;

// Effect uniforms
const bloomStrength = uniform(1.0);
const bloomThreshold = uniform(0.5);
const vignetteIntensity = uniform(0.5);
const saturationAmount = uniform(1.2);
const colorTint = uniform(new THREE.Color(1.0, 0.95, 0.9));

async function init() {
  // Camera
  camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
  camera.position.set(0, 2, 8);

  // Scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x111111);

  // Add objects with emissive materials (for bloom)
  createScene();

  // Renderer
  renderer = new THREE.WebGPURenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setPixelRatio(window.devicePixelRatio);
  document.body.appendChild(renderer.domElement);
  await renderer.init();

  // Create post-processing pipeline
  setupPostProcessing();

  // Controls
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.target.set(0, 1, 0);

  // Events
  window.addEventListener('resize', onWindowResize);

  renderer.setAnimationLoop(animate);
}

function createScene() {
  // Floor
  const floorGeometry = new THREE.PlaneGeometry(20, 20);
  const floorMaterial = new THREE.MeshStandardNodeMaterial({
    color: 0x222222
  });
  const floor = new THREE.Mesh(floorGeometry, floorMaterial);
  floor.rotation.x = -Math.PI / 2;
  scene.add(floor);

  // Emissive spheres (will bloom)
  const sphereGeometry = new THREE.SphereGeometry(0.5, 32, 32);

  const colors = [0xff0044, 0x00ff88, 0x4488ff, 0xffaa00, 0xff00ff];

  for (let i = 0; i < 5; i++) {
    const material = new THREE.MeshStandardNodeMaterial();

    // Base color
    material.colorNode = color(colors[i]).mul(0.3);

    // Animated emissive
    material.emissiveNode = Fn(() => {
      const pulse = oscSine(time.mul(1.0 + i * 0.2)).mul(0.5).add(0.5);
      return color(colors[i]).mul(pulse.mul(2.0).add(0.5));
    })();

    material.roughnessNode = float(0.2);
    material.metalnessNode = float(0.8);

    const sphere = new THREE.Mesh(sphereGeometry, material);
    sphere.position.set(
      Math.cos((i / 5) * Math.PI * 2) * 3,
      1 + Math.sin(i) * 0.5,
      Math.sin((i / 5) * Math.PI * 2) * 3
    );
    scene.add(sphere);
  }

  // Central reflective sphere
  const centerMaterial = new THREE.MeshStandardNodeMaterial();
  centerMaterial.colorNode = color(0x888888);
  centerMaterial.roughnessNode = float(0.1);
  centerMaterial.metalnessNode = float(1.0);

  const centerSphere = new THREE.Mesh(new THREE.SphereGeometry(1, 64, 64), centerMaterial);
  centerSphere.position.y = 1;
  scene.add(centerSphere);

  // Lights
  const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
  scene.add(ambientLight);

  const pointLight = new THREE.PointLight(0xffffff, 50);
  pointLight.position.set(5, 10, 5);
  scene.add(pointLight);
}

function setupPostProcessing() {
  // Create post-processing instance
  postProcessing = new THREE.PostProcessing(renderer);

  // Create scene pass
  const scenePass = pass(scene, camera);
  const sceneColor = scenePass.getTextureNode('output');

  // --- Effect Chain ---

  // 1. Bloom
  const bloomPass = bloom(sceneColor);
  bloomPass.threshold = bloomThreshold;
  bloomPass.strength = bloomStrength;
  bloomPass.radius = uniform(0.5);

  // Add bloom to scene
  let output = sceneColor.add(bloomPass);

  // 2. Color Grading
  output = saturation(output, saturationAmount);
  output = output.mul(colorTint);

  // 3. Vignette (custom effect)
  const vignette = Fn(() => {
    const uv = screenUV;
    const dist = uv.sub(0.5).length();
    return float(1.0).sub(dist.mul(vignetteIntensity).pow(2.0)).saturate();
  });

  output = output.mul(vignette());

  // 4. Optional: Scanlines
  const scanlines = Fn(() => {
    const scanline = screenUV.y.mul(screenSize.y).mul(0.5).sin().mul(0.05).add(0.95);
    return scanline;
  });

  // Uncomment for CRT effect:
  // output = output.mul(scanlines());

  // Set final output
  postProcessing.outputNode = output;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
  controls.update();

  // Render with post-processing
  postProcessing.render();
}

init();

// Export uniforms for external control (e.g., GUI)
export { bloomStrength, bloomThreshold, vignetteIntensity, saturationAmount, colorTint };
