package main

import "base:runtime"
import "core:fmt"
import "core:image/png"
import "core:math"
import "core:math/linalg"
import "core:math/rand"
import "core:mem"
import "core:mem/virtual"
import "core:os"
import "core:strings"

// Vendor libs
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"
import stbtt "vendor:stb/truetype"

Vec2 :: linalg.Vector2f32
Vec3 :: linalg.Vector3f32
Vec4 :: linalg.Vector4f32

// --- Constants ---
MAX_ENTITIES :: 1024
MAX_BOX :: 32

// Minecraft Style Layout
INVENTORY_COLS :: 9
INVENTORY_ROWS :: 4
INVENTORY_MAIN_ROWS :: 3
INVENTORY_SIZE :: INVENTORY_COLS * INVENTORY_ROWS

// UI Constants (Light / Flat Style)
UI_ROUNDNESS :: 0.0
UI_COLOR_WINDOW_BG :: [4]f32{0.92, 0.92, 0.92, 1.0}
UI_COLOR_SLOT_BG :: [4]f32{0.82, 0.82, 0.82, 1.0}
UI_COLOR_SLOT_HOVER :: [4]f32{0.75, 0.75, 0.75, 1.0}
UI_COLOR_BORDER :: [4]f32{0.6, 0.6, 0.6, 1.0}
UI_COLOR_TEXT :: [4]f32{0.1, 0.1, 0.1, 1.0}
UI_COLOR_ACCENT :: [4]f32{0.0, 0.48, 1.0, 1.0}
UI_COLOR_BUTTON :: [4]f32{0.8, 0.8, 0.8, 1.0}
UI_COLOR_BUTTON_HOVER :: [4]f32{0.7, 0.7, 0.7, 1.0}

SLOT_SIZE: f32 : 40.0
SLOT_SPACING: f32 : 4.0
SLOT_PADDING: f32 : 4.0

// --- Data Types ---

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

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

// Sprites
Sprite :: struct {
	img:  sg.Image,
	view: sg.View, // Added view for sgl
	smp:  sg.Sampler,
	w, h: i32,
}

Item_Kind :: enum u16 {
	None,
	Sword,
	Potion,
	Stone,
	Shovel,
}

Item_Info :: struct {
	name:      string,
	max_stack: u32,
	color:     [4]f32, // Fallback color
	sprite_id: int, // -1 if no sprite
}

// Global Item Database (mutable to set sprite_ids at runtime)
item_db: [Item_Kind]Item_Info

Box_Id :: distinct u32

Box_Slot :: struct {
	kind:  Item_Kind,
	count: u32,
}

Box_System :: struct {
	is_open: bool,
	slots:   #soa[dynamic]Box_Slot,
}

Drag_State :: struct {
	active:      bool,
	item:        Box_Slot,
	source_box:  Box_Id,
	source_slot: int,
}

// --- UI System Types ---

Font_Atlas :: struct {
	img:       sg.Image,
	view:      sg.View, // Added view for sgl
	smp:       sg.Sampler,
	char_data: [96]stbtt.bakedchar,
	width:     i32,
	height:    i32,
}

UI_Context :: struct {
	mouse_pos:     [2]f32,
	mouse_down:    bool,
	mouse_pressed: bool,
	hot_id:        u64,
	active_id:     u64,
	font:          Font_Atlas,
	white_img:     sg.Image,
	white_view:    sg.View, // Added view for sgl
	white_smp:     sg.Sampler,
	curr_x:        f32,
	curr_y:        f32,
	curr_w:        f32,
	line_height:   f32,
}

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

AABB :: struct {
	min: Vec3,
	max: Vec3,
}

Garden_State :: enum u8 {
	Dry, // Сухой
	Water,
	Plant, // Растение
}

Entity_Id :: distinct u32

Entity :: struct {
	pos:          Vec3,
	vel:          Vec3,
	color:        Vec3,
	kind:         Entity_Kind,
	scale:        Vec3,
	box_id:       Box_Id,
	is_physics:   bool,
	collider:     AABB,
	garden_state: Garden_State,
}

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

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

App_State :: struct {
	arena:              virtual.Arena,
	allocator:          mem.Allocator,
	pass_action:        sg.Pass_Action,
	ui:                 UI_Context,
	ui_pip:             sgl.Pipeline,
	scene_pip:          sgl.Pipeline,
	mesh_cube:          Mesh,
	box_sys:            #soa[dynamic]Box_System,
	entities:           #soa[dynamic]Entity,
	player_id:          Entity_Id,
	dpi_scale:          f32,
	camera:             Camera,
	last_input:         Input_State,
	input:              Input_State,
	selected_color:     [3]f32,
	active_hotbar_slot: int,
	drag:               Drag_State,
	sprites:            [dynamic]Sprite,
	debug_cube_mesh:    Mesh,
	debug_line_mesh:    Mesh,
}

state: App_State

// --- Resource Loading ---

