package tess

import "core:math"

import draw ".."

//INTERNAL
SMOOTH_CIRCLE_ERROR_RATE :: 0.1

auto_segments :: proc(radius: f32, arc_degrees: f32) -> int {
	if radius <= 0 do return 4
	phys_radius := radius * draw.GLOB.dpi_scaling
	acos_arg := clamp(2 * math.pow(1 - SMOOTH_CIRCLE_ERROR_RATE / phys_radius, 2) - 1, -1, 1)
	theta := math.acos(acos_arg)
	if theta <= 0 do return 4
	full_circle_segments := int(math.ceil(2 * math.PI / theta))
	segments := int(f32(full_circle_segments) * arc_degrees / 360.0)
	min_segments := max(int(math.ceil(f64(arc_degrees / 90.0))), 4)
	return max(segments, min_segments)
}

// ----- Internal helpers -----

// Color is premultiplied: the tessellated fragment shader passes it through directly
// and the blend state is ONE, ONE_MINUS_SRC_ALPHA.
//INTERNAL
solid_vertex :: proc(position: draw.Vec2, color: draw.Color) -> draw.Vertex_2D {
	return draw.Vertex_2D{position = position, color = draw.premultiply_color(color)}
}

//INTERNAL
emit_rectangle :: proc(
	x, y, width, height: f32,
	color: draw.Color,
	vertices: []draw.Vertex_2D,
	offset: int,
) {
	vertices[offset + 0] = solid_vertex({x, y}, color)
	vertices[offset + 1] = solid_vertex({x + width, y}, color)
	vertices[offset + 2] = solid_vertex({x + width, y + height}, color)
	vertices[offset + 3] = solid_vertex({x, y}, color)
	vertices[offset + 4] = solid_vertex({x + width, y + height}, color)
	vertices[offset + 5] = solid_vertex({x, y + height}, color)
}

//INTERNAL
extrude_line :: proc(
	start, end_pos: draw.Vec2,
	thickness: f32,
	color: draw.Color,
	vertices: []draw.Vertex_2D,
	offset: int,
) -> int {
	direction := end_pos - start
	delta_x := direction[0]
	delta_y := direction[1]
	length := math.sqrt(delta_x * delta_x + delta_y * delta_y)
	if length < 0.0001 do return 0

	scale := thickness / (2 * length)
	perpendicular := draw.Vec2{-delta_y * scale, delta_x * scale}

	p0 := start + perpendicular
	p1 := start - perpendicular
	p2 := end_pos - perpendicular
	p3 := end_pos + perpendicular

	vertices[offset + 0] = solid_vertex(p0, color)
	vertices[offset + 1] = solid_vertex(p1, color)
	vertices[offset + 2] = solid_vertex(p2, color)
	vertices[offset + 3] = solid_vertex(p0, color)
	vertices[offset + 4] = solid_vertex(p2, color)
	vertices[offset + 5] = solid_vertex(p3, color)

	return 6
}

// ----- Public draw -----

pixel :: proc(layer: ^draw.Layer, pos: draw.Vec2, color: draw.Color) {
	vertices: [6]draw.Vertex_2D
	emit_rectangle(pos[0], pos[1], 1, 1, color, vertices[:], 0)
	draw.prepare_shape(layer, vertices[:])
}

triangle :: proc(
	layer: ^draw.Layer,
	v1, v2, v3: draw.Vec2,
	color: draw.Color,
	origin: draw.Vec2 = {},
	rotation: f32 = 0,
) {
	if !draw.needs_transform(origin, rotation) {
		vertices := [3]draw.Vertex_2D{solid_vertex(v1, color), solid_vertex(v2, color), solid_vertex(v3, color)}
		draw.prepare_shape(layer, vertices[:])
		return
	}
	bounds_min := draw.Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)}
	transform := draw.build_pivot_rotation(bounds_min, origin, rotation)
	local_v1 := v1 - bounds_min
	local_v2 := v2 - bounds_min
	local_v3 := v3 - bounds_min
	vertices := [3]draw.Vertex_2D {
		solid_vertex(draw.apply_transform(transform, local_v1), color),
		solid_vertex(draw.apply_transform(transform, local_v2), color),
		solid_vertex(draw.apply_transform(transform, local_v3), color),
	}
	draw.prepare_shape(layer, vertices[:])
}

