package pncdsl

// Status is the outcome of one tick of a Runner.
type Status int

const (
	StatusRunning Status = iota
	StatusDone
	StatusFailed
)

// Ctx is the per-tick context passed to running actions.
type Ctx struct {
	Game    *Game
	DT      float64
	Scene   *Scene
	Hotspot *Hotspot
	Item    *Item
}

// Action is an immutable spec of work; Start makes a fresh Runner with
// state. Storing an Action in a struct field (e.g. Hotspot.OnUse) is safe
// because the engine always calls Start before ticking, so two invocations
// can never share mutable state.
type Action interface {
	Start() Runner
}

// Runner is one in-flight execution of an Action. Tick advances it one
// frame and returns whether it's still running, done, or failed.
type Runner interface {
	Tick(ctx *Ctx) Status
}

// ----- immediate action helper -----------------------------------------

type immediateAction struct {
	fn func(*Ctx) Status
}

func (a *immediateAction) Start() Runner { return a }
func (a *immediateAction) Tick(ctx *Ctx) Status {
	if a.fn == nil {
		return StatusDone
	}
	return a.fn(ctx)
}

// Custom wraps a user function as an Action. Returns Done on first tick
// unless the function itself returns StatusRunning.
func Custom(fn func(*Ctx) Status) Action { return &immediateAction{fn: fn} }

// ----- Seq --------------------------------------------------------------

type seqAction struct{ children []Action }
type seqRunner struct {
	spec    *seqAction
	idx     int
	current Runner
}

func Seq(actions ...Action) Action {
	return &seqAction{children: flattenSeq(actions)}
}

func flattenSeq(in []Action) []Action {
	out := make([]Action, 0, len(in))
	for _, a := range in {
		if a == nil {
			continue
		}
		if s, ok := a.(*seqAction); ok {
			out = append(out, s.children...)
		} else {
			out = append(out, a)
		}
	}
	return out
}

func (a *seqAction) Start() Runner { return &seqRunner{spec: a} }

func (r *seqRunner) Tick(ctx *Ctx) Status {
	for {
		if r.idx >= len(r.spec.children) {
			return StatusDone
		}
		if r.current == nil {
			r.current = r.spec.children[r.idx].Start()
		}
		s := r.current.Tick(ctx)
		switch s {
		case StatusDone:
			r.idx++
			r.current = nil
			// keep going only if the just-finished action consumed no time
			// (zero-dt would loop forever otherwise — that's fine here since
			// immediate actions complete in one Tick call).
			continue
		case StatusFailed:
			return StatusFailed
		default:
			return StatusRunning
		}
	}
}

// ----- Par --------------------------------------------------------------

type parAction struct{ children []Action }
type parRunner struct {
	runners []Runner
	done    []bool
}

func Par(actions ...Action) Action { return &parAction{children: actions} }

func (a *parAction) Start() Runner {
	rs := make([]Runner, len(a.children))
	for i, c := range a.children {
		rs[i] = c.Start()
	}
	return &parRunner{runners: rs, done: make([]bool, len(rs))}
}

func (r *parRunner) Tick(ctx *Ctx) Status {
	allDone := true
	for i, rn := range r.runners {
		if r.done[i] {
			continue
		}
		s := rn.Tick(ctx)
		if s == StatusFailed {
			return StatusFailed
		}
		if s == StatusDone {
			r.done[i] = true
			continue
		}
		allDone = false
	}
	if allDone {
		return StatusDone
	}
	return StatusRunning
}

// ----- If ---------------------------------------------------------------

type ifAction struct {
	cond Condition
	then Action
	els  Action
}

func If(cond Condition, then Action, els ...Action) Action {
	var e Action
	if len(els) > 0 {
		e = Seq(els...)
	}
	return &ifAction{cond: cond, then: then, els: e}
}

func (a *ifAction) Start() Runner { return &ifRunner{spec: a} }

type ifRunner struct {
	spec    *ifAction
	started bool
	inner   Runner
}

