/**
 * Custom GLSL shaders for the AIOrb effect.
 *
 * Uses Three.js ShaderMaterial conventions (GLSL 100-style syntax;
 * Three.js auto-converts to GLSL 300 es for WebGL2).
 *
 * Built-in uniforms/attributes provided by Three.js:
 *   modelMatrix, viewMatrix, projectionMatrix, modelViewMatrix,
 *   normalMatrix, cameraPosition, position, normal, uv
 */
// ─── Shared noise utilities ────────────────────────────────────────────────
const noiseGLSL = /* glsl */ `
float hash(vec3 p) {
    p = fract(p * vec3(0.1031, 0.1030, 0.0973));
    p += dot(p, p.yxz + 33.33);
    return fract((p.x + p.y) * p.z);
}

float noise(vec3 x) {
    vec3 i = floor(x);
    vec3 f = fract(x);
    f = f * f * (3.0 - 2.0 * f);

    return mix(
        mix(mix(hash(i + vec3(0,0,0)), hash(i + vec3(1,0,0)), f.x),
            mix(hash(i + vec3(0,1,0)), hash(i + vec3(1,1,0)), f.x), f.y),
        mix(mix(hash(i + vec3(0,0,1)), hash(i + vec3(1,0,1)), f.x),
            mix(hash(i + vec3(0,1,1)), hash(i + vec3(1,1,1)), f.x), f.y),
        f.z);
}
`;
// ─── Dot Sphere ─────────────────────────────────────────────────────────────
const dotSphereVertex = /* glsl */ `
uniform float u_time;

varying vec3 vNormal;
varying vec3 vWorldPosition;
varying vec3 vViewDirection;
varying float vDisplacement;

${noiseGLSL}

void main() {
    // Multi-octave noise for organic surface displacement
    float n1 = noise(normal * 1.8 + u_time * 0.8);
    float n2 = noise(normal * 3.6 - u_time * 0.5) * 0.5;
    float disp = (n1 + n2 - 0.75) * 0.15;

    // Visible traveling wave across the surface
    disp += sin(normal.x * 4.0 + u_time * 2.5) *
            sin(normal.z * 4.0 + u_time * 1.8) * 0.06;

    // Global breathing
    disp += sin(u_time * 1.5) * 0.025;

    vec3 newPos = position + normal * disp;
    vDisplacement = disp;

    vNormal = normalize(normalMatrix * normal);
    vec4 worldPos = modelMatrix * vec4(newPos, 1.0);
    vWorldPosition = worldPos.xyz;
    vViewDirection = normalize(cameraPosition - worldPos.xyz);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);
}
`;
const dotSphereFragment = /* glsl */ `
precision highp float;

uniform float u_time;
uniform vec3  u_color1;
uniform vec3  u_color2;
uniform vec3  u_color3;
uniform float u_dotSize;
uniform float u_gridDensity;
uniform float u_rimIntensity;

varying vec3 vNormal;
varying vec3 vWorldPosition;
varying vec3 vViewDirection;
varying float vDisplacement;

void main() {
    vec3 N = normalize(vNormal);
    vec3 V = normalize(vViewDirection);

    // ── Fresnel ──────────────────────────────────────────
    float NdotV   = max(dot(N, V), 0.0);
    float fresnel = pow(1.0 - NdotV, 2.5);

    // ── Dot pattern on the sphere surface ────────────────
    vec3 pos = normalize(vWorldPosition);

    // Multi-axis rotation so dots tumble in 3D, not just left-to-right
    // Y-axis
    float rY = u_time * 0.2;
    float cY = cos(rY), sY = sin(rY);
    vec3 rotPos = vec3(
        pos.x * cY - pos.z * sY,
        pos.y,
        pos.x * sY + pos.z * cY
    );
    // X-axis (tilt)
    float rX = u_time * 0.13;
    float cX = cos(rX), sX = sin(rX);
    rotPos = vec3(
        rotPos.x,
        rotPos.y * cX - rotPos.z * sX,
        rotPos.y * sX + rotPos.z * cX
    );
    // Z-axis (slow roll)
    float rZ = u_time * 0.08;
    float cZ = cos(rZ), sZ = sin(rZ);
    rotPos = vec3(
        rotPos.x * cZ - rotPos.y * sZ,
        rotPos.x * sZ + rotPos.y * cZ,
        rotPos.z
    );

    float theta    = acos(clamp(rotPos.y, -1.0, 1.0));
    float phi      = atan(rotPos.z, rotPos.x);
    float sinTheta = sin(theta);

    float latGrid = theta * u_gridDensity / 3.14159265;
    float lonGrid = phi  * u_gridDensity * max(sinTheta, 0.15) / 3.14159265;

    vec2  cell    = fract(vec2(latGrid, lonGrid));
    float dotDist = length(cell - 0.5);

    // Dots react to surface displacement – swell on peaks, shrink in valleys
    float dispInfluence = smoothstep(-0.05, 0.15, vDisplacement);
    float pulse = 0.75 + dispInfluence * 0.5
                + sin(u_time * 1.5 + theta * 3.0 + phi * 2.0) * 0.15;
    float adjustedDotSize = u_dotSize * pulse;
    // Sharper edge for more visible dots
    float dotMask = 1.0 - smoothstep(adjustedDotSize - 0.008, adjustedDotSize + 0.008, dotDist);

    // ── Multi-colour rim based on normal direction ───────
    float cAngle = u_time * 0.35;
    float cc = cos(cAngle), sc = sin(cAngle);
    vec3 colorDir = vec3(
        N.x * cc - N.z * sc,
        N.y,
        N.x * sc + N.z * cc
    );

    float vert = colorDir.y * 0.5 + 0.5;
    vec3  rimColor = mix(u_color3, u_color1, vert);
    float midW     = 1.0 - abs(vert - 0.5) * 2.0;
    rimColor = mix(rimColor, u_color2, midW * 0.6);

    // ── Compose ──────────────────────────────────────────
    // Dots: bright coloured points with hot white core
    vec3  dotColor  = rimColor * 2.0 + vec3(0.2);
    float dotBright = dotMask * 0.85;

    // Rim glow (independent of dots – visible at edges)
    float rimGlow   = pow(fresnel, 1.5) * u_rimIntensity;
    vec3  glowColor = rimColor * rimGlow;

    vec3  col   = dotColor * dotBright + glowColor;
    // Between dots: transparent → waves show through; on dots: near-opaque
    float alpha = max(dotBright, pow(fresnel, 1.0) * 0.7);

    gl_FragColor = vec4(col, alpha);
}
`;
// ─── Inner wave layers (blurred flowing colour inside the orb) ──────────────
const waveVertex = /* glsl */ `
uniform float u_time;
uniform float u_flowSpeed;

varying vec3 vNormal;
varying vec3 vWorldPosition;
varying vec3 vViewDirection;

${noiseGLSL}

void main() {
    // Organic displacement matching the outer dot sphere deformation
    float t = u_time * 0.8;
    float n1 = noise(normal * 1.8 + t);
    float n2 = noise(normal * 3.6 - t * 0.625) * 0.5;
    float disp = (n1 + n2 - 0.75) * 0.15;
    disp += sin(normal.x * 4.0 + t * 3.125) *
            sin(normal.z * 4.0 + t * 2.25) * 0.06;
    disp += sin(t * 1.875) * 0.025;

    vec3 newPos = position + normal * disp;

    vNormal = normalize(normalMatrix * normal);
    vec4 worldPos = modelMatrix * vec4(newPos, 1.0);
    vWorldPosition = worldPos.xyz;
    vViewDirection = normalize(cameraPosition - worldPos.xyz);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPos, 1.0);
}
`;
const waveFragment = /* glsl */ `
precision highp float;

uniform float u_time;
uniform vec3  u_color1;
uniform vec3  u_color2;
uniform vec3  u_color3;
uniform float u_intensity;
uniform float u_noiseScale;
uniform float u_flowSpeed;

varying vec3 vNormal;
varying vec3 vWorldPosition;
varying vec3 vViewDirection;

${noiseGLSL}

void main() {
    vec3 N = normalize(vNormal);
    vec3 V = normalize(vViewDirection);
    vec3 pos = normalize(vWorldPosition);

    float t = u_time * u_flowSpeed;

    // ── Three overlapping soft wave layers ───────────────

    // Layer 1 – large blobs, clearly moving
    vec3 flow1 = vec3(sin(t * 0.8), cos(t * 0.6), sin(t * 0.5)) * 0.8;
    float w1 = noise(pos * u_noiseScale + flow1);
    w1 = smoothstep(0.2, 0.8, w1);

    // Layer 2 – medium detail, different axis
    vec3 flow2 = vec3(cos(t * 0.7), sin(t * 0.9), cos(t * 0.6)) * 0.7;
    float w2 = noise(pos * u_noiseScale * 1.6 + flow2 + 100.0);
    w2 = smoothstep(0.25, 0.75, w2);

    // Layer 3 – faster, finer detail
    vec3 flow3 = vec3(sin(t * 1.1), cos(t * 0.8), sin(t * 1.2)) * 0.6;
    float w3 = noise(pos * u_noiseScale * 2.8 + flow3 + 200.0);
    w3 = smoothstep(0.3, 0.7, w3);

    // ── Colour each layer – bright and glowing ───────────
    vec3 col = u_color1 * w1 * 0.8
             + u_color2 * w2 * 0.6
             + u_color3 * w3 * 0.5;

    // Vertical base wash
    float yGrad = pos.y * 0.5 + 0.5;
    col += mix(u_color3, u_color1, yGrad) * 0.15;

    // ── Edge fade – glowing centre, soft feathered rim ────
    float NdotV = max(dot(N, V), 0.0);
    float centerFade = smoothstep(0.0, 0.55, NdotV);

    col *= u_intensity;
    float alpha = centerFade * 0.85;

    gl_FragColor = vec4(col, alpha);
}
`;
// ─── Glow layers (inner rim + outer atmosphere) ─────────────────────────────
const glowVertex = /* glsl */ `
varying vec3 vNormal;
varying vec3 vViewDirection;

void main() {
    vNormal = normalize(normalMatrix * normal);
    vec4 worldPos = modelMatrix * vec4(position, 1.0);
    vViewDirection = normalize(cameraPosition - worldPos.xyz);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
const glowFragment = /* glsl */ `
precision highp float;