load_sprite :: proc(path: string) -> int {
	data, ok := os.read_entire_file(path, context.temp_allocator)
	if !ok {
		fmt.println("Failed to load sprite:", path)
		return -1
	}

	img, err := png.load_from_bytes(data, {.alpha_add_if_missing}, context.temp_allocator)
	if err != nil {
		fmt.println("Failed to decode PNG:", path, err)
		return -1
	}

	sg_img := sg.make_image(
		{
			width = i32(img.width),
			height = i32(img.height),
			pixel_format = .RGBA8,
			data = {
				mip_levels = {0 = {ptr = raw_data(img.pixels.buf), size = len(img.pixels.buf)}},
			},
		},
	)

	sg_view := sg.make_view({texture = {image = sg_img}})
	smp := sg.make_sampler({min_filter = .NEAREST, mag_filter = .NEAREST})

	sprite := Sprite {
		img  = sg_img,
		view = sg_view,
		smp  = smp,
		w    = i32(img.width),
		h    = i32(img.height),
	}

	append(&state.sprites, sprite)
	return len(state.sprites) - 1
}

init_item_db :: proc() {
	// Default values
	item_db[.None] = {"Empty", 0, {0, 0, 0, 0}, -1}
	item_db[.Sword] = {"Iron Sword", 1, {0.6, 0.6, 0.7, 1.0}, -1}
	item_db[.Potion] = {"Health Potion", 64, {0.9, 0.3, 0.3, 1.0}, -1}
	item_db[.Stone] = {"Cobblestone", 64, {0.5, 0.5, 0.5, 1.0}, -1}
	item_db[.Shovel] = {"Shovel", 1, {0.6, 0.4, 0.2, 1.0}, -1}

	// Load Sprites
	item_db[.Shovel].sprite_id = load_sprite("res/images/shovel.png")
}

// --- UI Implementation ---

ui_init :: proc(ctx: ^UI_Context) {
	// Font
	font_data, ok := os.read_entire_file(
		"res/fonts/Inter_18pt-Regular.ttf",
		context.temp_allocator,
	)
	if !ok {
		ctx.font.width = 512; ctx.font.height = 512
		ctx.font.img = sg.make_image(
			{width = 512, height = 512, pixel_format = .RGBA8, usage = {dynamic_update = true}},
		)
		ctx.font.view = sg.make_view({texture = {image = ctx.font.img}})
	} else {
		ctx.font.width = 512; ctx.font.height = 512
		pixels := make([]u8, ctx.font.width * ctx.font.height, context.temp_allocator)
		stbtt.BakeFontBitmap(
			raw_data(font_data),
			0,
			18.0,
			raw_data(pixels),
			ctx.font.width,
			ctx.font.height,
			32,
			96,
			&ctx.font.char_data[0],
		)
		rgba_pixels := make([]u32, ctx.font.width * ctx.font.height, context.temp_allocator)
		for i in 0 ..< len(pixels) do rgba_pixels[i] = (u32(pixels[i]) << 24) | 0x00FFFFFF
		ctx.font.img = sg.make_image(
			{
				width = ctx.font.width,
				height = ctx.font.height,
				pixel_format = .RGBA8,
				data = {
					mip_levels = {0 = {ptr = raw_data(rgba_pixels), size = len(rgba_pixels) * 4}},
				},
			},
		)
		ctx.font.view = sg.make_view({texture = {image = ctx.font.img}})
		ctx.font.smp = sg.make_sampler({min_filter = .LINEAR, mag_filter = .LINEAR})
	}

	// White Texture
	white_pixel: u32 = 0xFFFFFFFF
	ctx.white_img = sg.make_image(
		{
			width = 1,
			height = 1,
			pixel_format = .RGBA8,
			data = {mip_levels = {0 = {ptr = &white_pixel, size = 4}}},
		},
	)
	ctx.white_view = sg.make_view({texture = {image = ctx.white_img}})
	ctx.white_smp = sg.make_sampler({min_filter = .NEAREST, mag_filter = .NEAREST})
}

ui_begin_frame :: proc(ctx: ^UI_Context, mx, my: f32, m_down, m_pressed: bool) {
	ctx.mouse_pos = {mx, my}
	ctx.mouse_down = m_down
	ctx.mouse_pressed = m_pressed
	ctx.hot_id = 0
}

ui_id :: proc(ptr: rawptr) -> u64 {return u64(uintptr(ptr))}

// --- Primitives ---

ui_draw_rect :: proc(x, y, w, h: f32, color: [4]f32) {
	sgl.enable_texture()
	// FIX: Use .view instead of .img
	sgl.texture(state.ui.white_view, state.ui.white_smp)
	sgl.begin_quads()
	sgl.c4f(color.r, color.g, color.b, color.a)
	sgl.v2f(x, y); sgl.v2f(x + w, y); sgl.v2f(x + w, y + h); sgl.v2f(x, y + h)
	sgl.end()
}

ui_draw_sprite :: proc(sprite_id: int, x, y, w, h: f32) {
	if sprite_id < 0 || sprite_id >= len(state.sprites) do return
	sprite := state.sprites[sprite_id]

	sgl.enable_texture()
	// FIX: Use .view instead of .img
	sgl.texture(sprite.view, sprite.smp)
	sgl.begin_quads()
	sgl.c4f(1, 1, 1, 1)
	sgl.v2f_t2f(x, y, 0, 0)
	sgl.v2f_t2f(x + w, y, 1, 0)
	sgl.v2f_t2f(x + w, y + h, 1, 1)
	sgl.v2f_t2f(x, y + h, 0, 1)
	sgl.end()
}

