package dispatcher

import (
	"archive/zip"
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/filemode"
	"github.com/go-git/go-git/v5/plumbing/object"
	"github.com/tidbcloud/AgentTeam/desktop/internal/memorywatcher"
	"github.com/tidbcloud/AgentTeam/desktop/internal/nativememory"
	"github.com/tidbcloud/AgentTeam/desktop/internal/vault"
	"golang.org/x/text/unicode/norm"
)

var nativeMemoryCheckpointMinInterval = 5 * time.Minute

const nativeMemoryArchiveLimit = int64(68 << 20)

type nativeMemoryArchiveFile struct {
	path    string
	content []byte
	mode    os.FileMode
}

type nativeMemoryRefreshHooks struct {
	beforeSwitch func() error
	commit       func(context.Context, *vault.Clone) (string, error)
	complete     func(*nativememory.Sync, string, string) error
}

const nativeMemoryReplaceTxnSchema = 1

type nativeMemoryReplacePhase string

const (
	nativeMemoryReplaceBackingUp  nativeMemoryReplacePhase = "backing_up"
	nativeMemoryReplaceInstalling nativeMemoryReplacePhase = "installing"
	nativeMemoryReplaceSwitched   nativeMemoryReplacePhase = "switched"
	nativeMemoryReplaceCommitted  nativeMemoryReplacePhase = "committed"
)

type nativeMemoryReplaceManifest struct {
	SchemaVersion   int                      `json:"schemaVersion"`
	TargetVersionID string                   `json:"targetVersionId"`
	OldRevision     string                   `json:"oldRevision"`
	NewRevision     string                   `json:"newRevision,omitempty"`
	Phase           nativeMemoryReplacePhase `json:"phase"`
	OldEntries      []string                 `json:"oldEntries"`
	NewEntries      []string                 `json:"newEntries"`
}

type nativeMemoryCommitter struct {
	*memorywatcher.CloneCommitter
	clone *vault.Clone
	sync  *nativememory.Sync
}

func newNativeMemoryCommitter(clone *vault.Clone, syncer *nativememory.Sync) *nativeMemoryCommitter {
	return &nativeMemoryCommitter{CloneCommitter: memorywatcher.NewCloneCommitter(clone), clone: clone, sync: syncer}
}

func (c *nativeMemoryCommitter) CommitPending(ctx context.Context, message string, opts memorywatcher.CommitOpts) (string, error) {
	if err := c.Recover(ctx); err != nil {
		return "", err
	}
	// Native local revisions are immutable journal entries. Cloud coalescing is
	// represented by sequence ranges, never by amending an earlier local entry.
	opts.Amend = false
	hash, err := c.CloneCommitter.CommitPending(ctx, message, opts)
	if err != nil {
		return "", err
	}
	if err := c.sync.RecordLocalRevision(hash); err != nil {
		return "", err
	}
	return hash, nil
}

func (c *nativeMemoryCommitter) Recover(ctx context.Context) error {
	head, err := c.clone.HeadRevision(ctx)
	if err != nil || head == "" {
		return err
	}
	return c.sync.RecordLocalRevision(head)
}

type nativeMemoryPusher struct{ sync *nativememory.Sync }

func (p nativeMemoryPusher) Push(ctx context.Context, _ memorywatcher.PushOpts) (string, error) {
	result, err := p.sync.Publish(ctx)
	if errors.Is(err, nativememory.ErrNothingPending) {
		return "", memorywatcher.ErrNothingToPush
	}
	if err != nil {
		return "", err
	}
	return result.VersionID, nil
}

