const canvas = document.getElementById("glCanvas");
const gl = canvas.getContext("webgl");
const shaderButton = document.getElementById("shaderButton");
const shaderTextArea = document.getElementById("shaderTextArea");

if (!gl) {
  alert("WebGL not supported in your browser!");
}

let fragmentShaderSource = `
    precision highp float;
    uniform vec2 iResolution;
    uniform float iTime;

    // Your shader functions here
    float field(in vec3 p, float s) {
        float strength = 7. + .03 * log(1.e-6 + fract(sin(iTime) * 4373.11));
        float accum = s/4.;
        float prev = 0.;
        float tw = 0.;
        for (int i = 0; i < 26; ++i) {
            float mag = dot(p, p);
            p = abs(p) / mag + vec3(-.5, -.4, -1.5);
            float w = exp(-float(i) / 7.);
            accum += w * exp(-strength * pow(abs(mag - prev), 2.2));
            tw += w;
            prev = mag;
        }
        return max(0., 5. * accum / tw - .7);
    }

    float field2(in vec3 p, float s) {
        float strength = 7. + .03 * log(1.e-6 + fract(sin(iTime) * 4373.11));
        float accum = s/4.;
        float prev = 0.;
        float tw = 0.;
        for (int i = 0; i < 18; ++i) {
            float mag = dot(p, p);
            p = abs(p) / mag + vec3(-.5, -.4, -1.5);
            float w = exp(-float(i) / 7.);
            accum += w * exp(-strength * pow(abs(mag - prev), 2.2));
            tw += w;
            prev = mag;
        }
        return max(0., 5. * accum / tw - .7);
    }

    vec3 nrand3(vec2 co) {
        vec3 a = fract(cos(co.x * 8.3e-3 + co.y) * vec3(1.3e5, 4.7e5, 2.9e5));
        vec3 b = fract(sin(co.x * 0.3e-3 + co.y) * vec3(8.1e5, 1.0e5, 0.1e5));
        return mix(a, b, 0.5);
    }

    void main() {
        vec2 uv = gl_FragCoord.xy / iResolution.xy;
        vec2 uvs = uv * iResolution.xy / max(iResolution.x, iResolution.y);
        vec3 p = vec3(uvs / 4., 0) + vec3(1., -1.3, 0.);
        p += 0.2 * vec3(sin(iTime / 16.), sin(iTime / 12.), sin(iTime / 128.));
        float t = field(p, 0.0);
        float v = (1. - exp((abs(uv.x - 0.5) - 0.5) * 6.)) * (1. - exp((abs(uv.y - 0.5) - 0.5) * 6.));
        vec3 p2 = vec3(uvs / (4. + sin(iTime * 0.11) * 0.2 + 0.2 + sin(iTime * 0.15) * 0.3 + 0.4), 1.5) + vec3(2., -1.3, -1.);
        p2 += 0.25 * vec3(sin(iTime / 16.), sin(iTime / 12.), sin(iTime / 128.));
        float t2 = field2(p2, 0.0);
        vec4 c2 = mix(vec4(0.4), vec4(1.0), v) * vec4(1.3 * t2 * t2 * t2, 1.8 * t2 * t2, t2, t2);
        vec2 seed = p.xy * 2.0;
        seed = floor(seed * iResolution.x);
        vec3 rnd = nrand3(seed);
        vec4 starcolor = vec4(pow(rnd.y, 40.0));
        vec2 seed2 = p2.xy * 2.0;
        seed2 = floor(seed2 * iResolution.x);
        vec3 rnd2 = nrand3(seed2);
        starcolor += vec4(pow(rnd2.y, 40.0));
        gl_FragColor = mix(vec4(0.0), vec4(1.0), v) * vec4(1.5 * t * t * t, 1.2 * t * t, t, 1.0) + c2 + starcolor;
    }
`;

// Load shader from localStorage if available
const savedShader = localStorage.getItem("fragmentShader");
if (savedShader) {
  fragmentShaderSource = savedShader;
}

// Compile shader function
function compileShader(gl, source, type) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error("Shader compilation error:", gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

// Create shader program
const vertexShaderSource = `
    attribute vec2 position;
    void main() {
        gl_Position = vec4(position, 0.0, 1.0);
    }
`;

const vertexShader = compileShader(gl, vertexShaderSource, gl.VERTEX_SHADER);
let fragmentShader = compileShader(gl, fragmentShaderSource, gl.FRAGMENT_SHADER);

const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
  console.error("Shader program linking error:", gl.getProgramInfoLog(shaderProgram));
}

gl.useProgram(shaderProgram);

// Set up vertex buffer
const vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);

const positionAttributeLocation = gl.getAttribLocation(shaderProgram, "position");
gl.enableVertexAttribArray(positionAttributeLocation);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);

// Set uniform locations
let resolutionUniformLocation = gl.getUniformLocation(shaderProgram, "iResolution");
let timeUniformLocation = gl.getUniformLocation(shaderProgram, "iTime");
let mouseUniformLocation = gl.getUniformLocation(shaderProgram, "iMouse");

// Mouse state
let mouseX = 0;
let mouseY = 0;
let mouseInitialized = false;