// Draw an anti-aliased triangle via extruded edge quads.
// Interior vertices get the full premultiplied color; outer fringe vertices get BLANK (0,0,0,0).
// The rasterizer linearly interpolates between them, producing a smooth 1-pixel AA band.
// `aa_px` controls the extrusion width in logical pixels (default 1.0).
// This proc emits 21 vertices (3 interior + 6 edge quads × 3 verts each).
triangle_aa :: proc(
	layer: ^draw.Layer,
	v1, v2, v3: draw.Vec2,
	color: draw.Color,
	aa_px: f32 = draw.DFT_FEATHER_PX,
	origin: draw.Vec2 = {},
	rotation: f32 = 0,
) {
	// Apply rotation if needed, then work in world space.
	p0, p1, p2: draw.Vec2
	if !draw.needs_transform(origin, rotation) {
		p0 = v1
		p1 = v2
		p2 = v3
	} else {
		bounds_min := draw.Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)}
		transform := draw.build_pivot_rotation(bounds_min, origin, rotation)
		p0 = draw.apply_transform(transform, v1 - bounds_min)
		p1 = draw.apply_transform(transform, v2 - bounds_min)
		p2 = draw.apply_transform(transform, v3 - bounds_min)
	}

	// Compute outward edge normals (unit length, pointing away from triangle interior).
	// Winding-independent: we check against the centroid to ensure normals point outward.
	centroid_x := (p0.x + p1.x + p2.x) / 3.0
	centroid_y := (p0.y + p1.y + p2.y) / 3.0

	edge_normal :: proc(edge_start, edge_end: draw.Vec2, centroid_x, centroid_y: f32) -> draw.Vec2 {
		delta_x := edge_end.x - edge_start.x
		delta_y := edge_end.y - edge_start.y
		length := math.sqrt(delta_x * delta_x + delta_y * delta_y)
		if length < 0.0001 do return {0, 0}
		inverse_length := 1.0 / length
		// Perpendicular: (-delta_y, delta_x) normalized
		normal_x := -delta_y * inverse_length
		normal_y := delta_x * inverse_length
		// Midpoint of the edge
		midpoint_x := (edge_start.x + edge_end.x) * 0.5
		midpoint_y := (edge_start.y + edge_end.y) * 0.5
		// If normal points toward centroid, flip it
		if normal_x * (centroid_x - midpoint_x) + normal_y * (centroid_y - midpoint_y) > 0 {
			normal_x = -normal_x
			normal_y = -normal_y
		}
		return {normal_x, normal_y}
	}

	normal_01 := edge_normal(p0, p1, centroid_x, centroid_y)
	normal_12 := edge_normal(p1, p2, centroid_x, centroid_y)
	normal_20 := edge_normal(p2, p0, centroid_x, centroid_y)

	extrude_distance := aa_px * draw.GLOB.dpi_scaling

	// Outer fringe vertices: each edge vertex extruded outward
	outer_0_01 := p0 + normal_01 * extrude_distance
	outer_1_01 := p1 + normal_01 * extrude_distance
	outer_1_12 := p1 + normal_12 * extrude_distance
	outer_2_12 := p2 + normal_12 * extrude_distance
	outer_2_20 := p2 + normal_20 * extrude_distance
	outer_0_20 := p0 + normal_20 * extrude_distance

	// Premultiplied interior color (solid_vertex does premul internally).
	// Outer fringe is BLANK = {0,0,0,0} which is already premul.
	transparent := draw.BLANK

	// 3 interior + 6 × 3 edge-quad = 21 vertices
	vertices: [21]draw.Vertex_2D

	// Interior triangle
	vertices[0] = solid_vertex(p0, color)
	vertices[1] = solid_vertex(p1, color)
	vertices[2] = solid_vertex(p2, color)

	// Edge quad: p0→p1 (2 triangles)
	vertices[3] = solid_vertex(p0, color)
	vertices[4] = solid_vertex(p1, color)
	vertices[5] = solid_vertex(outer_1_01, transparent)
	vertices[6] = solid_vertex(p0, color)
	vertices[7] = solid_vertex(outer_1_01, transparent)
	vertices[8] = solid_vertex(outer_0_01, transparent)

	// Edge quad: p1→p2 (2 triangles)
	vertices[9] = solid_vertex(p1, color)
	vertices[10] = solid_vertex(p2, color)
	vertices[11] = solid_vertex(outer_2_12, transparent)
	vertices[12] = solid_vertex(p1, color)
	vertices[13] = solid_vertex(outer_2_12, transparent)
	vertices[14] = solid_vertex(outer_1_12, transparent)

	// Edge quad: p2→p0 (2 triangles)
	vertices[15] = solid_vertex(p2, color)
	vertices[16] = solid_vertex(p0, color)
	vertices[17] = solid_vertex(outer_0_20, transparent)
	vertices[18] = solid_vertex(p2, color)
	vertices[19] = solid_vertex(outer_0_20, transparent)
	vertices[20] = solid_vertex(outer_2_20, transparent)

	draw.prepare_shape(layer, vertices[:])
}