func openNativeMemoryJournal(ctx context.Context, agentID, workDir string, access MemoryAccess) (*vault.Clone, *nativememory.Sync, error) {
	clone, err := vault.EnsureLocalJournal(ctx, access.MemoryID, agentID, filepath.Join(workDir, "memory"))
	if err != nil {
		return nil, nil, err
	}
	syncer, err := nativememory.Open(nativememory.Options{
		Root: clone.Path(), StatePath: filepath.Join(workDir, ".memory-sync", fmt.Sprintf("native-%s-g%d.json", safeVersionSegment(access.MemoryID), access.ContentGeneration)), Remote: access.NativeRemote,
		MinInterval: nativeMemoryCheckpointMinInterval,
		Binding: nativememory.Binding{
			MemoryID: access.MemoryID, ContentID: access.ContentID, ContentVersionID: access.ContentVersionID,
			Generation: access.ContentGeneration, AcknowledgedSequence: access.AcknowledgedSequence, ReplicaID: access.ActiveReplicaID,
		},
	})
	if err != nil {
		return nil, nil, err
	}
	if err := recoverNativeMemoryReplaceTxn(ctx, clone, syncer); err != nil {
		return nil, nil, err
	}
	if syncer.RemoteRefreshVersion() != "" {
		if err := refreshNativeMemoryJournal(ctx, clone, syncer, access); err != nil {
			return nil, nil, err
		}
	}
	baseline, localContent, err := hydrateNativeMemoryJournal(ctx, clone, access)
	if err != nil {
		return nil, nil, err
	}
	if baseline != "" && !localContent {
		if err := syncer.AdoptRemoteRevision(baseline); err != nil {
			return nil, nil, err
		}
	}
	committer := newNativeMemoryCommitter(clone, syncer)
	if err := committer.Recover(ctx); err != nil {
		return nil, nil, err
	}
	return clone, syncer, nil
}

func hydrateNativeMemoryJournal(ctx context.Context, clone *vault.Clone, access MemoryAccess) (string, bool, error) {
	head, err := clone.HeadRevision(ctx)
	if err != nil || head != "" {
		return head, true, err
	}
	hadLocalContent, err := nativeMemoryRootHasUserFiles(clone.Path())
	if err != nil {
		return "", false, err
	}
	files, err := readNativeMemoryArchive(ctx, access)
	if err != nil {
		return "", hadLocalContent, err
	}
	conflictPrefix := path.Join(".conflicts", "cloud-"+safeVersionSegment(access.ContentVersionID))
	for _, item := range files {
		target, err := safeNativeMemoryTarget(clone.Path(), item.path)
		if err != nil {
			return "", hadLocalContent, err
		}
		if existing, err := os.ReadFile(target); err == nil {
			if bytes.Equal(existing, item.content) {
				continue
			}
			conflictRel := path.Join(conflictPrefix, item.path)
			target, err = safeNativeMemoryTarget(clone.Path(), conflictRel)
			if err != nil {
				return "", hadLocalContent, err
			}
		} else if !os.IsNotExist(err) {
			return "", hadLocalContent, err
		}
		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
			return "", hadLocalContent, err
		}
		if err := os.WriteFile(target, item.content, item.mode); err != nil {
			return "", hadLocalContent, err
		}
	}
	if access.ImportExistingLocalContent {
		imported, err := mergeNativeFallbackMemory(clone.Path())
		if err != nil {
			return "", hadLocalContent, err
		}
		hadLocalContent = hadLocalContent || imported
	}
	hash, err := clone.CommitPending(ctx, "Initialize native Memory from exact cloud version", vault.CommitPendingOpts{})
	if errors.Is(err, vault.ErrNothingToCommit) {
		return "", hadLocalContent, nil
	}
	return hash, hadLocalContent, err
}

