class_name Torch extends Node2D
## Torch furniture entity — buildable wall-hung or floor-standing light source.
##
## Rendered as a small stick + wrapped head + flame using _draw() in the same
## 3/4-perspective convention as Wall / Bed / Workbench. Ghost state (40% alpha)
## while construction is in progress; solid + lit once _completed.
##
## Light model (docs/architecture.md "LightingSystem"):
##   Emits a PointLight2D with a procedural radial-gradient texture (soft
##   falloff). The Godot visual light is additive with CanvasModulate for
##   night-time darkness. The sim-side light radius (LIGHT_RADIUS tiles,
##   Manhattan distance) is registered with World.light_sources so that
##   is_tile_lit() can answer "in darkness" thought queries cheaply.
##
## Light-source duck-type interface (shared with Hearth / Workbench):
##   is_on()           → bool
##   get_light_tile()  → Vector2i
##   get_light_radius() → int
##
## Build model (same BuildJob interface as Wall / Bed / Workbench):
##   BUILD_TICKS ticks via the standard BuildJob toil; PointLight2D enabled
##   only after _complete().
##
## Save/load: to_dict / from_dict capture tile, label_text, build_progress,
## _completed, _is_on. Phase 16 wires these into the full save layer.
##
## World registration: World.register_light_source / World.unregister_light_source
## called from _ready / _exit_tree.

const TILE_SIZE_PX: int = 16

## Sim ticks to build a torch (30 ticks ≈ 1.5 sim seconds at 1×).
const BUILD_TICKS: int = 30

## Sim-side light radius in tiles (Manhattan). Max 8 per architecture.md.
const LIGHT_RADIUS: int = 6

## Pixel size of the procedural radial gradient used for the PointLight2D.
## Larger values give a smoother falloff; 64 is sufficient for a 6-tile radius.
const LIGHT_TEXTURE_SIZE: int = 64

# ── exports ───────────────────────────────────────────────────────────────────

## Tile position of this torch in world-tile coordinates.
@export var tile: Vector2i = Vector2i.ZERO

## Player-visible label. Drives Audit logs and job descriptions.
@export var label_text: String = "Torch"

# ── state ─────────────────────────────────────────────────────────────────────

## Ticks of construction work applied so far. 0..BUILD_TICKS.
var build_progress: int = 0

## True once build_progress >= BUILD_TICKS.
var _completed: bool = false

## Whether the torch is emitting light. Always true for Phase 11;
## Phase 17 may add fuel consumption or a player on/off toggle.
var _is_on: bool = true

## PointLight2D child node for Godot's visual lighting pipeline.
## Built in _ready(); enabled only once _complete() fires.
var _light: PointLight2D = null


# ── lifecycle ─────────────────────────────────────────────────────────────────

func _ready() -> void:
	# Bottom-anchor so Y-sort occludes pawns correctly (same pivot as Wall/Bed).
	position = Vector2(
		tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0,
		tile.y * TILE_SIZE_PX + TILE_SIZE_PX
	)
	World.register_light_source(self)
	World.register_build_site(self)
	_light = _build_point_light_2d()
	add_child(_light)
	_light.enabled = false   # dark until built
	queue_redraw()


func _exit_tree() -> void:
	World.unregister_light_source(self)
	World.unregister_build_site(self)


## One-shot initialiser. Call after add_child() so _ready() has already fired.
func setup(p_tile: Vector2i) -> void:
	tile = p_tile
	position = Vector2(
		tile.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0,
		tile.y * TILE_SIZE_PX + TILE_SIZE_PX
	)
	queue_redraw()


# ── BuildJob interface (matches Wall / Bed / Workbench shape) ─────────────────

## True while the torch still needs construction work.
func is_buildable() -> bool:
	return not _completed


## Human-readable label for job descriptions and Audit logs.
func label() -> String:
	return label_text


## Called by the BUILD toil in JobRunner once per sim tick while the pawn works.
## Advances build_progress and completes the torch at BUILD_TICKS.
func on_build_tick() -> void:
	if _completed:
		return
	build_progress += 1
	queue_redraw()
	if build_progress >= BUILD_TICKS:
		_complete()


## True once the torch has been fully built.
func is_completed() -> bool:
	return _completed


## Torches are walkable — pawns step around them visually but the tile remains
## passable for pathfinding purposes.
func blocks_pathing_when_complete() -> bool:
	return false


# ── light-source duck-type interface ──────────────────────────────────────────
## Used by World.is_tile_lit() and the "in darkness" thought.
## Hearth / Workbench exposes the same three functions.

## True when the torch is built and switched on.
func is_on() -> bool:
	return _completed and _is_on


## The tile this light source occupies (for distance calculations).
func get_light_tile() -> Vector2i:
	return tile


