package schema

import "errors"

// Phase is the lifecycle state of a shard through the migration pipeline.
//
// Underlying string values appear verbatim in status JSON; do not change them
// without coordinating a wire-format migration.
type Phase string

const (
	PhasePending  Phase = "pending"
	PhaseRunning  Phase = "running"
	PhaseImported Phase = "imported"
	PhaseFailed   Phase = "failed"
	PhaseUnknown  Phase = "unknown"
)

// ErrIllegalTransition is returned by Phase.Transition when the requested
// transition is not allowed by the state machine.
var ErrIllegalTransition = errors.New("illegal phase transition")

// legalTransition is the directed graph of permitted transitions.
//
// Verified is treated as terminal — once a shard is verified, it stays put.
var legalTransition = map[Phase]map[Phase]bool{
	PhasePending:  {PhaseRunning: true, PhaseFailed: true},
	PhaseRunning:  {PhaseImported: true, PhaseFailed: true},
	PhaseImported: {PhaseFailed: true},  // verify regression
	PhaseFailed:   {PhaseRunning: true}, // retry
	PhaseUnknown:  {PhasePending: true, PhaseRunning: true, PhaseFailed: true},
}

// isLegalTransition reports whether moving from one phase to another is
// permitted by the state machine.
//
// Single source of truth — the package-level map is not exported, so callers
// can't mutate it from outside.
func isLegalTransition(from, to Phase) bool {
	return legalTransition[from][to]
}

// Transition returns nil if moving from p to target is permitted by the state
// machine, otherwise ErrIllegalTransition.
//
// Same-phase transitions (p == target) are allowed as no-ops.
func (p Phase) Transition(target Phase) error {
	if p == target {
		return nil
	}
	if isLegalTransition(p, target) {
		return nil
	}
	return ErrIllegalTransition
}
