#include <flutter/runtime_effect.glsl>

// Output surface size in pixels.
uniform vec2 uResolution;
// One source-texel step in UV space: (1/width, 1/height).
uniform vec2 uTexel;
// 1.0 enables CRT post-process effects, 0.0 keeps only base AA.
uniform float uEffectsEnabled;
// Engine time in seconds used to animate scanline travel.
uniform float uTime;
// Source frame produced by the software renderer.
uniform sampler2D uTexture;

out vec4 fragColor;

// Perceptual brightness approximation used for edge detection.
float luma(vec3 color) {
  return dot(color, vec3(0.299, 0.587, 0.114));
}

void main() {
  // Convert fragment coordinates to normalized UV coordinates.
  vec2 uv = FlutterFragCoord().xy / uResolution;

  if (uEffectsEnabled > 0.5) {
    // Barrel-like warp to emulate curved CRT glass.
    vec2 centered = uv * 2.0 - 1.0;
    float radius2 = dot(centered, centered);
    centered *= 1.0 + radius2 * 0.045;
    uv = centered * 0.5 + 0.5;

    // Fill outside warped bounds with a darker consumer-TV charcoal bezel.
    if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
      vec2 clampedUv = clamp(uv, 0.0, 1.0);
      vec2 edgeDelta = uv - clampedUv;
      float overflow = max(abs(edgeDelta.x), abs(edgeDelta.y));

      // Sample near-edge scene colors and spread them onto the bezel.
      vec2 inwardDir = normalize(-edgeDelta + vec2(1e-6));
      vec2 bleedStep = vec2(uTexel.x * 1.6, uTexel.y * 1.6);
      vec2 bleedUv1 = clamp(clampedUv + inwardDir * bleedStep, 0.0, 1.0);
      vec2 bleedUv2 = clamp(clampedUv + inwardDir * bleedStep * 2.6, 0.0, 1.0);
      vec2 bleedUv3 = clamp(clampedUv + inwardDir * bleedStep * 4.2, 0.0, 1.0);
      vec3 edgeBleedColor =
          texture(uTexture, clampedUv).rgb * 0.52 +
          texture(uTexture, bleedUv1).rgb * 0.28 +
          texture(uTexture, bleedUv2).rgb * 0.14 +
          texture(uTexture, bleedUv3).rgb * 0.06;

      // Approximate concave bezel depth by measuring how far this fragment is
      // from the emissive screen boundary in aspect-corrected UV space.
      vec2 aspectScale = vec2(uResolution.x / max(uResolution.y, 1.0), 1.0);
      float bezelDistance = length(edgeDelta * aspectScale);

      // Corners receive less direct bleed because the nearest lit area is
      // diagonally offset, so attenuate glow toward corner regions.
      vec2 clampedCentered = clampedUv * 2.0 - 1.0;
      float cornerFactor = smoothstep(0.60, 1.15, length(clampedCentered));

        float verticalShade = 0.88 + 0.07 * (1.0 - (FlutterFragCoord().y / uResolution.y));
        float depthShade = 1.0 - smoothstep(0.0, 0.058, overflow) * 0.34;
        float grain = sin(FlutterFragCoord().x * 0.21 + FlutterFragCoord().y * 0.11) * 0.006;
        float moldedHighlight = smoothstep(0.072, 0.0, overflow) * 0.028;

        // Deeper arcade-style profile: tighter, scene-tinted bleed rolloff.
        float bezelGlow = exp(-bezelDistance * 82.0) * mix(1.0, 0.56, cornerFactor);
        float innerLip = exp(-bezelDistance * 170.0) * 0.10;
        float bleedStrength = smoothstep(0.12, 0.0, overflow) * (0.78 - cornerFactor * 0.26);

      vec3 bezelColor =
          vec3(0.225, 0.225, 0.215) * verticalShade * depthShade +
          edgeBleedColor * bezelGlow * bleedStrength * 1.12 +
          edgeBleedColor * innerLip * 0.36 +
          vec3(moldedHighlight) +
          vec3(grain);
      fragColor = vec4(bezelColor, 1.0);
      return;
    }
  }

  // Read the base color from the source frame.
  vec4 centerSample = texture(uTexture, uv);

  // Sample 4-neighborhood (N/S/E/W) around the current pixel.
  vec3 sampleN = texture(uTexture, uv + vec2(0.0, -uTexel.y)).rgb;
  vec3 sampleS = texture(uTexture, uv + vec2(0.0, uTexel.y)).rgb;
  vec3 sampleE = texture(uTexture, uv + vec2(uTexel.x, 0.0)).rgb;
  vec3 sampleW = texture(uTexture, uv + vec2(-uTexel.x, 0.0)).rgb;

  // Compute local luma range; wider range means a stronger edge.
  float lumaCenter = luma(centerSample.rgb);
  float lumaMin = min(
    lumaCenter,
    min(min(luma(sampleN), luma(sampleS)), min(luma(sampleE), luma(sampleW)))
  );
  float lumaMax = max(
    lumaCenter,
    max(max(luma(sampleN), luma(sampleS)), max(luma(sampleE), luma(sampleW)))
  );

  float edgeSpan = max(lumaMax - lumaMin, 0.0001);
  // Convert raw edge strength into a smooth 0..1 blending amount.
  float edgeAmount = smoothstep(0.03, 0.18, edgeSpan);

  // Average neighbors and blend toward that average only near edges.
  // This acts like a lightweight edge-aware anti-aliasing pass.
  vec3 neighborhoodAvg = (sampleN + sampleS + sampleE + sampleW) * 0.25;
  vec3 aaColor = mix(centerSample.rgb, neighborhoodAvg, edgeAmount * 0.45);

  // Preserve source alpha and output the anti-aliased color.
  vec3 outColor = aaColor;

  if (uEffectsEnabled > 0.5) {
    // Horizontal scanline modulation.
    float scanlineBand = 0.88 + 0.12 * sin(uv.y * uResolution.y * 3.14159265);

    // Slow bright line crawling down the screen.
    float sweepPos = fract(uTime * 0.08);
    float sweepBand = 1.0 + 0.16 * exp(-pow((uv.y - sweepPos) * 120.0, 2.0));

    // Slight center brightening and edge falloff (CRT phosphor + lens feel).
    vec2 centeredUv = uv * 2.0 - 1.0;
    float vignette = smoothstep(1.15, 0.25, length(centeredUv));
    float centerLift = 1.0 + 0.08 * (1.0 - length(centeredUv));

    outColor *= scanlineBand * sweepBand * centerLift;
    outColor *= mix(0.62, 1.0, vignette);
  }

  fragColor = vec4(outColor, centerSample.a);
}
