package artifactsync

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/fs"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"slices"
	"strings"

	distributionreference "github.com/distribution/reference"
	"gopkg.in/yaml.v3"

	devfullruntime "github.com/tidbcloud/controlplane9/personas/dev-full/runtime"
)

const (
	SnapshotVersion           = "cp9.tidbcloud.com/artifact-snapshot/v1alpha1"
	SnapshotFile              = "snapshot.json"
	ArtifactTriggerDir        = "triggers"
	RefreshTransactionFile    = ".refresh-pending.json"
	RefreshTransactionVersion = "cp9.tidbcloud.com/artifact-refresh/v1alpha1"
)

var (
	formatTokenPattern          = regexp.MustCompile(`\$\{([1-9]\d*)\}`)
	remainingPlaceholderPattern = regexp.MustCompile(`\b(?:TPML|TMPL|IMAGE)_[A-Z0-9_]+\b`)
)

type Registry struct {
	APIVersion   string                 `yaml:"apiVersion"`
	Kind         string                 `yaml:"kind"`
	Environments map[string]Environment `yaml:"environments"`
	Artifacts    []Artifact             `yaml:"artifacts"`
	Blocked      []BlockedArtifact      `yaml:"blocked"`
}

type BlockedArtifact struct {
	Name     string `yaml:"name"`
	Kind     string `yaml:"kind"`
	Consumer string `yaml:"consumer"`
	Reason   string `yaml:"reason"`
}

type Environment struct {
	Substitutions map[string]string `yaml:"substitutions"`
}

type Artifact struct {
	Name    string  `yaml:"name"`
	Kind    string  `yaml:"kind"`
	Dev     Locator `yaml:"dev"`
	Staging Locator `yaml:"staging"`
}

type Locator struct {
	Repo      string   `yaml:"repo"`
	Ref       string   `yaml:"ref"`
	File      string   `yaml:"file,omitempty"`
	Regex     string   `yaml:"regex,omitempty"`
	Format    string   `yaml:"format,omitempty"`
	Directory string   `yaml:"directory,omitempty"`
	Files     []string `yaml:"files,omitempty"`
}

type Snapshot struct {
	APIVersion     string             `json:"apiVersion"`
	Environment    string             `json:"environment"`
	RegistryDigest string             `json:"registry_digest"`
	Artifacts      []ResolvedArtifact `json:"artifacts"`
	Digest         string             `json:"digest"`
}

type ResolvedArtifact struct {
	Name          string   `json:"name"`
	Kind          string   `json:"kind"`
	Repo          string   `json:"repo"`
	Ref           string   `json:"ref"`
	Commit        string   `json:"commit"`
	Image         string   `json:"image,omitempty"`
	ImageOverride bool     `json:"image_override,omitempty"`
	Files         []string `json:"files,omitempty"`
	CRDNames      []string `json:"crd_names,omitempty"`
	Digest        string   `json:"digest"`
	LocalPath     string   `json:"local_path,omitempty"`
}

type Options struct {
	Environment          string
	OutputDir            string
	Selected             map[string]bool
	Current              *Snapshot
	ImageOverride        string
	GitHubAppClientID    string
	TokenHelper          string
	ChangedArtifacts     map[string]bool
	RecoveredArtifacts   map[string]bool
	RecoveredTransaction bool
	TrackTransaction     bool
}

type checkout struct {
	root   string
	commit string
}

type crdDocument struct {
	APIVersion string      `yaml:"apiVersion"`
	Kind       string      `yaml:"kind"`
	Metadata   crdMetadata `yaml:"metadata"`
}

type crdMetadata struct {
	Name string `yaml:"name"`
}

func DefaultRegistry() (*Registry, string, error) {
	data := devfullruntime.ArtifactRegistry
	var registry Registry
	if err := yaml.Unmarshal(data, &registry); err != nil {
		return nil, "", fmt.Errorf("parse artifact registry: %w", err)
	}
	return &registry, digestBytes(data), nil
}

func LoadSnapshot(outputDir string) (*Snapshot, error) {
	outputDir, err := filepath.Abs(outputDir)
	if err != nil {
		return nil, fmt.Errorf("resolve artifact snapshot directory: %w", err)
	}
	data, err := os.ReadFile(filepath.Join(outputDir, SnapshotFile))
	if err != nil {
		return nil, err
	}
	var snapshot Snapshot
	if err := json.Unmarshal(data, &snapshot); err != nil {
		return nil, fmt.Errorf("parse artifact snapshot: %w", err)
	}
	if snapshot.APIVersion != SnapshotVersion || strings.TrimSpace(snapshot.Digest) == "" {
		return nil, fmt.Errorf("artifact snapshot is not a supported immutable snapshot")
	}
	digest, err := snapshotDigest(&snapshot)
	if err != nil {
		return nil, err
	}
	if digest != snapshot.Digest {
		return nil, fmt.Errorf("artifact snapshot digest mismatch")
	}
	for i := range snapshot.Artifacts {
		artifact := &snapshot.Artifacts[i]
		if artifact.Kind != "crd" {
			continue
		}
		localPath, err := snapshotLocalPath(outputDir, artifact.LocalPath)
		if err != nil {
			return nil, fmt.Errorf("validate artifact CRD %s path: %w", artifact.Name, err)
		}
		if err := validateCRDDirectory(localPath, artifact.Files, artifact.CRDNames, artifact.Digest); err != nil {
			return nil, fmt.Errorf("validate artifact CRD %s: %w", artifact.Name, err)
		}
		artifact.LocalPath = localPath
	}
	return &snapshot, nil
}

func OutputDir(stateDir string) string {
	return filepath.Join(stateDir, "artifacts")
}

type RefreshTransaction struct {
	APIVersion       string   `json:"apiVersion"`
	Artifacts        []string `json:"artifacts"`
	RuntimeArtifacts []string `json:"runtime_artifacts"`
	TargetSnapshot   Snapshot `json:"target_snapshot"`
}

