package tests

import "core:fmt"
import "core:os"
import "core:strings"
import "core:testing"
import "../src/adapters"
import "../src/core"
import "../src/gui"
import "../src/shared"
import "../src/ui"

// ─────────────────────────────────────────────────────────────
// Milestone 34E: Layout constant validation at multiple resolutions
// ─────────────────────────────────────────────────────────────

@test
layout_constants_match_hardcoded_values :: proc(t: ^testing.T) {
	testing.expect(t, shared.LAYOUT.sidebar_width == 282, "sidebar_width should match historical 282")
	testing.expect(t, shared.LAYOUT.right_margin == 20, "right_margin should match historical 20")
	testing.expect(t, shared.LAYOUT.min_main_width == 960, "min_main_width should match historical 960")
	testing.expect(t, shared.LAYOUT.top_reserved_height == 252, "top_reserved_height should match historical 252")
	testing.expect(t, shared.LAYOUT.min_lower_y == 450, "min_lower_y should match historical 450")
	testing.expect(t, shared.LAYOUT.compact_height == 860, "compact_height should match historical 860")
}

@test
compute_main_width_formula :: proc(t: ^testing.T) {
	// 1920x1080 standard
	w := shared.compute_main_width(1920)
	testing.expect(t, w == 1618, fmt.tprintf("1920px screen: expected 1618, got %d", w))

	// 1366x768 compact
	w2 := shared.compute_main_width(1366)
	testing.expect(t, w2 == 1064, fmt.tprintf("1366px screen: expected 1064, got %d", w2))

	// Ultrawide 2560x1440
	w3 := shared.compute_main_width(2560)
	testing.expect(t, w3 == 2258, fmt.tprintf("2560px screen: expected 2258, got %d", w3))

	// Minimum floor
	w4 := shared.compute_main_width(1200)
	testing.expect(t, w4 == 960, fmt.tprintf("narrow screen: expected 960 floor, got %d", w4))
}

@test
compute_lower_y_formula :: proc(t: ^testing.T) {
	// 1920x1080 standard
	y := shared.compute_lower_y(1080)
	testing.expect(t, y == 828, fmt.tprintf("1080px height: expected 828, got %d", y))

	// 1366x768 compact
	y2 := shared.compute_lower_y(768)
	testing.expect(t, y2 == 516, fmt.tprintf("768px height: expected 516, got %d", y2))

	// Minimum floor
	y3 := shared.compute_lower_y(600)
	testing.expect(t, y3 == 450, fmt.tprintf("short screen: expected 450 floor, got %d", y3))
}

@test
screen_profile_classification :: proc(t: ^testing.T) {
	// Compact: height < 860
	p1 := shared.screen_profile(1366, 768)
	testing.expect(t, p1 == .Compact, "1366x768 should be Compact")

	// Standard: height >= 860, width < 1920
	p2 := shared.screen_profile(1440, 900)
	testing.expect(t, p2 == .Standard, "1440x900 should be Standard")

	// Wide: width >= 1920
	p3 := shared.screen_profile(1920, 1080)
	testing.expect(t, p3 == .Wide, "1920x1080 should be Wide")

	// Ultrawide
	p4 := shared.screen_profile(2560, 1440)
	testing.expect(t, p4 == .Wide, "2560x1440 should be Wide")
}

@test
is_compact_helper :: proc(t: ^testing.T) {
	testing.expect(t, shared.is_compact(768), "768px should be compact")
	testing.expect(t, shared.is_compact(859), "859px should be compact")
	testing.expect(t, !shared.is_compact(860), "860px should not be compact")
	testing.expect(t, !shared.is_compact(1080), "1080px should not be compact")
}

// ─────────────────────────────────────────────────────────────
// Milestone 39A: GUI integration smoke tests for full local flow
// ─────────────────────────────────────────────────────────────

@test
gui_bubble_type_name_roundtrip :: proc(t: ^testing.T) {
	types := []core.Bubble_Type{.Normal, .Thought, .Shout, .Whisper, .Narration, .Sound_Effect}
	names := []string{"Normal", "Thought", "Shout", "Whisper", "Narration", "SFX"}

	for name, i in names {
		nt := gui.bubble_type_from_name(name)
		testing.expect(t, nt == types[i], fmt.tprintf("bubble_type_from_name(%q) should map to correct type", name))
		testing.expect(t, gui.bubble_type_name(types[i]) == name, fmt.tprintf("bubble_type_name should return %q", name))
	}
}