ui_draw_outline :: proc(x, y, w, h, thickness: f32, color: [4]f32) {
	sgl.disable_texture()
	sgl.c4f(color.r, color.g, color.b, color.a)
	sgl.begin_quads()
	sgl.v2f(x, y); sgl.v2f(x + w, y); sgl.v2f(x + w, y + thickness); sgl.v2f(x, y + thickness) // Top
	sgl.v2f(
		x,
		y + h - thickness,
	); sgl.v2f(x + w, y + h - thickness); sgl.v2f(x + w, y + h); sgl.v2f(x, y + h) // Bottom
	sgl.v2f(x, y); sgl.v2f(x + thickness, y); sgl.v2f(x + thickness, y + h); sgl.v2f(x, y + h) // Left
	sgl.v2f(
		x + w - thickness,
		y,
	); sgl.v2f(x + w, y); sgl.v2f(x + w, y + h); sgl.v2f(x + w - thickness, y + h) // Right
	sgl.end()
}

ui_draw_text :: proc(text: string, x, y: f32, color: [4]f32) {
	if state.ui.font.img.id == 0 do return
	sgl.enable_texture()
	// FIX: Use .view instead of .img
	sgl.texture(state.ui.font.view, state.ui.font.smp)
	sgl.begin_quads()
	sgl.c4f(color.r, color.g, color.b, color.a)
	cur_x := x; cur_y := y
	for char in text {
		if char < 32 || char > 126 do continue
		q: stbtt.aligned_quad
		stbtt.GetBakedQuad(
			&state.ui.font.char_data[0],
			512,
			512,
			i32(char) - 32,
			&cur_x,
			&cur_y,
			&q,
			true,
		)
		sgl.v2f_t2f(q.x0, q.y0, q.s0, q.t0); sgl.v2f_t2f(q.x1, q.y0, q.s1, q.t0)
		sgl.v2f_t2f(q.x1, q.y1, q.s1, q.t1); sgl.v2f_t2f(q.x0, q.y1, q.s0, q.t1)
	}
	sgl.end()
}

ui_measure_text :: proc(text: string) -> f32 {
	x: f32 = 0; y: f32 = 0
	for char in text {
		if char < 32 || char > 126 do continue
		q: stbtt.aligned_quad
		stbtt.GetBakedQuad(&state.ui.font.char_data[0], 512, 512, i32(char) - 32, &x, &y, &q, true)
	}
	return x
}

// --- Widgets ---

ui_window_begin :: proc(title: string, x, y, w, h: f32) {
	ctx := &state.ui
	ui_draw_rect(x, y, w, h, UI_COLOR_WINDOW_BG)
	ui_draw_outline(x, y, w, h, 1.0, UI_COLOR_BORDER)
	ui_draw_text(title, x + 15, y + 25, UI_COLOR_TEXT)
	sgl.disable_texture(
		
	); sgl.begin_lines(); sgl.c4f(0.8, 0.8, 0.8, 0.5); sgl.v2f(x, y + 40); sgl.v2f(x + w, y + 40); sgl.end()
	ctx.curr_x = x + 15; ctx.curr_y = y + 50; ctx.curr_w = w - 30; ctx.line_height = 30
}

ui_label :: proc(text: string) {
	ctx := &state.ui
	ui_draw_text(text, ctx.curr_x, ctx.curr_y + 18, UI_COLOR_TEXT)
	ctx.curr_y += ctx.line_height
}

ui_button :: proc(text: string) -> bool {
	ctx := &state.ui
	id := ui_id(raw_data(text))
	x, y, w, h := ctx.curr_x, ctx.curr_y, ctx.curr_w, ctx.line_height - 5
	hovered :=
		ctx.mouse_pos.x >= x &&
		ctx.mouse_pos.x < x + w &&
		ctx.mouse_pos.y >= y &&
		ctx.mouse_pos.y < y + h
	if hovered {ctx.hot_id = id; if ctx.mouse_pressed do ctx.active_id = id}
	clicked := false
	if !ctx.mouse_down &&
	   ctx.hot_id == id &&
	   ctx.active_id == id {clicked = true; ctx.active_id = 0}
	bg_color := hovered ? UI_COLOR_BUTTON_HOVER : UI_COLOR_BUTTON
	if ctx.active_id == id do bg_color = {0.85, 0.85, 0.9, 1.0}
	ui_draw_rect(x, y, w, h, bg_color)
	tw := ui_measure_text(text)
	ui_draw_text(text, x + (w - tw) / 2, y + 18, UI_COLOR_ACCENT)
	ctx.curr_y += ctx.line_height
	return clicked
}

