package ui

import "core:fmt"
import "core:hash"
import "core:math"

// --- 1. Типы данных ---

Vec2 :: [2]f32
Rect :: struct {
	x, y, w, h: f32,
}
Color :: [4]u8

Id :: distinct u32

Input :: struct {
	mouse_pos:     Vec2,
	mouse_delta:   Vec2,
	mouse_down:    bool,
	mouse_pressed: bool,
	measure_text:  proc(text: string) -> f32,
}

// --- 2. Команды ---

Command :: union {
	Cmd_Rect,
	Cmd_Text,
	Cmd_Clip,
}

Cmd_Rect :: struct {
	rect:  Rect,
	color: Color,
}
Cmd_Text :: struct {
	pos:   Vec2,
	color: Color,
	text:  string,
}
Cmd_Clip :: struct {
	rect:       Rect,
	is_actived: bool,
}

// --- 3. Контекст ---

Context :: struct {
	state:             map[Id]State_Variant,
	commands:          [dynamic]Command,
	layout_stack:      [dynamic]Layout,
	id_stack:          [dynamic]Id,
	input, last_input: Input,
	hover_id:          Id,
	hot_id:            Id,
	drag_start_pos:    Vec2,
	drag_start_rect:   Rect,
	current_window_id: Id,
	windows_order:     [dynamic]Id,
	top_window_id:     Id,
}

State_Variant :: union {
	State_Window,
}

State_Window :: struct {
	rect:     Rect,
	is_open:  bool,
	min_size: Vec2,
	commands: [dynamic]Command,
}

// --- 4. Лейаут ---

Layout_Mode :: enum {
	Vertical,
	Horizontal,
	Flow,
}

Layout :: struct {
	rect:         Rect,
	cursor:       Vec2,
	row_h:        f32,
	spacing:      f32,
	widths:       [16]f32,
	widths_count: int,
	width_idx:    int,
	max_pos:      Vec2,
	min_w:        f32,
	mode:         Layout_Mode,
}

// --- 5. API: Core ---

init :: proc(ctx: ^Context) {
	ctx.state = make(map[Id]State_Variant)
	ctx.commands = make([dynamic]Command, 0, 1024)
	ctx.layout_stack = make([dynamic]Layout, 0, 16)
	ctx.id_stack = make([dynamic]Id, 0, 16)
	ctx.windows_order = make([dynamic]Id, 0, 16)
}

begin :: proc(ctx: ^Context, input: Input) {
	ctx.last_input = ctx.input
	ctx.input = input

	clear(&ctx.commands)
	clear(&ctx.layout_stack)
	clear(&ctx.id_stack)

	ctx.hover_id = 0
	ctx.top_window_id = 0

	// Проходим по списку окон В ОБРАТНОМ ПОРЯДКЕ (от верхнего к нижнему)
	#reverse for id in ctx.windows_order {
		if id in ctx.state {
			// Получаем состояние окна (нам нужен только rect)
			// Важно: берем копию или ссылку, здесь не важно, мы только читаем
			state := ctx.state[id].(State_Window)

			// Если окно открыто и мышь внутри него
			if state.is_open && point_in_rect(ctx.input.mouse_pos, state.rect) {
				ctx.top_window_id = id
				break // Нашли самое верхнее, остальные перекрыты
			}
		}
	}

	// Seed ID
	append(&ctx.id_stack, 2166136261)
	push_layout(ctx, {0, 0, 10000, 10000})
}

end :: proc(ctx: ^Context) {
	// Проходим по списку порядка окон
	for id in ctx.windows_order {
		if id in ctx.state {
			state := ctx.state[id].(State_Window)

			// Если окно открыто, копируем его команды в главный список
			if state.is_open {
				for cmd in state.commands {
					append(&ctx.commands, cmd)
				}
			}
		}
	}

	// Сбрасываем текущее окно, на всякий случай
	ctx.current_window_id = 0
}

// --- 6. API: Виджеты ---