// RefreshTransactionData records the exact artifact generation and immutable
// target payload while a refresh publishes its snapshot and triggers.
func RefreshTransactionData(outputDir string, selected, runtimeSelected map[string]bool, target *Snapshot) ([]byte, error) {
	if target == nil {
		return nil, errors.New("artifact refresh transaction requires a target snapshot")
	}
	normalized, err := snapshotForDisk(outputDir, target)
	if err != nil {
		return nil, fmt.Errorf("normalize artifact refresh transaction target: %w", err)
	}
	if normalized.APIVersion != SnapshotVersion {
		return nil, errors.New("artifact refresh transaction target snapshot is not supported")
	}
	targetSnapshotDigest, err := snapshotDigest(normalized)
	if err != nil {
		return nil, err
	}
	if strings.TrimSpace(target.Digest) != "" && target.Digest != targetSnapshotDigest {
		return nil, errors.New("artifact refresh transaction target snapshot digest mismatch")
	}
	normalized.Digest = targetSnapshotDigest
	names := make([]string, 0, len(selected))
	known := map[string]bool{}
	for i := range normalized.Artifacts {
		known[normalized.Artifacts[i].Name] = true
	}
	for name, enabled := range selected {
		if enabled {
			name = strings.TrimSpace(name)
			if name == "" {
				return nil, errors.New("artifact refresh transaction contains an empty artifact name")
			}
			if !known[name] {
				return nil, fmt.Errorf("artifact refresh transaction target is missing %s", name)
			}
			names = append(names, name)
		}
	}
	if len(names) == 0 {
		return nil, errors.New("artifact refresh transaction requires at least one artifact")
	}
	slices.Sort(names)
	runtimeNames := make([]string, 0, len(runtimeSelected))
	for name, enabled := range runtimeSelected {
		if !enabled {
			continue
		}
		name = strings.TrimSpace(name)
		if name == "" || !selected[name] || !known[name] {
			return nil, fmt.Errorf("artifact refresh transaction contains invalid runtime target %q", name)
		}
		runtimeNames = append(runtimeNames, name)
	}
	slices.Sort(runtimeNames)
	data, err := json.Marshal(RefreshTransaction{
		APIVersion:       RefreshTransactionVersion,
		Artifacts:        names,
		RuntimeArtifacts: runtimeNames,
		TargetSnapshot:   *normalized,
	})
	if err != nil {
		return nil, fmt.Errorf("marshal artifact refresh transaction: %w", err)
	}
	return append(data, '\n'), nil
}

// ParseRefreshTransaction parses the durable pending-refresh marker used by
// local and remote artifact publication.
func ParseRefreshTransaction(data []byte) (*RefreshTransaction, error) {
	var transaction RefreshTransaction
	if err := json.Unmarshal(data, &transaction); err != nil {
		return nil, fmt.Errorf("parse artifact refresh transaction: %w", err)
	}
	if transaction.APIVersion != RefreshTransactionVersion || len(transaction.Artifacts) == 0 ||
		strings.TrimSpace(transaction.TargetSnapshot.Digest) == "" ||
		transaction.TargetSnapshot.APIVersion != SnapshotVersion {
		return nil, errors.New("artifact refresh transaction is not supported")
	}
	digest, err := snapshotDigest(&transaction.TargetSnapshot)
	if err != nil {
		return nil, err
	}
	if digest != transaction.TargetSnapshot.Digest {
		return nil, errors.New("artifact refresh transaction target snapshot digest mismatch")
	}
	var raw struct {
		RuntimeArtifacts json.RawMessage `json:"runtime_artifacts"`
	}
	if err := json.Unmarshal(data, &raw); err != nil {
		return nil, fmt.Errorf("parse artifact refresh transaction runtime targets: %w", err)
	}
	if raw.RuntimeArtifacts == nil {
		transaction.RuntimeArtifacts = append([]string(nil), transaction.Artifacts...)
	} else if bytes.Equal(bytes.TrimSpace(raw.RuntimeArtifacts), []byte("null")) {
		return nil, errors.New("artifact refresh transaction runtime targets are invalid")
	}
	seen := map[string]bool{}
	for i, name := range transaction.Artifacts {
		name = strings.TrimSpace(name)
		if name == "" || seen[name] {
			return nil, errors.New("artifact refresh transaction contains an invalid or duplicate artifact")
		}
		transaction.Artifacts[i] = name
		seen[name] = true
	}
	slices.Sort(transaction.Artifacts)
	runtimeSeen := map[string]bool{}
	for i, name := range transaction.RuntimeArtifacts {
		name = strings.TrimSpace(name)
		if name == "" || runtimeSeen[name] || !seen[name] {
			return nil, errors.New("artifact refresh transaction contains an invalid or duplicate runtime target")
		}
		transaction.RuntimeArtifacts[i] = name
		runtimeSeen[name] = true
	}
	slices.Sort(transaction.RuntimeArtifacts)
	return &transaction, nil
}

// ClearRefreshTransaction removes the disposable marker after runtime
// convergence has been observed.
func ClearRefreshTransaction(outputDir string) error {
	path := filepath.Join(outputDir, RefreshTransactionFile)
	if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
		return err
	}
	if _, err := os.Stat(path); err != nil && !errors.Is(err, os.ErrNotExist) {
		return err
	} else if err == nil {
		return errors.New("artifact refresh transaction marker remains after clear")
	}
	return nil
}

// ValidateRefreshSelection refuses to continue a partial transaction without
// the complete artifact set that was originally published.
func ValidateRefreshSelection(pending, selected map[string]bool) error {
	missing := make([]string, 0)
	extra := make([]string, 0)
	for name := range pending {
		if !selected[name] {
			missing = append(missing, name)
		}
	}
	for name := range selected {
		if selected[name] && !pending[name] {
			extra = append(extra, name)
		}
	}
	if len(missing) == 0 && len(extra) == 0 {
		return nil
	}
	slices.Sort(missing)
	slices.Sort(extra)
	return fmt.Errorf(
		"artifact refresh transaction requires exact set (missing=%s extra=%s)",
		strings.Join(missing, ", "), strings.Join(extra, ", "),
	)
}

func ValidateRefreshGeneration(pending *RefreshTransaction, resolvedDigest string) error {
	if pending == nil {
		return nil
	}
	if strings.TrimSpace(resolvedDigest) == "" || pending.TargetSnapshot.Digest != resolvedDigest {
		return fmt.Errorf(
			"pending artifact refresh target digest %s does not match resolved digest %s",
			pending.TargetSnapshot.Digest, resolvedDigest,
		)
	}
	return nil
}

