struct Frame {
    viewport_px: vec2<f32>,
    _pad: vec2<f32>,
};

@group(0) @binding(0) var<uniform> u: Frame;

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) local_px: vec2<f32>,
    @location(1) half_px: vec2<f32>,
    @location(2) fill: vec4<f32>,
    @location(3) border: vec4<f32>,
    @location(4) border_thickness_px: f32,
    @location(5) corner_radius_px: f32,
};

@vertex
fn vs(
    @builtin(vertex_index) vid: u32,
    @location(0) rect_xywh_px: vec4<f32>,
    @location(1) fill: vec4<f32>,
    @location(2) border: vec4<f32>,
    @location(3) thickness_radius: vec2<f32>,
) -> VsOut {
    var corners = array<vec2<f32>, 6>(
        vec2<f32>(0.0, 0.0),
        vec2<f32>(1.0, 0.0),
        vec2<f32>(0.0, 1.0),
        vec2<f32>(1.0, 0.0),
        vec2<f32>(1.0, 1.0),
        vec2<f32>(0.0, 1.0),
    );
    let c = corners[vid];
    let pos_px = rect_xywh_px.xy + c * rect_xywh_px.zw;
    let half_px = rect_xywh_px.zw * 0.5;
    let center_px = rect_xywh_px.xy + half_px;
    let local_px = pos_px - center_px;
    let ndc = vec2<f32>(
        (pos_px.x / u.viewport_px.x) * 2.0 - 1.0,
        1.0 - (pos_px.y / u.viewport_px.y) * 2.0,
    );
    var out: VsOut;
    out.clip = vec4<f32>(ndc, 0.0, 1.0);
    out.local_px = local_px;
    out.half_px = half_px;
    out.fill = fill;
    out.border = border;
    out.border_thickness_px = thickness_radius.x;
    out.corner_radius_px = thickness_radius.y;
    return out;
}

fn sd_rounded_rect(p: vec2<f32>, half: vec2<f32>, r: f32) -> f32 {
    let smaller_half = min(half.x, half.y);
    let r_clamped = clamp(r, 0.0, smaller_half);
    let q = abs(p) - half + vec2<f32>(r_clamped, r_clamped);
    let outside = length(max(q, vec2<f32>(0.0, 0.0)));
    let inside = min(max(q.x, q.y), 0.0);
    return outside + inside - r_clamped;
}

@fragment
fn fs(in: VsOut) -> @location(0) vec4<f32> {
    let aa = 0.5;
    let sd_outer = sd_rounded_rect(in.local_px, in.half_px, in.corner_radius_px);
    let cov_outer = clamp(0.5 - sd_outer / aa, 0.0, 1.0);
    if in.border_thickness_px <= 0.0 {
        return in.fill * cov_outer;
    }
    let inner_half = max(in.half_px - vec2<f32>(in.border_thickness_px, in.border_thickness_px), vec2<f32>(0.0, 0.0));
    let inner_radius = max(in.corner_radius_px - in.border_thickness_px, 0.0);
    let sd_inner = sd_rounded_rect(in.local_px, inner_half, inner_radius);
    let cov_inner = clamp(0.5 - sd_inner / aa, 0.0, 1.0);
    let cov_border = max(cov_outer - cov_inner, 0.0);
    let fill_premul = in.fill * cov_inner;
    let border_premul = in.border * cov_border;
    let inv_border_alpha = 1.0 - border_premul.a;
    return border_premul + fill_premul * inv_border_alpha;
}