## The sim-side Manhattan-distance radius of this light source.
func get_light_radius() -> int:
	return LIGHT_RADIUS


## Toggle the torch on/off. Phase 17 may call this from a fuel-depletion system
## or player action; safe to call any time including before _complete().
func set_on(value: bool) -> void:
	_is_on = value
	if _light != null:
		_light.enabled = _completed and _is_on
	Audit.log("light", "torch at %s set_on → %s" % [tile, value])


# ── save / load ───────────────────────────────────────────────────────────────

## Serialise all persistent state for World save (wired in Phase 16).
func to_dict() -> Dictionary:
	return {
		"class_id": &"torch",
		"tile_x": tile.x,
		"tile_y": tile.y,
		"label_text": label_text,
		"build_progress": build_progress,
		"completed": _completed,
		"is_on": _is_on,
	}


## Restore from a dict produced by to_dict().
func from_dict(d: Dictionary) -> void:
	tile = Vector2i(int(d.get("tile_x", 0)), int(d.get("tile_y", 0)))
	label_text = str(d.get("label_text", "Torch"))
	build_progress = int(d.get("build_progress", 0))
	_completed = bool(d.get("completed", false))
	_is_on = bool(d.get("is_on", true))
	# _light is rebuilt in _ready() — no need to restore the node.
	setup(tile)


# ── render ─────────────────────────────────────────────────────────────────────

func _draw() -> void:
	# 3/4-perspective torch — fits within the tile (16×16 local box).
	# Origin (0,0) = tile bottom-centre. Tile spans local Y: -16 to 0.
	# Ghost (not yet built) draws at 0.4 alpha.
	var alpha: float = 1.0 if _completed else 0.4

	# Stick — narrow vertical brown rect, centred horizontally.
	draw_rect(Rect2(Vector2(-1.0, -9.0), Vector2(2.0, 9.0)), Color(0.35, 0.22, 0.10, alpha))

	# Wrapped head — slightly wider, darker, at the top of the stick.
	draw_rect(Rect2(Vector2(-2.0, -13.0), Vector2(4.0, 4.0)), Color(0.20, 0.14, 0.06, alpha))

	# Flame — only when built and on; two overlapping circles for depth.
	if _completed and _is_on:
		draw_circle(Vector2(0.0, -15.0), 2.5, Color(1.0, 0.65, 0.10, alpha))
		draw_circle(Vector2(0.0, -16.0), 1.5, Color(1.0, 0.90, 0.40, alpha))


# ── internal helpers ──────────────────────────────────────────────────────────

## Construct and return the PointLight2D that provides Godot-side visual
## lighting. The light is positioned at the flame, not the tile base.
func _build_point_light_2d() -> PointLight2D:
	var p := PointLight2D.new()
	p.texture = _build_radial_light_texture(LIGHT_TEXTURE_SIZE)
	# Scale the texture so its radius in world-pixels matches LIGHT_RADIUS tiles.
	p.texture_scale = float(LIGHT_RADIUS) * float(TILE_SIZE_PX) / float(LIGHT_TEXTURE_SIZE) * 2.0
	p.color = Color(1.0, 0.85, 0.55, 1.0)   # warm fire tint
	p.energy = 1.2
	# Offset upward so the light originates from the flame position.
	p.position = Vector2(0.0, -15.0)
	return p


## Build a soft radial gradient Image and return it as an ImageTexture.
## White centre, fades to transparent at the edge via smoothstep falloff.
## Called once per torch in _ready(); result is ~4 KB of VRAM at 64×64 RGBA8.
static func _build_radial_light_texture(size: int) -> Texture2D:
	var img := Image.create(size, size, false, Image.FORMAT_RGBA8)
	var cx: float = float(size) / 2.0
	var cy: float = float(size) / 2.0
	var max_r: float = float(size) / 2.0
	for x in size:
		for y in size:
			var dx: float = float(x) - cx
			var dy: float = float(y) - cy
			var d: float = sqrt(dx * dx + dy * dy)
			var t: float = clampf(1.0 - d / max_r, 0.0, 1.0)
			# Smoothstep so the edge is soft rather than a hard circle cutoff.
			var a: float = t * t * (3.0 - 2.0 * t)
			img.set_pixel(x, y, Color(1.0, 1.0, 1.0, a))
	return ImageTexture.create_from_image(img)


## Called when build_progress reaches BUILD_TICKS.
func _complete() -> void:
	_completed = true
	if _light != null:
		_light.enabled = _is_on
	queue_redraw()
	World.clear_designation_at(tile)
	Audit.log("torch", "built at %s" % tile)
	# Phase 13 — notify BeautySystem so nearby tile beauty scores update.
	var bs = World.get("beauty_system")
	if bs != null:
		bs.register_furniture(self)
		bs.recompute_around(tile)
