package main

import "base:runtime"
import "core:fmt"
import "core:math"
import "core:math/linalg"
import "core:math/rand"
import "core:mem"
import "core:mem/virtual"

// Vendor libs
import mu "third-party:microui"
import sapp "third-party:sokol/app"
import sg "third-party:sokol/gfx"
import sgl "third-party:sokol/gl"
import sglue "third-party:sokol/glue"
import slog "third-party:sokol/log"

// --- Constants ---
MAX_ENTITIES :: 100_000

// --- Data Types ---

Vertex :: struct {
	pos:   [3]f32,
	color: [3]f32,
}

Mesh :: struct {
	vertices: #soa[dynamic]Vertex,
	indices:  [dynamic]u16,
}

Entity_Kind :: enum u8 {
	Player,
	Enemy,
	Cube,
}

Entity :: struct {
	pos:   linalg.Vector3f32,
	vel:   linalg.Vector3f32,
	color: linalg.Vector3f32,
	kind:  Entity_Kind,
	scale: f32,
}

Camera :: struct {
	pitch: f32,
	yaw:   f32,
	dist:  f32,
}

Input_State :: struct {
	keys:         map[sapp.Keycode]bool,
	mouse_dx:     f32,
	mouse_dy:     f32,
	mouse_x:      f32,
	mouse_y:      f32,
	left_clicked: bool,
	locked:       bool,
}

App_State :: struct {
	arena:          virtual.Arena,
	allocator:      mem.Allocator,
	pass_action:    sg.Pass_Action,
	mu_ctx:         mu.Context,
	atlas_img:      sg.Image,
	atlas_view:     sg.View,
	atlas_smp:      sg.Sampler,
	ui_pip:         sgl.Pipeline,
	scene_pip:      sgl.Pipeline,
	mesh_cube:      Mesh,
	entities:       #soa[dynamic]Entity,
	dpi_scale:      f32,
	camera:         Camera,
	input:          Input_State,
	selected_color: [3]f32,
}

state: App_State

// --- Math Helpers ---

get_mouse_ray :: proc(
	mx, my: f32,
	view, proj: linalg.Matrix4f32,
) -> (
	origin, dir: linalg.Vector3f32,
) {
	// Используем физические размеры фреймбуфера
	w := sapp.widthf()
	h := sapp.heightf()

	// Переводим логические координаты мыши в физические
	// 1. NDC (-1..1)
	// Y инвертирован: в окне 0 вверху, в NDC 1 вверху
	x := (2.0 * mx) / w - 1.0
	y := 1.0 - (2.0 * my) / h

	// 2. Clip Space
	ray_clip := linalg.Vector4f32{x, y, -1.0, 1.0}

	// 3. Eye Space
	inv_proj := linalg.matrix4_inverse(proj)
	ray_eye := inv_proj * ray_clip
	ray_eye = {ray_eye.x, ray_eye.y, -1.0, 0.0}

	// 4. World Space
	inv_view := linalg.matrix4_inverse(view)
	ray_world := inv_view * ray_eye

	// Позиция камеры
	origin = {inv_view[3][0], inv_view[3][1], inv_view[3][2]}
	dir = linalg.normalize(ray_world.xyz)

	return origin, dir
}

ray_plane_intersect :: proc(
	origin, dir: linalg.Vector3f32,
	plane_y: f32,
) -> (
	pos: linalg.Vector3f32,
	hit: bool,
) {
	if abs(dir.y) < 0.001 do return {}, false
	t := (plane_y - origin.y) / dir.y
	if t < 0 do return {}, false
	return origin + dir * t, true
}

// --- Systems ---

