package domain

import "fmt"

// TaskStatus and its transition rules are the single source of truth for the
// §4.4 status machine. Every handler and worker validates transitions here —
// never inline.
type TaskStatus string

const (
	StatusImported         TaskStatus = "imported"
	StatusAtomizing        TaskStatus = "atomizing"
	StatusAtomized         TaskStatus = "atomized"
	StatusPublished        TaskStatus = "published"
	StatusClaimRequested   TaskStatus = "claim_requested"
	StatusAssigned         TaskStatus = "assigned"
	StatusInProgress       TaskStatus = "in_progress"
	StatusInReview         TaskStatus = "in_review"
	StatusChangesRequested TaskStatus = "changes_requested"
	StatusApproved         TaskStatus = "approved"
	StatusArchived         TaskStatus = "archived"
)

// AllStatuses lists every valid status (used by tests and validation).
var AllStatuses = []TaskStatus{
	StatusImported, StatusAtomizing, StatusAtomized, StatusPublished,
	StatusClaimRequested, StatusAssigned, StatusInProgress, StatusInReview,
	StatusChangesRequested, StatusApproved, StatusArchived,
}

func (s TaskStatus) Valid() bool {
	for _, v := range AllStatuses {
		if s == v {
			return true
		}
	}
	return false
}

// transitions encodes §4.4 exactly:
//
//	imported ──subdivide──► atomizing ──service ok──► atomized (children created atomized)
//	atomizing ──service failed──► imported (recovery; archived also allowed from any)
//	atomized ──publish──► published
//	published ──dev requests──► claim_requested ──approve──► assigned
//	published ──assign AI──► assigned
//	assigned ──dev starts──► in_progress ──dev submits──► in_review
//	assigned ──AI callback succeeded──► in_review
//	in_review ──► approved | changes_requested ──► in_progress
//	claim_requested ──withdraw/decline──► published
//	assigned|in_progress ──unassign/abandon──► published
//	any ──► archived (terminal)
var transitions = map[TaskStatus]map[TaskStatus]bool{
	StatusImported:  {StatusAtomizing: true},
	StatusAtomizing: {StatusAtomized: true, StatusImported: true},
	StatusAtomized:  {StatusPublished: true, StatusAtomizing: true}, // re-run subdivision
	StatusPublished: {StatusClaimRequested: true, StatusAssigned: true},
	StatusClaimRequested: {
		StatusAssigned:  true,
		StatusPublished: true,
	},
	StatusAssigned: {
		StatusInProgress: true,
		StatusInReview:   true, // AI work-performer callback path
		StatusPublished:  true,
	},
	StatusInProgress: {
		StatusInReview:  true,
		StatusPublished: true,
	},
	StatusInReview: {
		StatusApproved:         true,
		StatusChangesRequested: true,
		StatusAssigned:         true, // AI job failed → back to assigned (§5.2)
	},
	StatusChangesRequested: {StatusInProgress: true},
	StatusApproved:         {},
	StatusArchived:         {},
}

// CanTransition reports whether from → to is a legal §4.4 move. Archiving is
// allowed from any state except archived itself.
func CanTransition(from, to TaskStatus) bool {
	if !from.Valid() || !to.Valid() {
		return false
	}
	if to == StatusArchived {
		return from != StatusArchived
	}
	return transitions[from][to]
}

// ErrBadTransition describes a rejected move for error envelopes.
type ErrBadTransition struct {
	From, To TaskStatus
}

func (e *ErrBadTransition) Error() string {
	return fmt.Sprintf("illegal status transition %s → %s", e.From, e.To)
}

// Transition validates and returns the error form handlers translate to 409.
func Transition(from, to TaskStatus) error {
	if !CanTransition(from, to) {
		return &ErrBadTransition{From: from, To: to}
	}
	return nil
}