func validateRefreshTarget(outputDir string, transaction *RefreshTransaction) (*Snapshot, error) {
	target := transaction.TargetSnapshot
	if target.Environment == "" || len(target.Artifacts) == 0 {
		return nil, errors.New("artifact refresh transaction target snapshot is incomplete")
	}
	seen := map[string]bool{}
	for i := range target.Artifacts {
		artifact := &target.Artifacts[i]
		if artifact.Name == "" || seen[artifact.Name] {
			return nil, errors.New("artifact refresh transaction target snapshot has an invalid artifact set")
		}
		seen[artifact.Name] = true
		if strings.TrimSpace(artifact.Digest) == "" {
			return nil, fmt.Errorf("artifact refresh transaction target %s has no digest", artifact.Name)
		}
		switch artifact.Kind {
		case "service":
			if err := validateImage(artifact.Image); err != nil {
				return nil, fmt.Errorf("validate artifact refresh target %s: %w", artifact.Name, err)
			}
		case "crd":
			localPath, err := snapshotLocalPath(outputDir, artifact.LocalPath)
			if err != nil {
				return nil, fmt.Errorf("validate artifact refresh target %s path: %w", artifact.Name, err)
			}
			if err := validateCRDDirectory(localPath, artifact.Files, artifact.CRDNames, artifact.Digest); err != nil {
				return nil, fmt.Errorf("validate artifact refresh target %s: %w", artifact.Name, err)
			}
		default:
			return nil, fmt.Errorf("artifact refresh transaction target %s has unsupported kind %q", artifact.Name, artifact.Kind)
		}
	}
	if err := ValidateRefreshGeneration(transaction, target.Digest); err != nil {
		return nil, err
	}
	return &target, nil
}

func cloneSelection(selected map[string]bool) map[string]bool {
	cloned := make(map[string]bool, len(selected))
	for name, enabled := range selected {
		if enabled {
			cloned[name] = true
		}
	}
	return cloned
}

func loadRefreshTransaction(outputDir string) (*RefreshTransaction, error) {
	data, err := os.ReadFile(filepath.Join(outputDir, RefreshTransactionFile))
	if err != nil {
		return nil, err
	}
	return ParseRefreshTransaction(data)
}

func Ensure(ctx context.Context, options *Options) (*Snapshot, error) {
	current, err := LoadSnapshot(options.OutputDir)
	if err == nil {
		registry, registryDigest, registryErr := DefaultRegistry()
		if registryErr != nil {
			return nil, registryErr
		}
		if current.Environment != options.Environment {
			return nil, fmt.Errorf("persisted artifact snapshot environment %s does not match requested %s", current.Environment, options.Environment)
		}
		if current.RegistryDigest != registryDigest {
			return nil, fmt.Errorf("persisted artifact snapshot uses a different registry; run `cp9 artifacts.refresh` explicitly")
		}
		if err := validateSnapshotRegistry(current, registry); err != nil {
			return nil, err
		}
		if _, err := writeSnapshot(options.OutputDir, current, nil, false); err != nil {
			return nil, err
		}
		return current, nil
	}
	if !errors.Is(err, os.ErrNotExist) {
		return nil, err
	}
	return Resolve(ctx, options)
}

func Resolve(ctx context.Context, options *Options) (*Snapshot, error) {
	environment := strings.TrimSpace(options.Environment)
	if environment != "dev" && environment != "staging" {
		return nil, fmt.Errorf("artifact environment %q is invalid (expected dev or staging)", environment)
	}
	if strings.TrimSpace(options.OutputDir) == "" {
		return nil, errors.New("artifact output directory is required")
	}
	if err := os.MkdirAll(options.OutputDir, 0o700); err != nil {
		return nil, fmt.Errorf("create artifact output directory: %w", err)
	}
	options.ChangedArtifacts = nil
	options.RecoveredArtifacts = nil
	options.RecoveredTransaction = false
	selected := options.Selected
	if pendingTransaction, pendingErr := loadRefreshTransaction(options.OutputDir); pendingErr == nil {
		pendingSelection := make(map[string]bool, len(pendingTransaction.Artifacts))
		for _, name := range pendingTransaction.Artifacts {
			pendingSelection[name] = true
		}
		if !options.TrackTransaction {
			return nil, errors.New("pending artifact refresh transaction requires `artifacts refresh` recovery")
		}
		if err := ValidateRefreshSelection(pendingSelection, selected); err != nil {
			return nil, err
		}
		if strings.TrimSpace(options.ImageOverride) != "" {
			return nil, errors.New("pending artifact refresh transaction cannot apply an image override")
		}
		target, err := validateRefreshTarget(options.OutputDir, pendingTransaction)
		if err != nil {
			return nil, err
		}
		if target.Environment != environment {
			return nil, fmt.Errorf("pending artifact refresh target environment %s does not match requested %s", target.Environment, environment)
		}
		options.RecoveredTransaction = true
		runtimeSelection := make(map[string]bool, len(pendingTransaction.RuntimeArtifacts))
		for _, name := range pendingTransaction.RuntimeArtifacts {
			runtimeSelection[name] = true
		}
		options.RecoveredArtifacts = runtimeSelection
		changedArtifacts, err := writeSnapshot(options.OutputDir, target, selected, true)
		if err != nil {
			return nil, err
		}
		options.ChangedArtifacts = changedArtifacts
		return target, nil
	} else if !errors.Is(pendingErr, os.ErrNotExist) {
		return nil, fmt.Errorf("load pending artifact refresh transaction: %w", pendingErr)
	}

	registry, registryDigest, err := DefaultRegistry()
	if err != nil {
		return nil, err
	}
	env, ok := registry.Environments[environment]
	if !ok {
		return nil, fmt.Errorf("artifact environment %q is not configured", environment)
	}
	selected = options.Selected
	if len(selected) == 0 {
		selected = make(map[string]bool, len(registry.Artifacts))
		for i := range registry.Artifacts {
			selected[registry.Artifacts[i].Name] = true
		}
	}
	known := map[string]bool{}
	for i := range registry.Artifacts {
		known[registry.Artifacts[i].Name] = true
	}
	for name := range selected {
		if !known[name] {
			return nil, fmt.Errorf("artifact %q is not registered", name)
		}
	}
	temporary, err := os.MkdirTemp("", "cp9-artifact-resolve-")
	if err != nil {
		return nil, fmt.Errorf("create artifact resolver workspace: %w", err)
	}
	defer os.RemoveAll(temporary)

	imageOverride := strings.TrimSpace(options.ImageOverride)
	if imageOverride != "" {
		if len(selected) != 1 {
			return nil, errors.New("image override requires exactly one artifact")
		}
		if options.Current == nil {
			return nil, errors.New("image override requires a current artifact snapshot")
		}
		if err := validateImage(imageOverride); err != nil {
			return nil, err
		}
	}
	if options.Current != nil && options.Current.Environment != environment {
		return nil, fmt.Errorf("cannot merge %s artifacts into %s snapshot", environment, options.Current.Environment)
	}
	if options.Current != nil {
		if err := validateSnapshotRefresh(options.Current, registry, selected); err != nil {
			return nil, err
		}
	}

	checkouts := map[string]checkout{}
	resolved := map[string]ResolvedArtifact{}
	if options.Current != nil {
		for i := range options.Current.Artifacts {
			artifact := options.Current.Artifacts[i]
			resolved[artifact.Name] = artifact
		}
	}
	for i := range registry.Artifacts {
		artifact := registry.Artifacts[i]
		if !selected[artifact.Name] {
			continue
		}
		if imageOverride != "" {
			item, ok := resolved[artifact.Name]
			if !ok {
				return nil, fmt.Errorf("artifact %q is not present in the current snapshot", artifact.Name)
			}
			if artifact.Kind != "service" {
				return nil, fmt.Errorf("artifact %q is not a service image", artifact.Name)
			}
			item.Image = imageOverride
			item.ImageOverride = true
			item.Digest = digestBytes([]byte(imageOverride))
			resolved[artifact.Name] = item
			continue
		}
		locator := artifact.locator(environment)
		key := locator.Repo + "\x00" + locator.Ref
		resolvedCheckout, ok := checkouts[key]
		if !ok {
			resolvedCheckout, err = clone(ctx, temporary, locator, options)
			if err != nil {
				return nil, fmt.Errorf("resolve %s artifact %s from %s@%s: %w", environment, artifact.Name, locator.Repo, locator.Ref, err)
			}
			checkouts[key] = resolvedCheckout
		}
		item, err := resolveArtifact(&artifact, locator, resolvedCheckout, env.Substitutions, options.OutputDir)
		if err != nil {
			return nil, fmt.Errorf(
				"resolve %s artifact %s from %s@%s (%s): %w",
				environment,
				artifact.Name,
				locator.Repo,
				locator.Ref,
				resolvedCheckout.commit,
				err,
			)
		}
		resolved[artifact.Name] = item
	}
	if len(resolved) != len(registry.Artifacts) {
		return nil, fmt.Errorf("artifact snapshot would be partial: resolved %d of %d entries", len(resolved), len(registry.Artifacts))
	}

	snapshot := &Snapshot{
		APIVersion:     SnapshotVersion,
		Environment:    environment,
		RegistryDigest: registryDigest,
	}
	for name := range resolved {
		artifact := resolved[name]
		snapshot.Artifacts = append(snapshot.Artifacts, artifact)
	}
	slices.SortFunc(snapshot.Artifacts, func(left, right ResolvedArtifact) int { return strings.Compare(left.Name, right.Name) })
	snapshot.Digest, err = snapshotDigest(snapshot)
	if err != nil {
		return nil, err
	}
	changedArtifacts, err := writeSnapshot(options.OutputDir, snapshot, selected, options.TrackTransaction)
	if err != nil {
		return nil, err
	}
	options.ChangedArtifacts = changedArtifacts
	return snapshot, nil
}

