class_name Crop extends Node2D
## Crop entity — a farm plant tile that grows through stages and is harvested by a pawn.
##
## Growth model (docs/implementation.md Phase 7):
##   SOWN → GROWING_1 → GROWING_2 → GROWING_3 → READY, each stage taking STAGE_TICKS sim ticks.
##   At 20 Hz, 200 ticks ≈ 10 sim seconds ≈ 2 in-game minutes at Fast speed.
##   "No growth indoors" rule (docs/design.md) lands in Phase 13 when the Roof flag
##   system is fully wired; for now crops always grow.
##
## A PlantProvider creates a Job whose INTERACT toil calls on_harvest_tick() or
## on_sow_tick() once per sim tick via JobRunner. Both are single-tick completions.
## The INTERACT toil finishes when is_harvestable() / is_sowable() returns false.
##
## World registration (World.register_crop / World.unregister_crop) is called here.

const TILE_SIZE_PX: int = 16

## Available crop kinds. Each maps to an ElvGames 64×32 sprite sheet
## (4 stages × 16w × 32h, plant anchored to bottom row).
const KIND_WHEAT: StringName      = &"wheat"
const KIND_POTATO: StringName     = &"potato"
const KIND_CORN: StringName       = &"corn"
const KIND_STRAWBERRY: StringName = &"strawberry"

## Sim ticks per growth stage. 200 ticks × 4 stages = 800 total.
## At 20 Hz × 5× speed = 100 ticks/sec → 8 real seconds per stage, 32 seconds full grow.
const STAGE_TICKS: int = 200
const STAGE_COUNT: int = 4

enum Stage { TILLED, SOWN, GROWING_1, GROWING_2, GROWING_3, READY }

## Per-kind sprite atlas. Strawberry / Corn are 80×32 (5 stages) — we still slice
## the first 4 cols, which gives the same progression (the 5th col is a
## post-harvest regrow frame we don't use).
const _CROP_TEXTURES: Dictionary = {
	KIND_WHEAT:      preload("res://art/sprites/crops/FG_Crops_Wheat.png"),
	KIND_POTATO:     preload("res://art/sprites/crops/FG_Crops_Potato.png"),
	KIND_CORN:       preload("res://art/sprites/crops/FG_Crops_Corns.png"),
	KIND_STRAWBERRY: preload("res://art/sprites/crops/FG_Crops_Strawberry.png"),
}

## Width / height of one stage cell in pixels.
const _STAGE_W: int = 16
const _STAGE_H: int = 32

@export var crop_kind: StringName = KIND_WHEAT
@export var tile: Vector2i = Vector2i.ZERO

var stage: Stage = Stage.SOWN
## Progress within the current growth stage; 0..STAGE_TICKS.
var stage_progress: int = 0

# Phase 13 — "no growth indoors" rule.  True once we've logged the first
# indoor detection for this crop instance so we don't flood the audit log.
var _logged_indoor: bool = false

# Child Sprite2D — created in _ready, region_rect updated whenever stage flips.
var _sprite: Sprite2D = null

const ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")


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

func _ready() -> void:
	position = _tile_to_world(tile)
	_build_sprite()
	World.register_crop(self)
	EventBus.sim_tick.connect(_on_sim_tick)
	queue_redraw()


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


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

## One-shot initialiser. Call after add_child() so _ready() has already fired.
func setup(p_tile: Vector2i, p_kind: StringName, p_stage: Stage = Stage.SOWN) -> void:
	tile = p_tile
	crop_kind = p_kind
	stage = p_stage
	stage_progress = 0
	position = _tile_to_world(tile)
	if _sprite != null:
		_sprite.texture = _texture_for(crop_kind)
	_refresh_sprite_region()
	queue_redraw()
	Audit.log("crop", "spawned %s at %s (stage=%s)" % [crop_kind, tile, Stage.keys()[stage]])


## True when this crop can be harvested by a pawn.
func is_harvestable() -> bool:
	return stage == Stage.READY


## True when this crop can be sown by a pawn (bare tilled soil, no plant yet).
func is_sowable() -> bool:
	return stage == Stage.TILLED


## Called by the INTERACT toil in JobRunner once per sim tick while a pawn harvests.
## Single-tick harvest: drops an output Item and resets to TILLED (re-sowable).
func on_harvest_tick() -> void:
	if not is_harvestable():
		return
	var item_type := _harvest_output_for(crop_kind)
	var it: Item = ITEM_SCENE.instantiate()
	get_parent().add_child(it)
	it.setup(item_type, 1, tile)
	stage = Stage.TILLED
	stage_progress = 0
	_refresh_sprite_region()
	Audit.log("crop", "harvested %s at %s → %s" % [crop_kind, tile, item_type])
	queue_redraw()