canvas.addEventListener('mousemove', (e) => {
  mouseX = e.clientX;
  mouseY = canvas.height - e.clientY; // Flip Y for shader coords
  mouseInitialized = true;
});

// Render loop
function render(time) {
  gl.viewport(0, 0, canvas.width, canvas.height);
  gl.uniform2f(resolutionUniformLocation, canvas.width, canvas.height);
  gl.uniform1f(timeUniformLocation, time * 0.001);
  
  if (mouseInitialized) {
    gl.uniform2f(mouseUniformLocation, mouseX, mouseY);
  } else {
    gl.uniform2f(mouseUniformLocation, canvas.width / 2, canvas.height / 2);
  }
  
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  requestAnimationFrame(render);
}

// Handle resize
function resizeCanvas() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  gl.viewport(0, 0, canvas.width, canvas.height);
}

window.addEventListener("resize", resizeCanvas);
resizeCanvas();

// Start rendering
requestAnimationFrame(render);

// Handle button click
shaderButton.addEventListener("click", () => {
  shaderTextArea.style.display = "block";
  shaderTextArea.value = fragmentShaderSource;
});

const shaderFileInput = document.getElementById('shaderFileInput');
const loadShaderButton = document.getElementById('loadShaderButton');

loadShaderButton.addEventListener('click', () => {
  shaderFileInput.click();
});

shaderFileInput.addEventListener('change', (e) => {
  const file = e.target.files[0];
  if (!file) return;
  
  const reader = new FileReader();
  reader.onload = (event) => {
    const cSource = event.target.result;
    const glslSource = parseCShader(cSource);
    if (glslSource) {
      fragmentShaderSource = glslSource;
      localStorage.setItem("fragmentShader", fragmentShaderSource);
      compileNewShader(fragmentShaderSource);
      alert('Shader loaded successfully!');
    } else {
      alert('Could not parse shader from file');
    }
  };
  reader.readAsText(file);
});

function parseCShader(source) {
  const mainImageMatch = source.match(/void\s+mainImage\s*\([^)]*\)\s*\{[\s\S]*?\n\}/);
  if (!mainImageMatch) return null;
  
  const mainImageBody = mainImageMatch[0];
  
  let glsl = 'precision highp float;\n';
  glsl += 'uniform vec2 iResolution;\n';
  glsl += 'uniform float iTime;\n';
  glsl += 'uniform vec2 iMouse;\n';
  glsl += '\n';
  
  const defines = source.match(/#define\s+\w+\s+[\d.]+/g);
  if (defines) {
    defines.forEach(def => {
      const match = def.match(/#define\s+(\w+)\s+([\d.]+)/);
      if (match) {
        let val = match[2];
        if (val === '02.') val = '2.0';
        else if (val.endsWith('.')) val += '0';
        glsl += `#define ${match[1]} ${val}\n`;
      }
    });
    glsl += '\n';
  }
  
  const functionRegex = /^(float|vec2|vec3|vec4|mat2|mat3|mat4)\s+\w+\s*\([^)]*\)\s*\{[\s\S]*?\n\}/gm;
  let match;
  const functions = [];
  while ((match = functionRegex.exec(source)) !== null) {
    if (!match[0].includes('mainImage')) {
      functions.push(match[0]);
    }
  }
  glsl += functions.join('\n\n');
  glsl += '\n\n';
  
  glsl += mainImageBody
    .replace(/void\s+mainImage\s*\([^)]*\)\s*\{/, 'void main() {')
    .replace(/fragColor\s*=/, 'gl_FragColor =')
    .replace(/\bfragCoord\b/g, 'gl_FragCoord.xy');
  
  return glsl;
}

function compileNewShader(source) {
  const newShader = compileShader(gl, source, gl.FRAGMENT_SHADER);
  if (!newShader) {
    console.error('Failed to compile new shader');
    return;
  }
  
  gl.deleteProgram(shaderProgram);
  
  const newProgram = gl.createProgram();
  gl.attachShader(newProgram, vertexShader);
  gl.attachShader(newProgram, newShader);
  gl.linkProgram(newProgram);
  
  if (!gl.getProgramParameter(newProgram, gl.LINK_STATUS)) {
    console.error('Shader program linking error:', gl.getProgramInfoLog(newProgram));
    return;
  }
  
  resolutionUniformLocation = gl.getUniformLocation(newProgram, "iResolution");
  timeUniformLocation = gl.getUniformLocation(newProgram, "iTime");
  mouseUniformLocation = gl.getUniformLocation(newProgram, "iMouse");
  
  gl.useProgram(newProgram);
  
  const newPositionAttributeLocation = gl.getAttribLocation(newProgram, "position");
  gl.enableVertexAttribArray(newPositionAttributeLocation);
  gl.vertexAttribPointer(newPositionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
  
  // Update global references
  shaderProgram = newProgram;
  fragmentShader = newShader;
}

// Handle textarea blur (when user is done editing)
shaderTextArea.addEventListener("blur", () => {
  shaderTextArea.style.display = "none";
  const newShaderSource = shaderTextArea.value;
  if (newShaderSource) {
    fragmentShaderSource = newShaderSource;
    localStorage.setItem("fragmentShader", fragmentShaderSource);
    compileNewShader(fragmentShaderSource);
  }
});