@test
gui_clamp_bubble_cursor :: proc(t: ^testing.T) {
	testing.expect(t, gui.clamp_bubble_cursor(0, 5) == 0, "zero count should return 0")
	testing.expect(t, gui.clamp_bubble_cursor(3, -1) == 0, "negative cursor should clamp to 0")
	testing.expect(t, gui.clamp_bubble_cursor(3, 3) == 2, "cursor at count should clamp to last index")
	testing.expect(t, gui.clamp_bubble_cursor(3, 10) == 2, "cursor beyond count should clamp to last index")
	testing.expect(t, gui.clamp_bubble_cursor(5, 2) == 2, "valid cursor should pass through")
}

@test
gui_layout_validation_coverage :: proc(t: ^testing.T) {
	panels: [dynamic]core.Page_Layout_Panel
	defer delete(panels)

	// 4 panels filling the page exactly
	for i in 0..<4 {
		panel := core.Page_Layout_Panel{
			panel_id     = fmt.tprintf("panel_%d", i),
			panel_number = i + 1,
			layout_cell  = core.Layout_Cell{
				x = f32(i%2) * 0.5,
				y = f32(i/2) * 0.5,
				w = 0.5,
				h = 0.5,
			},
		}
		append(&panels, panel)
	}

	layout := core.Page_Layout{
		page_number = 1,
		pattern_id  = "grid-2x2",
		width       = 800,
		height      = 1200,
		panels      = panels[:],
	}

	val := gui.validate_layout_page(layout, nil)
	testing.expect(t, val.total_panels == 4, "should count 4 panels")
	testing.expect(t, val.coverage_pct == 100.0, fmt.tprintf("coverage should be 100%%, got %.1f%%", val.coverage_pct))
	testing.expect(t, val.missing_bindings == 4, "all 4 panels should be missing images")
	testing.expect(t, val.bounds_violations == 0, "no bounds violations for valid cells")
}

@test
gui_layout_validation_bounds_violations :: proc(t: ^testing.T) {
	panels: [dynamic]core.Page_Layout_Panel
	defer delete(panels)

	// Panel extending beyond bounds
	panel := core.Page_Layout_Panel{
		panel_id     = "panel_1",
		panel_number = 1,
		layout_cell  = core.Layout_Cell{
			x = -0.1,
			y = 0.0,
			w = 1.2,
			h = 1.0,
		},
	}
	append(&panels, panel)

	layout := core.Page_Layout{
		page_number = 1,
		pattern_id  = "single-splash",
		width       = 800,
		height      = 1200,
		panels      = panels[:],
	}

	val := gui.validate_layout_page(layout, nil)
	testing.expect(t, val.bounds_violations == 1, "should detect bounds violation for negative x and width > 1")
}

@test
gui_layout_validation_missing_bindings :: proc(t: ^testing.T) {
	panels: [dynamic]core.Page_Layout_Panel
	defer delete(panels)

	panel := core.Page_Layout_Panel{
		panel_id     = "panel_1",
		panel_number = 1,
		layout_cell  = core.Layout_Cell{x = 0, y = 0, w = 1, h = 1},
	}
	append(&panels, panel)

	layout := core.Page_Layout{
		page_number = 1,
		pattern_id  = "single-splash",
		width       = 800,
		height      = 1200,
		panels      = panels[:],
	}

	// No panel images at all
	val := gui.validate_layout_page(layout, nil)
	testing.expect(t, val.missing_bindings == 1, "should detect missing binding for panel without image")

	// Panel image present
	images := make(map[string]core.Panel_Image)
	images["panel_1"] = core.Panel_Image{url = "test.png", width = 512, height = 512, seed = 42, prompt = "test"}
	val2 := gui.validate_layout_page(layout, images)
	testing.expect(t, val2.missing_bindings == 0, "should have no missing bindings when image exists")
	delete(images)
}

// ─────────────────────────────────────────────────────────────
// Milestone 39B: Error-path tests
// ─────────────────────────────────────────────────────────────

@test
gui_action_regenerate_page_layout_invalid_page :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	msg := gui.action_regenerate_page_layout(&controller, -1)
	testing.expect(t, msg == "Invalid layout page", "negative page index should return error")

	msg2 := gui.action_regenerate_page_layout(&controller, 0)
	testing.expect(t, msg2 == "Invalid layout page", "page index beyond layouts should return error")
}

@test
gui_action_regenerate_page_layout_no_panels :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	// Add empty layout
	layouts: [dynamic]core.Page_Layout
	defer delete(layouts)
	append(&layouts, core.Page_Layout{
		page_number = 1,
		pattern_id  = "single-splash",
		width       = 800,
		height      = 1200,
	})
	controller.state.page_layouts = layouts[:]
	defer delete(controller.state.page_layouts)

	msg := gui.action_regenerate_page_layout(&controller, 0)
	testing.expect(t, msg == "Page has no panels", "empty layout should return error")
}