func refreshNativeMemoryJournal(ctx context.Context, clone *vault.Clone, syncer *nativememory.Sync, access MemoryAccess) (returnErr error) {
	versionID := syncer.RemoteRefreshVersion()
	if versionID == "" {
		return nil
	}
	if !syncer.RemoteRefreshStarted() {
		// Reconcile the immutable Git journal before admitting any remote read.
		// A crash may leave a successful clean commit ahead of LocalRevision;
		// recovering it first turns that unseen local tree into durable pending
		// work, so the concurrent cloud head fails closed without opening B.
		head, err := clone.HeadRevision(ctx)
		if err != nil {
			return err
		}
		if head != "" {
			if err := syncer.RecordLocalRevision(head); err != nil {
				return err
			}
			if syncer.Pending() {
				return nativememory.ErrRemoteHeadChanged
			}
		}
		revision, err := clone.CommitPending(ctx, "Preserve local Memory edits before cloud refresh", vault.CommitPendingOpts{})
		if err == nil {
			if err := syncer.RecordLocalRevision(revision); err != nil {
				return err
			}
			return nativememory.ErrRemoteHeadChanged
		}
		if !errors.Is(err, vault.ErrNothingToCommit) {
			return err
		}
		if err := syncer.BeginRemoteRefresh(versionID); err != nil {
			return err
		}
	} else {
		// The live tree was never changed by a failed staged transaction. New
		// runtime work must cancel the marker and become durable pending before a
		// retry is allowed to read the cloud archive again.
		head, err := clone.HeadRevision(ctx)
		if err != nil {
			return err
		}
		revision, commitErr := clone.CommitPending(ctx, "Preserve local Memory edits after cloud refresh failure", vault.CommitPendingOpts{})
		if commitErr == nil {
			if err := syncer.AbortRemoteRefresh(versionID); err != nil {
				return err
			}
			if err := syncer.RecordLocalRevision(revision); err != nil {
				return err
			}
			return nativememory.ErrRemoteHeadChanged
		}
		if !errors.Is(commitErr, vault.ErrNothingToCommit) {
			return commitErr
		}
		if head != "" && head != syncer.LocalRevision() {
			if err := syncer.AbortRemoteRefresh(versionID); err != nil {
				return err
			}
			if err := syncer.RecordLocalRevision(head); err != nil {
				return err
			}
			return nativememory.ErrRemoteHeadChanged
		}
	}
	refreshAccess := access
	refreshAccess.ContentVersionID = versionID
	files, err := readNativeMemoryArchive(ctx, refreshAccess)
	if err != nil {
		return err
	}
	txn, err := stageNativeMemoryFiles(ctx, clone, versionID, files, access.nativeRefreshHooks)
	if err != nil {
		return err
	}
	defer func() { returnErr = errors.Join(returnErr, txn.rollback()) }()
	// ContentVersion identity is part of the local journal even when the
	// restored tree is byte-for-byte identical to its predecessor.
	commit := func() (string, error) {
		return clone.CommitPending(ctx, "Adopt exact restored cloud Memory version", vault.CommitPendingOpts{AllowEmpty: true})
	}
	if access.nativeRefreshHooks != nil && access.nativeRefreshHooks.commit != nil {
		commit = func() (string, error) { return access.nativeRefreshHooks.commit(ctx, clone) }
	}
	revision, err := commit()
	if err != nil {
		return err
	}
	if err := txn.recordCommittedRevision(revision); err != nil {
		return err
	}
	if err := verifyNativeMemoryRevision(clone, revision, files); err != nil {
		return err
	}
	complete := syncer.CompleteRemoteRefresh
	if access.nativeRefreshHooks != nil && access.nativeRefreshHooks.complete != nil {
		complete = func(versionID, revision string) error {
			return access.nativeRefreshHooks.complete(syncer, versionID, revision)
		}
	}
	if err := complete(versionID, revision); err != nil {
		return err
	}
	return txn.commit()
}

func readNativeMemoryArchive(ctx context.Context, access MemoryAccess) ([]nativeMemoryArchiveFile, error) {
	if access.NativeRemote == nil {
		return nil, errors.New("native memory: remote is not configured")
	}
	reader, err := access.NativeRemote.OpenVersionArchive(ctx, access.MemoryID, access.ContentVersionID)
	if err != nil {
		return nil, err
	}
	data, readErr := io.ReadAll(io.LimitReader(reader, nativeMemoryArchiveLimit+1))
	closeErr := reader.Close()
	if readErr != nil {
		return nil, readErr
	}
	if closeErr != nil {
		return nil, closeErr
	}
	if int64(len(data)) > nativeMemoryArchiveLimit {
		return nil, errors.New("native memory: archive exceeds byte limit")
	}
	archive, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
	if err != nil {
		return nil, fmt.Errorf("native memory: open archive: %w", err)
	}
	if len(archive.File) > 2_000 {
		return nil, errors.New("native memory: archive exceeds file limit")
	}
	seen := make(map[string]struct{}, len(archive.File))
	var declaredTotal uint64
	for _, item := range archive.File {
		if item.FileInfo().IsDir() {
			continue
		}
		if !item.Mode().IsRegular() {
			return nil, fmt.Errorf("native memory: archive path %q is not regular", item.Name)
		}
		rel, err := safeNativeArchivePath(item.Name)
		if err != nil {
			return nil, err
		}
		if _, exists := seen[rel]; exists {
			return nil, fmt.Errorf("native memory: archive contains duplicate path %q", rel)
		}
		seen[rel] = struct{}{}
		if item.UncompressedSize64 > 16<<20 || declaredTotal > (64<<20)-item.UncompressedSize64 {
			return nil, errors.New("native memory: archive exceeds byte limits")
		}
		declaredTotal += item.UncompressedSize64
	}
	files := make([]nativeMemoryArchiveFile, 0, len(seen))
	var total int64
	for _, item := range archive.File {
		if item.FileInfo().IsDir() {
			continue
		}
		rel, err := safeNativeArchivePath(item.Name)
		if err != nil {
			return nil, err
		}
		entry, err := item.Open()
		if err != nil {
			return nil, err
		}
		content, readErr := io.ReadAll(io.LimitReader(entry, (16<<20)+1))
		closeErr := entry.Close()
		if readErr != nil {
			return nil, readErr
		}
		if closeErr != nil {
			return nil, closeErr
		}
		if len(content) > 16<<20 || total > (64<<20)-int64(len(content)) {
			return nil, errors.New("native memory: archive exceeds byte limits")
		}
		total += int64(len(content))
		mode := os.FileMode(0o644)
		if item.Mode().Perm()&0o111 != 0 {
			mode = 0o755
		}
		files = append(files, nativeMemoryArchiveFile{path: rel, content: content, mode: mode})
	}
	if err := preflightNativeMemoryArchive(files); err != nil {
		return nil, err
	}
	return files, nil
}

