extends Node2D

const BOARD_WIDTH := 10
const BOARD_HEIGHT := 20
const CELL_SIZE := 24 # pixels

# Types of pieces
# Each shape is a list of Vector2i, relative to a pivot (0,0)
const SHAPES := {
  "I": [Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0), Vector2i(2,0)],
  "O": [Vector2i(0,0), Vector2i(1,0), Vector2i(0,1), Vector2i(1,1)],
  "T": [Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0), Vector2i(0,1)],
  "L": [Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0), Vector2i(1,1)],
  "J": [Vector2i(-1,1), Vector2i(-1,0), Vector2i(0,0), Vector2i(1,0)],
  "S": [Vector2i(0,0), Vector2i(1,0), Vector2i(-1,1), Vector2i(0,1)]
  }

# Link shape keys to colors
const COLORS := {
  "I": Color(0.0, 1.0, 1.0),
  "O": Color(1.0, 1.0, 0.0),
  "T": Color(0.6, 0.0, 0.8),
  "L": Color(1.0, 0.5, 0.0),
  "J": Color(0.0, 0.0, 1.0),
  "S": Color(0.0, 0.5, 0.0)
  }

# 2D grid: 0 = empty, else a String shape key
var grid: Array = []

# Current falling piece
var current_shape_key: String
var current_blocks: Array[Vector2i] = []
var current_pos: Vector2i
var current_color: Color

# Next piece
var next_shape_key: String = ""
var rng := RandomNumberGenerator.new()

# Drop timing
var drop_interval := 0.7
var drop_timer := 0.0
var is_game_over := false

# Signals to update HUD and preview
signal lines_cleared(total_lines: int, score: int, highscore: int)
signal next_piece_changed(shape_key: String, shapes: Dictionary, colors: Dictionary)
signal game_over(score: int, highscore: int, total_lines: int)

var score := 0
var highscore := 0
var total_lines_cleared := 0

func reset_game() -> void:
	score = 0
	total_lines_cleared = 0
	drop_timer = 0.0
	is_game_over = false
	
	_init_grid()
	_spawn_initial_pieces()
	
	# Update HUD to show reset stats
	emit_signal("lines_cleared", total_lines_cleared, score, highscore)
	queue_redraw()
	
func _ready() -> void:
	rng.randomize()
	_load_highscore()
	reset_game()

func _process(delta: float) -> void:
	if is_game_over:
		return
	
	drop_timer += delta
	if drop_timer >= drop_interval:
		drop_timer = 0.0
		_try_move(Vector2i(0, 1), true)

	_handle_input()
	queue_redraw()
	
func _init_grid() -> void:
	grid.resize(BOARD_HEIGHT)
	for y in range(BOARD_HEIGHT):
		grid[y] = []
		grid[y].resize(BOARD_WIDTH)
		for x in range(BOARD_WIDTH):
			grid[y][x] = "" # empty string = no blocks
	
func _spawn_initial_pieces() -> void:
	next_shape_key = _random_shape()
	_spawn_new_piece()

func _random_shape() -> String:
	var keys := SHAPES.keys()
	return keys[rng.randi() % keys.size()]

func _spawn_new_piece() -> void:
	# Move "next" to "current"
	current_shape_key = next_shape_key

	# Get the raw shape array (untyped)
	var shape_array: Array = SHAPES[current_shape_key]

	# Fill our typed Array[Vector2i]
	current_blocks.clear()
	for p in shape_array:
		current_blocks.append(p as Vector2i)

	# Color for this piece
	current_color = Color.WHITE
	if COLORS.has(current_shape_key):
		current_color = COLORS[current_shape_key] as Color

	# Spawn near top center
	current_pos = Vector2i(BOARD_WIDTH / 2, 1)

	# Choose the next upcoming piece
	next_shape_key = _random_shape()
	emit_signal("next_piece_changed", next_shape_key, SHAPES, COLORS)

	# Check for immediate collision → game over
	if _is_colliding(current_pos, current_blocks):
		_game_over()
		
func _handle_input() -> void:
	if is_game_over:
		return

	if Input.is_action_just_pressed("move_left"):
		_try_move(Vector2i(-1, 0))
	if Input.is_action_just_pressed("move_right"):
		_try_move(Vector2i(1, 0))
	if Input.is_action_pressed("soft_drop"):
		_try_move(Vector2i(0, 1))
	if Input.is_action_just_pressed("rotate"):
		_try_rotate()
		