@test
gui_action_add_bubble_creates_map :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	testing.expect(t, controller.state.speech_bubbles == nil, "speech_bubbles should start nil")

	msg := gui.action_add_bubble(&controller, "test_panel")
	testing.expect(t, msg == "Added bubble", "add bubble should succeed")
	testing.expect(t, controller.state.speech_bubbles != nil, "speech_bubbles map should be created")

	slice, ok := controller.state.speech_bubbles["test_panel"]
	testing.expect(t, ok, "panel key should exist in map")
	testing.expect(t, len(slice) == 1, "should have one bubble")
	testing.expect(t, slice[0].type == .Normal, "new bubble should be Normal type")
	testing.expect(t, slice[0].text == "New bubble", "new bubble should have default text")

	// Clean up owned strings
	core.dispose_speech_bubbles(&controller.state.speech_bubbles)
}

@test
gui_action_delete_bubble_edge_cases :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	// Delete from nil map
	msg := gui.action_delete_bubble(&controller, "panel_1", 0)
	testing.expect(t, strings.has_prefix(msg, "No bubbles"), "delete from nil map should return error")

	// Delete with invalid index on populated map
	bubbles: [dynamic]core.Speech_Bubble
	defer delete(bubbles)
	bubble := core.Speech_Bubble{id = "b1", panel_id = "panel_1", type = .Normal, text = "hello"}
	append(&bubbles, bubble)
	controller.state.speech_bubbles = make(map[string][]core.Speech_Bubble)
	controller.state.speech_bubbles["panel_1"] = bubbles[:]

	msg3 := gui.action_delete_bubble(&controller, "panel_1", -1)
	testing.expect(t, strings.has_prefix(msg3, "Bubble not found"), "negative index should return error")

	msg4 := gui.action_delete_bubble(&controller, "panel_1", 5)
	testing.expect(t, strings.has_prefix(msg4, "Bubble not found"), "out-of-range index should return error")
}

@test
gui_action_delete_bubble_removes_last_bubble :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	controller.state.speech_bubbles = make(map[string][]core.Speech_Bubble)

	bubbles: [dynamic]core.Speech_Bubble
	bubble := core.Speech_Bubble{id = "b1", panel_id = "panel_1", type = .Normal, text = "hello"}
	append(&bubbles, bubble)
	controller.state.speech_bubbles["panel_1"] = bubbles[:]

	msg := gui.action_delete_bubble(&controller, "panel_1", 0)
	testing.expect(t, msg == "Deleted bubble", "delete should succeed")

	_, ok := controller.state.speech_bubbles["panel_1"]
	testing.expect(t, !ok, "panel key should be removed when last bubble deleted")

	// Clean up
	core.dispose_speech_bubbles(&controller.state.speech_bubbles)
}

@test
gui_action_update_bubble_edge_cases :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	// Update from nil map
	msg := gui.action_update_bubble(&controller, "panel_1", 0, .Thought, "")
	testing.expect(t, strings.has_prefix(msg, "No bubbles"), "update from nil map should return error")

	// Update with invalid index on populated map
	bubbles: [dynamic]core.Speech_Bubble
	defer delete(bubbles)
	bubble := core.Speech_Bubble{id = "b1", panel_id = "panel_1", type = .Normal, text = "hello"}
	append(&bubbles, bubble)
	controller.state.speech_bubbles = make(map[string][]core.Speech_Bubble)
	controller.state.speech_bubbles["panel_1"] = bubbles[:]

	msg2 := gui.action_update_bubble(&controller, "panel_1", -1, .Thought, "")
	testing.expect(t, strings.has_prefix(msg2, "Bubble not found"), "negative index should return error")

	msg3 := gui.action_update_bubble(&controller, "panel_1", 5, .Thought, "")
	testing.expect(t, strings.has_prefix(msg3, "Bubble not found"), "out-of-range index should return error")
}

@test
gui_action_update_bubble_changes_type :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	controller.state.speech_bubbles = make(map[string][]core.Speech_Bubble)

	bubbles: [dynamic]core.Speech_Bubble
	bubble := core.Speech_Bubble{id = "b1", panel_id = "panel_1", type = .Normal, text = "hello"}
	append(&bubbles, bubble)
	controller.state.speech_bubbles["panel_1"] = bubbles[:]

	msg := gui.action_update_bubble(&controller, "panel_1", 0, .Thought, "")
	testing.expect(t, msg == "Bubble updated", "update should succeed")
	testing.expect(t, controller.state.speech_bubbles["panel_1"][0].type == .Thought, "bubble type should change")

	core.dispose_speech_bubbles(&controller.state.speech_bubbles)
}