func (a *Artifact) locator(environment string) *Locator {
	if environment == "staging" {
		return &a.Staging
	}
	return &a.Dev
}

func clone(ctx context.Context, temporary string, locator *Locator, options *Options) (checkout, error) {
	root := filepath.Join(temporary, digestBytes([]byte(locator.Repo + "\x00" + locator.Ref))[:16])
	cloneOnce := func(token []byte) error {
		if err := runGit(ctx, token, "init", "--quiet", root); err != nil {
			return err
		}
		if err := runGit(ctx, token, "-C", root, "remote", "add", "origin", "https://github.com/"+locator.Repo+".git"); err != nil {
			return err
		}
		if err := runGit(ctx, token, "-C", root, "fetch", "--quiet", "--depth=1", "origin", locator.Ref); err != nil {
			return err
		}
		return runGit(ctx, token, "-C", root, "checkout", "--quiet", "--detach", "FETCH_HEAD")
	}
	if err := cloneOnce(nil); err != nil {
		if removeErr := os.RemoveAll(root); removeErr != nil {
			return checkout{}, fmt.Errorf("reset owner repository checkout after clone failure: %w", removeErr)
		}
		token, tokenErr := githubToken(ctx, options)
		if tokenErr != nil {
			return checkout{}, fmt.Errorf("clone owner repository: %w; GitHub authorization: %w", err, tokenErr)
		}
		defer zeroBytes(token)
		if err := cloneOnce(token); err != nil {
			return checkout{}, fmt.Errorf("clone owner repository after GitHub authorization: %w", err)
		}
	}
	commit, err := commandOutput(ctx, root, nil, "git", "rev-parse", "HEAD")
	if err != nil {
		return checkout{}, err
	}
	commit = strings.TrimSpace(commit)
	if !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(commit) {
		return checkout{}, fmt.Errorf("resolved commit %q is invalid", commit)
	}
	return checkout{root: root, commit: commit}, nil
}

func runGit(ctx context.Context, token []byte, args ...string) error {
	command := exec.CommandContext(ctx, "git", args...)
	command.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
	var askpassDir string
	if len(token) > 0 {
		var err error
		askpassDir, err = os.MkdirTemp("", "cp9-git-askpass-")
		if err != nil {
			return err
		}
		defer os.RemoveAll(askpassDir)
		askpass := filepath.Join(askpassDir, "askpass.sh")
		if err := os.WriteFile(
			askpass,
			[]byte("#!/bin/sh\ncase $1 in *Username*) printf '%s' x-access-token;; *) printf '%s' \"$CP9_GITHUB_TOKEN\";; esac\n"),
			0o600,
		); err != nil {
			return err
		}
		if err := os.Chmod(askpass, 0o700); err != nil {
			return err
		}
		command.Env = append(command.Env, "GIT_ASKPASS="+askpass, "CP9_GITHUB_TOKEN="+string(token))
	}
	var stderr bytes.Buffer
	command.Stderr = &stderr
	if err := command.Run(); err != nil {
		return fmt.Errorf("git %s: %w: %s", args[0], err, strings.TrimSpace(stderr.String()))
	}
	return nil
}

func githubToken(ctx context.Context, options *Options) ([]byte, error) {
	if strings.TrimSpace(options.GitHubAppClientID) == "" || strings.TrimSpace(options.TokenHelper) == "" {
		return nil, errors.New("private owner repository requires services.github_app_client_id and the CP9 GitHub Device Flow helper")
	}
	// #nosec G204 -- TokenHelper is CP9's resolved in-repository Device Flow helper.
	command := exec.CommandContext(ctx, options.TokenHelper, options.GitHubAppClientID)
	command.Stderr = os.Stderr
	token, err := command.Output()
	if err != nil {
		return nil, err
	}
	token = bytes.TrimSpace(token)
	if !bytes.HasPrefix(token, []byte("ghu_")) {
		zeroBytes(token)
		return nil, errors.New("GitHub Device Flow returned an invalid user token")
	}
	return token, nil
}

