// Package state manages the persistent xetup deployment state used to resume
// after a system reboot. The state file is written before each reboot and
// deleted once all deployment steps have completed.
package state

import (
	"encoding/json"
	"os"
	"path/filepath"
	"time"

	"git.xetup.x9.cz/x9/xetup/internal/config"
)

// StepResult records the outcome of a single deployment step.
// Results are accumulated across reboot rounds so the final summary
// can show every step regardless of which round it ran in.
type StepResult struct {
	StepID  string        `json:"stepID"`
	Num     string        `json:"num"`
	Name    string        `json:"name"`
	Status  string        `json:"status"`  // "OK", "ERROR", "SKIPPED"
	Elapsed time.Duration `json:"elapsed"`
}

// State holds everything xetup needs to resume after a reboot.
type State struct {
	Config       config.Config  `json:"config"`
	ConfigPath   string         `json:"configPath"` // path to original config.json
	LogFile      string         `json:"logFile"`
	PendingSteps []string       `json:"pendingSteps"`           // step IDs to run next, in canonical order
	Results      []StepResult   `json:"results"`                // accumulated across all rounds
	RetryCounts  map[string]int `json:"retryCounts,omitempty"`  // per-step reboot retry counter
}

// Load reads the state file. Returns a non-nil error when the file is absent.
func Load() (*State, error) {
	data, err := os.ReadFile(statePath())
	if err != nil {
		return nil, err
	}
	var s State
	if err := json.Unmarshal(data, &s); err != nil {
		return nil, err
	}
	return &s, nil
}

// Save writes the state file atomically (write-to-temp + rename), creating
// parent directories as needed. This prevents corruption if the system
// crashes mid-write (e.g. BSOD, power loss).
func Save(s *State) error {
	p := statePath()
	dir := filepath.Dir(p)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return err
	}
	data, err := json.MarshalIndent(s, "", "  ")
	if err != nil {
		return err
	}
	tmp := p + ".tmp"
	if err := os.WriteFile(tmp, data, 0644); err != nil {
		return err
	}
	return os.Rename(tmp, p)
}

// Delete removes the state file. Silently ignores not-found.
func Delete() error {
	err := os.Remove(statePath())
	if os.IsNotExist(err) {
		return nil
	}
	return err
}

// Exists reports whether a state file is present on disk.
func Exists() bool {
	_, err := os.Stat(statePath())
	return err == nil
}