func (r *ifRunner) Tick(ctx *Ctx) Status {
	if !r.started {
		r.started = true
		take := r.spec.then
		if r.spec.cond == nil || !r.spec.cond.Eval(ctx) {
			take = r.spec.els
		}
		if take == nil {
			return StatusDone
		}
		r.inner = take.Start()
	}
	if r.inner == nil {
		return StatusDone
	}
	return r.inner.Tick(ctx)
}

// ----- Wait -------------------------------------------------------------

type waitAction struct{ seconds float64 }
type waitRunner struct {
	spec    *waitAction
	elapsed float64
}

func Wait(seconds float64) Action { return &waitAction{seconds: seconds} }
func (a *waitAction) Start() Runner { return &waitRunner{spec: a} }
func (r *waitRunner) Tick(ctx *Ctx) Status {
	r.elapsed += ctx.DT
	if r.elapsed >= r.spec.seconds {
		return StatusDone
	}
	return StatusRunning
}

// ----- Say --------------------------------------------------------------

type sayAction struct{ speaker, text string }
type sayRunner struct {
	spec     *sayAction
	elapsed  float64
	duration float64
	started  bool
}

func Say(speaker, text string) Action { return &sayAction{speaker: speaker, text: text} }

func (a *sayAction) Start() Runner { return &sayRunner{spec: a} }

func (r *sayRunner) Tick(ctx *Ctx) Status {
	if !r.started {
		r.started = true
		// duration scales with text length, with a 1.2s floor
		r.duration = 1.2 + float64(len(r.spec.text))*0.05
		ctx.Game.UI.SetSpeech(r.spec.speaker, r.spec.text)
	}
	r.elapsed += ctx.DT
	// skip on click
	if ctx.Game.input.consumedClick() {
		r.elapsed = r.duration
	}
	if r.elapsed >= r.duration {
		ctx.Game.UI.ClearSpeech()
		return StatusDone
	}
	return StatusRunning
}

// ----- GoTo -------------------------------------------------------------

type gotoAction struct{ scene string }

func GoTo(scene string) Action { return &gotoAction{scene: scene} }
func (a *gotoAction) Start() Runner { return a }
func (a *gotoAction) Tick(ctx *Ctx) Status {
	ctx.Game.changeScene(a.scene)
	return StatusDone
}

// ----- inventory --------------------------------------------------------

type giveAction struct{ item string }

func Give(item string) Action { return &giveAction{item: item} }
func (a *giveAction) Start() Runner { return a }
func (a *giveAction) Tick(ctx *Ctx) Status {
	ctx.Game.Inventory.Add(a.item)
	return StatusDone
}

type takeAwayAction struct{ item string }

func TakeAway(item string) Action { return &takeAwayAction{item: item} }
func (a *takeAwayAction) Start() Runner { return a }
func (a *takeAwayAction) Tick(ctx *Ctx) Status {
	ctx.Game.Inventory.Remove(a.item)
	return StatusDone
}

// RequireItem fails silently with a generic line if the player doesn't have
// the named item selected or in inventory. Used at the top of Use handlers.
type requireItemAction struct{ item string }

func RequireItem(item string) Action { return &requireItemAction{item: item} }
func (a *requireItemAction) Start() Runner { return a }
func (a *requireItemAction) Tick(ctx *Ctx) Status {
	if ctx.Game.Inventory.Has(a.item) {
		return StatusDone
	}
	ctx.Game.UI.FlashLine("Ehhez kell egy " + a.item + ".")
	return StatusFailed
}

// ----- flags / vars -----------------------------------------------------

type setFlagAction struct{ name string }

func SetFlag(name string) Action { return &setFlagAction{name: name} }
func (a *setFlagAction) Start() Runner { return a }
func (a *setFlagAction) Tick(ctx *Ctx) Status { ctx.Game.State.SetFlag(a.name); return StatusDone }

type clearFlagAction struct{ name string }

func ClearFlag(name string) Action { return &clearFlagAction{name: name} }
func (a *clearFlagAction) Start() Runner { return a }
func (a *clearFlagAction) Tick(ctx *Ctx) Status {
	ctx.Game.State.ClearFlag(a.name)
	return StatusDone
}

type setVarAction struct {
	name string
	v    any
}

func SetVar(name string, v any) Action { return &setVarAction{name: name, v: v} }
func (a *setVarAction) Start() Runner { return a }
func (a *setVarAction) Tick(ctx *Ctx) Status {
	ctx.Game.State.SetVar(a.name, a.v)
	return StatusDone
}

