class_name Bill extends RefCounted
## A bill is one entry in a workbench's recipe queue.
##
## Three modes per design.md "Bill modes (full Rimworld fidelity)":
##   FOREVER  — keep crafting until the player pauses or removes the bill.
##   COUNT    — craft exactly `target_count` times then auto-pause.
##   UNTIL_N  — keep a world-wide stockpile count of `recipe.output_type` at
##              >= `target_count`; auto-pauses when the threshold is met,
##              resumes when items are consumed below it.
##
## Save/load contract:
##   var b2 := Bill.from_dict(b.to_dict())
##   assert(b2.mode == b.mode and b2.completed_count == b.completed_count)

enum Mode {
	FOREVER,  ## Craft until paused by player.
	COUNT,    ## Craft exactly target_count times.
	UNTIL_N,  ## Maintain >= target_count of output_type in stockpiles.
}

## The recipe this bill executes. Must not be null when the bill is active.
var recipe: Recipe = null

## Which termination mode applies.
var mode: Mode = Mode.FOREVER

## COUNT: stop after this many completions. UNTIL_N: target world count.
var target_count: int = 0

## Number of successful craft outputs produced (used to gate COUNT mode).
var completed_count: int = 0

## True when the bill is manually paused by the player, or auto-paused by the
## engine (COUNT exhausted, UNTIL_N threshold met). CraftingProvider skips
## paused bills.
var paused: bool = false


# ── queries ───────────────────────────────────────────────────────────────────

## Returns true when CraftingProvider should pick up this bill.
## All three mode checks also short-circuit on paused.
func is_active() -> bool:
	if paused:
		return false
	match mode:
		Mode.FOREVER:
			return true
		Mode.COUNT:
			return completed_count < target_count
		Mode.UNTIL_N:
			return _world_output_count() < target_count
	return false


# ── state mutation ────────────────────────────────────────────────────────────

## Called by JobRunner._tick_craft after each successful craft output.
## Increments completed_count and auto-pauses COUNT bills that are exhausted.
func record_completion() -> void:
	completed_count += 1
	if mode == Mode.COUNT and completed_count >= target_count:
		paused = true


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

func to_dict() -> Dictionary:
	return {
		"recipe": recipe.to_dict() if recipe != null else null,
		"mode": mode,
		"target_count": target_count,
		"completed_count": completed_count,
		"paused": paused,
	}


static func from_dict(d: Dictionary) -> Bill:
	var b := Bill.new()
	b.mode = int(d.get("mode", Mode.FOREVER)) as Mode
	b.target_count = int(d.get("target_count", 0))
	b.completed_count = int(d.get("completed_count", 0))
	b.paused = bool(d.get("paused", false))
	var recipe_dict: Variant = d.get("recipe")
	b.recipe = Recipe.from_dict(recipe_dict) if recipe_dict is Dictionary else null
	return b


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

## Counts world items of recipe.output_type that are not currently being
## carried — used by UNTIL_N mode. Queries World.items directly (no cache).
func _world_output_count() -> int:
	if recipe == null:
		return 0
	var total: int = 0
	for item in World.items:
		if item.item_type == recipe.output_type and not item.being_carried:
			total += item.stack_size
	return total