ui_slider :: proc(label: string, val: ^f32, min_v, max_v: f32) {
	ctx := &state.ui
	id := ui_id(val)
	x, y, w, h := ctx.curr_x, ctx.curr_y, ctx.curr_w, ctx.line_height - 5
	ui_draw_text(label, x, y + 18, UI_COLOR_TEXT)
	track_x := x + 100; track_w := w - 100; track_h: f32 = 6.0; track_y := y + h / 2 - track_h / 2
	ui_draw_rect(track_x, track_y, track_w, track_h, {0.8, 0.8, 0.8, 1.0})
	thumb_r: f32 = 8.0
	norm := (val^ - min_v) / (max_v - min_v)
	thumb_x := track_x + norm * track_w; thumb_y := track_y + track_h / 2
	hovered :=
		ctx.mouse_pos.x >= track_x - thumb_r &&
		ctx.mouse_pos.x <= track_x + track_w + thumb_r &&
		ctx.mouse_pos.y >= y &&
		ctx.mouse_pos.y <= y + h
	if hovered {ctx.hot_id = id; if ctx.mouse_pressed do ctx.active_id = id}
	if ctx.active_id == id {
		if ctx.mouse_down {
			new_norm := (ctx.mouse_pos.x - track_x) / track_w
			val^ = min_v + math.clamp(new_norm, 0.0, 1.0) * (max_v - min_v)
		} else {ctx.active_id = 0}
	}
	ui_draw_rect(track_x, track_y, thumb_x - track_x, track_h, UI_COLOR_ACCENT)
	ui_draw_rect(thumb_x - thumb_r, thumb_y - thumb_r, thumb_r * 2, thumb_r * 2, {1, 1, 1, 1})
	ctx.curr_y += ctx.line_height
}

// --- Game Logic ---

push_box :: proc(size: u32) -> Box_Id {
	box := Box_System {
		slots = make(#soa[dynamic]Box_Slot, 0, size),
	}
	for i in 0 ..< size do append_soa(&box.slots, Box_Slot{.None, 0})
	id := len(state.box_sys)
	append_soa(&state.box_sys, box)
	return Box_Id(id)
}

push_entity :: proc(
	kind: Entity_Kind,
	pos: Vec3 = {0.0, 0.0, 0.0},
	scale: Vec3 = {1.0, 1.0, 1.0},
) -> Entity_Id {
	e := Entity {
		pos = pos,
		kind = kind,
		scale = scale,
		collider = AABB{min = {-0.5, -0.5, -0.5}, max = {0.5, 0.5, 0.5}},
	}
	switch kind {
	case .Player:
		e.color = {0.2, 0.8, 0.2}
		e.is_physics = true
	case .Enemy:
		e.color = {0.8, 0.3, 0.3}
		e.is_physics = true
	case .Cube:
		e.color = state.selected_color
		e.is_physics = true
	case .Garden:
		e.color = {130.0 / 255.0, 108.0 / 255.0, 65.0 / 255.0}
	}
	id := len(state.entities)
	append_soa(&state.entities, e)
	return Entity_Id(id)
}

// Проверка пересечения луча и AABB (Axis Aligned Bounding Box)
// Возвращает hit (попадание) и dist (дистанцию до точки входа)
ray_aabb_intersect :: proc(origin, dir: Vec3, box_min, box_max: Vec3) -> (bool, f32) {
	t_min := (box_min.x - origin.x) / dir.x
	t_max := (box_max.x - origin.x) / dir.x

	if t_min > t_max do t_min, t_max = t_max, t_min

	ty_min := (box_min.y - origin.y) / dir.y
	ty_max := (box_max.y - origin.y) / dir.y

	if ty_min > ty_max do ty_min, ty_max = ty_max, ty_min

	if (t_min > ty_max) || (ty_min > t_max) do return false, 0

	if ty_min > t_min do t_min = ty_min
	if ty_max < t_max do t_max = ty_max

	tz_min := (box_min.z - origin.z) / dir.z
	tz_max := (box_max.z - origin.z) / dir.z

	if tz_min > tz_max do tz_min, tz_max = tz_max, tz_min

	if (t_min > tz_max) || (tz_min > t_max) do return false, 0

	if tz_min > t_min do t_min = tz_min
	if tz_max < t_max do t_max = tz_max

	if t_min < 0 do return false, 0 // Пересечение сзади луча

	return true, t_min
}

// --- Initialization ---

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}})

	state.mesh_cube = make_cube_mesh(state.allocator)
	state.debug_line_mesh = debug_make_cube_lines(state.allocator)

	// 1. Сначала инициализируем массивы (включая sprites)
	state.mesh_cube = make_cube_mesh(state.allocator)
	state.entities = make(#soa[dynamic]Entity, 0, MAX_ENTITIES, state.allocator)
	state.box_sys = make(#soa[dynamic]Box_System, 0, MAX_BOX, state.allocator)
	state.sprites = make([dynamic]Sprite, 0, 16, state.allocator)

	// 2. Затем загружаем ресурсы (шрифты и спрайты предметов)
	ui_init(&state.ui)
	init_item_db() // Теперь спрайт лопаты добавится в уже созданный массив state.sprites

	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.9, 0.9, 0.95, 1}}},
	}

	state.player_id = push_entity(.Player)
	player_ref := &state.entities[state.player_id]
	player_ref.box_id = push_box(INVENTORY_SIZE)

	state.box_sys[player_ref.box_id].slots[0] = Box_Slot {
		kind  = .Shovel,
		count = 1,
	}
	state.box_sys[player_ref.box_id].slots[1] = Box_Slot {
		kind  = .Sword,
		count = 1,
	}
	state.box_sys[player_ref.box_id].slots[2] = Box_Slot {
		kind  = .Potion,
		count = 5,
	}

	// TODO:
	for x in 0 ..< 10 {
		for z in 0 ..< 10 {
			offset := Vec2{0.1 * f32(x), 0.1 * f32(z)}
			push_entity(
				.Garden,
				{0.0 + (1.0 * f32(x)) + offset.x, 0.0, 0.0 + (1.0 * f32(z)) + offset.y},
				{1.0, 0.1, 1.0},
			)
		}
	}

	state.camera.dist = 10.0; state.camera.pitch = 0.5; state.selected_color = {0.2, 0.5, 1.0}
}