window :: proc(ctx: ^Context, title: string, rect: Rect) -> bool {
	// ЗАПРЕТ ВЛОЖЕННЫХ ОКОН
	// Это предотвратит создание вложенного окна и выполнение его кода.
	assert(ctx.current_window_id == 0)

	id := get_id(ctx, title)
	ctx.current_window_id = id

	// 1. Инициализация
	if id not_in ctx.state {
		ctx.state[id] = State_Window {
			rect     = rect,
			is_open  = true,
			min_size = {64, 64},
			commands = make([dynamic]Command),
		}
		// Добавляем новое окно в список порядка (сверху)
		append(&ctx.windows_order, id)
	}

	// Берем указатель на состояние
	variant_ptr := &ctx.state[id]
	state := &variant_ptr.(State_Window)

	// ОЧИЩАЕМ буфер команд окна в начале его отрисовки
	clear(&state.commands)

	// --- ОБНОВЛЕННАЯ ЛОГИКА ФОКУСА ---
	m := ctx.input.mouse_pos
	hovered := point_in_rect(m, state.rect)

	// Мы можем взаимодействовать с окном (двигать, поднимать), ТОЛЬКО если:
	// 1. Мышь над ним
	// 2. И это окно является верхним под мышью (никто его не перекрывает)
	if hovered && ctx.top_window_id == id {
		if ctx.input.mouse_pressed {
			bring_to_front(ctx, id)
		}
	}

	// --- 2. Логика заголовка (Перемещение) ---
	header_h: f32 = 25
	header_rect := Rect{state.rect.x, state.rect.y, state.rect.w, header_h}
	header_id := get_id_from_id(id, 1)

	update_control(ctx, header_id, header_rect)

	if ctx.hot_id == header_id {
		if ctx.input.mouse_pressed {
			ctx.drag_start_pos = ctx.input.mouse_pos
			ctx.drag_start_rect = state.rect
		}

		// Проверяем, держит ли пользователь мышь
		if ctx.input.mouse_down {
			delta := ctx.input.mouse_pos - ctx.drag_start_pos
			state.rect.x = ctx.drag_start_rect.x + delta.x
			state.rect.y = ctx.drag_start_rect.y + delta.y
		} else {
			// Мышь отпустили — прекращаем драг
			ctx.hot_id = 0
		}
	}

	// --- 3. Логика ресайза (Правый нижний угол) ---
	resize_handle_size: f32 = 15
	resize_rect := Rect {
		state.rect.x + state.rect.w - resize_handle_size,
		state.rect.y + state.rect.h - resize_handle_size,
		resize_handle_size,
		resize_handle_size,
	}

	resize_id := get_id_from_id(id, 2)
	update_control(ctx, resize_id, resize_rect)

	if ctx.hot_id == resize_id {
		if ctx.input.mouse_pressed {
			ctx.drag_start_pos = ctx.input.mouse_pos
			ctx.drag_start_rect = state.rect
		}

		if ctx.input.mouse_down {
			delta := ctx.input.mouse_pos - ctx.drag_start_pos
			limit_w := math.max(64, state.min_size.x)
			limit_h := math.max(64, state.min_size.y)
			new_w := ctx.drag_start_rect.w + delta.x
			new_h := ctx.drag_start_rect.h + delta.y
			state.rect.w = math.clamp(new_w, limit_w, 10000)
			state.rect.h = math.clamp(new_h, limit_h, 10000)
		} else {
			// Мышь отпустили — прекращаем ресайз
			ctx.hot_id = 0
		}
	}


	push_cmd(
		ctx,
		Cmd_Rect{{state.rect.x + 4, state.rect.y + 4, state.rect.w, state.rect.h}, {0, 0, 0, 40}},
	)
	push_cmd(ctx, Cmd_Rect{state.rect, {45, 45, 45, 255}})

	header_col: Color
	switch {
	case ctx.hot_id == header_id:
		header_col = {90, 90, 90, 255}
	case:
		header_col = {60, 60, 60, 255}
	}
	push_cmd(ctx, Cmd_Rect{{state.rect.x, state.rect.y, state.rect.w, header_h}, header_col})
	// Вычисляем область для текста (ширина окна минус место под треугольник ресайза и отступы)
	title_rect := Rect {
		state.rect.x + 8,
		state.rect.y,
		state.rect.w - 20, // Оставляем место справа
		header_h,
	}
	draw_text_left_truncated(ctx, title, title_rect, {220, 220, 220, 255})


	// Рисуем индикатор ресайза в актуальной позиции (после обновления state.rect)
	// Пересчитываем позицию, так как state.rect мог измениться выше
	current_resize_rect := Rect {
		state.rect.x + state.rect.w - resize_handle_size,
		state.rect.y + state.rect.h - resize_handle_size,
		resize_handle_size,
		resize_handle_size,
	}

	// Цвет индикатора меняем для наглядности
	resize_col := ctx.hot_id == resize_id ? Color{120, 120, 120, 255} : Color{80, 80, 80, 255}
	push_cmd(ctx, Cmd_Rect{{current_resize_rect.x, current_resize_rect.y, 15, 15}, resize_col})

	// --- 5. Лейаут контента ---
	content_rect := state.rect
	content_rect.y += header_h
	content_rect.h -= header_h

	push_cmd(ctx, Cmd_Clip{content_rect, true})

	push_layout(ctx, content_rect)

	return true
}

