## Rock entity — mineable by a pawn with a Mine job. Drops a stone Item node
## when mined out.
##
## Mirrors Tree's chopping model; stone is harder so MINE_TICKS is longer.
## A MineProvider (Opus, Phase 4) creates a Job whose INTERACT toil calls
## on_mine_tick() once per sim tick via JobRunner.
##
## World registration (World.register_rock / World.unregister_rock) is called
## here but the methods land in World during Opus integration.

class_name Rock extends Node2D

const TILE_SIZE_PX: int = 16

## Sim ticks to mine a rock at 1× speed (120 ticks = 6 sim seconds at 20 Hz).
## Stone is harder than wood — MINE_TICKS > Tree.CHOP_TICKS.
const MINE_TICKS: int = 120
## Stone Items dropped on a successful mine.
const STONE_DROPS_ON_MINE: int = 1

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

var tile: Vector2i = Vector2i.ZERO
## 0..MINE_TICKS. Advanced by on_mine_tick(); rock is mined when equal to MINE_TICKS.
var mine_progress: int = 0
## True once a player has painted a mine designation on this rock. MineProvider
## ignores undesignated rocks (Rimworld parity — pawns don't auto-mine).
var mine_designated: bool = false

# Preloaded scene for spawned stone items.
const ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")

## Rock sprite atlas — re-uses the Grasslands tileset (already imported for the
## decoration overlay). These coords pick standalone single-tile boulders from
## the rock band (x=16..29, y=3 and y=5) — chosen because each has a clean
## green margin on all four sides, so they read as separate small rocks rather
## than chunks of a tiled cluster. The earlier (24,7)/(28,7)/(12,13) coords
## were autotile interior pieces; replaced 2026-05-12 after the user flagged
## the obvious tile-cluster artifacts on small rocks.
##
## Multi-tile cluster formations live at (22..23, 3..4) brown and (30..31, 3..4)
## gray — those are reserved for the upcoming BigRock entity (2×2 boulder).
const _ROCK_TEX: Texture2D = preload("res://art/tiles/FG_Grasslands_Spring.png")
const _ROCK_VARIANT_COORDS: Array[Vector2i] = [
	Vector2i(16, 3),  # brown round, medium
	Vector2i(20, 3),  # brown peaked, smaller
	Vector2i(16, 5),  # brown squat, low
	Vector2i(24, 3),  # gray round, medium
	Vector2i(28, 3),  # gray peaked, smaller
	Vector2i(24, 5),  # gray squat, low
]


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

func _ready() -> void:
	position = _tile_to_world(tile)
	_build_sprite()
	World.register_rock(self)


## Adds a Sprite2D child with one of the rock variants. Variant chosen
## deterministically from the tile coord so the same tile renders the same
## rock across boots and load/save.
func _build_sprite() -> void:
	var sprite := Sprite2D.new()
	sprite.name = "Sprite"
	sprite.texture = _ROCK_TEX
	sprite.region_enabled = true
	var coord: Vector2i = _ROCK_VARIANT_COORDS[(tile.x * 31 + tile.y * 17) % _ROCK_VARIANT_COORDS.size()]
	sprite.region_rect = Rect2(coord.x * TILE_SIZE_PX, coord.y * TILE_SIZE_PX, TILE_SIZE_PX, TILE_SIZE_PX)
	sprite.centered = true
	sprite.offset = Vector2.ZERO  # 16×16 tile, sits centered on the tile
	add_child(sprite)


func _exit_tree() -> void:
	World.unregister_rock(self)


# ── public API ────────────────────────────────────────────────────────────────

## One-shot initialiser. Call after add_child() so _ready() already fired.
func setup(start_tile: Vector2i) -> void:
	tile = start_tile
	mine_progress = 0
	position = _tile_to_world(tile)
	queue_redraw()
	Audit.log("rock", "spawned at %s" % tile)


## True when the rock hasn't been fully mined yet.
func is_mineable() -> bool:
	return mine_progress < MINE_TICKS


## Walk destination for a pawn approaching this rock. Small rocks are walkable
## (Phase 4 simplification), so the pawn stands on the rock tile while mining.
## BigRock overrides this to return a perimeter neighbour because its footprint
## is blocked. MineProvider always asks the entity for this rather than reading
## `.tile` directly, so single Rock and BigRock plug into the same walk toil.
func approach_tile_for(_pawn_tile: Vector2i) -> Vector2i:
	return tile


## Called by the INTERACT toil in JobRunner once per sim tick while the pawn
## works this rock. Advances mine_progress and triggers mined() when complete.
func on_mine_tick() -> void:
	if not is_mineable():
		return
	mine_progress += 1
	queue_redraw()
	if mine_progress >= MINE_TICKS:
		mined()


## Drop stone Item(s) and free this node. Called automatically by on_mine_tick()
## but also accessible for scripted removal (debug, storyteller events).
func mined() -> void:
	# Apply mine buff (veins_of_iron): multiply stone drops with stochastic rounding.
	var drop_count: int = Storyteller.multiply_drops(STONE_DROPS_ON_MINE, Storyteller.get_buff_multiplier(&"mine"))
	var item: Item = ITEM_SCENE.instantiate()
	get_parent().add_child(item)
	item.setup(Item.TYPE_STONE, drop_count, tile)
	Audit.log("rock", "mined at %s; %d stone drop(s) (buff mult=%.2f)" % [tile, drop_count, Storyteller.get_buff_multiplier(&"mine")])
	if Audio != null:
		Audio.play_sfx(&"mine_tick")
	World.clear_designation_at(tile)
	queue_free()


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

func to_dict() -> Dictionary:
	return {
		"class_id": &"rock",
		"tile_x": tile.x,
		"tile_y": tile.y,
		"mine_progress": mine_progress,
		"mine_designated": mine_designated,
	}


static func from_dict(d: Dictionary) -> Dictionary:
	return {
		"tile_x": int(d.get("tile_x", 0)),
		"tile_y": int(d.get("tile_y", 0)),
		"mine_progress": int(d.get("mine_progress", 0)),
		"mine_designated": bool(d.get("mine_designated", false)),
	}


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

func _draw() -> void:
	# Rock body comes from the Sprite2D child (see _build_sprite).
	# This _draw renders only the mine-progress crack overlaid on the sprite.
	if mine_progress > 0:
		var ratio := float(mine_progress) / float(MINE_TICKS)
		var crack_len := ratio * 5.0
		draw_line(
			Vector2(-1.0, -2.0),
			Vector2(-1.0 + crack_len, 1.0),
			Color(0.15, 0.12, 0.10, 0.85),
			1.5
		)


# ── helpers ───────────────────────────────────────────────────────────────────

func _tile_to_world(t: Vector2i) -> Vector2:
	return Vector2(
		t.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0,
		t.y * TILE_SIZE_PX + TILE_SIZE_PX / 2.0
	)