func resolveArtifact(
	artifact *Artifact,
	locator *Locator,
	resolvedCheckout checkout,
	substitutions map[string]string,
	outputDir string,
) (ResolvedArtifact, error) {
	item := ResolvedArtifact{Name: artifact.Name, Kind: artifact.Kind, Repo: locator.Repo, Ref: locator.Ref, Commit: resolvedCheckout.commit}
	if artifact.Kind == "service" {
		file, err := safePath(resolvedCheckout.root, locator.File, false)
		if err != nil {
			return item, err
		}
		data, err := os.ReadFile(file)
		if err != nil {
			return item, fmt.Errorf("read %s: %w", locator.File, err)
		}
		image, err := resolveImage(data, locator, substitutions)
		if err != nil {
			return item, fmt.Errorf("file %s: %w", locator.File, err)
		}
		item.Image = image
		item.Digest = digestBytes([]byte(image))
		return item, nil
	}
	directory, err := safePath(resolvedCheckout.root, locator.Directory, true)
	if err != nil {
		return item, err
	}
	files, names, digest, err := resolveCRDs(directory, locator.Files)
	if err != nil {
		return item, fmt.Errorf("directory %s: %w", locator.Directory, err)
	}
	relativeDestination := filepath.Join("crds", artifact.Name, strings.TrimPrefix(digest, "sha256:"))
	destination := filepath.Join(outputDir, relativeDestination)
	if err := materializeCRDs(directory, destination, files, names, digest); err != nil {
		return item, err
	}
	item.Files, item.CRDNames, item.Digest, item.LocalPath = files, names, digest, filepath.ToSlash(relativeDestination)
	return item, nil
}

func resolveImage(data []byte, locator *Locator, substitutions map[string]string) (string, error) {
	re := regexp.MustCompile(locator.Regex)
	matches := re.FindAllSubmatch(data, -1)
	if len(matches) != 1 {
		return "", fmt.Errorf("regex matched %d times; expected exactly 1", len(matches))
	}
	match := matches[0]
	var formatErr error
	image := formatTokenPattern.ReplaceAllStringFunc(locator.Format, func(token string) string {
		indexText := formatTokenPattern.FindStringSubmatch(token)[1]
		var index int
		if _, err := fmt.Sscanf(indexText, "%d", &index); err != nil || index >= len(match) || len(match[index]) == 0 {
			formatErr = fmt.Errorf("format references missing or empty capture group %s", token)
			return token
		}
		return string(match[index])
	})
	if formatErr != nil {
		return "", formatErr
	}
	if formatTokenPattern.MatchString(image) {
		return "", fmt.Errorf("format contains an unexpanded capture group")
	}
	var unknownPlaceholder string
	image = remainingPlaceholderPattern.ReplaceAllStringFunc(image, func(token string) string {
		value, ok := substitutions[token]
		if !ok {
			unknownPlaceholder = token
			return token
		}
		return value
	})
	if unknownPlaceholder != "" {
		return "", fmt.Errorf("unsupported placeholder %s remains after substitution", unknownPlaceholder)
	}
	image = strings.TrimSpace(image)
	if err := validateImage(image); err != nil {
		return "", fmt.Errorf("resolved %w", err)
	}
	return image, nil
}

func validateImage(image string) error {
	parsed, err := distributionreference.Parse(image)
	if err != nil {
		return fmt.Errorf("image %q is invalid: %w", image, err)
	}
	named, ok := parsed.(distributionreference.Named)
	if !ok || !strings.Contains(distributionreference.Domain(named), ".") || parsed.String() != image {
		return fmt.Errorf("image %q does not contain an explicit registry", image)
	}
	if _, tagged := parsed.(distributionreference.Tagged); !tagged {
		if _, digested := parsed.(distributionreference.Digested); !digested {
			return fmt.Errorf("image %q has neither a tag nor a digest", image)
		}
	}
	return nil
}

func resolveCRDs(root string, selected []string) (files, names []string, digest string, err error) {
	if selected != nil {
		if len(selected) == 0 {
			return nil, nil, "", errors.New("exact file list is empty")
		}
		files = append(files, selected...)
		for _, relative := range files {
			if filepath.ToSlash(filepath.Clean(filepath.FromSlash(relative))) != relative {
				return nil, nil, "", fmt.Errorf("repository path %q is not canonical", relative)
			}
			if ext := strings.ToLower(filepath.Ext(relative)); ext != ".yaml" && ext != ".yml" {
				return nil, nil, "", fmt.Errorf("repository path %s is not a YAML file", relative)
			}
			if _, err := safePath(root, relative, false); err != nil {
				return nil, nil, "", err
			}
		}
	} else if err := filepath.WalkDir(root, func(path string, entry fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if entry.Type()&os.ModeSymlink != 0 {
			return fmt.Errorf("symlink %s is not allowed", path)
		}
		if entry.IsDir() {
			return nil
		}
		if strings.EqualFold(entry.Name(), "kustomization.yaml") || strings.EqualFold(entry.Name(), "kustomization.yml") {
			return nil
		}
		ext := strings.ToLower(filepath.Ext(entry.Name()))
		if ext == ".yaml" || ext == ".yml" {
			relative, relErr := filepath.Rel(root, path)
			if relErr != nil {
				return relErr
			}
			files = append(files, filepath.ToSlash(relative))
		}
		return nil
	}); err != nil {
		return nil, nil, "", err
	}
	slices.Sort(files)
	if len(files) == 0 {
		return nil, nil, "", errors.New("contains no YAML files")
	}
	for i := 1; i < len(files); i++ {
		if files[i] == files[i-1] {
			return nil, nil, "", fmt.Errorf("contains duplicate YAML file path %s", files[i])
		}
	}
	hash := sha256.New()
	seen := map[string]bool{}
	for _, relative := range files {
		data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(relative)))
		if err != nil {
			return nil, nil, "", err
		}
		decoder := yaml.NewDecoder(bytes.NewReader(data))
		documents := 0
		for {
			var document crdDocument
			if err := decoder.Decode(&document); errors.Is(err, io.EOF) {
				break
			} else if err != nil {
				return nil, nil, "", fmt.Errorf("parse %s: %w", relative, err)
			}
			documents++
			name := strings.TrimSpace(document.Metadata.Name)
			if document.APIVersion != "apiextensions.k8s.io/v1" || document.Kind != "CustomResourceDefinition" || name == "" {
				return nil, nil, "", fmt.Errorf("%s document %d is not an apiextensions.k8s.io/v1 CustomResourceDefinition", relative, documents)
			}
			if seen[name] {
				return nil, nil, "", fmt.Errorf("duplicate CRD name %s", name)
			}
			seen[name] = true
			names = append(names, name)
		}
		if documents == 0 {
			return nil, nil, "", fmt.Errorf("%s contains no YAML document", relative)
		}
		_, _ = hash.Write([]byte(relative))
		_, _ = hash.Write([]byte{0})
		_, _ = hash.Write(data)
		_, _ = hash.Write([]byte{0})
	}
	slices.Sort(names)
	return files, names, "sha256:" + hex.EncodeToString(hash.Sum(nil)), nil
}