func preflightNativeMemoryArchive(files []nativeMemoryArchiveFile) error {
	keys := make([]string, 0, len(files))
	seen := make(map[string]string, len(files))
	for _, item := range files {
		// Desktop targets include case-insensitive and normalization-insensitive
		// filesystems. Reject their conservative common equivalence class on all
		// platforms so a cloud version has one portable materialization.
		key := strings.ToLower(norm.NFC.String(item.path))
		if prior, ok := seen[key]; ok {
			return fmt.Errorf("native memory: archive paths %q and %q collide on the target filesystem", prior, item.path)
		}
		seen[key] = item.path
		keys = append(keys, key)
	}
	sort.Strings(keys)
	for index := 1; index < len(keys); index++ {
		if strings.HasPrefix(keys[index], keys[index-1]+"/") {
			return fmt.Errorf("native memory: archive file/directory paths %q and %q conflict", seen[keys[index-1]], seen[keys[index]])
		}
	}
	return nil
}

type nativeMemoryReplaceTxn struct {
	clone      *vault.Clone
	root       string
	txnRoot    string
	stageRoot  string
	backupRoot string
	manifest   nativeMemoryReplaceManifest
	active     bool
}

func stageNativeMemoryFiles(ctx context.Context, clone *vault.Clone, targetVersionID string, files []nativeMemoryArchiveFile, hooks *nativeMemoryRefreshHooks) (*nativeMemoryReplaceTxn, error) {
	txn, err := prepareNativeMemoryFiles(ctx, clone, targetVersionID, files)
	if err != nil {
		return nil, err
	}
	if hooks != nil && hooks.beforeSwitch != nil {
		if err := hooks.beforeSwitch(); err != nil {
			return nil, errors.Join(err, txn.rollback())
		}
	}
	if err := txn.switchRoots(); err != nil {
		return nil, errors.Join(err, txn.rollback())
	}
	return txn, nil
}