// --- Frame ---

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_interaction(view, proj)
	system_builder(view, proj)

	system_render(view, proj)

	sgl.defaults(); sgl.push_pipeline(); sgl.load_pipeline(state.ui_pip)
	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()

	ui_begin_frame(
		&state.ui,
		state.input.mouse_x / state.dpi_scale,
		state.input.mouse_y / state.dpi_scale,
		state.input.left_down,
		state.input.left_clicked,
	)

	ui_window_begin("Stats", 20, 20, 220, 150)
	ui_label(fmt.tprintf("FPS: %.0f", 1.0 / dt))
	ui_label(fmt.tprintf("Entities: %d", len(state.entities)))
	if ui_button("Spawn Enemy") do push_entity(.Enemy, {0, 10, 0})

	ui_window_begin("Builder", 20, 200, 250, 180)
	ui_slider("Red", &state.selected_color.x, 0, 1)
	ui_slider("Green", &state.selected_color.y, 0, 1)
	ui_slider("Blue", &state.selected_color.z, 0, 1)
	ui_draw_rect(
		state.ui.curr_x,
		state.ui.curr_y,
		state.ui.curr_w,
		30,
		{state.selected_color.x, state.selected_color.y, state.selected_color.z, 1},
	)

	player_box_id := state.entities[state.player_id].box_id
	draw_hud(w, h)
	if state.box_sys[player_box_id].is_open do draw_inventory(player_box_id, w, h)

	if state.drag.active {
		info := item_db[state.drag.item.kind]
		mx, my := state.ui.mouse_pos.x, state.ui.mouse_pos.y
		if info.sprite_id >= 0 {
			ui_draw_sprite(info.sprite_id, mx - 20, my - 20, 40, 40)
		} else {
			ui_draw_rect(mx - 20, my - 20, 40, 40, info.color)
		}
		ui_draw_text(fmt.tprintf("%d", state.drag.item.count), mx, my, UI_COLOR_TEXT)
	}

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

	state.last_input = state.input
	state.input.mouse_dx = 0; state.input.mouse_dy = 0; state.input.left_clicked = false
}