system_input_player :: proc(dt: f32) {
	if len(state.entities) == 0 do return
	player := &state.entities[0]

	SPEED :: 10.0
	SENSITIVITY :: 0.15 * 0.02

	if state.input.locked {
		state.camera.yaw -= state.input.mouse_dx * SENSITIVITY
		state.camera.pitch += state.input.mouse_dy * SENSITIVITY
		state.camera.pitch = math.clamp(state.camera.pitch, -1.5, 1.5)
	}

	sin_yaw, cos_yaw := math.sincos(state.camera.yaw)
	forward := linalg.Vector3f32{sin_yaw, 0, cos_yaw}
	right := linalg.Vector3f32{cos_yaw, 0, -sin_yaw}

	move_dir := linalg.Vector3f32{0, 0, 0}
	if state.input.keys[.W] do move_dir -= forward
	if state.input.keys[.S] do move_dir += forward
	if state.input.keys[.A] do move_dir -= right
	if state.input.keys[.D] do move_dir += right

	target_vel_x: f32 = 0.0
	target_vel_z: f32 = 0.0

	if linalg.length2(move_dir) > 0.01 {
		move_dir = linalg.normalize(move_dir)
		target_vel_x = move_dir.x * SPEED
		target_vel_z = move_dir.z * SPEED
	}

	player.vel.x = target_vel_x
	player.vel.z = target_vel_z

	if state.input.keys[.SPACE] && player.pos.y <= 0.51 {
		player.vel.y = 10.0
	}
}

system_builder :: proc(view, proj: linalg.Matrix4f32) {
	// Исправлено: доступ к полю hover_root напрямую
	if state.input.locked || state.mu_ctx.hover_root != nil {
		return
	}

	if state.input.left_clicked {
		origin, dir := get_mouse_ray(state.input.mouse_x, state.input.mouse_y, view, proj)
		if hit_pos, hit := ray_plane_intersect(origin, dir, 0.5); hit {
			spawn_entity(hit_pos, .Cube)
		}
	}
}

system_physics :: proc(dt: f32) {
	GRAVITY :: 30.0
	FLOOR_Y :: 0.5

	for i in 0 ..< len(state.entities) {
		e := &state.entities[i]
		if e.kind == .Cube do continue

		e.vel.y -= GRAVITY * dt
		e.pos += e.vel * dt

		if e.pos.y < FLOOR_Y {
			e.pos.y = FLOOR_Y
			if e.kind == .Player {
				e.vel.y = 0
			} else {
				e.vel.y *= -0.8
				e.vel.x *= 0.95
				e.vel.z *= 0.95
			}
		}

		if e.pos.x > 50 do e.vel.x = -abs(e.vel.x)
		if e.pos.x < -50 do e.vel.x = abs(e.vel.x)
		if e.pos.z > 50 do e.vel.z = -abs(e.vel.z)
		if e.pos.z < -50 do e.vel.z = abs(e.vel.z)
	}
}

system_render :: proc(view, proj: linalg.Matrix4f32) {
	// Исправлено: Создаем локальные копии матриц, чтобы взять их адрес
	v := view
	p := proj

	sgl.defaults()
	sgl.push_pipeline()
	sgl.load_pipeline(state.scene_pip)

	sgl.matrix_mode_projection()
	sgl.load_matrix(cast(^f32)&p) // Адрес локальной копии
	sgl.matrix_mode_modelview()
	sgl.load_matrix(cast(^f32)&v) // Адрес локальной копии

	draw_grid(100, 1.0)

	for i in 0 ..< len(state.entities) {
		e := state.entities[i]
		draw_mesh_instance(&state.mesh_cube, e.pos, e.scale, e.color)
	}

	sgl.pop_pipeline()
}

// --- Helpers ---

get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
	if len(state.entities) == 0 {
		return linalg.MATRIX4F32_IDENTITY, linalg.MATRIX4F32_IDENTITY
	}

	player_pos := state.entities[0].pos

	cam_offset :=
		linalg.Vector3f32 {
			math.cos(state.camera.pitch) * math.sin(state.camera.yaw),
			math.sin(state.camera.pitch),
			math.cos(state.camera.pitch) * math.cos(state.camera.yaw),
		} *
		state.camera.dist

	cam_pos := player_pos + cam_offset

	view = linalg.matrix4_look_at_f32(cam_pos, player_pos, {0, 1, 0})

	aspect := sapp.widthf() / sapp.heightf()
	proj = linalg.matrix4_perspective_f32(60.0 * (math.PI / 180.0), aspect, 0.1, 200.0)

	return view, proj
}