func prepareNativeMemoryFiles(ctx context.Context, clone *vault.Clone, targetVersionID string, files []nativeMemoryArchiveFile) (*nativeMemoryReplaceTxn, error) {
	if clone == nil {
		return nil, errors.New("native memory: local journal is required")
	}
	targetVersionID = strings.TrimSpace(targetVersionID)
	if targetVersionID == "" {
		return nil, errors.New("native memory: refresh target version is required")
	}
	if err := preflightNativeMemoryArchive(files); err != nil {
		return nil, err
	}
	oldRevision, err := clone.HeadRevision(ctx)
	if err != nil {
		return nil, fmt.Errorf("native memory: read stable journal head: %w", err)
	}
	if oldRevision == "" {
		return nil, errors.New("native memory: stable journal head is required")
	}
	root := clone.Path()
	txnRoot := filepath.Join(filepath.Dir(root), "."+filepath.Base(root)+".native-refresh")
	cleanupRoot := filepath.Join(filepath.Dir(root), "."+filepath.Base(root)+".native-refresh.complete")
	if err := os.RemoveAll(cleanupRoot); err != nil {
		return nil, fmt.Errorf("native memory: clear completed refresh transaction: %w", err)
	}
	if _, err := os.Stat(txnRoot); err == nil {
		return nil, errors.New("native memory: an unrecovered refresh transaction already exists")
	} else if !os.IsNotExist(err) {
		return nil, err
	}
	if err := os.Mkdir(txnRoot, 0o700); err != nil {
		return nil, err
	}
	if err := syncSessionResumeStateDir(filepath.Dir(root)); err != nil {
		_ = os.RemoveAll(txnRoot)
		return nil, fmt.Errorf("native memory: persist refresh transaction directory: %w", err)
	}
	txn := &nativeMemoryReplaceTxn{
		clone: clone, root: root, txnRoot: txnRoot,
		stageRoot: filepath.Join(txnRoot, "stage"), backupRoot: filepath.Join(txnRoot, "backup"),
		manifest: nativeMemoryReplaceManifest{
			SchemaVersion: nativeMemoryReplaceTxnSchema, TargetVersionID: targetVersionID,
			OldRevision: oldRevision, Phase: nativeMemoryReplaceBackingUp,
		},
	}
	if err := os.MkdirAll(txn.stageRoot, 0o700); err != nil {
		_ = os.RemoveAll(txnRoot)
		return nil, err
	}
	for _, item := range files {
		target, err := safeNativeMemoryTarget(txn.stageRoot, item.path)
		if err != nil {
			_ = os.RemoveAll(txnRoot)
			return nil, err
		}
		if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
			_ = os.RemoveAll(txnRoot)
			return nil, err
		}
		if err := os.WriteFile(target, item.content, item.mode); err != nil {
			_ = os.RemoveAll(txnRoot)
			return nil, err
		}
	}
	entries, err := os.ReadDir(root)
	if err != nil {
		_ = os.RemoveAll(txnRoot)
		return nil, err
	}
	for _, entry := range entries {
		if !nativeMemoryLocalMetadataEntry(entry.Name()) {
			txn.manifest.OldEntries = append(txn.manifest.OldEntries, entry.Name())
		}
	}
	staged, err := os.ReadDir(txn.stageRoot)
	if err != nil {
		_ = os.RemoveAll(txnRoot)
		return nil, err
	}
	for _, entry := range staged {
		txn.manifest.NewEntries = append(txn.manifest.NewEntries, entry.Name())
	}
	sort.Strings(txn.manifest.OldEntries)
	sort.Strings(txn.manifest.NewEntries)
	if err := syncNativeMemoryStage(txn.stageRoot); err != nil {
		_ = os.RemoveAll(txnRoot)
		return nil, err
	}
	if err := txn.persistManifest(); err != nil {
		_ = os.RemoveAll(txnRoot)
		return nil, err
	}
	txn.active = true
	return txn, nil
}

func (t *nativeMemoryReplaceTxn) switchRoots() error {
	if err := os.MkdirAll(t.backupRoot, 0o700); err != nil {
		return err
	}
	for _, name := range t.manifest.OldEntries {
		if err := os.Rename(filepath.Join(t.root, name), filepath.Join(t.backupRoot, name)); err != nil {
			return err
		}
		if err := syncNativeMemoryRename(t.root, t.backupRoot); err != nil {
			return err
		}
	}
	t.manifest.Phase = nativeMemoryReplaceInstalling
	if err := t.persistManifest(); err != nil {
		return err
	}
	for _, name := range t.manifest.NewEntries {
		if err := os.Rename(filepath.Join(t.stageRoot, name), filepath.Join(t.root, name)); err != nil {
			return err
		}
		if err := syncNativeMemoryRename(t.stageRoot, t.root); err != nil {
			return err
		}
	}
	t.manifest.Phase = nativeMemoryReplaceSwitched
	return t.persistManifest()
}

func (t *nativeMemoryReplaceTxn) recordCommittedRevision(revision string) error {
	revision = strings.TrimSpace(revision)
	if !validNativeMemoryRevision(revision) {
		return errors.New("native memory: invalid committed refresh revision")
	}
	t.manifest.NewRevision = revision
	t.manifest.Phase = nativeMemoryReplaceCommitted
	return t.persistManifest()
}

func (t *nativeMemoryReplaceTxn) commit() error {
	if t == nil || !t.active {
		return nil
	}
	// CompleteRemoteRefresh has already made the new head durable. From this
	// boundary recovery must finish cleanup, never roll the tree back.
	t.active = false
	return t.retire()
}

func (t *nativeMemoryReplaceTxn) rollback() error {
	if t == nil || !t.active {
		return nil
	}
	if err := resetNativeMemoryJournal(t.clone, t.manifest.OldRevision); err != nil {
		return err
	}
	if err := t.retire(); err != nil {
		return err
	}
	t.active = false
	return nil
}

