// Package gitops models the ArgoCD/Flux control loop: git is the desired
// state, a reconciler continuously converges the live cluster to match it
// — SYNC (create/update), PRUNE (delete what git removed), SELF-HEAL
// (revert manual drift), in dependency/SYNC-WAVE order — plus an
// SLO-gated progressive-delivery promotion. Stdlib-only, deterministic.
package gitops

import "sort"

// Resource is a deployable object. Wave orders application (lower waves
// first), like ArgoCD sync waves (e.g. CRDs before the workloads that use
// them). Spec is the desired content; a difference means out-of-sync.
type Resource struct {
	Name string
	Wave int
	Spec string
}

// State is the live cluster (or a desired set), keyed by name.
type State map[string]Resource

// Action records what the reconciler did, in order.
type Action struct {
	Op   string // "apply" | "prune"
	Name string
}

// Result summarizes a reconcile pass.
type Result struct {
	Created []string
	Healed  []string // existed but drifted/changed -> reverted to desired
	Pruned  []string
	Order   []Action
}

// Reconcile converges `live` toward `desired` (git). It applies desired
// resources in sync-wave order (creating or healing), and — if prune is
// enabled — deletes live resources that git no longer declares. Returns
// the result and the converged live state.
func Reconcile(desired []Resource, live State, prune bool) (Result, State) {
	if live == nil {
		live = State{}
	}
	wanted := map[string]Resource{}
	for _, r := range desired {
		wanted[r.Name] = r
	}

	// Apply in ascending wave, then name (deterministic).
	ordered := append([]Resource(nil), desired...)
	sort.Slice(ordered, func(i, j int) bool {
		if ordered[i].Wave != ordered[j].Wave {
			return ordered[i].Wave < ordered[j].Wave
		}
		return ordered[i].Name < ordered[j].Name
	})

	var res Result
	for _, r := range ordered {
		cur, exists := live[r.Name]
		switch {
		case !exists:
			live[r.Name] = r
			res.Created = append(res.Created, r.Name)
			res.Order = append(res.Order, Action{"apply", r.Name})
		case cur.Spec != r.Spec:
			// Drift (manual change) or a git update -> revert/update to desired.
			live[r.Name] = r
			res.Healed = append(res.Healed, r.Name)
			res.Order = append(res.Order, Action{"apply", r.Name})
		}
	}

	if prune {
		var extra []string
		for name := range live {
			if _, ok := wanted[name]; !ok {
				extra = append(extra, name)
			}
		}
		sort.Strings(extra)
		for _, name := range extra {
			delete(live, name)
			res.Pruned = append(res.Pruned, name)
			res.Order = append(res.Order, Action{"prune", name})
		}
	}
	return res, live
}

// Diff reports resources that are out of sync between git (desired) and
// the cluster (live): missing, drifted, or (if pruning) extra.
func Diff(desired []Resource, live State, prune bool) []string {
	wanted := map[string]Resource{}
	for _, r := range desired {
		wanted[r.Name] = r
	}
	var out []string
	for name, r := range wanted {
		cur, ok := live[name]
		if !ok || cur.Spec != r.Spec {
			out = append(out, name)
		}
	}
	if prune {
		for name := range live {
			if _, ok := wanted[name]; !ok {
				out = append(out, name)
			}
		}
	}
	sort.Strings(out)
	return out
}

// PromoteOrRollback is progressive delivery in one decision: promote the
// new version only if it's healthy (SLO-gated), else keep the current
// (instant rollback — the old version is still live). For the full
// shadow→canary→ramp ladder see gw-12.
func PromoteOrRollback(current, candidate string, healthy bool) (active string, rolledBack bool) {
	if healthy {
		return candidate, false
	}
	return current, true
}