window_end :: proc(ctx: ^Context) {
	l := current_layout(ctx)

	id := ctx.current_window_id
	if id in ctx.state {
		variant_ptr := &ctx.state[id]
		state := &variant_ptr.(State_Window)

		min_w := l.min_w + 16.0 // + padding
		min_h := (l.max_pos.y - state.rect.y) + 8.0

		state.min_size = {min_w, min_h}
	}

	pop_layout(ctx)

	push_cmd(ctx, Cmd_Clip{{}, false})

	// Сбрасываем текущее окно
	ctx.current_window_id = 0
}

button :: proc(ctx: ^Context, text: string) -> bool {
	id := get_id(ctx, text)

	w: f32 = 0
	l := current_layout(ctx)

	// В Flow режиме запрашиваем полную ширину текста + отступы
	if l.mode == .Flow {
		padding: f32 = 20
		w = measure_text(ctx, text) + padding
	}

	// layout_next теперь вернет w, возможно сжатое (clamped), если места мало
	rect := layout_next(ctx, w)

	update_control(ctx, id, rect)

	clicked := false
	bg_color: Color
	switch {
	case ctx.hot_id == id:
		bg_color = {50, 50, 50, 255}
		if ctx.last_input.mouse_down && !ctx.input.mouse_down {
			ctx.hot_id = 0
			clicked = true
		}
	case ctx.hover_id == id:
		bg_color = {90, 90, 90, 255}
	case:
		bg_color = {70, 70, 70, 255}
	}

	push_cmd(ctx, Cmd_Rect{rect, bg_color})

	draw_text_centered_truncated(ctx, text, rect, {255, 255, 255, 255})

	return clicked
}

label :: proc(ctx: ^Context, text: string) {
	rect := layout_next(ctx)
	draw_text_left_truncated(ctx, text, rect, {200, 200, 200, 255})
}

row :: proc(ctx: ^Context, widths: []f32, height: f32 = 0) {
	l := current_layout(ctx)

	l.width_idx = 0
	l.widths_count = min(len(widths), 16)
	l.row_h = height > 0 ? height : 28

	total_width := l.rect.w
	total_spacing := f32(l.widths_count - 1) * l.spacing
	available_width := total_width - total_spacing

	fixed_width: f32 = 0
	dynamic_count := 0
	current_row_min_w: f32 = 0

	for w in widths {
		if w == -1 {
			dynamic_count += 1
			current_row_min_w += 20 // Минимальная ширина для динамической кнопки
		} else if w > 0 && w <= 1.0 {
			fixed_width += total_width * w
			current_row_min_w += 20 // Проценты тоже могут сжиматься, ставим безопасный минимум
		} else {
			fixed_width += w
			current_row_min_w += w // Фиксированные пиксели — это жесткое ограничение
		}
	}

	// Добавляем отступы к минимальной ширине
	current_row_min_w += total_spacing

	// Обновляем глобальный минимум лейаута
	l.min_w = math.max(l.min_w, current_row_min_w)
	// -------------------------------------

	remaining_width := math.max(0, available_width - fixed_width)
	auto_width := dynamic_count > 0 ? remaining_width / f32(dynamic_count) : 0

	for i in 0 ..< l.widths_count {
		w := widths[i]
		if w == -1 {
			l.widths[i] = auto_width
		} else if w > 0 && w <= 1.0 {
			l.widths[i] = total_width * w
		} else {
			l.widths[i] = w
		}
	}
}

flow :: proc(ctx: ^Context, height: f32 = 0) {
	l := current_layout(ctx)
	l.mode = .Flow
	l.row_h = height > 0 ? height : 28
	l.width_idx = 0 // Сбрасываем индексы, они не нужны в Flow
}