func safePath(root, relative string, directory bool) (string, error) {
	if filepath.IsAbs(relative) || relative == "" {
		return "", fmt.Errorf("repository path %q must be relative", relative)
	}
	root, err := filepath.EvalSymlinks(root)
	if err != nil {
		return "", fmt.Errorf("resolve repository root: %w", err)
	}
	path := filepath.Join(root, filepath.FromSlash(relative))
	resolved, err := filepath.EvalSymlinks(path)
	if err != nil {
		return "", fmt.Errorf("resolve repository path %s: %w", relative, err)
	}
	rel, err := filepath.Rel(root, resolved)
	if err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
		return "", fmt.Errorf("repository path %s escapes its checkout", relative)
	}
	info, err := os.Stat(resolved)
	if err != nil {
		return "", err
	}
	if info.IsDir() != directory || (!directory && !info.Mode().IsRegular()) {
		return "", fmt.Errorf("repository path %s has the wrong file type", relative)
	}
	return resolved, nil
}

func copyFiles(root, destination string, files []string) error {
	for _, relative := range files {
		target := filepath.Join(destination, filepath.FromSlash(relative))
		data, err := os.ReadFile(filepath.Join(root, filepath.FromSlash(relative)))
		if err != nil {
			return err
		}
		if err := os.MkdirAll(filepath.Dir(target), 0o700); err != nil {
			return err
		}
		// #nosec G703 -- relative comes from the already-contained resolver walk.
		if err := os.WriteFile(target, data, 0o600); err != nil {
			return err
		}
	}
	return nil
}

func materializeCRDs(root, destination string, files, names []string, digest string) error {
	if _, err := os.Stat(destination); err == nil {
		return validateCRDDirectory(destination, files, names, digest)
	} else if !os.IsNotExist(err) {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(destination), 0o700); err != nil {
		return err
	}
	temporary, err := os.MkdirTemp(filepath.Dir(destination), ".crds-*")
	if err != nil {
		return err
	}
	defer os.RemoveAll(temporary)
	if err := copyFiles(root, temporary, files); err != nil {
		return err
	}
	if err := os.Rename(temporary, destination); err != nil {
		if _, statErr := os.Stat(destination); statErr == nil {
			return validateCRDDirectory(destination, files, names, digest)
		}
		return err
	}
	return nil
}

func validateCRDDirectory(root string, files, names []string, digest string) error {
	actualFiles, actualNames, actualDigest, err := resolveCRDs(root, nil)
	if err != nil {
		return err
	}
	if actualDigest != digest || !slices.Equal(actualFiles, files) || !slices.Equal(actualNames, names) {
		return fmt.Errorf("artifact CRD directory %s does not match digest %s", root, digest)
	}
	return nil
}

func validateSnapshotRegistry(snapshot *Snapshot, registry *Registry) error {
	if len(snapshot.Artifacts) != len(registry.Artifacts) {
		return fmt.Errorf("artifact snapshot is partial: contains %d of %d registry entries", len(snapshot.Artifacts), len(registry.Artifacts))
	}
	known, seen, err := validateSnapshotEntries(snapshot, registry)
	if err != nil {
		return err
	}
	if len(seen) != len(known) {
		return fmt.Errorf("artifact snapshot is missing registry entries")
	}
	return nil
}

func validateSnapshotRefresh(snapshot *Snapshot, registry *Registry, selected map[string]bool) error {
	known, seen, err := validateSnapshotEntries(snapshot, registry)
	if err != nil {
		return err
	}
	for name := range known {
		if !seen[name] && !selected[name] {
			return fmt.Errorf("new artifact registry entry %s must be selected for refresh", name)
		}
	}
	return nil
}

func validateSnapshotEntries(snapshot *Snapshot, registry *Registry) (map[string]string, map[string]bool, error) {
	known := make(map[string]string, len(registry.Artifacts))
	for i := range registry.Artifacts {
		known[registry.Artifacts[i].Name] = registry.Artifacts[i].Kind
	}
	seen := map[string]bool{}
	for i := range snapshot.Artifacts {
		artifact := &snapshot.Artifacts[i]
		if known[artifact.Name] != artifact.Kind || seen[artifact.Name] {
			return nil, nil, fmt.Errorf("artifact snapshot entry %s has an invalid or duplicate kind", artifact.Name)
		}
		seen[artifact.Name] = true
	}
	return known, seen, nil
}

func snapshotDigest(snapshot *Snapshot) (string, error) {
	digestInput := *snapshot
	digestInput.Artifacts = append([]ResolvedArtifact(nil), snapshot.Artifacts...)
	digestInput.Digest = ""
	for i := range digestInput.Artifacts {
		digestInput.Artifacts[i].LocalPath = ""
	}
	data, err := json.Marshal(digestInput)
	if err != nil {
		return "", fmt.Errorf("marshal artifact snapshot digest input: %w", err)
	}
	return digestBytes(data), nil
}