spawn_entity :: proc(pos: linalg.Vector3f32, kind: Entity_Kind) {
	e: Entity
	e.pos = pos
	e.kind = kind
	e.scale = 1.0

	switch kind {
	case .Player:
		e.color = {0.2, 0.8, 0.2}
	case .Enemy:
		e.color = {0.8, 0.3, 0.3}
		e.scale = rand.float32_range(0.5, 1.5)
		e.vel.x = rand.float32_range(-5, 5)
		e.vel.z = rand.float32_range(-5, 5)
		e.vel.y = rand.float32_range(5, 15)
	case .Cube:
		e.color = state.selected_color
	}

	append_soa(&state.entities, e)
}

// --- Mesh Gen ---

make_cube_mesh :: proc(allocator: mem.Allocator) -> Mesh {
	m: Mesh
	m.vertices = make(#soa[dynamic]Vertex, 0, 24, allocator)
	m.indices = make([dynamic]u16, 0, 36, allocator)

	c_front := [3]f32{1.0, 0.5, 0.2}
	c_back := [3]f32{0.8, 0.4, 0.1}
	c_left := [3]f32{0.9, 0.45, 0.15}
	c_right := [3]f32{0.9, 0.45, 0.15}
	c_top := [3]f32{1.0, 0.6, 0.3}
	c_bottom := [3]f32{0.6, 0.3, 0.1}

	add_face :: proc(m: ^Mesh, p1, p2, p3, p4: [3]f32, color: [3]f32) {
		start_idx := u16(len(m.vertices))
		append_soa(&m.vertices, Vertex{p1, color})
		append_soa(&m.vertices, Vertex{p2, color})
		append_soa(&m.vertices, Vertex{p3, color})
		append_soa(&m.vertices, Vertex{p4, color})
		append(&m.indices, start_idx + 0, start_idx + 1, start_idx + 2)
		append(&m.indices, start_idx + 0, start_idx + 2, start_idx + 3)
	}

	add_face(&m, {-0.5, -0.5, 0.5}, {-0.5, 0.5, 0.5}, {0.5, 0.5, 0.5}, {0.5, -0.5, 0.5}, c_front)
	add_face(
		&m,
		{0.5, -0.5, -0.5},
		{0.5, 0.5, -0.5},
		{-0.5, 0.5, -0.5},
		{-0.5, -0.5, -0.5},
		c_back,
	)
	add_face(&m, {-0.5, 0.5, 0.5}, {-0.5, 0.5, -0.5}, {0.5, 0.5, -0.5}, {0.5, 0.5, 0.5}, c_top)
	add_face(
		&m,
		{-0.5, -0.5, -0.5},
		{-0.5, -0.5, 0.5},
		{0.5, -0.5, 0.5},
		{0.5, -0.5, -0.5},
		c_bottom,
	)
	add_face(&m, {0.5, -0.5, 0.5}, {0.5, 0.5, 0.5}, {0.5, 0.5, -0.5}, {0.5, -0.5, -0.5}, c_right)
	add_face(
		&m,
		{-0.5, -0.5, -0.5},
		{-0.5, 0.5, -0.5},
		{-0.5, 0.5, 0.5},
		{-0.5, -0.5, 0.5},
		c_left,
	)

	return m
}

// --- App Lifecycle ---

init :: proc "c" () {
	context = runtime.default_context()

	_ = virtual.arena_init_growing(&state.arena, 64 * mem.Megabyte)
	state.allocator = virtual.arena_allocator(&state.arena)

	sg.setup({environment = sglue.environment(), logger = {func = slog.func}})
	sgl.setup({logger = {func = slog.func}})

	mu.init(&state.mu_ctx)
	state.mu_ctx.text_width = mu.default_atlas_text_width
	state.mu_ctx.text_height = mu.default_atlas_text_height

	// Исправлено: убрана инициализация state.camera.pos
	state.selected_color = {0.2, 0.5, 1.0}

	w := mu.DEFAULT_ATLAS_WIDTH
	h := mu.DEFAULT_ATLAS_HEIGHT
	pixels := make([]u32, w * h, context.temp_allocator)
	for alpha, i in mu.default_atlas_alpha {
		pixels[i] = 0x00FFFFFF | (u32(alpha) << 24)
	}
	state.atlas_img = sg.make_image(
		{
			width = i32(w),
			height = i32(h),
			pixel_format = .RGBA8,
			data = {mip_levels = {0 = {ptr = raw_data(pixels), size = len(pixels) * 4}}},
		},
	)
	state.atlas_view = sg.make_view({texture = {image = state.atlas_img}})
	state.atlas_smp = sg.make_sampler({min_filter = .NEAREST, mag_filter = .NEAREST})

	ui_pip_desc: sg.Pipeline_Desc
	ui_pip_desc.colors[0].blend.enabled = true
	ui_pip_desc.colors[0].blend.src_factor_rgb = .SRC_ALPHA
	ui_pip_desc.colors[0].blend.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA
	state.ui_pip = sgl.make_pipeline(ui_pip_desc)

	scene_pip_desc: sg.Pipeline_Desc
	scene_pip_desc.depth.write_enabled = true
	scene_pip_desc.depth.compare = .LESS_EQUAL
	scene_pip_desc.cull_mode = .FRONT
	state.scene_pip = sgl.make_pipeline(scene_pip_desc)

	state.pass_action = {
		colors = {0 = {load_action = .CLEAR, clear_value = {0.1, 0.1, 0.15, 1}}},
	}

	state.mesh_cube = make_cube_mesh(state.allocator)
	state.entities = make(#soa[dynamic]Entity, 0, MAX_ENTITIES, state.allocator)

	spawn_entity({0, 5, 0}, .Player)
	state.camera.dist = 10.0
	state.camera.pitch = 0.5

	for i in 0 ..< 50 {
		x := rand.float32_range(-40, 40)
		z := rand.float32_range(-40, 40)
		y := rand.float32_range(10, 50)
		spawn_entity({x, y, z}, .Enemy)
	}
}

frame :: proc "c" () {
	context = runtime.default_context()
	defer free_all(context.temp_allocator)

	state.dpi_scale = sapp.dpi_scale()
	dt := f32(sapp.frame_duration())

	system_input_player(dt)
	system_physics(dt)

	view, proj := get_camera_matrices()
	system_builder(view, proj)

	state.input.mouse_dx = 0
	state.input.mouse_dy = 0
	state.input.left_clicked = false

	mu.begin(&state.mu_ctx)

	if mu.begin_window(&state.mu_ctx, "Stats", {10, 10, 200, 120}) {
		mu.label(&state.mu_ctx, fmt.tprintf("FPS: %.0f", 1.0 / dt))
		mu.label(&state.mu_ctx, fmt.tprintf("Entities: %d", len(state.entities)))
		mu.label(&state.mu_ctx, "WASD to Move")
		mu.label(&state.mu_ctx, "Click to Spawn Cube")
		mu.end_window(&state.mu_ctx)
	}

	if mu.begin_window(&state.mu_ctx, "Builder", {10, 140, 200, 150}) {
		mu.label(&state.mu_ctx, "Cube Color:")
		mu.slider(&state.mu_ctx, &state.selected_color.x, 0, 1, 0, "R: %.2f")
		mu.slider(&state.mu_ctx, &state.selected_color.y, 0, 1, 0, "G: %.2f")
		mu.slider(&state.mu_ctx, &state.selected_color.z, 0, 1, 0, "B: %.2f")

		r := mu.layout_next(&state.mu_ctx)
		mu.draw_rect(
			&state.mu_ctx,
			r,
			{
				u8(state.selected_color.x * 255),
				u8(state.selected_color.y * 255),
				u8(state.selected_color.z * 255),
				255,
			},
		)

		mu.end_window(&state.mu_ctx)
	}
	mu.end(&state.mu_ctx)

	system_render(view, proj)
	render_ui()

	sg.begin_pass({action = state.pass_action, swapchain = sglue.swapchain()})
	sgl.draw()
	sg.end_pass()
	sg.commit()
}

// --- Render Impl ---

draw_mesh_instance :: proc(m: ^Mesh, pos: linalg.Vector3f32, scale: f32, tint: linalg.Vector3f32) {
	sgl.push_matrix()
	sgl.translate(pos.x, pos.y, pos.z)
	sgl.scale(scale, scale, scale)

	sgl.begin_triangles()
	for i := 0; i < len(m.indices); i += 1 {
		idx := m.indices[i]
		v := m.vertices[idx]
		sgl.c3f(v.color.x * tint.x, v.color.y * tint.y, v.color.z * tint.z)
		sgl.v3f(v.pos.x, v.pos.y, v.pos.z)
	}
	sgl.end()
	sgl.pop_matrix()
}

draw_grid :: proc(slices: int, spacing: f32) {
	sgl.begin_lines()
	sgl.c3f(0.3, 0.3, 0.3)
	half := f32(slices) * spacing * 0.5
	for i in 0 ..= slices {
		p := -half + f32(i) * spacing
		sgl.v3f(-half, 0, p); sgl.v3f(half, 0, p)
		sgl.v3f(p, 0, -half); sgl.v3f(p, 0, half)
	}
	sgl.end()
}

render_ui :: proc() {
	sgl.push_pipeline()
	sgl.load_pipeline(state.ui_pip)
	sgl.enable_texture()
	sgl.texture(state.atlas_view, state.atlas_smp)

	sgl.matrix_mode_projection()
	sgl.push_matrix()
	sgl.load_identity()

	w := sapp.widthf() / state.dpi_scale
	h := sapp.heightf() / state.dpi_scale
	sgl.ortho(0.0, w, h, 0.0, -1.0, +1.0)

	sgl.matrix_mode_modelview()
	sgl.push_matrix()
	sgl.load_identity()

	sgl.begin_quads()
	cmd: ^mu.Command
	for v in mu.next_command_iterator(&state.mu_ctx, &cmd) {
		#partial switch c in v {
		case ^mu.Command_Rect:
			draw_rect(c.rect, c.color)
		case ^mu.Command_Text:
			draw_text(c.str, c.pos, c.color)
		case ^mu.Command_Icon:
			draw_icon(c.id, c.rect, c.color)
		case ^mu.Command_Clip:
			sgl.end()
			sgl.scissor_rect(
				f32(c.rect.x) * state.dpi_scale,
				f32(c.rect.y) * state.dpi_scale,
				f32(c.rect.w) * state.dpi_scale,
				f32(c.rect.h) * state.dpi_scale,
				true,
			)
			sgl.begin_quads()
		}
	}
	sgl.end()

	sgl.pop_matrix()
	sgl.matrix_mode_projection()
	sgl.pop_matrix()
	sgl.pop_pipeline()
}

draw_rect :: proc(r: mu.Rect, c: mu.Color) {
	push_quad(r, mu.default_atlas[mu.DEFAULT_ATLAS_WHITE], c)
}

draw_text :: proc(str: string, pos: mu.Vec2, c: mu.Color) {
	p := pos
	for ch in str {
		idx := int(ch)
		if idx > 127 do idx = 0
		src := mu.default_atlas[mu.DEFAULT_ATLAS_FONT + idx]
		push_quad({p.x, p.y, src.w, src.h}, src, c)
		p.x += src.w
	}
}

draw_icon :: proc(id: mu.Icon, r: mu.Rect, c: mu.Color) {
	src := mu.default_atlas[int(id)]
	x := r.x + (r.w - src.w) / 2
	y := r.y + (r.h - src.h) / 2
	push_quad({x, y, src.w, src.h}, src, c)
}

push_quad :: proc(dst, src: mu.Rect, c: mu.Color) {
	iw := 1.0 / f32(mu.DEFAULT_ATLAS_WIDTH)
	ih := 1.0 / f32(mu.DEFAULT_ATLAS_HEIGHT)
	u0, v0 := f32(src.x) * iw, f32(src.y) * ih
	u1, v1 := f32(src.x + src.w) * iw, f32(src.y + src.h) * ih
	x0, y0 := f32(dst.x), f32(dst.y)
	x1, y1 := f32(dst.x + dst.w), f32(dst.y + dst.h)

	sgl.c4b(c.r, c.g, c.b, c.a)
	sgl.v2f_t2f(x0, y0, u0, v0)
	sgl.v2f_t2f(x1, y0, u1, v0)
	sgl.v2f_t2f(x1, y1, u1, v1)
	sgl.v2f_t2f(x0, y1, u0, v1)
}

// --- Input Glue ---

map_mouse_button :: proc(btn: sapp.Mousebutton) -> (mu.Mouse, bool) {
	switch btn {
	case .LEFT:
		return .LEFT, true
	case .RIGHT:
		return .RIGHT, true
	case .MIDDLE:
		return .MIDDLE, true
	case .INVALID:
		return nil, false
	}
	return nil, false
}

map_key :: proc(key: sapp.Keycode) -> (mu.Key, bool) {
	#partial switch key {
	case .LEFT_SHIFT, .RIGHT_SHIFT:
		return .SHIFT, true
	case .LEFT_CONTROL, .RIGHT_CONTROL:
		return .CTRL, true
	case .LEFT_ALT, .RIGHT_ALT:
		return .ALT, true
	case .ENTER:
		return .RETURN, true
	case .BACKSPACE:
		return .BACKSPACE, true
	}
	return nil, false
}

