#version 440

layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;

layout(std140, binding = 0) uniform buf {
  mat4 qt_Matrix;
  float qt_Opacity;
  float paddedX;
  float paddedY;
  float paddedW;
  float paddedH;
  float smoothFactor;
  int rectCount;
  int myIndex;
  vec4 color;
  int hasInverted;
  float invertedRadius;
  vec4 invertedOuter;
  vec4 invertedInner;
  vec4 rectData[80];
};

float sdRoundedBox(vec2 p, vec2 center, vec2 halfSize, float radius) {
  vec2 d = abs(p - center) - halfSize + vec2(radius);
  return length(max(d, vec2(0.0))) + min(max(d.x, d.y), 0.0) - radius;
}

float sdRoundedBox4(vec2 p, vec2 center, vec2 halfSize, vec4 r) {
  // r = (topRight, bottomRight, bottomLeft, topLeft)
  p -= center;
  r.xy = (p.x > 0.0) ? r.xy : r.wz;
  r.x = (p.y > 0.0) ? r.y : r.x;
  vec2 q = abs(p) - halfSize + r.x;
  return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
}

float sdBox(vec2 p, vec2 center, vec2 halfSize) {
  vec2 d = abs(p - center) - halfSize;
  return length(max(d, vec2(0.0))) + min(max(d.x, d.y), 0.0);
}

float smin(float a, float b, float k) {
  // Cubic smooth min (C2 continuous — no curvature kinks at blend boundary)
  float h = max(k - abs(a - b), 0.0) / k;
  return min(a, b) - h * h * h * k * (1.0 / 6.0);
}

float smax(float a, float b, float k) {
  float h = max(k - abs(a - b), 0.0) / k;
  return max(a, b) + h * h * h * k * (1.0 / 6.0);
}

float smaxSharpA(float a, float b, float k) {
  // smax variant that keeps a's boundary sharp (no inward rounding at a = 0).
  // Used for the frame outer edge so it always fills to the edges.
  float h = max(k - abs(a - b), 0.0) / k;
  float blend = h * h * h * k * (1.0 / 6.0);
  blend *= smoothstep(0.0, k * 0.5, -a);
  return max(a, b) + blend;
}