draw_inventory :: proc(box_id: Box_Id, screen_w, screen_h: f32) {
	slots := &state.box_sys[box_id].slots
	row_width := f32(INVENTORY_COLS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
	main_h := f32(INVENTORY_MAIN_ROWS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
	win_padding := f32(10.0)
	win_w := row_width + win_padding * 2
	win_h := main_h + win_padding * 2 + 20
	start_x := (screen_w - win_w) / 2
	start_y := (screen_h - win_h) / 2 - 50.0

	ui_draw_rect(start_x, start_y, win_w, win_h, UI_COLOR_WINDOW_BG)
	ui_draw_outline(start_x, start_y, win_w, win_h, 1.0, UI_COLOR_BORDER)
	ui_draw_text("Backpack", start_x + win_padding, start_y + 5, UI_COLOR_TEXT)

	ctx := &state.ui
	grid_start_x := start_x + win_padding
	grid_start_y := start_y + 30
	slots_count := INVENTORY_MAIN_ROWS * INVENTORY_COLS

	for i in 0 ..< slots_count {
		if i >= len(slots) do break
		col := i % INVENTORY_COLS; row := i / INVENTORY_COLS
		sx := grid_start_x + f32(col) * (SLOT_SIZE + SLOT_SPACING)
		sy := grid_start_y + f32(row) * (SLOT_SIZE + SLOT_SPACING)
		slot := &slots[i]
		hovered :=
			ctx.mouse_pos.x >= sx &&
			ctx.mouse_pos.x < sx + SLOT_SIZE &&
			ctx.mouse_pos.y >= sy &&
			ctx.mouse_pos.y < sy + SLOT_SIZE

		bg_color := UI_COLOR_SLOT_BG
		if hovered do bg_color = UI_COLOR_SLOT_HOVER
		ui_draw_rect(sx, sy, SLOT_SIZE, SLOT_SIZE, bg_color)
		ui_draw_outline(sx, sy, SLOT_SIZE, SLOT_SIZE, 1.0, {0.7, 0.7, 0.7, 1.0})

		if slot.kind != .None {
			info := item_db[slot.kind]
			pad := SLOT_PADDING
			if info.sprite_id >= 0 {
				ui_draw_sprite(
					info.sprite_id,
					sx + pad,
					sy + pad,
					SLOT_SIZE - pad * 2,
					SLOT_SIZE - pad * 2,
				)
			} else {
				ui_draw_rect(
					sx + pad,
					sy + pad,
					SLOT_SIZE - pad * 2,
					SLOT_SIZE - pad * 2,
					info.color,
				)
			}
			if slot.count > 1 {
				count_str := fmt.tprintf("%d", slot.count)
				tw := ui_measure_text(count_str)
				ui_draw_text(
					count_str,
					sx + SLOT_SIZE - tw - 2,
					sy + SLOT_SIZE - 12,
					UI_COLOR_TEXT,
				)
			}
		}

		if hovered {
			if ctx.mouse_pressed && !state.drag.active && slot.kind != .None {
				state.drag.active =
					true; state.drag.item = slot^; state.drag.source_box = box_id; state.drag.source_slot = i
				slot.kind = .None; slot.count = 0
			} else if !ctx.mouse_down && state.last_input.left_down && state.drag.active {
				if slot.kind ==
				   .None {slot^ = state.drag.item; state.drag.active = false} else if slot.kind == state.drag.item.kind {slot.count += state.drag.item.count; state.drag.active = false} else {temp := slot^; slot^ = state.drag.item; state.drag.item = temp}
			}
		}
	}
}

draw_hud :: proc(screen_w, screen_h: f32) {
	player_box_id := state.entities[state.player_id].box_id
	slots := &state.box_sys[player_box_id].slots
	start_slot_idx := INVENTORY_SIZE - INVENTORY_COLS
	total_w := f32(INVENTORY_COLS) * (SLOT_SIZE + SLOT_SPACING) - SLOT_SPACING
	start_x := (screen_w - total_w) / 2.0
	start_y := screen_h - SLOT_SIZE - 10.0
	ctx := &state.ui

	for i in 0 ..< INVENTORY_COLS {
		idx := start_slot_idx + i
		slot := &slots[idx]
		sx := start_x + f32(i) * (SLOT_SIZE + SLOT_SPACING)
		sy := start_y
		hovered :=
			ctx.mouse_pos.x >= sx &&
			ctx.mouse_pos.x < sx + SLOT_SIZE &&
			ctx.mouse_pos.y >= sy &&
			ctx.mouse_pos.y < sy + SLOT_SIZE

		ui_draw_rect(sx, sy, SLOT_SIZE, SLOT_SIZE, {0.9, 0.9, 0.9, 0.8})
		ui_draw_outline(sx, sy, SLOT_SIZE, SLOT_SIZE, 1.0, UI_COLOR_BORDER)

		if i == state.active_hotbar_slot {
			ui_draw_outline(sx - 2, sy - 2, SLOT_SIZE + 4, SLOT_SIZE + 4, 3.0, UI_COLOR_ACCENT)
		}

		if slot.kind != .None {
			info := item_db[slot.kind]
			pad := SLOT_PADDING
			if info.sprite_id >= 0 {
				ui_draw_sprite(
					info.sprite_id,
					sx + pad,
					sy + pad,
					SLOT_SIZE - pad * 2,
					SLOT_SIZE - pad * 2,
				)
			} else {
				ui_draw_rect(
					sx + pad,
					sy + pad,
					SLOT_SIZE - pad * 2,
					SLOT_SIZE - pad * 2,
					info.color,
				)
			}
			if slot.count > 1 {
				count_str := fmt.tprintf("%d", slot.count)
				tw := ui_measure_text(count_str)
				ui_draw_text(
					count_str,
					sx + SLOT_SIZE - tw - 2,
					sy + SLOT_SIZE - 12,
					UI_COLOR_TEXT,
				)
			}
		}

		if state.box_sys[player_box_id].is_open && hovered {
			if ctx.mouse_pressed && !state.drag.active && slot.kind != .None {
				state.drag.active =
					true; state.drag.item = slot^; state.drag.source_box = player_box_id; state.drag.source_slot = idx
				slot.kind = .None; slot.count = 0
			} else if !ctx.mouse_down && state.last_input.left_down && state.drag.active {
				if slot.kind ==
				   .None {slot^ = state.drag.item; state.drag.active = false} else if slot.kind == state.drag.item.kind {slot.count += state.drag.item.count; state.drag.active = false} else {temp := slot^; slot^ = state.drag.item; state.drag.item = temp}
			}
		}
	}
}

system_interaction :: proc(view, proj: linalg.Matrix4f32) {
	// Если курсор заблокирован или открыт инвентарь - не взаимодействуем
	if state.input.locked do return
	if len(state.entities) > 0 && state.box_sys[state.entities[state.player_id].box_id].is_open do return

	// Получаем луч из камеры
	ray_origin, ray_dir := get_mouse_ray(state.input.mouse_x, state.input.mouse_y, view, proj)

	closest_dist := f32(10000.0)
	hit_entity_idx := -1

	// 1. Ищем ближайшую сущность, с которой пересекся луч
	for i in 0 ..< len(state.entities) {
		// Не взаимодействуем с самим игроком
		if Entity_Id(i) == state.player_id do continue

		e := &state.entities[i]

		// Переводим локальный AABB в мировые координаты с учетом позиции и масштаба
		world_min := e.pos + (e.collider.min * e.scale)
		world_max := e.pos + (e.collider.max * e.scale)

		if hit, dist := ray_aabb_intersect(ray_origin, ray_dir, world_min, world_max); hit {
			if dist < closest_dist {
				closest_dist = dist
				hit_entity_idx = i
			}
		}
	}

	// 2. Если нашли сущность
	if hit_entity_idx != -1 {
		target := &state.entities[hit_entity_idx]

		proj_copy := proj
		view_copy := view

		// --- HOVER (Наведение) ---
		// Рисуем желтую обводку вокруг объекта
		sgl.defaults()
		sgl.push_pipeline()
		sgl.load_pipeline(state.scene_pip)
		sgl.matrix_mode_projection(); sgl.load_matrix(cast(^f32)&proj_copy)
		sgl.matrix_mode_modelview(); sgl.load_matrix(cast(^f32)&view_copy)

		// Рисуем линии чуть шире и желтым цветом
		sgl.push_matrix()
		sgl.translate(target.pos.x, target.pos.y, target.pos.z)
		// Чуть увеличиваем масштаб обводки, чтобы она была видна поверх объекта
		scale_hover := target.scale * 1.05
		sgl.scale(scale_hover.x, scale_hover.y, scale_hover.z)

		sgl.begin_lines()
		sgl.c3f(1.0, 1.0, 0.0) // Желтый цвет
		// Используем существующий меш линий, но игнорируем его черный цвет, задавая свой через c3f
		// (Примечание: в debug_draw_mesh_lines используется цвет вершин, поэтому напишем упрощенный код здесь)
		m := &state.debug_line_mesh
		for idx in m.indices {
			v := m.vertices[idx]
			sgl.v3f(v.pos.x, v.pos.y, v.pos.z)
		}
		sgl.end()
		sgl.pop_matrix()
		sgl.pop_pipeline()

		// --- CLICK (Клик) ---
		if state.input.left_clicked {
			fmt.println("Clicked on entity:", target.kind, "ID:", hit_entity_idx)

			// Пример взаимодействия:
			// 1. Если это грядка - меняем состояние
			if target.kind == .Garden {
				if target.garden_state == .Dry {
					target.garden_state = .Water
					target.color = {0.2, 0.2, 0.8} // Синий
				} else if target.garden_state == .Water {
					target.garden_state = .Plant
					target.color = {0.2, 0.8, 0.2} // Зеленый
				} else {
					target.garden_state = .Dry
					target.color = {130.0 / 255.0, 108.0 / 255.0, 65.0 / 255.0} // Коричневый
				}
			} else {
				// 2. Если это куб или враг - меняем цвет на случайный или подбрасываем

				target.color = {rand.float32(), rand.float32(), rand.float32()}
				target.vel.y += 5.0 // Подбросить
			}

			// Сбрасываем клик, чтобы не сработал builder (строительство) в этом же кадре
			state.input.left_clicked = false
		}
	}
}

system_input_player :: proc(dt: f32) {
	if len(state.entities) == 0 do return
	player := &state.entities[state.player_id]
	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 := Vec3{sin_yaw, 0, cos_yaw}
	right := Vec3{cos_yaw, 0, -sin_yaw}

	move_dir := Vec3{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

	if linalg.length2(move_dir) > 0.01 {
		move_dir = linalg.normalize(move_dir)
		player.vel.x = move_dir.x * SPEED
		player.vel.z = move_dir.z * SPEED
	} else {
		player.vel.x = 0
		player.vel.z = 0
	}

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

	if state.input.keys[._1] do state.active_hotbar_slot = 0
	if state.input.keys[._2] do state.active_hotbar_slot = 1
	if state.input.keys[._3] do state.active_hotbar_slot = 2
	if state.input.keys[._4] do state.active_hotbar_slot = 3
	if state.input.keys[._5] do state.active_hotbar_slot = 4
	if state.input.keys[._6] do state.active_hotbar_slot = 5
	if state.input.keys[._7] do state.active_hotbar_slot = 6
	if state.input.keys[._8] do state.active_hotbar_slot = 7
	if state.input.keys[._9] do state.active_hotbar_slot = 8

	if state.last_input.keys[.TAB] && !state.input.keys[.TAB] {
		state.box_sys[player.box_id].is_open = !state.box_sys[player.box_id].is_open
		state.input.locked = !state.box_sys[player.box_id].is_open
		sapp.lock_mouse(state.input.locked)

		if !state.box_sys[player.box_id].is_open && state.drag.active {
			state.box_sys[state.drag.source_box].slots[state.drag.source_slot] = state.drag.item
			state.drag.active = false
		}
	}
}

system_builder :: proc(view, proj: linalg.Matrix4f32) {
	if state.input.locked do return
	if state.input.mouse_x < 300 do return
	if len(state.entities) > 0 && state.box_sys[state.entities[state.player_id].box_id].is_open do 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 {
			push_entity(.Cube, hit_pos)
		}
	}
}

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.is_physics 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]

		// 1. Рисуем сам цветной куб
		draw_mesh_instance(&state.mesh_cube, e.pos, e.scale, e.color)

		// 2. Если это Garden, рисуем черную обводку поверх
		if e.kind == .Garden {
			// offset сдвигает глубину линий чуть ближе к камере,
			// чтобы они не проваливались внутрь куба (Z-fighting)
			// sgl.offset(1.0, 1.0)
			debug_draw_mesh_lines(&state.debug_line_mesh, e.pos, e.scale)
			// sgl.offset(0.0, 0.0) // Обязательно сбрасываем обратно
		}
	}

	sgl.pop_pipeline()
}