event :: proc "c" (ev: ^sapp.Event) {
	context = runtime.default_context()
	dpi := sapp.dpi_scale()

	#partial switch ev.type {
	case .KEY_DOWN:
		state.input.keys[ev.key_code] = true
	case .KEY_UP:
		state.input.keys[ev.key_code] = false
	case .MOUSE_MOVE:
		state.input.mouse_x = ev.mouse_x
		state.input.mouse_y = ev.mouse_y

		if state.input.locked {
			state.input.mouse_dx = ev.mouse_dx
			state.input.mouse_dy = ev.mouse_dy
		}
	case .MOUSE_DOWN:
		if ev.mouse_button == .LEFT {
			state.input.left_clicked = true
		}
	}

	if ev.type == .KEY_DOWN && ev.key_code == .ESCAPE {
		state.input.locked = !state.input.locked
		sapp.lock_mouse(state.input.locked)
	}

	if !state.input.locked {
		mx := i32(ev.mouse_x / dpi)
		my := i32(ev.mouse_y / dpi)

		#partial switch ev.type {
		case .MOUSE_DOWN:
			if btn, ok := map_mouse_button(ev.mouse_button); ok {
				mu.input_mouse_down(&state.mu_ctx, mx, my, btn)
			}
		case .MOUSE_UP:
			if btn, ok := map_mouse_button(ev.mouse_button); ok {
				mu.input_mouse_up(&state.mu_ctx, mx, my, btn)
			}
		case .MOUSE_MOVE:
			mu.input_mouse_move(&state.mu_ctx, mx, my)
		case .MOUSE_SCROLL:
			mu.input_scroll(&state.mu_ctx, 0, i32(ev.scroll_y))
		case .KEY_DOWN:
			if k, ok := map_key(ev.key_code); ok do mu.input_key_down(&state.mu_ctx, k)
		case .KEY_UP:
			if k, ok := map_key(ev.key_code); ok do mu.input_key_up(&state.mu_ctx, k)
		case .CHAR:
			if ev.char_code >= 32 && ev.char_code != 127 {
				mu.input_text(&state.mu_ctx, fmt.tprintf("%r", rune(ev.char_code)))
			}
		}
	}
}

cleanup :: proc "c" () {
	context = runtime.default_context()
	virtual.arena_destroy(&state.arena)
	sgl.shutdown()
	sg.shutdown()
}

main :: proc() {
	sapp.run(
		{
			init_cb = init,
			frame_cb = frame,
			cleanup_cb = cleanup,
			event_cb = event,
			width = 1280,
			height = 720,
			window_title = "Sokol 3D Builder",
			icon = {sokol_default = true},
			logger = {func = slog.func},
			high_dpi = true,
			swap_interval = 0,
		},
	)
}