func _try_move(offset: Vector2i, lock_on_fail: bool = false) -> void:
	var new_pos := current_pos + offset
	if not _is_colliding(new_pos, current_blocks):
		current_pos = new_pos
	elif lock_on_fail and offset.y > 0:
		_lock_piece()

func _try_rotate() -> void:
	var rotated: Array[Vector2i] = []
	for b in current_blocks:
		# (x, y) → (-y, x)
		rotated.append(Vector2i(-b.y, b.x))

	if not _is_colliding(current_pos, rotated):
		current_blocks = rotated
		
func _is_colliding(pos: Vector2i, blocks: Array[Vector2i]) -> bool:
	for b in blocks:
		var x := pos.x + b.x
		var y := pos.y + b.y
		if x < 0 or x >= BOARD_WIDTH or y < 0 or y >= BOARD_HEIGHT:
			return true
		if grid[y][x] != "":
			return true
	return false
	
func _lock_piece() -> void:
	# Transfer current piece into grid
	var points: int
	for b in current_blocks:
		var x := current_pos.x + b.x
		var y := current_pos.y + b.y
		if y >= 0 and y < BOARD_HEIGHT and x >= 0 and x < BOARD_WIDTH:
			grid[y][x] = current_shape_key

	var cleared := _clear_full_lines()

	# scoring (simple rules)
	if cleared > 0:
		total_lines_cleared += cleared
		match cleared:
			1: 
				points = 100
			2: 
				points = 300
			3: 
				points = 500
			4: 
				points = 800
			_: 
				points = cleared * 200
		score += points
		if score > highscore:
			highscore = score
			_save_highscore()

		emit_signal("lines_cleared", total_lines_cleared, score, highscore)

	_spawn_new_piece()

func _clear_full_lines() -> int:
	var cleared_lines := 0
	var y := BOARD_HEIGHT - 1

	while y >= 0:
		var is_full := true
		for x in range(BOARD_WIDTH):
			if grid[y][x] == "":
				is_full = false
				break

		if is_full:
			# Remove this row and drop everything above it down by 1
			_drop_lines_above(y)
			cleared_lines += 1
			# IMPORTANT:
			# Do NOT change y here.
			# After dropping, a new row has moved into index y,
			# so we re-check the same y in the next loop iteration.
		else:
			# Only move up when the current row was not cleared
			y -= 1

	return cleared_lines
	
func _drop_lines_above(row: int) -> void:
	for y in range(row, 0, -1):
		for x in range(BOARD_WIDTH):
			grid[y][x] = grid[y - 1][x]
	# top row becomes empty
	for x in range(BOARD_WIDTH):
		grid[0][x] = ""
		
func _draw() -> void:
	# Draw background grid (optional)
	for y in range(BOARD_HEIGHT):
		for x in range(BOARD_WIDTH):
			var rect := Rect2(
				Vector2(x * CELL_SIZE, y * CELL_SIZE),
				Vector2(CELL_SIZE, CELL_SIZE)
			)
			draw_rect(rect, Color(0.05, 0.05, 0.05), false, 1.0)

	# Draw placed blocks
	for y in range(BOARD_HEIGHT):
		for x in range(BOARD_WIDTH):
			var key: String = grid[y][x]
			if key != "":
				var color: Color = Color.WHITE
				if COLORS.has(key):
					color = COLORS[key] as Color
				_draw_cell(x, y, color)

	# Draw current falling piece
	for b in current_blocks:
		var x := current_pos.x + b.x
		var y := current_pos.y + b.y
		_draw_cell(x, y, current_color)
		
func _draw_cell(x: int, y: int, color: Color) -> void:
	var pos := Vector2(x * CELL_SIZE, y * CELL_SIZE)
	var rect := Rect2(pos, Vector2(CELL_SIZE, CELL_SIZE))
	draw_rect(rect.grow(-1), color, true)  # small border

func _game_over() -> void:
	is_game_over = true
	
	# Make sure HUD shows final values
	emit_signal("lines_cleared", total_lines_cleared, score, highscore)
	
	# Inform main scene that the game ended
	emit_signal("game_over", score, highscore, total_lines_cleared)

func _save_highscore() -> void:
	var cfg := ConfigFile.new()
	cfg.set_value("stats", "highscore", highscore)
	cfg.save("user://tetris.cfg")

func _load_highscore() -> void:
	var cfg := ConfigFile.new()
	var err := cfg.load("user://tetris.cfg")
	if err == OK:
		highscore = int(cfg.get_value("stats", "highscore", 0))
	else:
		highscore = 0
		