triangle_lines :: proc(
	layer: ^draw.Layer,
	v1, v2, v3: draw.Vec2,
	color: draw.Color,
	thickness: f32 = draw.DFT_STROKE_THICKNESS,
	origin: draw.Vec2 = {},
	rotation: f32 = 0,
	temp_allocator := context.temp_allocator,
) {
	vertices := make([]draw.Vertex_2D, 18, temp_allocator)
	defer delete(vertices, temp_allocator)
	write_offset := 0

	if !draw.needs_transform(origin, rotation) {
		write_offset += extrude_line(v1, v2, thickness, color, vertices, write_offset)
		write_offset += extrude_line(v2, v3, thickness, color, vertices, write_offset)
		write_offset += extrude_line(v3, v1, thickness, color, vertices, write_offset)
	} else {
		bounds_min := draw.Vec2{min(v1.x, v2.x, v3.x), min(v1.y, v2.y, v3.y)}
		transform := draw.build_pivot_rotation(bounds_min, origin, rotation)
		transformed_v1 := draw.apply_transform(transform, v1 - bounds_min)
		transformed_v2 := draw.apply_transform(transform, v2 - bounds_min)
		transformed_v3 := draw.apply_transform(transform, v3 - bounds_min)
		write_offset += extrude_line(transformed_v1, transformed_v2, thickness, color, vertices, write_offset)
		write_offset += extrude_line(transformed_v2, transformed_v3, thickness, color, vertices, write_offset)
		write_offset += extrude_line(transformed_v3, transformed_v1, thickness, color, vertices, write_offset)
	}

	if write_offset > 0 {
		draw.prepare_shape(layer, vertices[:write_offset])
	}
}

triangle_fan :: proc(
	layer: ^draw.Layer,
	points: []draw.Vec2,
	color: draw.Color,
	origin: draw.Vec2 = {},
	rotation: f32 = 0,
	temp_allocator := context.temp_allocator,
) {
	if len(points) < 3 do return

	triangle_count := len(points) - 2
	vertex_count := triangle_count * 3
	vertices := make([]draw.Vertex_2D, vertex_count, temp_allocator)
	defer delete(vertices, temp_allocator)

	if !draw.needs_transform(origin, rotation) {
		for i in 1 ..< len(points) - 1 {
			idx := (i - 1) * 3
			vertices[idx + 0] = solid_vertex(points[0], color)
			vertices[idx + 1] = solid_vertex(points[i], color)
			vertices[idx + 2] = solid_vertex(points[i + 1], color)
		}
	} else {
		bounds_min := draw.Vec2{max(f32), max(f32)}
		for point in points {
			bounds_min.x = min(bounds_min.x, point.x)
			bounds_min.y = min(bounds_min.y, point.y)
		}
		transform := draw.build_pivot_rotation(bounds_min, origin, rotation)
		for i in 1 ..< len(points) - 1 {
			idx := (i - 1) * 3
			vertices[idx + 0] = solid_vertex(draw.apply_transform(transform, points[0] - bounds_min), color)
			vertices[idx + 1] = solid_vertex(draw.apply_transform(transform, points[i] - bounds_min), color)
			vertices[idx + 2] = solid_vertex(draw.apply_transform(transform, points[i + 1] - bounds_min), color)
		}
	}

	draw.prepare_shape(layer, vertices)
}

triangle_strip :: proc(
	layer: ^draw.Layer,
	points: []draw.Vec2,
	color: draw.Color,
	origin: draw.Vec2 = {},
	rotation: f32 = 0,
	temp_allocator := context.temp_allocator,
) {
	if len(points) < 3 do return

	triangle_count := len(points) - 2
	vertex_count := triangle_count * 3
	vertices := make([]draw.Vertex_2D, vertex_count, temp_allocator)
	defer delete(vertices, temp_allocator)

	if !draw.needs_transform(origin, rotation) {
		for i in 0 ..< triangle_count {
			idx := i * 3
			if i % 2 == 0 {
				vertices[idx + 0] = solid_vertex(points[i], color)
				vertices[idx + 1] = solid_vertex(points[i + 1], color)
				vertices[idx + 2] = solid_vertex(points[i + 2], color)
			} else {
				vertices[idx + 0] = solid_vertex(points[i + 1], color)
				vertices[idx + 1] = solid_vertex(points[i], color)
				vertices[idx + 2] = solid_vertex(points[i + 2], color)
			}
		}
	} else {
		bounds_min := draw.Vec2{max(f32), max(f32)}
		for point in points {
			bounds_min.x = min(bounds_min.x, point.x)
			bounds_min.y = min(bounds_min.y, point.y)
		}
		transform := draw.build_pivot_rotation(bounds_min, origin, rotation)
		for i in 0 ..< triangle_count {
			idx := i * 3
			if i % 2 == 0 {
				vertices[idx + 0] = solid_vertex(draw.apply_transform(transform, points[i] - bounds_min), color)
				vertices[idx + 1] = solid_vertex(draw.apply_transform(transform, points[i + 1] - bounds_min), color)
				vertices[idx + 2] = solid_vertex(draw.apply_transform(transform, points[i + 2] - bounds_min), color)
			} else {
				vertices[idx + 0] = solid_vertex(draw.apply_transform(transform, points[i + 1] - bounds_min), color)
				vertices[idx + 1] = solid_vertex(draw.apply_transform(transform, points[i] - bounds_min), color)
				vertices[idx + 2] = solid_vertex(draw.apply_transform(transform, points[i + 2] - bounds_min), color)
			}
		}
	}

	draw.prepare_shape(layer, vertices)
}