func writeSnapshot(
	outputDir string,
	snapshot *Snapshot,
	selected map[string]bool,
	trackTransaction bool,
) (map[string]bool, error) {
	readyTriggers, err := artifactTriggerFiles(outputDir, snapshot, selected, "ready")
	if err != nil {
		return nil, err
	}
	normalized, err := snapshotForDisk(outputDir, snapshot)
	if err != nil {
		return nil, err
	}
	data, err := json.MarshalIndent(normalized, "", "  ")
	if err != nil {
		return nil, fmt.Errorf("marshal artifact snapshot: %w", err)
	}
	data = append(data, '\n')
	transactionPath := filepath.Join(outputDir, RefreshTransactionFile)
	previousTransaction, transactionReadErr := os.ReadFile(transactionPath)
	if transactionReadErr != nil && !errors.Is(transactionReadErr, os.ErrNotExist) {
		return nil, fmt.Errorf("read pending artifact refresh transaction: %w", transactionReadErr)
	}
	snapshotPath := filepath.Join(outputDir, SnapshotFile)
	previousSnapshot, readErr := os.ReadFile(snapshotPath)
	if readErr != nil && !errors.Is(readErr, os.ErrNotExist) {
		return nil, fmt.Errorf("read artifact snapshot: %w", readErr)
	}
	snapshotChanged := readErr != nil || !bytes.Equal(previousSnapshot, data)
	triggerChanges, err := artifactFilesNeedingWrite(readyTriggers)
	if err != nil {
		return nil, err
	}
	runtimeTargets, err := artifactRuntimeTargets(previousSnapshot, readErr == nil, snapshot, selected, triggerChanges)
	if err != nil {
		return nil, err
	}
	changedArtifacts := cloneSelection(runtimeTargets)
	var pendingTransaction *RefreshTransaction
	if transactionReadErr == nil {
		pendingTransaction, err = ParseRefreshTransaction(previousTransaction)
		if err != nil {
			return nil, fmt.Errorf("parse pending artifact refresh transaction: %w", err)
		}
		if pendingTransaction.TargetSnapshot.Digest == snapshot.Digest {
			runtimeTargets = map[string]bool{}
			for _, name := range pendingTransaction.RuntimeArtifacts {
				runtimeTargets[name] = true
			}
		}
	}
	for i := range snapshot.Artifacts {
		artifact := &snapshot.Artifacts[i]
		if artifact.Kind == "service" && runtimeTargets[artifact.Name] {
			changedArtifacts[artifact.Name] = true
		}
	}
	pendingTriggers, err := artifactTriggerFiles(outputDir, snapshot, runtimeTargets, "pending")
	if err != nil {
		return nil, err
	}
	originalTriggers, err := readArtifactFileStates(readyTriggers)
	if err != nil {
		return nil, err
	}
	restoreTriggers := func() error {
		return restoreArtifactFiles(originalTriggers)
	}
	transactionChanged := false
	restoreTransaction := func() error {
		return restoreArtifactFile(transactionPath, previousTransaction, transactionReadErr == nil)
	}
	if trackTransaction && selected != nil && (snapshotChanged || len(triggerChanges) > 0 || transactionReadErr == nil) {
		transaction, transactionErr := RefreshTransactionData(outputDir, selected, runtimeTargets, snapshot)
		if transactionErr != nil {
			return nil, transactionErr
		}
		if transactionReadErr != nil || !bytes.Equal(previousTransaction, transaction) {
			if err := writeArtifactFile(outputDir, transactionPath, transaction); err != nil {
				return nil, fmt.Errorf("publish pending artifact refresh transaction: %w", err)
			}
			transactionChanged = true
		}
	}
	if err := publishArtifactFiles(pendingTriggers); err != nil {
		if transactionChanged {
			return nil, errors.Join(err, restoreTransaction())
		}
		return nil, err
	}
	// A pending trigger makes Tilt reject the intermediate graph. The last ready
	// trigger releases the complete snapshot without copying configuration into
	// the trigger files.
	if snapshotChanged {
		if err := writeArtifactFile(outputDir, snapshotPath, data); err != nil {
			rollbackErr := restoreTriggers()
			if transactionChanged {
				rollbackErr = errors.Join(rollbackErr, restoreTransaction())
			}
			return nil, errors.Join(fmt.Errorf("publish artifact snapshot: %w", err), rollbackErr)
		}
	}
	if err := publishArtifactFiles(readyTriggers); err != nil {
		rollbackErr := restoreTriggers()
		if snapshotChanged {
			rollbackErr = errors.Join(rollbackErr, restoreArtifactFile(snapshotPath, previousSnapshot, readErr == nil))
		}
		if transactionChanged {
			rollbackErr = errors.Join(rollbackErr, restoreTransaction())
		}
		return nil, errors.Join(err, rollbackErr)
	}
	return changedArtifacts, nil
}

func artifactFilesNeedingWrite(files map[string][]byte) (map[string]bool, error) {
	changed := map[string]bool{}
	for path, data := range files {
		previous, err := os.ReadFile(path)
		if err == nil {
			if !bytes.Equal(previous, data) {
				changed[strings.TrimSuffix(filepath.Base(path), ".json")] = true
			}
			continue
		}
		if !errors.Is(err, os.ErrNotExist) {
			return nil, fmt.Errorf("read artifact file %s: %w", path, err)
		}
		changed[strings.TrimSuffix(filepath.Base(path), ".json")] = true
	}
	return changed, nil
}

func artifactRuntimeTargets(
	previousData []byte,
	previousExists bool,
	target *Snapshot,
	selected, triggerChanges map[string]bool,
) (map[string]bool, error) {
	targets := cloneSelection(triggerChanges)
	if selected == nil {
		return targets, nil
	}
	previousDigests := map[string]string{}
	if previousExists {
		var previous Snapshot
		if err := json.Unmarshal(previousData, &previous); err != nil {
			return nil, fmt.Errorf("parse prior artifact snapshot: %w", err)
		}
		digest, err := snapshotDigest(&previous)
		if err != nil {
			return nil, err
		}
		if digest != previous.Digest {
			return nil, errors.New("prior artifact snapshot digest mismatch")
		}
		for i := range previous.Artifacts {
			previousDigests[previous.Artifacts[i].Name] = previous.Artifacts[i].Digest
		}
	}
	for i := range target.Artifacts {
		artifact := &target.Artifacts[i]
		if selected[artifact.Name] && previousDigests[artifact.Name] != artifact.Digest {
			targets[artifact.Name] = true
		}
	}
	return targets, nil
}

// artifactTrigger is an operational publication gate. Configuration remains
// exclusively in the versioned snapshot.
type artifactTrigger struct {
	Name  string `json:"name"`
	State string `json:"state"`
}

func ArtifactTriggerData(name, state string) ([]byte, error) {
	if strings.TrimSpace(name) == "" || (state != "pending" && state != "ready") {
		return nil, errors.New("artifact trigger requires a name and pending or ready state")
	}
	data, err := json.Marshal(artifactTrigger{Name: name, State: state})
	if err != nil {
		return nil, err
	}
	return append(data, '\n'), nil
}