func recoverNativeMemoryReplaceTxn(_ context.Context, clone *vault.Clone, syncer *nativememory.Sync) error {
	root := clone.Path()
	txnRoot := filepath.Join(filepath.Dir(root), "."+filepath.Base(root)+".native-refresh")
	if _, err := os.Stat(filepath.Join(txnRoot, "manifest.json")); err != nil {
		if os.IsNotExist(err) {
			if _, rootErr := os.Stat(txnRoot); os.IsNotExist(rootErr) {
				return nil
			} else if rootErr != nil {
				return rootErr
			}
			// switchRoots is reachable only after the manifest rename and its
			// directory fsync. A transaction directory without a manifest is
			// therefore an interrupted prepare whose staged files have never
			// touched the live journal.
			if err := os.RemoveAll(txnRoot); err != nil {
				return fmt.Errorf("native memory: discard unprepared refresh transaction: %w", err)
			}
			if err := syncSessionResumeStateDir(filepath.Dir(root)); err != nil {
				return fmt.Errorf("native memory: persist unprepared refresh transaction cleanup: %w", err)
			}
			return nil
		}
		return err
	}
	manifest, err := readNativeMemoryReplaceManifest(txnRoot)
	if err != nil {
		return err
	}
	txn := &nativeMemoryReplaceTxn{
		clone: clone, root: root, txnRoot: txnRoot,
		stageRoot: filepath.Join(txnRoot, "stage"), backupRoot: filepath.Join(txnRoot, "backup"),
		manifest: manifest, active: true,
	}
	if manifest.NewRevision != "" && !syncer.RemoteRefreshStarted() &&
		syncer.RemoteVersionID() == manifest.TargetVersionID && syncer.LocalRevision() == manifest.NewRevision {
		txn.active = false
		return txn.retire()
	}
	return txn.rollback()
}

func (t *nativeMemoryReplaceTxn) persistManifest() error {
	data, err := json.MarshalIndent(t.manifest, "", "  ")
	if err != nil {
		return fmt.Errorf("native memory: encode refresh transaction: %w", err)
	}
	tmp, err := os.CreateTemp(t.txnRoot, ".manifest-*.tmp")
	if err != nil {
		return err
	}
	tmpName := tmp.Name()
	cleanup := func() { _ = os.Remove(tmpName) }
	if _, err := tmp.Write(data); err != nil {
		_ = tmp.Close()
		cleanup()
		return err
	}
	if err := tmp.Sync(); err != nil {
		_ = tmp.Close()
		cleanup()
		return err
	}
	if err := tmp.Close(); err != nil {
		cleanup()
		return err
	}
	if err := os.Rename(tmpName, filepath.Join(t.txnRoot, "manifest.json")); err != nil {
		cleanup()
		return err
	}
	if err := syncSessionResumeStateDir(t.txnRoot); err != nil {
		return fmt.Errorf("native memory: persist refresh transaction: %w", err)
	}
	return nil
}

func readNativeMemoryReplaceManifest(txnRoot string) (nativeMemoryReplaceManifest, error) {
	if _, err := os.Stat(txnRoot); err != nil {
		return nativeMemoryReplaceManifest{}, err
	}
	raw, err := os.ReadFile(filepath.Join(txnRoot, "manifest.json"))
	if err != nil {
		if os.IsNotExist(err) {
			return nativeMemoryReplaceManifest{}, errors.New("native memory: refresh transaction manifest is missing")
		}
		return nativeMemoryReplaceManifest{}, err
	}
	var manifest nativeMemoryReplaceManifest
	if err := json.Unmarshal(raw, &manifest); err != nil {
		return nativeMemoryReplaceManifest{}, fmt.Errorf("native memory: decode refresh transaction: %w", err)
	}
	if manifest.SchemaVersion != nativeMemoryReplaceTxnSchema || strings.TrimSpace(manifest.TargetVersionID) == "" || !validNativeMemoryRevision(manifest.OldRevision) {
		return nativeMemoryReplaceManifest{}, errors.New("native memory: invalid refresh transaction manifest")
	}
	switch manifest.Phase {
	case nativeMemoryReplaceBackingUp, nativeMemoryReplaceInstalling, nativeMemoryReplaceSwitched:
		if manifest.NewRevision != "" {
			return nativeMemoryReplaceManifest{}, errors.New("native memory: invalid pre-commit refresh transaction")
		}
	case nativeMemoryReplaceCommitted:
		if !validNativeMemoryRevision(manifest.NewRevision) {
			return nativeMemoryReplaceManifest{}, errors.New("native memory: invalid committed refresh transaction")
		}
	default:
		return nativeMemoryReplaceManifest{}, errors.New("native memory: invalid refresh transaction phase")
	}
	for _, entries := range [][]string{manifest.OldEntries, manifest.NewEntries} {
		seen := make(map[string]struct{}, len(entries))
		for _, name := range entries {
			if name == "" || filepath.Base(name) != name || nativeMemoryLocalMetadataEntry(name) {
				return nativeMemoryReplaceManifest{}, errors.New("native memory: invalid refresh transaction entry")
			}
			if _, ok := seen[name]; ok {
				return nativeMemoryReplaceManifest{}, errors.New("native memory: duplicate refresh transaction entry")
			}
			seen[name] = struct{}{}
		}
	}
	return manifest, nil
}

