package delivery

// A saga coordinates a multi-step workflow across services WITHOUT a
// distributed transaction (2PC): each step has a forward action and a
// compensating action that semantically undoes it. If a step fails, the
// orchestrator runs the compensations for the already-completed steps in
// REVERSE order, leaving the system in a consistent (rolled-back) state.

// Step is one stage of a saga.
type Step struct {
	Name string
	Do   func() error
	Undo func() error // compensation: semantically undo Do
}

// Saga is an ordered list of steps run by an orchestrator.
type Saga struct {
	Steps []Step
	// OnProgress is called with the number of forward steps completed,
	// after each success — a hook to PERSIST progress so a crash can
	// resume (see RunFrom).
	OnProgress func(completedForward int)
}

// Result records what happened.
type Result struct {
	CompletedForward []string // steps whose Do succeeded
	Compensated      []string // steps whose Undo ran (reverse order)
	Failed           bool
	FailedAt         string
}

// Run executes the saga from the beginning.
func (s Saga) Run() Result { return s.RunFrom(0) }

// RunFrom resumes a saga assuming steps [0:start) already completed
// forward (e.g. after a crash whose persisted progress = start). On a
// failure it compensates ALL completed steps (including the pre-start
// ones) in reverse order.
func (s Saga) RunFrom(start int) Result {
	res := Result{}
	for i := 0; i < start; i++ {
		res.CompletedForward = append(res.CompletedForward, s.Steps[i].Name)
	}

	for i := start; i < len(s.Steps); i++ {
		if err := s.Steps[i].Do(); err != nil {
			res.Failed = true
			res.FailedAt = s.Steps[i].Name
			// Compensate every completed step in reverse order.
			for j := i - 1; j >= 0; j-- {
				if s.Steps[j].Undo != nil {
					_ = s.Steps[j].Undo()
				}
				res.Compensated = append(res.Compensated, s.Steps[j].Name)
			}
			return res
		}
		res.CompletedForward = append(res.CompletedForward, s.Steps[i].Name)
		if s.OnProgress != nil {
			s.OnProgress(i + 1)
		}
	}
	return res
}