// --- Внутренние функции ---

@(private)
get_id :: proc(ctx: ^Context, data: string) -> Id {
	seed := ctx.id_stack[len(ctx.id_stack) - 1]
	h := hash.fnv32a(transmute([]u8)data, u32(seed))
	return Id(h)
}

@(private)
get_id_from_id :: proc(id: Id, salt: u32) -> Id {
	// Простое комбинирование хешей
	h := hash.fnv32a(transmute([]u8)string("salt"), u32(id) + salt)
	return Id(h)
}

@(private)
push_cmd :: proc(ctx: ^Context, cmd: Command) {
	assert(ctx.current_window_id != 0)

	// Если мы сейчас внутри окна
	// Получаем состояние окна
	variant_ptr := &ctx.state[ctx.current_window_id]
	// Важно: используем указатель, чтобы append сработал
	state := &variant_ptr.(State_Window)
	append(&state.commands, cmd)
}

@(private)
bring_to_front :: proc(ctx: ^Context, id: Id) {
	// 1. Ищем индекс этого окна в списке
	idx := -1
	for win_id, i in ctx.windows_order {
		if win_id == id {
			idx = i
			break
		}
	}

	// 2. Если нашли и оно не последнее — перемещаем
	if idx != -1 && idx < len(ctx.windows_order) - 1 {
		ordered_remove(&ctx.windows_order, idx)
		append(&ctx.windows_order, id)
	}
}

@(private)
update_control :: proc(ctx: ^Context, id: Id, rect: Rect) {
	m := ctx.input.mouse_pos

	// Геометрическая проверка
	hovered := point_in_rect(m, rect)

	// --- НОВАЯ ПРОВЕРКА: ПЕРЕКРЫТИЕ ---
	// Если мышь геометрически над виджетом, НО:
	// 1. Есть какое-то окно под мышью (top_window_id != 0)
	// 2. И это окно - НЕ то, в котором находится наш виджет
	// ТОГДА: Мышь перекрыта другим окном.
	if hovered && ctx.top_window_id != 0 && ctx.top_window_id != ctx.current_window_id {
		hovered = false
	}

	// Если мышь нажата где-то в другом месте
	if ctx.hot_id != 0 && ctx.hot_id != id {
		return
	}

	if hovered {
		ctx.hover_id = id
		if ctx.input.mouse_pressed {
			ctx.hot_id = id
		}
	}
}

@(private)
push_layout :: proc(ctx: ^Context, rect: Rect) {
	l: Layout
	l.rect = rect

	padding: f32 = 8
	l.cursor = {rect.x + padding, rect.y + padding}
	l.max_pos = l.cursor

	l.rect.w -= padding * 2
	l.spacing = 4
	l.row_h = 28
	append(&ctx.layout_stack, l)
}

@(private)
pop_layout :: proc(ctx: ^Context) {
	pop(&ctx.layout_stack)
}

@(private)
current_layout :: proc(ctx: ^Context) -> ^Layout {
	return &ctx.layout_stack[len(ctx.layout_stack) - 1]
}

@(private)
layout_next :: proc(ctx: ^Context, desired_w: f32 = 0) -> Rect {
	l := current_layout(ctx)
	res: Rect

	switch l.mode {
	case .Horizontal:
		if l.width_idx < l.widths_count {
			w := l.widths[l.width_idx]
			res = {l.cursor.x, l.cursor.y, w, l.row_h}
			l.cursor.x += w + l.spacing
			l.width_idx += 1
			if l.width_idx == l.widths_count {
				l.cursor.x = l.rect.x + 8
				l.cursor.y += l.row_h + l.spacing
			}
		}

	case .Vertical:
		res = {l.cursor.x, l.cursor.y, l.rect.w, l.row_h}
		l.cursor.y += l.row_h + l.spacing

	case .Flow:
		// 1. Сколько места осталось на текущей строке?
		// l.rect.x + l.rect.w = правая граница контейнера
		// l.cursor.x = текущая позиция
		right_boundary := l.rect.x + l.rect.w
		available_w := right_boundary - l.cursor.x

		// 2. Минимальная ширина, при которой есть смысл рисовать кнопку (например, "A...")
		min_useful_w: f32 = 40.0

		w := desired_w

		// Влезает ли элемент целиком?
		if w <= available_w {
			// Влезает, оставляем как есть
		} else {
			// Не влезает.
			// Проверяем: мы в начале строки?
			at_start_of_line := (l.cursor.x <= l.rect.x + 8.0) // +8 это padding

			if !at_start_of_line && available_w < min_useful_w {
				// Мы НЕ в начале строки, и места осталось мало (< 40px).
				// Сжимать нет смысла, переносим на новую строку.
				l.cursor.x = l.rect.x + 8
				l.cursor.y += l.row_h + l.spacing

				// На новой строке доступна вся ширина
				available_w = l.rect.w
			}

			// Теперь (возможно, уже на новой строке) проверяем, влезает ли он.
			// Если все еще не влезает (окно очень узкое), СЖИМАЕМ его до ширины окна.
			w = math.min(w, available_w)
		}

		res = {l.cursor.x, l.cursor.y, w, l.row_h}
		l.cursor.x += w + l.spacing
	}

	l.max_pos.x = math.max(l.max_pos.x, res.x + res.w)
	l.max_pos.y = math.max(l.max_pos.y, res.y + res.h)

	return res
}

