class_name BlobberParty
extends Node3D

# Discrete cell-step party controller (Wizardry / M&M style).
# Reads cell3d_t bytes straight out of the dungeon Dictionary to query wall
# flags; no collision shape needed. Stair cells trigger a level switch.

signal level_changed(new_level: int)
signal stepped(cell: Vector2i, level: int)
signal blocked(face: int)

const FACE_N := 0
const FACE_E := 1
const FACE_S := 2
const FACE_W := 3

# cell3d_t byte offsets (see src/blobber/cell3d.h)
const OFF_FLOOR := 0
const OFF_WALLS := 2  # walls[0..3]

# b_floor_t values
const FT_VOID       := 0
const FT_STONE      := 1
const FT_STAIR_UP   := 5
const FT_STAIR_DOWN := 6

@export var step_duration: float = 0.18
@export var turn_duration: float = 0.14
@export var eye_height_ratio: float = 0.55  # fraction of cell_size above floor

var cell: Vector2i = Vector2i.ZERO
var level: int = 0
var facing: int = FACE_N

var _cells: PackedByteArray
var _width: int = 0
var _height: int = 0
var _n_levels: int = 1
var _cell_size: float = 3.0
var _cell_stride: int = 12
var _levels_meta: Array = []
var _busy: bool = false

func setup(dungeon: Dictionary) -> void:
	_cells = dungeon.get("cells", PackedByteArray())
	var dims: Vector3i = dungeon.get("dimensions", Vector3i(79, 1, 29))
	_width = dims.x
	_n_levels = dims.y
	_height = dims.z
	_cell_size = float(dungeon.get("cell_size", 3.0))
	_cell_stride = int(dungeon.get("cell_stride", 12))
	_levels_meta = dungeon.get("levels", [])

	var entry := _entry_cell()
	cell = entry
	level = 0
	facing = FACE_N
	_snap_to_cell(true)

# --- Public controls -------------------------------------------------------

func try_step_forward() -> void:
	_try_step(_facing_delta(facing))

func try_step_back() -> void:
	_try_step(-_facing_delta(facing))

func try_step_left() -> void:
	_try_step(_facing_delta((facing + 3) % 4))

func try_step_right() -> void:
	_try_step(_facing_delta((facing + 1) % 4))

func try_turn_left() -> void:
	if _busy: return
	facing = (facing + 3) % 4
	_tween_rotation()

func try_turn_right() -> void:
	if _busy: return
	facing = (facing + 1) % 4
	_tween_rotation()

func try_use_stair() -> void:
	if _busy: return
	var f := _cell_floor(level, cell.x, cell.y)
	if f == FT_STAIR_DOWN and level + 1 < _n_levels:
		_change_level(level + 1)
	elif f == FT_STAIR_UP and level > 0:
		_change_level(level - 1)

# --- Input -----------------------------------------------------------------

func _unhandled_key_input(event: InputEvent) -> void:
	if not (event is InputEventKey) or not event.pressed or event.echo:
		return
	var key: int = event.keycode
	match key:
		KEY_W, KEY_UP:    try_step_forward()
		KEY_S, KEY_DOWN:  try_step_back()
		KEY_A:            try_step_left()
		KEY_D:            try_step_right()
		KEY_Q, KEY_LEFT:  try_turn_left()
		KEY_E, KEY_RIGHT: try_turn_right()
		KEY_SPACE, KEY_ENTER, KEY_PERIOD, KEY_GREATER, KEY_LESS: try_use_stair()

# --- Movement internals ----------------------------------------------------

func _try_step(delta: Vector2i) -> void:
	if _busy: return
	var face: int = _delta_to_face(delta)
	if face < 0:
		return
	if _cell_wall(level, cell.x, cell.y, face) != 0:  # W_NONE == 0
		blocked.emit(face)
		return
	var nx := cell.x + delta.x
	var ny := cell.y + delta.y
	if nx < 0 or ny < 0 or nx >= _width or ny >= _height:
		return
	var dest_floor := _cell_floor(level, nx, ny)
	if dest_floor == FT_VOID:
		return
	cell = Vector2i(nx, ny)
	stepped.emit(cell, level)
	_tween_position()

func _change_level(new_level: int) -> void:
	# Land on the paired stair on the adjacent level. Plan guarantees the XYs
	# match, so we keep our cell coordinates.
	level = new_level
	level_changed.emit(level)
	_snap_to_cell(true)

# --- Cell byte reads -------------------------------------------------------

func _cell_index(lvl: int, x: int, y: int) -> int:
	return ((lvl * _height + y) * _width + x) * _cell_stride

func _cell_floor(lvl: int, x: int, y: int) -> int:
	return _cells[_cell_index(lvl, x, y) + OFF_FLOOR]

func _cell_wall(lvl: int, x: int, y: int, face: int) -> int:
	return _cells[_cell_index(lvl, x, y) + OFF_WALLS + face]

# --- Geometry --------------------------------------------------------------

func _facing_delta(f: int) -> Vector2i:
	match f:
		FACE_N: return Vector2i(0, -1)
		FACE_E: return Vector2i(1, 0)
		FACE_S: return Vector2i(0, 1)
		FACE_W: return Vector2i(-1, 0)
	return Vector2i.ZERO

func _delta_to_face(d: Vector2i) -> int:
	if d == Vector2i(0, -1): return FACE_N
	if d == Vector2i(1, 0):  return FACE_E
	if d == Vector2i(0, 1):  return FACE_S
	if d == Vector2i(-1, 0): return FACE_W
	return -1

func _cell_world_position(lvl: int, x: int, y: int) -> Vector3:
	var s := _cell_size
	return Vector3(
		(float(x) + 0.5) * s,
		-float(lvl) * s - s * (1.0 - eye_height_ratio),
		(float(y) + 0.5) * s)

func _facing_yaw(f: int) -> float:
	# Camera default forward = -Z. N (-y → -z) = 0. Then +x is -PI/2, etc.
	match f:
		FACE_N: return 0.0
		FACE_E: return -PI * 0.5
		FACE_S: return PI
		FACE_W: return PI * 0.5
	return 0.0

func _snap_to_cell(reset_rotation: bool) -> void:
	position = _cell_world_position(level, cell.x, cell.y)
	if reset_rotation:
		rotation.y = _facing_yaw(facing)

func _tween_position() -> void:
	var target := _cell_world_position(level, cell.x, cell.y)
	_busy = true
	var tw := create_tween()
	tw.tween_property(self, "position", target, step_duration)
	tw.finished.connect(func(): _busy = false)

func _tween_rotation() -> void:
	var target_yaw := _facing_yaw(facing)
	# Take the shortest angular path.
	var cur := rotation.y
	var diff := wrapf(target_yaw - cur, -PI, PI)
	target_yaw = cur + diff
	_busy = true
	var tw := create_tween()
	tw.tween_property(self, "rotation:y", target_yaw, turn_duration)
	tw.finished.connect(func(): _busy = false)

# --- Entry point -----------------------------------------------------------

func _entry_cell() -> Vector2i:
	if _levels_meta.size() > 0:
		var lv0: Dictionary = _levels_meta[0]
		var su: Vector2i = lv0.get("stairs_up", Vector2i(-1, -1))
		if su.x >= 0:
			return su
		var sd: Vector2i = lv0.get("stairs_down", Vector2i(-1, -1))
		if sd.x >= 0:
			return sd
	# Fallback: first standable cell on level 0.
	for y in _height:
		for x in _width:
			var f := _cell_floor(0, x, y)
			if f != FT_VOID:
				return Vector2i(x, y)
	return Vector2i.ZERO
