struct Frame {
    clip_from_world:  mat4x4<f32>,
    stroke_color:     vec4<f32>,
    construction:     vec4<f32>,
    pixels_per_mm:    f32,
    dash_period_px:   f32,
    dash_on_ratio:    f32,
    _pad:             f32,
};

struct Instance {
    @location(0) center:        vec2<f32>,
    @location(1) radius_mm:     f32,
    @location(2) half_width_px: f32,
    @location(3) start_rad:     f32,
    @location(4) sweep_rad:     f32,
    @location(5) aabb_min_mm:   vec2<f32>,
    @location(6) aabb_max_mm:   vec2<f32>,
    @location(7) pick_id:       u32,
    @location(8) style_bits:    u32,
};

struct VsOut {
    @builtin(position) clip:        vec4<f32>,
    @location(0) local_px:          vec2<f32>,
    @location(1) radius_px:         f32,
    @location(2) half_width_px:     f32,
    @location(3) start_rad:         f32,
    @location(4) sweep_rad:         f32,
    @location(5) @interpolate(flat) style_bits: u32,
    @location(6) @interpolate(flat) pick_id:    u32,
};

struct FsOut {
    @location(0) color:   vec4<f32>,
    @location(1) pick_id: u32,
};

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

const CORNERS: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
    vec2<f32>(0.0, 0.0),
    vec2<f32>(1.0, 0.0),
    vec2<f32>(0.0, 1.0),
    vec2<f32>(0.0, 1.0),
    vec2<f32>(1.0, 0.0),
    vec2<f32>(1.0, 1.0),
);

const TWO_PI: f32 = 6.283185307179586;
const CONSTRUCTION_BIT: u32 = 1u;

@vertex
fn vs(@builtin(vertex_index) vid: u32, inst: Instance) -> VsOut {
    var corners = CORNERS;
    let corner = corners[vid];
    let expand_mm = (inst.half_width_px + 1.0) / u.pixels_per_mm;
    let bbox_min = inst.aabb_min_mm - vec2<f32>(expand_mm, expand_mm);
    let bbox_max = inst.aabb_max_mm + vec2<f32>(expand_mm, expand_mm);
    let offset_mm = mix(bbox_min, bbox_max, corner);
    let world_mm = inst.center + offset_mm;
    let clip = u.clip_from_world * vec4<f32>(world_mm, 0.0, 1.0);

    var out: VsOut;
    out.clip = clip;
    out.local_px = offset_mm * u.pixels_per_mm;
    out.radius_px = inst.radius_mm * u.pixels_per_mm;
    out.half_width_px = inst.half_width_px;
    out.start_rad = inst.start_rad;
    out.sweep_rad = inst.sweep_rad;
    out.style_bits = inst.style_bits;
    out.pick_id = inst.pick_id;
    return out;
}

struct ArcPoint {
    distance_px: f32,
    along_px:    f32,
};

fn arc_sample(p: vec2<f32>, radius_px: f32, start_rad: f32, sweep_rad: f32) -> ArcPoint {
    let r_px = length(p);
    let full_circle = sweep_rad >= TWO_PI - 1.0e-4;
    if (full_circle) {
        let theta = atan2(p.y, p.x);
        var delta = theta - start_rad;
        delta = delta - floor(delta / TWO_PI) * TWO_PI;
        var out: ArcPoint;
        out.distance_px = abs(r_px - radius_px);
        out.along_px = delta * radius_px;
        return out;
    }
    let theta = atan2(p.y, p.x);
    var delta = theta - start_rad;
    delta = delta - floor(delta / TWO_PI) * TWO_PI;
    var theta_nearest: f32;
    var along_rad: f32;
    if (delta <= sweep_rad) {
        theta_nearest = theta;
        along_rad = delta;
    } else {
        let to_end = delta - sweep_rad;
        let to_start = TWO_PI - delta;
        if (to_end < to_start) {
            theta_nearest = start_rad + sweep_rad;
            along_rad = sweep_rad;
        } else {
            theta_nearest = start_rad;
            along_rad = 0.0;
        }
    }
    let nearest = vec2<f32>(cos(theta_nearest), sin(theta_nearest)) * radius_px;
    var out: ArcPoint;
    out.distance_px = length(p - nearest);
    out.along_px = along_rad * radius_px;
    return out;
}

@fragment
fn fs(in: VsOut) -> FsOut {
    let sample = arc_sample(in.local_px, in.radius_px, in.start_rad, in.sweep_rad);
    let aa = 0.5;
    let coverage = 1.0 - smoothstep(in.half_width_px - aa, in.half_width_px + aa, sample.distance_px);
    if (coverage <= 0.0) {
        discard;
    }

    let is_construction = (in.style_bits & CONSTRUCTION_BIT) != 0u;
    let dash_visible = !is_construction
        || u.dash_period_px <= 0.0
        || fract(sample.along_px / u.dash_period_px) <= u.dash_on_ratio;
    let color_coverage = select(0.0, coverage, dash_visible);

    let base = select(u.stroke_color, u.construction, is_construction);
    let a = base.a * color_coverage;

    var out: FsOut;
    out.color = vec4<f32>(base.rgb * a, a);
    out.pick_id = in.pick_id;
    return out;
}