// ----- audio ------------------------------------------------------------

type playMusicAction struct{ name string }

func PlayMusic(name string) Action { return &playMusicAction{name: name} }
func (a *playMusicAction) Start() Runner { return a }
func (a *playMusicAction) Tick(ctx *Ctx) Status {
	ctx.Game.Audio.PlayMusic(a.name)
	return StatusDone
}

type stopMusicAction struct{}

func StopMusic() Action { return &stopMusicAction{} }
func (a *stopMusicAction) Start() Runner { return a }
func (a *stopMusicAction) Tick(ctx *Ctx) Status { ctx.Game.Audio.StopMusic(); return StatusDone }

type playSoundAction struct{ name string }

func PlaySound(name string) Action { return &playSoundAction{name: name} }
func (a *playSoundAction) Start() Runner { return a }
func (a *playSoundAction) Tick(ctx *Ctx) Status {
	ctx.Game.Audio.PlaySound(a.name)
	return StatusDone
}

// ----- dialogue ---------------------------------------------------------

type runDialogueAction struct{ name string }

func RunDialogue(name string) Action { return &runDialogueAction{name: name} }
func (a *runDialogueAction) Start() Runner { return &runDialogueRunner{spec: a} }

type runDialogueRunner struct {
	spec    *runDialogueAction
	started bool
}

func (r *runDialogueRunner) Tick(ctx *Ctx) Status {
	if !r.started {
		r.started = true
		ctx.Game.startDialogue(r.spec.name)
	}
	if ctx.Game.dialogueActive() {
		return StatusRunning
	}
	return StatusDone
}

type endDialogueAction struct{}

func EndDialogue() Action { return &endDialogueAction{} }
func (a *endDialogueAction) Start() Runner { return a }
func (a *endDialogueAction) Tick(ctx *Ctx) Status {
	ctx.Game.endDialogue()
	return StatusDone
}

type gotoNodeAction struct{ node string }

func GotoNode(node string) Action { return &gotoNodeAction{node: node} }
func (a *gotoNodeAction) Start() Runner { return a }
func (a *gotoNodeAction) Tick(ctx *Ctx) Status {
	ctx.Game.gotoDialogueNode(a.node)
	return StatusDone
}

// ----- scripts ----------------------------------------------------------

type runScriptAction struct{ name string }

func RunScript(name string) Action { return &runScriptAction{name: name} }
func (a *runScriptAction) Start() Runner { return &runScriptRunner{spec: a} }

type runScriptRunner struct {
	spec  *runScriptAction
	inner Runner
}

func (r *runScriptRunner) Tick(ctx *Ctx) Status {
	if r.inner == nil {
		s, ok := ctx.Game.ScriptManager.Get(r.spec.name)
		if !ok || s.Actions == nil {
			return StatusFailed
		}
		r.inner = s.Actions.Start()
	}
	return r.inner.Tick(ctx)
}

// ----- character movement ----------------------------------------------

type walkAction struct {
	character string
	to        Point
}

func Walk(character string, to Point) Action { return &walkAction{character: character, to: to} }

func (a *walkAction) Start() Runner { return &walkRunner{spec: a} }

type walkRunner struct {
	spec    *walkAction
	started bool
}

func (r *walkRunner) Tick(ctx *Ctx) Status {
	if !r.started {
		r.started = true
		ctx.Game.walkCharacter(r.spec.character, r.spec.to)
	}
	if ctx.Game.characterMoving(r.spec.character) {
		return StatusRunning
	}
	return StatusDone
}

// ----- misc -------------------------------------------------------------

type showEndAction struct{ text string }

func ShowEnd(text string) Action { return &showEndAction{text: text} }
func (a *showEndAction) Start() Runner { return &showEndRunner{spec: a} }

type showEndRunner struct {
	spec    *showEndAction
	started bool
}

func (r *showEndRunner) Tick(ctx *Ctx) Status {
	if !r.started {
		r.started = true
		ctx.Game.showEndCard(r.spec.text)
	}
	return StatusRunning // never finishes; player closes the window
}
