// SunHero.jsx — lifecycle-safe React host for the Siteworks live sun shader.
// The shader is ported from CosmosMuseum/cosmos-museum-sun (MIT) and keeps the
// supplied hero export's exact orange/violet grade and pointer-driven motion.

const SITEWORKS_SUN_VERTEX_SHADER = `void main(){ gl_Position = vec4(position.xy, 0.0, 1.0); }`;

const SITEWORKS_SUN_FRAGMENT_SHADER = `
precision highp float;
uniform float iTime;
uniform vec3 iResolution;
uniform mat3 uCameraRotation;
uniform float uExposure, uSat, uContrast, uBlack, uWhite;
uniform vec3 uOrange, uViolet;

vec4 hash4(vec4 n){return fract(sin(n)*1399763.5453123);}

float noise4q(vec4 x){
vec4 n3=vec4(0,0.25,0.5,0.75);
vec4 p2=floor(x.wwww+n3);
vec4 b=floor(x.xxxx+n3)+floor(x.yyyy+n3)*157.0+floor(x.zzzz+n3)*113.0;
vec4 p1=b+fract(p2*0.00390625)*vec4(164352.0,-164352.0,163840.0,-163840.0);
p2=b+fract((p2+1.0)*0.00390625)*vec4(164352.0,-164352.0,163840.0,-163840.0);
vec4 f1=fract(x.xxxx+n3);
vec4 f2=fract(x.yyyy+n3);
f1=f1*f1*(3.0-2.0*f1);
f2=f2*f2*(3.0-2.0*f2);
vec4 n1=vec4(0,1.0,157.0,158.0);
vec4 n2=vec4(113.0,114.0,270.0,271.0);
vec4 vs1=mix(hash4(p1),hash4(n1.yyyy+p1),f1);
vec4 vs2=mix(hash4(n1.zzzz+p1),hash4(n1.wwww+p1),f1);
vec4 vs3=mix(hash4(p2),hash4(n1.yyyy+p2),f1);
vec4 vs4=mix(hash4(n1.zzzz+p2),hash4(n1.wwww+p2),f1);
vs1=mix(vs1,vs2,f2);vs3=mix(vs3,vs4,f2);
vs2=mix(hash4(n2.xxxx+p1),hash4(n2.yyyy+p1),f1);
vs4=mix(hash4(n2.zzzz+p1),hash4(n2.wwww+p1),f1);
vs2=mix(vs2,vs4,f2);
vs4=mix(hash4(n2.xxxx+p2),hash4(n2.yyyy+p2),f1);
vec4 vs5=mix(hash4(n2.zzzz+p2),hash4(n2.wwww+p2),f1);
vs4=mix(vs4,vs5,f2);
f1=fract(x.zzzz+n3);f2=fract(x.wwww+n3);
f1=f1*f1*(3.0-2.0*f1);f2=f2*f2*(3.0-2.0*f2);
vs1=mix(vs1,vs2,f1);vs3=mix(vs3,vs4,f1);vs1=mix(vs1,vs3,f2);
float r=dot(vs1,vec4(0.25));
return r*r*(3.0-2.0*r);
}

float noiseSpere(vec3 ray,vec3 pos,float r,mat3 mr,float zoom,vec3 subnoise,float anim){
float b=dot(ray,pos);
float c=dot(pos,pos)-b*b;
vec3 r1=vec3(0.0);
float s=0.0;
float d=0.03125;
float d2=zoom/(d*d);
float ar=5.0;
for(int i=0;i<3;i++){
float rq=r*r;
if(c<rq){
float l1=sqrt(rq-c);
r1=ray*(b-l1)-pos;
r1=r1*mr;
s+=abs(noise4q(vec4(r1*d2+subnoise*ar,anim*ar))*d);
}
ar-=2.0;d*=4.0;d2*=0.0625;r=r-r*0.02;
}
return s;
}

float ring(vec3 ray,vec3 pos,float r,float size){
float b=dot(ray,pos);
float c=dot(pos,pos)-b*b;
return max(0.0,1.0-size*abs(r-sqrt(c)));
}

float ringRayNoise(vec3 ray,vec3 pos,float r,float size,mat3 mr,float anim){
float b=dot(ray,pos);
vec3 pr=ray*b-pos;
float c=length(pr);
pr*=mr;pr=normalize(pr);
float s=max(0.0,1.0-size*abs(r-c));
float nd=noise4q(vec4(pr*1.0,-anim+c))*2.0;
nd=pow(nd,2.0);
float n=0.4;
float ns=1.0;
if(c>r){
n=noise4q(vec4(pr*10.0,-anim+c));
ns=noise4q(vec4(pr*50.0,-anim*2.5+c*2.0))*2.0;
}
n=n*n*nd*ns;
return pow(s,4.0)+s*s*n;
}

vec4 noiseSpace(vec3 ray,vec3 pos,float r,mat3 mr,float zoom,vec3 subnoise,float anim){
float b=dot(ray,pos);
float c=dot(pos,pos)-b*b;
vec3 r1=vec3(0.0);
float s=0.0;
float d=0.0625*1.5;
float d2=zoom/d;
float rq=r*r;
float l1=sqrt(abs(rq-c));
r1=(ray*(b-l1)-pos)*mr;
r1*=d2;
s+=abs(noise4q(vec4(r1+subnoise,anim))*d);
s+=abs(noise4q(vec4(r1*0.5+subnoise,anim))*d*2.0);
s+=abs(noise4q(vec4(r1*0.25+subnoise,anim))*d*4.0);
return vec4(s*2.0,
abs(noise4q(vec4(r1*0.1+subnoise,anim))),
abs(noise4q(vec4(r1*0.1+subnoise*6.0,anim))),
abs(noise4q(vec4(r1*0.1+subnoise*13.0,anim))));
}

float sphereZero(vec3 ray,vec3 pos,float r){
float b=dot(ray,pos);
float c=dot(pos,pos)-b*b;
return c<r*r?0.0:1.0;
}

void main(){
vec2 fragCoord=gl_FragCoord.xy;
vec2 p=(-iResolution.xy+2.0*fragCoord.xy)/iResolution.y+vec2(0.0007,0.0005);
float time=iTime;

vec3 ray=normalize(vec3(p*1.34,2.0));
vec3 pos=vec3(0.0,0.0,3.0);

mat3 mr=uCameraRotation;

/* brand grade: molten orange core, violet flare, warm white hotspots */
vec3 cOrange=uOrange;
vec3 cAmber=mix(uOrange,vec3(1.0,0.92,0.55),0.62);
vec3 cViolet=uViolet;
vec3 cLilac=mix(uViolet,vec3(1.0,0.98,1.0),0.45);
vec3 cWhite=vec3(1.0,0.97,0.90);

float s1=noiseSpere(ray,pos,1.0,mr,0.5,vec3(0.0),time);
s1=pow(min(1.0,s1*2.4),2.0);
float s2=noiseSpere(ray,pos,1.0,mr,4.0,vec3(83.23,34.34,67.453),time);
s2=min(1.0,s2*2.2);

vec4 fragColor=vec4(mix(cAmber,cWhite,pow(s1,40.0))*s1,1.0);
fragColor+=vec4(mix(mix(cOrange,cViolet,pow(s2,2.4)),cAmber,pow(s2,7.0))*s2,1.0);
fragColor.xyz-=vec3(ring(ray,pos,1.03,11.0))*2.0;
fragColor=max(vec4(0.0),fragColor);

float s3=ringRayNoise(ray,pos,0.96,1.0,mr,time);
fragColor.xyz+=mix(vec3(1.0,0.66,0.20),mix(cAmber,cWhite,0.6),pow(s3,2.4))*s3;

float zero=sphereZero(ray,pos,0.9);
if(zero>0.0){
vec4 s4=noiseSpace(ray,pos,100.0,mr,0.05,vec3(1.0,2.0,4.0),0.0);
s4.x=pow(s4.x,3.0);
fragColor.xyz+=mix(mix(cViolet,cLilac,s4.y*1.6),cOrange,s4.w*0.45)*s4.x*pow(s4.z*2.5,3.0)*0.26*zero;
}

fragColor=max(vec4(0.0),fragColor);
// grade: exposure -> levels (black/white) -> contrast -> saturation
vec3 g=fragColor.rgb*uExposure;
g=(g-uBlack)/max(0.001,uWhite-uBlack);
g=(g-0.5)*uContrast+0.5;
float lum=dot(g,vec3(0.2126,0.7152,0.0722));
g=mix(vec3(lum),g,uSat);
fragColor=vec4(clamp(g,0.0,1.0),1.0);
gl_FragColor=fragColor;
}`;