// Функция для обрезки текста с добавлением "..."
@(private)
truncate_text :: proc(ctx: ^Context, text: string, max_w: f32) -> string {
	if measure_text(ctx, text) <= max_w {
		return text
	}

	// Это упрощенная версия. В идеале нужно использовать string builder и аллокатор
	// Но для IMGUI часто просто режут строку.

	ellipsis := "..."
	ellipsis_w := measure_text(ctx, ellipsis)

	// Если даже точки не влезают
	if ellipsis_w >= max_w {
		return "."
	}

	// Пытаемся найти длину подстроки, которая влезет вместе с точками
	// Это линейный поиск, можно оптимизировать бинарным, но для UI сойдет
	for i := len(text) - 1; i > 0; i -= 1 {
		sub := text[:i]
		w := measure_text(ctx, sub)
		if w + ellipsis_w <= max_w {
			// Нашли! Собираем новую строку.
			// Используем temp_allocator, так как строка живет 1 кадр
			return fmt.tprintf("{}{}", sub, ellipsis)
		}
	}

	return ellipsis
}

// Рисует текст, центрируя его в rect. Если не влезает — обрезает и ставит "..."
@(private)
draw_text_centered_truncated :: proc(ctx: ^Context, text: string, rect: Rect, color: Color) {
	// Отступы по бокам, чтобы текст не прилипал к краям
	padding: f32 = 4
	max_w := math.max(0, rect.w - padding * 2)

	display_text := text
	text_w := measure_text(ctx, text)

	// Если текст шире доступного места — обрезаем
	if text_w > max_w {
		display_text = truncate_text(ctx, text, max_w)
		text_w = measure_text(ctx, display_text)
	}

	tx := rect.x + (rect.w - text_w) * 0.5
	ty := rect.y + (rect.h - 14) * 0.5 // 14 - примерная высота шрифта по умолчанию

	push_cmd(ctx, Cmd_Text{{tx, ty}, color, display_text})
}

// Для заголовков и лейблов (выравнивание слева)
@(private)
draw_text_left_truncated :: proc(ctx: ^Context, text: string, rect: Rect, color: Color) {
	max_w := math.max(0, rect.w)

	display_text := text
	if measure_text(ctx, text) > max_w {
		display_text = truncate_text(ctx, text, max_w)
	}

	// Центрируем только по вертикали
	ty := rect.y + (rect.h - 14) * 0.5
	push_cmd(ctx, Cmd_Text{{rect.x, ty}, color, display_text})
}

@(private)
measure_text :: proc(ctx: ^Context, text: string) -> f32 {
	if ctx.input.measure_text != nil {
		return ctx.input.measure_text(text)
	}
	return f32(len(text) * 8)
}

@(private)
shrink_rect :: proc(r: Rect, amount: f32) -> Rect {
	return Rect{r.x + amount, r.y + amount, r.w - amount * 2, r.h - amount * 2}
}

// Хелпер для проверки попадания точки в прямоугольник
@(private)
point_in_rect :: proc(p: Vec2, r: Rect) -> bool {
	return p.x >= r.x && p.x <= r.x + r.w && p.y >= r.y && p.y <= r.y + r.h
}