uniform float u_time;
uniform vec3  u_color1;
uniform vec3  u_color2;
uniform vec3  u_color3;
uniform float u_intensity;
uniform float u_falloff;

varying vec3 vNormal;
varying vec3 vViewDirection;

void main() {
    vec3 N = normalize(vNormal);
    vec3 V = normalize(vViewDirection);

    float NdotV   = max(dot(N, V), 0.0);
    float fresnel = pow(1.0 - NdotV, u_falloff);

    // Rotate colour mapping
    float a  = u_time * 0.35;
    float ca = cos(a), sa = sin(a);
    vec3 dir = vec3(
        N.x * ca - N.z * sa,
        N.y,
        N.x * sa + N.z * ca
    );

    float vert = dir.y * 0.5 + 0.5;
    vec3  col  = mix(u_color3, u_color1, vert);
    float midW = 1.0 - abs(vert - 0.5) * 2.0;
    col = mix(col, u_color2, midW * 0.6);

    // Subtle glow pulsing
    float pulse = 0.88 + sin(u_time * 1.0) * 0.12;
    float alpha = fresnel * u_intensity * pulse;

    gl_FragColor = vec4(col * alpha, alpha);
}
`;

export { dotSphereFragment, dotSphereVertex, glowFragment, glowVertex, waveFragment, waveVertex };