func artifactTriggerFiles(outputDir string, snapshot *Snapshot, selected map[string]bool, state string) (map[string][]byte, error) {
	triggerDir := filepath.Join(outputDir, ArtifactTriggerDir)
	if err := os.MkdirAll(triggerDir, 0o700); err != nil {
		return nil, fmt.Errorf("create artifact trigger directory: %w", err)
	}
	files := map[string][]byte{}
	for i := range snapshot.Artifacts {
		artifact := &snapshot.Artifacts[i]
		if artifact.Kind != "service" || (selected != nil && !selected[artifact.Name]) {
			continue
		}
		data, err := ArtifactTriggerData(artifact.Name, state)
		if err != nil {
			return nil, fmt.Errorf("marshal artifact trigger %s: %w", artifact.Name, err)
		}
		files[filepath.Join(triggerDir, artifact.Name+".json")] = data
	}
	return files, nil
}

type artifactFileState struct {
	exists bool
	data   []byte
}

func readArtifactFileStates(files map[string][]byte) (map[string]artifactFileState, error) {
	states := make(map[string]artifactFileState, len(files))
	for path := range files {
		data, err := os.ReadFile(path)
		if err != nil && !errors.Is(err, os.ErrNotExist) {
			return nil, fmt.Errorf("read artifact file %s: %w", path, err)
		}
		states[path] = artifactFileState{exists: err == nil, data: data}
	}
	return states, nil
}

func restoreArtifactFiles(states map[string]artifactFileState) error {
	var restoreErr error
	for path, state := range states {
		restoreErr = errors.Join(restoreErr, restoreArtifactFile(path, state.data, state.exists))
	}
	return restoreErr
}

func publishArtifactFiles(files map[string][]byte) error {
	originals, err := readArtifactFileStates(files)
	if err != nil {
		return err
	}
	for path, data := range files {
		state := originals[path]
		if state.exists && bytes.Equal(state.data, data) {
			continue
		}
		if err := writeArtifactFile(filepath.Dir(path), path, data); err != nil {
			return errors.Join(fmt.Errorf("publish artifact file %s: %w", path, err), restoreArtifactFiles(originals))
		}
	}
	return nil
}

func writeArtifactFile(directory, path string, data []byte) error {
	temporary, err := os.CreateTemp(directory, ".artifact-*.tmp")
	if err != nil {
		return err
	}
	temporaryName := temporary.Name()
	defer os.Remove(temporaryName)
	if err := temporary.Chmod(0o600); err != nil {
		return errors.Join(err, temporary.Close())
	}
	if _, err := temporary.Write(data); err != nil {
		return errors.Join(err, temporary.Close())
	}
	if err := temporary.Sync(); err != nil {
		return errors.Join(err, temporary.Close())
	}
	if err := temporary.Close(); err != nil {
		return err
	}
	return os.Rename(temporaryName, path)
}

func restoreArtifactFile(path string, data []byte, exists bool) error {
	if exists {
		return writeArtifactFile(filepath.Dir(path), path, data)
	}
	if err := os.Remove(path); err != nil && !errors.Is(err, os.ErrNotExist) {
		return err
	}
	return nil
}

func snapshotForDisk(outputDir string, snapshot *Snapshot) (*Snapshot, error) {
	outputDir, err := filepath.Abs(outputDir)
	if err != nil {
		return nil, fmt.Errorf("resolve artifact snapshot directory: %w", err)
	}
	normalized := *snapshot
	normalized.Artifacts = append([]ResolvedArtifact(nil), snapshot.Artifacts...)
	for i := range normalized.Artifacts {
		artifact := &normalized.Artifacts[i]
		if artifact.Kind != "crd" || !filepath.IsAbs(artifact.LocalPath) {
			continue
		}
		relative, err := filepath.Rel(outputDir, artifact.LocalPath)
		if err != nil || relative == ".." || strings.HasPrefix(relative, ".."+string(filepath.Separator)) {
			return nil, fmt.Errorf("artifact CRD %s path escapes its snapshot directory", artifact.Name)
		}
		artifact.LocalPath = filepath.ToSlash(relative)
	}
	return &normalized, nil
}

func snapshotLocalPath(outputDir, relative string) (string, error) {
	if relative == "" || filepath.IsAbs(relative) {
		return "", fmt.Errorf("snapshot path %q must be relative", relative)
	}
	clean := filepath.Clean(filepath.FromSlash(relative))
	if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) {
		return "", fmt.Errorf("snapshot path %q escapes its snapshot directory", relative)
	}
	return filepath.Join(outputDir, clean), nil
}

// PublishSnapshot atomically restores or promotes an already validated snapshot.
func PublishSnapshot(outputDir string, snapshot *Snapshot) error {
	if snapshot == nil {
		return errors.New("artifact snapshot is required")
	}
	if err := os.MkdirAll(outputDir, 0o700); err != nil {
		return fmt.Errorf("create artifact output directory: %w", err)
	}
	digest, err := snapshotDigest(snapshot)
	if err != nil {
		return err
	}
	if digest != snapshot.Digest {
		return errors.New("artifact snapshot digest mismatch")
	}
	_, err = writeSnapshot(outputDir, snapshot, nil, false)
	return err
}

// PublishSnapshotKeepingRefreshTransaction restores a snapshot and retargets
// the pending marker until the caller observes runtime rollback.
func PublishSnapshotKeepingRefreshTransaction(
	outputDir string,
	snapshot *Snapshot,
	selected map[string]bool,
) (map[string]bool, error) {
	if snapshot == nil {
		return nil, errors.New("artifact snapshot is required")
	}
	if err := os.MkdirAll(outputDir, 0o700); err != nil {
		return nil, fmt.Errorf("create artifact output directory: %w", err)
	}
	digest, err := snapshotDigest(snapshot)
	if err != nil {
		return nil, err
	}
	if digest != snapshot.Digest {
		return nil, errors.New("artifact snapshot digest mismatch")
	}
	return writeSnapshot(outputDir, snapshot, selected, true)
}

func commandOutput(ctx context.Context, dir string, env []string, name string, args ...string) (string, error) {
	command := exec.CommandContext(ctx, name, args...)
	command.Dir = dir
	if env != nil {
		command.Env = env
	}
	output, err := command.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("%s %s: %w: %s", name, strings.Join(args, " "), err, strings.TrimSpace(string(output)))
	}
	return string(output), nil
}

func digestBytes(data []byte) string {
	sum := sha256.Sum256(data)
	return "sha256:" + hex.EncodeToString(sum[:])
}

func zeroBytes(value []byte) {
	for i := range value {
		value[i] = 0
	}
}