var siteworksThreePromise;

function loadSiteworksThree() {
  if (window.__siteworksThree) return Promise.resolve(window.__siteworksThree);
  if (window.__siteworksThreeError) return Promise.reject(window.__siteworksThreeError);

  if (!siteworksThreePromise) {
    siteworksThreePromise = new Promise((resolve, reject) => {
      const onReady = () => {
        cleanup();
        resolve(window.__siteworksThree);
      };
      const onError = () => {
        cleanup();
        reject(window.__siteworksThreeError || new Error("Three.js did not load."));
      };
      const cleanup = () => {
        clearTimeout(timeout);
        window.removeEventListener("siteworks:three-ready", onReady);
        window.removeEventListener("siteworks:three-error", onError);
      };
      const timeout = setTimeout(() => {
        cleanup();
        reject(new Error("Three.js load timed out."));
      }, 15000);

      window.addEventListener("siteworks:three-ready", onReady, { once: true });
      window.addEventListener("siteworks:three-error", onError, { once: true });

      if (window.__siteworksThree) onReady();
      else if (window.__siteworksThreeError) onError();
    });
  }
  return siteworksThreePromise;
}

function mountSiteworksSun(THREE, container) {
  if (!container || container.dataset.sunMounted === "1") return () => {};
  container.dataset.sunMounted = "1";

  let renderer;
  try {
    renderer = new THREE.WebGLRenderer({
      antialias: false,
      alpha: false,
      preserveDrawingBuffer: true,
    });
  } catch (error) {
    delete container.dataset.sunMounted;
    container.classList.add("sun-failed");
    throw error;
  }

  renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
  container.appendChild(renderer.domElement);

  const camera = new THREE.PerspectiveCamera(50, 1, 0.1, 100);
  camera.position.set(0, 0, 5);

  const uniforms = {
    iTime: { value: 0 },
    iResolution: { value: new THREE.Vector3(1, 1, 1) },
    uCameraRotation: { value: new THREE.Matrix3() },
    uExposure: { value: 1.1 },
    uSat: { value: 1.6 },
    uContrast: { value: 1 },
    uBlack: { value: 0.23 },
    uWhite: { value: 1 },
    uOrange: { value: new THREE.Color("#ffb03a") },
    uViolet: { value: new THREE.Color("#8c6bf0") },
  };

  const scene = new THREE.Scene();
  const geometry = new THREE.PlaneGeometry(2, 2);
  const material = new THREE.ShaderMaterial({
    vertexShader: SITEWORKS_SUN_VERTEX_SHADER,
    fragmentShader: SITEWORKS_SUN_FRAGMENT_SHADER,
    uniforms,
    depthTest: false,
    depthWrite: false,
  });
  const mesh = new THREE.Mesh(geometry, material);
  mesh.frustumCulled = false;
  scene.add(mesh);

  const motionQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
  let reducedMotion = motionQuery.matches;
  const INTRO_DELAY = 0.25;
  const INTRO_DURATION = 2.6;
  let targetExposure = 1.1;
  let intro = reducedMotion ? 1 : 0;
  let introStart = null;
  let inViewport = true;
  let pageVisible = !document.hidden;
  let destroyed = false;
  let raf = 0;
  let running = false;
  let liveFrames = 0;
  let lastTime = 0;
  const animationStart = performance.now();
  const tempMatrix = new THREE.Matrix3();
  const aim = { az: 0, pol: 0 };
  const cur = { az: 0, pol: 0 };

  const draw = (time) => {
    if (destroyed) return;
    lastTime = time;
    cur.az += (aim.az - cur.az) * 0.045;
    cur.pol += (aim.pol - cur.pol) * 0.045;
    const az = cur.az + time * 0.05;
    const pol = cur.pol;
    const rad = 5;
    camera.position.set(
      rad * Math.cos(pol) * Math.sin(az),
      rad * Math.sin(pol),
      rad * Math.cos(pol) * Math.cos(az)
    );
    camera.lookAt(0, 0, 0);
    camera.updateMatrixWorld();

    if (intro < 1) {
      if (introStart == null) introStart = performance.now() / 1000;
      const progress = Math.max(
        0,
        Math.min(
          1,
          (performance.now() / 1000 - introStart - INTRO_DELAY) / INTRO_DURATION
        )
      );
      intro = progress * progress * (3 - 2 * progress);
      uniforms.uExposure.value = targetExposure * intro;
    }

    uniforms.iTime.value = time;
    tempMatrix.getNormalMatrix(camera.matrixWorld);
    uniforms.uCameraRotation.value.copy(tempMatrix);
    renderer.render(scene, camera);
  };

  const size = () => {
    if (destroyed) return;
    const width = Math.max(1, container.clientWidth);
    const height = Math.max(1, container.clientHeight);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
    renderer.setSize(width, height, false);
    uniforms.iResolution.value.set(
      width * renderer.getPixelRatio(),
      height * renderer.getPixelRatio(),
      1
    );
    if (!running) draw(lastTime);
  };

  const shouldAnimate = () =>
    !destroyed && inViewport && pageVisible && !reducedMotion;

  const stopLoop = () => {
    if (raf) cancelAnimationFrame(raf);
    raf = 0;
    running = false;
  };

  const frame = (now) => {
    raf = 0;
    if (!shouldAnimate()) {
      running = false;
      return;
    }
    liveFrames += 1;
    draw((now - animationStart) / 1000);
    raf = requestAnimationFrame(frame);
  };

  const startLoop = () => {
    if (!shouldAnimate() || running) return;
    running = true;
    raf = requestAnimationFrame(frame);
  };

  const syncLoop = () => {
    if (shouldAnimate()) startLoop();
    else stopLoop();
  };

  const onPointerMove = (event) => {
    if (reducedMotion || destroyed) return;
    const rect = container.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;
    const nx = Math.max(
      -1,
      Math.min(1, (event.clientX - centerX) / (window.innerWidth * 0.5))
    );
    const ny = Math.max(
      -1,
      Math.min(1, (event.clientY - centerY) / (window.innerHeight * 0.5))
    );
    aim.az = nx * 0.85;
    aim.pol = -ny * 0.5;
  };

  const onVisibilityChange = () => {
    pageVisible = !document.hidden;
    syncLoop();
  };

  const onMotionChange = (event) => {
    reducedMotion = event.matches;
    if (reducedMotion) {
      intro = 1;
      uniforms.uExposure.value = targetExposure;
      stopLoop();
      draw(lastTime);
    } else {
      startLoop();
    }
  };

  const previousSunGrade = window.__sunGrade;
  const gradeSun = (grade = {}) => {
    if (grade.exposure != null) {
      targetExposure = grade.exposure;
      uniforms.uExposure.value = grade.exposure * intro;
    }
    if (grade.saturation != null) uniforms.uSat.value = grade.saturation;
    if (grade.contrast != null) uniforms.uContrast.value = grade.contrast;
    if (grade.black != null) uniforms.uBlack.value = grade.black;
    if (grade.white != null) uniforms.uWhite.value = grade.white;
    if (grade.orange) uniforms.uOrange.value.set(grade.orange);
    if (grade.violet) uniforms.uViolet.value.set(grade.violet);
    if (!running) draw(lastTime);
  };
  window.__sunGrade = gradeSun;
  if (window.__sunGradePending) gradeSun(window.__sunGradePending);

  size();
  if (!reducedMotion) uniforms.uExposure.value = 0;
  draw(0);

  const introEarly = setTimeout(() => {
    if (destroyed || liveFrames > 0 || intro >= 1) return;
    intro = 1;
    uniforms.uExposure.value = targetExposure;
    draw(0);
  }, 400);

  const introBail = setTimeout(() => {
    if (destroyed || intro >= 1) return;
    intro = 1;
    uniforms.uExposure.value = targetExposure;
    draw(lastTime);
  }, 4000);

  let resizeObserver;
  let intersectionObserver;
  let usingResizeFallback = false;

  if (window.ResizeObserver) {
    resizeObserver = new ResizeObserver(size);
    resizeObserver.observe(container);
  } else {
    usingResizeFallback = true;
    window.addEventListener("resize", size, { passive: true });
  }

  if (window.IntersectionObserver) {
    intersectionObserver = new IntersectionObserver(
      ([entry]) => {
        inViewport = entry.isIntersecting;
        syncLoop();
      },
      { threshold: 0 }
    );
    intersectionObserver.observe(container);
  }

  window.addEventListener("pointermove", onPointerMove, { passive: true });
  document.addEventListener("visibilitychange", onVisibilityChange);
  if (motionQuery.addEventListener) motionQuery.addEventListener("change", onMotionChange);
  else motionQuery.addListener(onMotionChange);

  container.classList.add("sun-live");
  startLoop();
  window.dispatchEvent(new Event("sun-ready"));

  return () => {
    destroyed = true;
    stopLoop();
    clearTimeout(introEarly);
    clearTimeout(introBail);
    if (resizeObserver) resizeObserver.disconnect();
    if (intersectionObserver) intersectionObserver.disconnect();
    if (usingResizeFallback) window.removeEventListener("resize", size);
    window.removeEventListener("pointermove", onPointerMove);
    document.removeEventListener("visibilitychange", onVisibilityChange);
    if (motionQuery.removeEventListener) motionQuery.removeEventListener("change", onMotionChange);
    else motionQuery.removeListener(onMotionChange);

    if (window.__sunGrade === gradeSun) {
      if (previousSunGrade) window.__sunGrade = previousSunGrade;
      else delete window.__sunGrade;
    }

    scene.remove(mesh);
    geometry.dispose();
    material.dispose();
    if (renderer.renderLists) renderer.renderLists.dispose();
    renderer.dispose();
    if (renderer.forceContextLoss) renderer.forceContextLoss();
    renderer.domElement.remove();
    container.classList.remove("sun-live", "sun-failed");
    delete container.dataset.sunMounted;
  };
}

function SunHero({ className = "" }) {
  const hostRef = useRef(null);

  useEffect(() => {
    const host = hostRef.current;
    if (!host) return undefined;

    let cancelled = false;
    let unmountSun = () => {};
    host.dataset.sunStatus = "loading";

    loadSiteworksThree()
      .then((THREE) => {
        if (cancelled) return;
        unmountSun = mountSiteworksSun(THREE, host);
        host.dataset.sunStatus = "ready";
      })
      .catch((error) => {
        if (cancelled) return;
        host.dataset.sunStatus = "failed";
        host.classList.add("sun-failed");
        console.error("Siteworks sun shader could not start.", error);
      });

    return () => {
      cancelled = true;
      unmountSun();
      delete host.dataset.sunStatus;
    };
  }, []);

  return (
    <div className={`sun-wrap${className ? ` ${className}` : ""}`} aria-hidden="true">
      <div ref={hostRef} className="sun-host" data-sun-host="" />
    </div>
  );
}

Object.assign(window, { SunHero });