## Called by the INTERACT toil in JobRunner once per sim tick while a pawn sows.
## Single-tick sow: transitions TILLED → SOWN so growth begins on the next sim tick.
func on_sow_tick() -> void:
	if not is_sowable():
		return
	stage = Stage.SOWN
	stage_progress = 0
	_refresh_sprite_region()
	Audit.log("crop", "sown %s at %s" % [crop_kind, tile])
	queue_redraw()


# ── growth ────────────────────────────────────────────────────────────────────

func _on_sim_tick(_n: int) -> void:
	if stage == Stage.READY or stage == Stage.TILLED:
		return
	# Phase 13 — crops don't grow indoors (no sunlight under a roof).
	# World.is_indoor() returns false while RoomDetector has not yet fired, so
	# outdoor crops planted during boot are unaffected.
	if World.is_indoor(tile):
		if not _logged_indoor:
			Audit.log("crop", "%s at %s won't grow (indoor)" % [crop_kind, tile])
			_logged_indoor = true
		return
	# Crop has moved outdoors or was never indoors — reset the log flag so a
	# future re-roofing produces another audit line.
	_logged_indoor = false
	stage_progress += 1
	if stage_progress >= STAGE_TICKS:
		stage_progress = 0
		stage = (int(stage) + 1) as Stage
		_refresh_sprite_region()
		queue_redraw()
		if stage == Stage.READY:
			Audit.log("crop", "%s ready at %s" % [crop_kind, tile])


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

func to_dict() -> Dictionary:
	return {
		"class_id": &"crop",
		"tile_x": tile.x,
		"tile_y": tile.y,
		"crop_kind": String(crop_kind),
		"stage": int(stage),
		"stage_progress": stage_progress,
	}


## Returns a plain Dictionary spec for World to instantiate from.
## Crops cannot reconstruct themselves standalone — they need a parent in the
## scene tree. World adds the node, then calls setup() from the returned dict.
static func from_dict(d: Dictionary) -> Dictionary:
	return {
		"tile_x": int(d.get("tile_x", 0)),
		"tile_y": int(d.get("tile_y", 0)),
		"crop_kind": StringName(d.get("crop_kind", "wheat")),
		"stage": int(d.get("stage", Stage.SOWN)),
		"stage_progress": int(d.get("stage_progress", 0)),
	}


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

func _draw() -> void:
	# Tilled-soil base draws under the plant sprite. Stays visible at every stage
	# so the player sees the patch as cultivated.
	var soil_color := Color(0.32, 0.20, 0.10)
	var soil_dark  := Color(0.22, 0.14, 0.06)
	draw_rect(Rect2(Vector2(-7.0, -7.0), Vector2(14.0, 14.0)), soil_color)
	draw_rect(Rect2(Vector2(-7.0, -7.0), Vector2(14.0, 14.0)), soil_dark, false, 1.0)


# ── sprite helpers ────────────────────────────────────────────────────────────

func _build_sprite() -> void:
	_sprite = Sprite2D.new()
	_sprite.name = "Sprite"
	_sprite.texture = _texture_for(crop_kind)
	_sprite.region_enabled = true
	_sprite.centered = true
	# 32-tall sprite anchored so its bottom edge sits at the tile's bottom row
	# (local +8 from tile centre). Sprite half-height = 16 → offset.y = 8 - 16 = -8.
	_sprite.offset = Vector2(0, -8)
	# Draw the plant above the soil rect but below pawns/items.
	_sprite.z_index = 0
	add_child(_sprite)
	_refresh_sprite_region()


func _refresh_sprite_region() -> void:
	if _sprite == null:
		return
	var idx := _sprite_stage_index(stage)
	if idx < 0:
		_sprite.visible = false
		return
	_sprite.visible = true
	_sprite.region_rect = Rect2(idx * _STAGE_W, 0, _STAGE_W, _STAGE_H)


## Map game-stage to one of the 4 sprite columns. TILLED has no plant frame.
## SOWN..GROWING_2 step through cols 0..2; GROWING_3 and READY both land on
## col 3 (mature). The harvest designation overlay is what cues the player
## that READY is ready — sprite alone doesn't need a fifth frame.
func _sprite_stage_index(s: Stage) -> int:
	match s:
		Stage.TILLED:    return -1
		Stage.SOWN:      return 0
		Stage.GROWING_1: return 1
		Stage.GROWING_2: return 2
		Stage.GROWING_3: return 3
		Stage.READY:     return 3
		_: return 0


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

func _texture_for(kind: StringName) -> Texture2D:
	return _CROP_TEXTURES.get(kind, _CROP_TEXTURES[KIND_WHEAT])


func _harvest_output_for(kind: StringName) -> StringName:
	match kind:
		KIND_WHEAT:      return Item.TYPE_GRAIN
		KIND_POTATO:     return Item.TYPE_VEGETABLE
		KIND_CORN:       return Item.TYPE_GRAIN
		KIND_STRAWBERRY: return Item.TYPE_VEGETABLE
		_: return Item.TYPE_VEGETABLE


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
	)