func (t *nativeMemoryReplaceTxn) retire() error {
	cleanupRoot := filepath.Join(filepath.Dir(t.root), "."+filepath.Base(t.root)+".native-refresh.complete")
	if err := os.RemoveAll(cleanupRoot); err != nil {
		return err
	}
	if err := os.Rename(t.txnRoot, cleanupRoot); err != nil {
		return err
	}
	if err := syncSessionResumeStateDir(filepath.Dir(t.root)); err != nil {
		return fmt.Errorf("native memory: persist refresh transaction retirement: %w", err)
	}
	if err := os.RemoveAll(cleanupRoot); err != nil {
		return err
	}
	return syncSessionResumeStateDir(filepath.Dir(t.root))
}

func resetNativeMemoryJournal(clone *vault.Clone, revision string) error {
	if clone == nil || !validNativeMemoryRevision(revision) {
		return errors.New("native memory: invalid stable journal revision")
	}
	entries, err := os.ReadDir(clone.Path())
	if err != nil {
		return err
	}
	for _, entry := range entries {
		if nativeMemoryLocalMetadataEntry(entry.Name()) {
			continue
		}
		if err := os.RemoveAll(filepath.Join(clone.Path(), entry.Name())); err != nil {
			return err
		}
	}
	repo := clone.Repository()
	head, err := repo.Head()
	if err != nil {
		return err
	}
	hash := plumbing.NewHash(revision)
	if _, err := repo.CommitObject(hash); err != nil {
		return err
	}
	if err := repo.Storer.SetReference(plumbing.NewHashReference(head.Name(), hash)); err != nil {
		return err
	}
	worktree, err := repo.Worktree()
	if err != nil {
		return err
	}
	if err := worktree.Reset(&git.ResetOptions{Commit: hash, Mode: git.HardReset}); err != nil {
		return err
	}
	status, err := worktree.Status()
	if err != nil {
		return err
	}
	if !status.IsClean() {
		return errors.New("native memory: recovered stable journal is not clean")
	}
	return syncSessionResumeStateDir(clone.Path())
}

func validNativeMemoryRevision(revision string) bool {
	revision = strings.TrimSpace(revision)
	if len(revision) != 40 {
		return false
	}
	_, err := hex.DecodeString(revision)
	return err == nil
}