void main() {
  vec2 pixel = vec2(paddedX, paddedY) + qt_TexCoord0 * vec2(paddedW, paddedH);

  // Phase 1: compute per-rect SDF, track owner. We can't smin yet because
  // excluded pairs need to skip the smooth blend, which requires pairwise pass
  // below.
  float dArr[16];
  int owner = -2;
  float minDist = 1e10;

  for (int i = 0; i < rectCount; i++) {
    vec4 rect = rectData[i * 5]; // cx, cy, hw, hh
    vec4 props =
        rectData[i * 5 + 1]; // excludeMask(int bits), offsetX, offsetY, minEig
    vec4 invDm = rectData[i * 5 + 2]; // inverse deform matrix
    vec4 sh = rectData[i * 5 + 3];    // screenHalfX, screenHalfY, 0, 0
    vec4 radii =
        rectData[i * 5 + 4]; // effective per-corner radii (tr, br, bl, tl)

    // Offset center for asymmetric deformation
    vec2 center = rect.xy + props.yz;

    // AABB early-out: skip rects far from this pixel
    vec2 extent = sh.xy + vec2(smoothFactor * 1.5);
    if (abs(pixel.x - center.x) > extent.x ||
        abs(pixel.y - center.y) > extent.y) {
      dArr[i] = 1e10;
      continue;
    }

    // Apply pre-computed inverse deformation to the evaluation point
    mat2 invDeform = mat2(invDm.xy, invDm.zw);
    vec2 transformedPixel = center + invDeform * (pixel - center);

    // Use pre-computed effective per-corner radii
    float d = sdRoundedBox4(transformedPixel, center, rect.zw, radii);

    // Use pre-computed minimum eigenvalue for SDF correction
    d *= max(props.w, 0.01);

    // Scale SDF on the axis facing a nearby border to narrow the smin blend
    // zone in that direction only, without reducing k (which would cause sharp
    // edges).
    if (hasInverted != 0) {
      vec2 screenHalf = sh.xy;

      float distY0 =
          (center.y + screenHalf.y) - (invertedInner.y - invertedInner.w);
      float distY1 =
          (invertedInner.y + invertedInner.w) - (center.y - screenHalf.y);
      float distX0 =
          (center.x + screenHalf.x) - (invertedInner.x - invertedInner.z);
      float distX1 =
          (invertedInner.x + invertedInner.z) - (center.x - screenHalf.x);

      // 0 = far from border, 1 = at border (max compression)
      float yProx = 1.0 - min(smoothstep(0.0, smoothFactor, distY0),
                              smoothstep(0.0, smoothFactor, distY1));
      float xProx = 1.0 - min(smoothstep(0.0, smoothFactor, distX0),
                              smoothstep(0.0, smoothFactor, distX1));

      // Smooth axis weights: gradient-based at corners, face-based inside.
      vec2 q = abs(pixel - center) - screenHalf;
      vec2 qp = max(q, vec2(0.0));
      float cornerLen = length(qp);

      // Gradient direction in corner region (smooth 90-degree rotation)
      float gradX = qp.x / max(cornerLen, 0.001);
      float gradY = qp.y / max(cornerLen, 0.001);

      // Smooth face weights for inside/edge (no hard branch)
      float faceY = smoothstep(-4.0, 4.0, q.y - q.x);
      float faceX = 1.0 - faceY;

      // Blend: gradient in corner region, face-based inside
      float t = smoothstep(0.0, 2.0, cornerLen);
      float xWeight = mix(faceX, gradX, t);
      float yWeight = mix(faceY, gradY, t);

      float boost = 3.0;
      float scale = 1.0 + (xProx * xWeight + yProx * yWeight) * boost;
      d *= scale;
    }

    dArr[i] = d;
    if (d < smoothFactor && d < minDist) {
      minDist = d;
      owner = i;
    }
  }

  // Phase 2: hard-min baseline over all rects.
  float mergedSdf = 1e10;
  for (int i = 0; i < rectCount; i++) {
    mergedSdf = min(mergedSdf, dArr[i]);
  }

  // Phase 3: pair-wise smin contributions, skipping excluded pairs. Pair smin
  // <= min, so taking the min over all non-excluded pair smins gives the
  // smoothly-merged SDF.
  for (int i = 0; i < rectCount; i++) {
    if (dArr[i] >= 1e9)
      continue;
    int excludeMask = floatBitsToInt(rectData[i * 5 + 1].x);
    for (int j = i + 1; j < rectCount; j++) {
      if (dArr[j] >= 1e9)
        continue;
      if ((excludeMask & (1 << j)) != 0)
        continue;
      // smin only deviates from min within smoothFactor
      if (abs(dArr[i] - dArr[j]) >= smoothFactor)
        continue;
      mergedSdf = min(mergedSdf, smin(dArr[i], dArr[j], smoothFactor));
    }
  }

  if (hasInverted != 0) {
    float dOuter = sdBox(pixel, invertedOuter.xy, invertedOuter.zw) - 1.0;
    float dInner =
        sdRoundedBox(pixel, invertedInner.xy, invertedInner.zw, invertedRadius);

    // Border sinks: track the opposite rect edge, clamped to border thickness
    float innerTop = invertedInner.y - invertedInner.w;
    float innerBot = invertedInner.y + invertedInner.w;
    float innerLeft = invertedInner.x - invertedInner.z;
    float innerRight = invertedInner.x + invertedInner.z;
    float outerTop = invertedOuter.y - invertedOuter.w;
    float outerBot = invertedOuter.y + invertedOuter.w;
    float outerLeft = invertedOuter.x - invertedOuter.z;
    float outerRight = invertedOuter.x + invertedOuter.z;

    float sinkValue = 0.0;
    for (int i = 0; i < rectCount; i++) {
      vec4 rect = rectData[i * 5];
      vec4 sinkProps = rectData[i * 5 + 1];
      vec2 sinkSh = rectData[i * 5 + 3].xy;

      // Screen-space center (with offset) and pre-computed AABB half-extents
      vec2 ctr = rect.xy + sinkProps.yz;

      // Delay sink to absorb smin blend depth (cubic smin max = k/6)
      float preOff = smoothFactor * (1.0 / 6.0);

      // Top border: track rect's BOTTOM edge, only within border thickness
      float topPen = clamp(innerTop - (ctr.y + sinkSh.y) - preOff, 0.0,
                           innerTop - outerTop);

      // Bottom border: track rect's TOP edge
      float botPen = clamp((ctr.y - sinkSh.y) - innerBot - preOff, 0.0,
                           outerBot - innerBot);

      // Left border: track rect's RIGHT edge
      float leftPen = clamp(innerLeft - (ctr.x + sinkSh.x) - preOff, 0.0,
                            innerLeft - outerLeft);

      // Right border: track rect's LEFT edge
      float rightPen = clamp((ctr.x - sinkSh.x) - innerRight - preOff, 0.0,
                             outerRight - innerRight);

      // Lateral distance from pixel to rect's extent along each edge
      float hLat = max(abs(pixel.x - ctr.x) - sinkSh.x, 0.0);
      float vLat = max(abs(pixel.y - ctr.y) - sinkSh.y, 0.0);

      // Perpendicular proximity: full strength in border, fade inside inner
      // area
      float topZone =
          1.0 - smoothstep(innerTop, innerTop + smoothFactor, pixel.y);
      float botZone = smoothstep(innerBot - smoothFactor, innerBot, pixel.y);
      float leftZone =
          1.0 - smoothstep(innerLeft, innerLeft + smoothFactor, pixel.x);
      float rightZone =
          smoothstep(innerRight - smoothFactor, innerRight, pixel.x);

      float s = smoothFactor * 2.0;
      float sink = max(max(topPen * smoothstep(s, 0.0, hLat) * topZone,
                           botPen * smoothstep(s, 0.0, hLat) * botZone),
                       max(leftPen * smoothstep(s, 0.0, vLat) * leftZone,
                           rightPen * smoothstep(s, 0.0, vLat) * rightZone));
      sinkValue = max(sinkValue, sink);
    }

    dInner -= sinkValue;

    float dFrame = smaxSharpA(dOuter, -dInner, smoothFactor);

    mergedSdf = smin(mergedSdf, dFrame, smoothFactor);
    if (dFrame < minDist) {
      owner = -1;
    }
  }

  // Each renderer only outputs pixels it owns, but allow rendering
  // blend zones to prevent gaps (mergedSdf < smoothFactor means in blend)
  // myIndex == -1: inverted rect renders border-owned pixels
  // myIndex >= 0: individual rect renders its owned pixels
  if (owner != myIndex && mergedSdf > smoothFactor)
    discard;

  float fw = fwidth(mergedSdf);
  float alpha = 1.0 - smoothstep(-fw, fw, mergedSdf);
  fragColor = vec4(color.rgb * alpha, alpha) * qt_Opacity;
}