get_camera_matrices :: proc() -> (view, proj: linalg.Matrix4f32) {
	if len(state.entities) == 0 do return linalg.MATRIX4F32_IDENTITY, linalg.MATRIX4F32_IDENTITY
	player_pos := state.entities[state.player_id].pos
	cam_offset :=
		Vec3 {
			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
	view = linalg.matrix4_look_at_f32(player_pos + cam_offset, player_pos, {0, 1, 0})
	proj = linalg.matrix4_perspective_f32(
		60.0 * (math.PI / 180.0),
		sapp.widthf() / sapp.heightf(),
		0.1,
		200.0,
	)
	return view, proj
}

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)
	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},
			Vertex{p2, color},
			Vertex{p3, color},
			Vertex{p4, color},
		)
		append(
			&m.indices,
			start_idx,
			start_idx + 1,
			start_idx + 2,
			start_idx,
			start_idx + 2,
			start_idx + 3,
		)
	}
	c := [3]f32{1, 1, 1} // Base color, tinted later
	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
}

debug_make_cube_lines :: proc(allocator: mem.Allocator) -> Mesh {
	m: Mesh
	// Нам нужно всего 8 уникальных вершин для каркаса
	m.vertices = make(#soa[dynamic]Vertex, 0, 8, allocator)
	m.indices = make([dynamic]u16, 0, 24, allocator) // 12 ребер * 2 индекса

	// Черный цвет для вершин
	black := [3]f32{0, 0, 0}

	// Добавляем 8 углов куба
	// Нижняя грань (y = -0.5)
	append_soa(&m.vertices, Vertex{{-0.5, -0.5, -0.5}, black}) // 0: лево-низ-зад
	append_soa(&m.vertices, Vertex{{0.5, -0.5, -0.5}, black}) // 1: право-низ-зад
	append_soa(&m.vertices, Vertex{{0.5, -0.5, 0.5}, black}) // 2: право-низ-перед
	append_soa(&m.vertices, Vertex{{-0.5, -0.5, 0.5}, black}) // 3: лево-низ-перед

	// Верхняя грань (y = 0.5)
	append_soa(&m.vertices, Vertex{{-0.5, 0.5, -0.5}, black}) // 4: лево-верх-зад
	append_soa(&m.vertices, Vertex{{0.5, 0.5, -0.5}, black}) // 5: право-верх-зад
	append_soa(&m.vertices, Vertex{{0.5, 0.5, 0.5}, black}) // 6: право-верх-перед
	append_soa(&m.vertices, Vertex{{-0.5, 0.5, 0.5}, black}) // 7: лево-верх-перед

	// Соединяем линиями (пары индексов)
	append(
		&m.indices,
		// Нижний квадрат
		0,
		1,
		1,
		2,
		2,
		3,
		3,
		0,
		// Верхний квадрат
		4,
		5,
		5,
		6,
		6,
		7,
		7,
		4,
		// Вертикальные стойки
		0,
		4,
		1,
		5,
		2,
		6,
		3,
		7,
	)

	return m
}

debug_draw_mesh_lines :: proc(m: ^Mesh, pos: Vec3, scale: Vec3) {
	sgl.push_matrix()
	sgl.translate(pos.x, pos.y, pos.z)
	sgl.scale(scale.x, scale.y, scale.z)

	// Важно: используем begin_lines вместо begin_triangles
	sgl.begin_lines()

	for i := 0; i < len(m.indices); i += 1 {
		idx := m.indices[i]
		v := m.vertices[idx]

		// Используем цвет из вершины (черный)
		sgl.c3f(v.color.x, v.color.y, v.color.z)
		sgl.v3f(v.pos.x, v.pos.y, v.pos.z)
	}

	sgl.end()
	sgl.pop_matrix()
}

draw_mesh_instance :: proc(m: ^Mesh, pos: Vec3, scale: Vec3, tint: Vec3) {
	sgl.push_matrix()
	sgl.translate(pos.x, pos.y, pos.z)
	sgl.scale(scale.x, scale.y, scale.z)
	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()
}

get_mouse_ray :: proc(mx, my: f32, view, proj: linalg.Matrix4f32) -> (origin, dir: Vec3) {
	x := (2.0 * mx) / sapp.widthf() - 1.0
	y := 1.0 - (2.0 * my) / sapp.heightf()
	ray_eye := linalg.matrix4_inverse(proj) * Vec4{x, y, -1.0, 1.0}
	ray_eye = {ray_eye.x, ray_eye.y, -1.0, 0.0}
	inv_view := linalg.matrix4_inverse(view)
	ray_world := inv_view * ray_eye
	return {inv_view[3][0], inv_view[3][1], inv_view[3][2]}, linalg.normalize(ray_world.xyz)
}

ray_plane_intersect :: proc(origin, dir: Vec3, plane_y: f32) -> (pos: Vec3, 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
}

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
			state.input.left_down = true
		}
	case .MOUSE_UP:
		if ev.mouse_button == .LEFT {
			state.input.left_down = false
		}
	}
	if ev.type == .KEY_DOWN && ev.key_code == .ESCAPE {
		state.input.locked = !state.input.locked
		sapp.lock_mouse(state.input.locked)
	}
}

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 = 1920,
			height = 1080,
			window_title = "Sandbox (Dev)",
			icon = {sokol_default = false},
			logger = {func = slog.func},
			high_dpi = true,
			swap_interval = 1,
		},
	)
}