@test
gui_action_update_bubble_changes_text :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)
	controller := ui.new_controller(state)
	defer ui.dispose_job_manager(&controller.jobs)

	controller.state.speech_bubbles = make(map[string][]core.Speech_Bubble)

	bubbles: [dynamic]core.Speech_Bubble
	bubble := core.Speech_Bubble{id = "b1", panel_id = "panel_1", type = .Normal, text = "old text"}
	append(&bubbles, bubble)
	controller.state.speech_bubbles["panel_1"] = bubbles[:]

	msg := gui.action_update_bubble(&controller, "panel_1", 0, .Normal, "new text")
	testing.expect(t, msg == "Bubble updated", "update should succeed")
	testing.expect(t, controller.state.speech_bubbles["panel_1"][0].text == "new text", "bubble text should change")

	core.dispose_speech_bubbles(&controller.state.speech_bubbles)
}

// ─────────────────────────────────────────────────────────────
// Milestone 39C: Ownership/lifecycle audits for new GUI surfaces
// ─────────────────────────────────────────────────────────────

@test
gui_bubble_map_disposal_cleans_strings :: proc(t: ^testing.T) {
	state := core.new_initial_state()
	defer core.dispose_state(&state)

	// Create bubbles with owned strings
	state.speech_bubbles = make(map[string][]core.Speech_Bubble)

	bubbles: [dynamic]core.Speech_Bubble
	bubble := core.Speech_Bubble{
		id           = fmt.aprintf("bubble_%d", 1),
		panel_id     = fmt.aprintf("panel_%d", 1),
		type         = .Normal,
		text         = fmt.aprintf("Test dialogue %d", 1),
		speaker_id   = fmt.aprintf("char_%d", 1),
		tail_direction = "bottom",
		style        = core.DEFAULT_BUBBLE_STYLE,
	}
	append(&bubbles, bubble)
	state.speech_bubbles["panel_1"] = bubbles[:]

	// Verify bubbles exist before dispose
	testing.expect(t, len(state.speech_bubbles) == 1, "should have one panel entry")
	testing.expect(t, len(state.speech_bubbles["panel_1"]) == 1, "should have one bubble")

	// Dispose should clean up owned strings without errors
	core.dispose_speech_bubbles(&state.speech_bubbles)
	// Note: dispose_speech_bubbles deletes the map shell, so we don't check state after
	testing.expect(t, true, "dispose completed without crash")
}

@test
gui_layout_page_cursor_clamp_stability :: proc(t: ^testing.T) {
	testing.expect(t, gui.clamp_layout_cursor(0, 5) == 0, "zero layouts should return 0")
	testing.expect(t, gui.clamp_layout_cursor(3, -1) == 0, "negative cursor should clamp to 0")
	testing.expect(t, gui.clamp_layout_cursor(3, 3) == 2, "cursor at count should clamp to last")
	testing.expect(t, gui.clamp_layout_cursor(5, 2) == 2, "valid cursor should pass through")
}

@test
gui_collect_layout_panels_for_page_edge_cases :: proc(t: ^testing.T) {
	layouts: [dynamic]core.Page_Layout
	defer delete(layouts)

	// Empty layouts
	result := gui.collect_layout_panels_for_page(layouts[:], 0)
	testing.expect(t, result == nil, "empty layouts should return nil")

	// Invalid page cursor
	append(&layouts, core.Page_Layout{page_number = 1, pattern_id = "single-splash"})
	result2 := gui.collect_layout_panels_for_page(layouts[:], -1)
	testing.expect(t, result2 == nil, "negative cursor should return nil")

	result3 := gui.collect_layout_panels_for_page(layouts[:], 5)
	testing.expect(t, result3 == nil, "out-of-range cursor should return nil")
}

@test
gui_count_bubbles_for_panel_nil_map :: proc(t: ^testing.T) {
	count := gui.count_bubbles_for_panel(nil, "panel_1")
	testing.expect(t, count == 0, "nil map should return 0")

	// Non-nil map with no entry
	images := make(map[string][]core.Speech_Bubble)
	defer core.dispose_speech_bubbles(&images)
	count2 := gui.count_bubbles_for_panel(images, "panel_1")
	testing.expect(t, count2 == 0, "missing key should return 0")
}