func syncNativeMemoryStage(root string) error {
	var dirs []string
	err := filepath.WalkDir(root, func(path string, entry os.DirEntry, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if entry.IsDir() {
			dirs = append(dirs, path)
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		syncErr := file.Sync()
		closeErr := file.Close()
		return errors.Join(syncErr, closeErr)
	})
	if err != nil {
		return err
	}
	for index := len(dirs) - 1; index >= 0; index-- {
		if err := syncSessionResumeStateDir(dirs[index]); err != nil {
			return err
		}
	}
	return nil
}

func syncNativeMemoryRename(fromDir, toDir string) error {
	if err := syncSessionResumeStateDir(fromDir); err != nil {
		return err
	}
	if filepath.Clean(fromDir) == filepath.Clean(toDir) {
		return nil
	}
	return syncSessionResumeStateDir(toDir)
}

func verifyNativeMemoryRevision(clone *vault.Clone, revision string, files []nativeMemoryArchiveFile) error {
	expected := make(map[string]nativeMemoryArchiveFile, len(files))
	for _, item := range files {
		expected[item.path] = item
	}
	commit, err := clone.Repository().CommitObject(plumbing.NewHash(revision))
	if err != nil {
		return err
	}
	tree, err := commit.Tree()
	if err != nil {
		return err
	}
	seen := make(map[string]struct{}, len(expected))
	err = tree.Files().ForEach(func(file *object.File) error {
		parts := strings.Split(filepath.ToSlash(file.Name), "/")
		if len(parts) > 0 && nativeMemoryLocalMetadataEntry(parts[0]) {
			return nil
		}
		want, ok := expected[filepath.ToSlash(file.Name)]
		if !ok {
			return fmt.Errorf("native memory: committed refresh contains unexpected path %q", file.Name)
		}
		content, err := file.Contents()
		if err != nil {
			return err
		}
		executable := file.Mode == filemode.Executable
		if content != string(want.content) || executable != (want.mode.Perm()&0o111 != 0) {
			return fmt.Errorf("native memory: committed refresh does not match archive path %q", file.Name)
		}
		seen[want.path] = struct{}{}
		return nil
	})
	if err != nil {
		return err
	}
	if len(seen) != len(expected) {
		return errors.New("native memory: committed refresh is missing archive paths")
	}
	return nil
}

func nativeMemoryLocalMetadataEntry(name string) bool {
	return name == ".git" || name == ".derived" || strings.HasPrefix(name, ".agentteam-")
}

func mergeNativeFallbackMemory(memoryRoot string) (bool, error) {
	source := filepath.Join(filepath.Dir(memoryRoot), "MEMORY.md")
	content, err := os.ReadFile(source)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, err
	}
	target := filepath.Join(memoryRoot, "MEMORY.md")
	if existing, err := os.ReadFile(target); err == nil {
		if bytes.Equal(existing, content) {
			return false, nil
		}
		target = filepath.Join(memoryRoot, ".conflicts", "local-import", "MEMORY.md")
	} else if !os.IsNotExist(err) {
		return false, err
	}
	if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
		return false, err
	}
	if err := os.WriteFile(target, content, 0o644); err != nil {
		return false, err
	}
	return true, nil
}

func nativeMemoryRootHasUserFiles(root string) (bool, error) {
	entries, err := os.ReadDir(root)
	if err != nil {
		return false, err
	}
	for _, entry := range entries {
		name := entry.Name()
		if name == ".git" || strings.HasPrefix(name, ".agentteam-") {
			continue
		}
		return true, nil
	}
	return false, nil
}

func safeNativeArchivePath(value string) (string, error) {
	if strings.TrimSpace(value) != value || strings.Contains(value, "\\") {
		return "", fmt.Errorf("native memory: unsafe archive path %q", value)
	}
	cleaned := path.Clean(value)
	if value == "" || cleaned == "." || cleaned != value || strings.HasPrefix(cleaned, "/") || cleaned == ".." || strings.HasPrefix(cleaned, "../") || strings.ContainsRune(cleaned, 0) {
		return "", fmt.Errorf("native memory: unsafe archive path %q", value)
	}
	for _, segment := range strings.Split(cleaned, "/") {
		if segment == ".git" || segment == ".derived" || strings.HasPrefix(segment, ".agentteam-") {
			return "", fmt.Errorf("native memory: reserved archive path %q", value)
		}
	}
	return cleaned, nil
}

func safeNativeMemoryTarget(root, rel string) (string, error) {
	target := filepath.Clean(root)
	parts := strings.Split(filepath.ToSlash(rel), "/")
	for index, segment := range parts {
		target = filepath.Join(target, filepath.FromSlash(segment))
		info, err := os.Lstat(target)
		if os.IsNotExist(err) {
			continue
		}
		if err != nil {
			return "", err
		}
		if info.Mode()&os.ModeSymlink != 0 {
			return "", fmt.Errorf("native memory: archive target %q traverses a symlink", rel)
		}
		if index < len(parts)-1 && !info.IsDir() {
			return "", fmt.Errorf("native memory: archive target %q traverses a non-directory", rel)
		}
	}
	return target, nil
}

func safeVersionSegment(value string) string {
	normalized := strings.TrimSpace(value)
	prefix := normalized
	if len(prefix) > 16 {
		prefix = prefix[:16]
	}
	prefix = strings.Map(func(r rune) rune {
		if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '-' || r == '_' {
			return r
		}
		return '-'
	}, prefix)
	digest := sha256.Sum256([]byte(normalized))
	return prefix + "-" + hex.EncodeToString(digest[:6])
}
