package server

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/c4pt0r/agfs/agfs-server/pkg/filesystem"
	"github.com/pingcap/failpoint"

	backendpkg "github.com/mem9-ai/drive9/pkg/backend"
	"github.com/mem9-ai/drive9/pkg/datastore"
	"github.com/mem9-ai/drive9/pkg/journal"
	"github.com/mem9-ai/drive9/pkg/logger"
	"github.com/mem9-ai/drive9/pkg/pathutil"
)

const maxFSLayerBodyBytes = 1 << 20
const maxFSLayerEntryBodyBytes = 128 << 20
const fsLayerRollbackTimeout = 60 * time.Second

type fsLayerCreateRequest struct {
	LayerID        string            `json:"layer_id,omitempty"`
	BaseRootPath   string            `json:"base_root_path"`
	Name           string            `json:"name,omitempty"`
	Tags           map[string]string `json:"tags,omitempty"`
	DurabilityMode string            `json:"durability_mode,omitempty"`
	ActorID        string            `json:"actor_id,omitempty"`
}

type fsLayerResponse struct {
	LayerID            string            `json:"layer_id"`
	BaseRootPath       string            `json:"base_root_path"`
	Name               string            `json:"name"`
	Tags               map[string]string `json:"tags,omitempty"`
	State              string            `json:"state"`
	DurabilityMode     string            `json:"durability_mode"`
	ActorID            string            `json:"actor_id"`
	DurableSeq         int64             `json:"durable_seq"`
	ParentLayerID      string            `json:"parent_layer_id,omitempty"`
	OriginSeq          int64             `json:"origin_seq"`
	OriginCheckpointID string            `json:"origin_checkpoint_id,omitempty"`
	RootLayerID        string            `json:"root_layer_id,omitempty"`
	Depth              int               `json:"depth,omitempty"`
	Origin             string            `json:"origin,omitempty"`
	CreatedAt          time.Time         `json:"created_at"`
	UpdatedAt          time.Time         `json:"updated_at"`
	SealedAt           *time.Time        `json:"sealed_at,omitempty"`
}

type fsLayerForkRequest struct {
	LayerID      string `json:"layer_id,omitempty"`
	Name         string `json:"name,omitempty"`
	ActorID      string `json:"actor_id,omitempty"`
	CheckpointID string `json:"checkpoint_id,omitempty"`
}

type fsLayerEntryResponse struct {
	LayerID                string    `json:"layer_id"`
	Path                   string    `json:"path"`
	ParentPath             string    `json:"parent_path"`
	Name                   string    `json:"name"`
	Op                     string    `json:"op"`
	Kind                   string    `json:"kind"`
	BaseInodeID            string    `json:"base_inode_id"`
	BaseRevision           int64     `json:"base_revision"`
	StorageType            string    `json:"storage_type"`
	StorageRef             string    `json:"storage_ref"`
	StorageRefHash         string    `json:"storage_ref_hash"`
	StorageEncryptionMode  string    `json:"storage_encryption_mode"`
	StorageEncryptionKeyID string    `json:"storage_encryption_key_id"`
	ChecksumSHA256         string    `json:"checksum_sha256"`
	SizeBytes              int64     `json:"size_bytes"`
	Mode                   uint32    `json:"mode"`
	Content                []byte    `json:"content,omitempty"`
	ContentText            string    `json:"content_text,omitempty"`
	EntrySeq               int64     `json:"entry_seq"`
	CreatedAt              time.Time `json:"created_at"`
	UpdatedAt              time.Time `json:"updated_at"`
}

type fsLayerEntryRequest struct {
	Path                   string `json:"path"`
	Op                     string `json:"op,omitempty"`
	Kind                   string `json:"kind,omitempty"`
	BaseInodeID            string `json:"base_inode_id,omitempty"`
	BaseRevision           int64  `json:"base_revision,omitempty"`
	StorageType            string `json:"storage_type,omitempty"`
	StorageRef             string `json:"storage_ref,omitempty"`
	StorageRefHash         string `json:"storage_ref_hash,omitempty"`
	StorageEncryptionMode  string `json:"storage_encryption_mode,omitempty"`
	StorageEncryptionKeyID string `json:"storage_encryption_key_id,omitempty"`
	Content                []byte `json:"content,omitempty"`
	ContentType            string `json:"content_type,omitempty"`
	ContentText            string `json:"content_text,omitempty"`
	ChecksumSHA256         string `json:"checksum_sha256,omitempty"`
	SizeBytes              int64  `json:"size_bytes,omitempty"`
	Mode                   uint32 `json:"mode,omitempty"`
}

type fsLayerCommitResponse struct {
	Status    string                  `json:"status"`
	LayerID   string                  `json:"layer_id"`
	Applied   int                     `json:"applied,omitempty"`
	Conflicts []fsLayerCommitConflict `json:"conflicts,omitempty"`
}

type fsLayerCommitConflict struct {
	Path         string `json:"path"`
	Reason       string `json:"reason"`
	BaseRevision int64  `json:"base_revision,omitempty"`
	WantRevision int64  `json:"want_revision,omitempty"`
}

type fsLayerCheckpointRequest struct {
	CheckpointID string `json:"checkpoint_id,omitempty"`
	Label        string `json:"label,omitempty"`
}

type fsLayerCheckpointResponse struct {
	CheckpointID string    `json:"checkpoint_id"`
	LayerID      string    `json:"layer_id"`
	DurableSeq   int64     `json:"durable_seq"`
	Label        string    `json:"label"`
	CreatedAt    time.Time `json:"created_at"`
}

type fsLayerEventResponse struct {
	EventID   string    `json:"event_id"`
	LayerID   string    `json:"layer_id"`
	Seq       int64     `json:"seq"`
	ActorID   string    `json:"actor_id"`
	Op        string    `json:"op"`
	Path      string    `json:"path"`
	CreatedAt time.Time `json:"created_at"`
}

func (s *Server) handleFSLayers(w http.ResponseWriter, r *http.Request) {
	b := backendFromRequest(r)
	if b == nil {
		errJSON(w, http.StatusUnauthorized, "missing tenant scope")
		return
	}
	store := b.Store()
	switch {
	case strings.HasPrefix(r.URL.Path, "/v1/layer-checkpoints/"):
		s.handleFSLayerCheckpointObject(w, r, store)
	case r.URL.Path == "/v1/layers":
		switch r.Method {
		case http.MethodPost:
			s.handleFSLayerCreate(w, r, store)
		case http.MethodGet:
			s.handleFSLayerList(w, r, store)
		default:
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
		}
	case strings.HasPrefix(r.URL.Path, "/v1/layers/"):
		s.handleFSLayerObject(w, r, b, store)
	default:
		errJSON(w, http.StatusNotFound, "not found")
	}
}

func (s *Server) handleFSLayerCheckpointObject(w http.ResponseWriter, r *http.Request, store *datastore.Store) {
	if r.Method != http.MethodGet {
		errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	rawID := strings.TrimPrefix(r.URL.EscapedPath(), "/v1/layer-checkpoints/")
	if rawID == "" {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	checkpointID, err := url.PathUnescape(rawID)
	if err != nil || strings.TrimSpace(checkpointID) == "" {
		errJSON(w, http.StatusBadRequest, "invalid checkpoint id")
		return
	}
	checkpoint, err := store.GetFSLayerCheckpoint(r.Context(), checkpointID)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	layer, err := store.GetFSLayer(r.Context(), checkpoint.LayerID)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	if !authorizeFS(w, r, FSOpRead, layer.BaseRootPath) {
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(toFSLayerCheckpointResponse(checkpoint))
}

func (s *Server) handleFSLayerCreate(w http.ResponseWriter, r *http.Request, store *datastore.Store) {
	defer func() { _ = r.Body.Close() }()
	var req fsLayerCreateRequest
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerBodyBytes)).Decode(&req); err != nil {
		errJSON(w, http.StatusBadRequest, "malformed JSON")
		return
	}
	layerID := strings.TrimSpace(req.LayerID)
	if layerID == "" {
		layerID = journal.NewID("layer")
	}
	actorID := strings.TrimSpace(req.ActorID)
	if actorID == "" {
		actorID = strings.TrimSpace(r.Header.Get("X-Dat9-Actor"))
	}
	baseRoot, err := pathutil.CanonicalizeDir(req.BaseRootPath)
	if err != nil {
		metricEvent(r.Context(), "fs_invalid_path")
		errJSON(w, http.StatusBadRequest, fmt.Sprintf("invalid fs layer base root: %v", err))
		return
	}
	if !authorizeFS(w, r, FSOpWrite, baseRoot) {
		return
	}
	layer := datastore.FSLayer{
		LayerID:        layerID,
		BaseRootPath:   baseRoot,
		Name:           req.Name,
		Tags:           req.Tags,
		DurabilityMode: datastore.FSLayerDurabilityMode(strings.TrimSpace(req.DurabilityMode)),
		ActorID:        actorID,
	}
	if err := store.CreateFSLayer(r.Context(), &layer); err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	created, err := store.GetFSLayer(r.Context(), layerID)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(toFSLayerResponse(created))
}

func (s *Server) handleFSLayerList(w http.ResponseWriter, r *http.Request, store *datastore.Store) {
	layers, err := store.ListFSLayers(r.Context())
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	out := make([]fsLayerResponse, 0, len(layers))
	scope := ScopeFromContext(r.Context())
	if scope == nil {
		errJSON(w, http.StatusUnauthorized, "missing tenant scope")
		return
	}
	for i := range layers {
		if !fsLayerVisibleToScope(scope, &layers[i]) {
			continue
		}
		out = append(out, toFSLayerResponse(&layers[i]))
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]any{"layers": out})
}

func fsLayerVisibleToScope(scope *TenantScope, layer *datastore.FSLayer) bool {
	if scope == nil || layer == nil {
		return false
	}
	if !scope.IsScoped {
		return true
	}
	return scope.AuthorizeFS(FSOpRead, layer.BaseRootPath) == nil || scope.AuthorizeFS(FSOpWrite, layer.BaseRootPath) == nil
}

func authorizeFSLayerEntryMutation(w http.ResponseWriter, r *http.Request, entry *datastore.FSLayerEntry) bool {
	if entry == nil {
		errJSON(w, http.StatusBadRequest, "fs layer entry is required")
		return false
	}
	switch entry.Op {
	case datastore.FSLayerEntryOpWhiteout:
		return authorizeFS(w, r, FSOpDelete, entry.Path)
	case datastore.FSLayerEntryOpRename:
		target := fsLayerRenameTarget(entry)
		return authorizeFSPair(w, r, FSOpDelete, entry.Path, FSOpWrite, target)
	default:
		return authorizeFS(w, r, FSOpWrite, entry.Path)
	}
}

func fsLayerRenameTarget(entry *datastore.FSLayerEntry) string {
	if entry == nil {
		return ""
	}
	if entry.ContentText != "" {
		return entry.ContentText
	}
	return string(entry.ContentBlob)
}

func (s *Server) handleFSLayerObject(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, store *datastore.Store) {
	rest := strings.TrimPrefix(r.URL.EscapedPath(), "/v1/layers/")
	rawID, sub, hasSub := strings.Cut(rest, "/")
	if rawID == "" {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	layerRef, err := url.PathUnescape(rawID)
	if err != nil || layerRef == "" {
		errJSON(w, http.StatusBadRequest, "invalid layer ref")
		return
	}
	layer, err := store.ResolveFSLayerRef(r.Context(), layerRef)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	layerID := layer.LayerID
	switch {
	case !hasSub:
		switch r.Method {
		case http.MethodGet:
			if !authorizeFS(w, r, FSOpRead, layer.BaseRootPath) {
				return
			}
			w.Header().Set("Content-Type", "application/json")
			_ = json.NewEncoder(w).Encode(toFSLayerResponse(layer))
		case http.MethodDelete:
			if !authorizeFS(w, r, FSOpWrite, layer.BaseRootPath) {
				return
			}
			cascade := strings.EqualFold(r.URL.Query().Get("cascade"), "true") || r.URL.Query().Get("cascade") == "1"
			if err := store.DeleteFSLayer(r.Context(), layerID, datastore.DeleteFSLayerOptions{Cascade: cascade}); err != nil {
				writeFSLayerStoreError(w, r, err)
				return
			}
			w.Header().Set("Content-Type", "application/json")
			_ = json.NewEncoder(w).Encode(map[string]any{"status": "abandoned", "layer_id": layerID})
		default:
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
		}
	case sub == "fork":
		if r.Method != http.MethodPost {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpWrite, layer.BaseRootPath) {
			return
		}
		s.handleFSLayerFork(w, r, store, layer)
	case sub == "chain":
		if r.Method != http.MethodGet {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpRead, layer.BaseRootPath) {
			return
		}
		s.handleFSLayerChain(w, r, store, layer)
	case sub == "diff":
		if r.Method != http.MethodGet {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpRead, layer.BaseRootPath) {
			return
		}
		maxSeq, hasMaxSeq, err := fsLayerMaxSeqFromRequest(r)
		if err != nil {
			errJSON(w, http.StatusBadRequest, err.Error())
			return
		}
		var entries []datastore.FSLayerEntry
		replayLog := fsLayerReplayLogFromRequest(r)
		// Chain-aware: child layers merge parent@origin_seq (D8). max_seq
		// only caps the tip layer; ancestors stay pin-limited.
		if layer.HasParent() {
			if replayLog && hasMaxSeq {
				entries, err = store.ListFSLayerChainEffectiveLogAtSeq(r.Context(), layerID, maxSeq)
			} else if replayLog {
				entries, err = store.ListFSLayerChainEffectiveLog(r.Context(), layerID)
			} else if hasMaxSeq {
				entries, err = store.ListFSLayerChainMergedEntriesAtSeq(r.Context(), layerID, maxSeq)
			} else {
				entries, err = store.ListFSLayerChainMergedEntries(r.Context(), layerID)
			}
		} else if replayLog && hasMaxSeq {
			entries, err = store.ListFSLayerEntryLogAtSeq(r.Context(), layerID, maxSeq)
		} else if replayLog {
			entries, err = store.ListFSLayerEntryLog(r.Context(), layerID)
		} else if hasMaxSeq {
			entries, err = store.ListFSLayerEntriesAtSeq(r.Context(), layerID, maxSeq)
		} else {
			entries, err = store.ListFSLayerEntries(r.Context(), layerID)
		}
		if err != nil {
			writeFSLayerStoreError(w, r, err)
			return
		}
		out := make([]fsLayerEntryResponse, 0, len(entries))
		for i := range entries {
			out = append(out, toFSLayerEntryResponse(&entries[i], false))
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{"entries": out})
	case sub == "checkpoints":
		if r.Method != http.MethodPost {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpWrite, layer.BaseRootPath) {
			return
		}
		s.handleFSLayerCheckpoint(w, r, store, layerID)
	case sub == "uploads/initiate" || strings.HasPrefix(sub, "uploads/"):
		s.handleFSLayerUploads(w, r, b, layer, sub)
	case sub == "entries":
		switch r.Method {
		case http.MethodPost, http.MethodPut:
			if !authorizeFS(w, r, FSOpWrite, layer.BaseRootPath) {
				return
			}
			s.handleFSLayerEntryUpsert(w, r, b, store, layer)
		case http.MethodGet:
			s.handleFSLayerEntryGet(w, r, store, layer)
		default:
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
		}
	case sub == "objects":
		if r.Method == http.MethodGet {
			s.handleFSLayerObjectRead(w, r, b, store, layer)
			return
		}
		if r.Method != http.MethodPost && r.Method != http.MethodPut {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpWrite, layer.BaseRootPath) {
			return
		}
		s.handleFSLayerObjectUpload(w, r, b, store, layer)
	case sub == "events":
		if r.Method != http.MethodGet {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpRead, layer.BaseRootPath) {
			return
		}
		s.handleFSLayerEvents(w, r, store, layer)
	case sub == "rollback":
		if r.Method != http.MethodPost {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpWrite, layer.BaseRootPath) {
			return
		}
		if err := store.RollbackFSLayer(r.Context(), layerID); err != nil {
			writeFSLayerStoreError(w, r, err)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(map[string]any{"status": "ok"})
	case sub == "commit":
		if r.Method != http.MethodPost {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		if !authorizeFS(w, r, FSOpWrite, layer.BaseRootPath) {
			return
		}
		s.handleFSLayerCommit(w, r, b, store, layer)
	default:
		errJSON(w, http.StatusNotFound, "not found")
	}
}

func fsLayerPathQuery(r *http.Request) string {
	return r.URL.Query().Get("path")
}

func (s *Server) handleFSLayerObjectRead(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, store *datastore.Store, layer *datastore.FSLayer) {
	rawPath := fsLayerPathQuery(r)
	if rawPath == "" {
		errJSON(w, http.StatusBadRequest, "path is required")
		return
	}
	path, err := canonicalFSLayerServerPath(rawPath, "", string(datastore.FSLayerEntryKindFile))
	if err != nil {
		if errors.Is(err, pathutil.ErrInvalidPath) {
			metricEvent(r.Context(), "fs_invalid_path")
		}
		errJSON(w, http.StatusBadRequest, fmt.Sprintf("invalid fs layer object path: %v", err))
		return
	}
	if !fsLayerPathWithinBase(path, layer.BaseRootPath) {
		errJSON(w, http.StatusBadRequest, "fs layer object path is outside base root")
		return
	}
	if !authorizeFS(w, r, FSOpRead, path) {
		return
	}
	maxSeq, hasMaxSeq, err := fsLayerMaxSeqFromRequest(r)
	if err != nil {
		errJSON(w, http.StatusBadRequest, err.Error())
		return
	}
	var tipMax *int64
	if hasMaxSeq {
		tipMax = &maxSeq
	}
	if !layer.HasParent() {
		hasDrafts, draftErr := store.FSLayerHasOverlayDrafts(r.Context(), layer.LayerID, tipMax)
		if draftErr != nil {
			writeFSLayerStoreError(w, r, draftErr)
			return
		}
		if !hasDrafts {
			serveFSLayerObjectPointLookup(w, r, b, store, layer.LayerID, path, tipMax)
			return
		}
	}
	tree, err := store.MaterializeFSLayerOverlay(r.Context(), layer.LayerID, tipMax)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	if n, ok := datastore.OverlayLookup(tree, path); ok {
		if n.Whiteout {
			errJSON(w, http.StatusNotFound, "not found")
			return
		}
		if n.HasBody && datastore.OverlayViewHidden(tree, path) {
			errJSON(w, http.StatusNotFound, "not found")
			return
		}
		if n.HasBody && n.Kind == datastore.FSLayerEntryKindFile {
			entry := datastore.OverlayNodeEntry(n)
			writeFSLayerEntryObject(w, r, b, &entry)
			return
		}
		if n.HasBody {
			errJSON(w, http.StatusBadRequest, "fs layer object path is not a file upsert")
			return
		}
		hidden, primary, candErr := fsLayerObjectMainCandidates(r.Context(), store, layer, tree, path, n, true)
		if candErr != nil {
			writeFSLayerStoreError(w, r, candErr)
			return
		}
		if hidden {
			errJSON(w, http.StatusNotFound, "not found")
			return
		}
		writeFSLayerAuthorizedMainObject(w, r, b, path, primary)
		return
	}
	hidden, primary, candErr := fsLayerObjectMainCandidates(r.Context(), store, layer, tree, path, datastore.FSLayerOverlayNode{}, false)
	if candErr != nil {
		writeFSLayerStoreError(w, r, candErr)
		return
	}
	if hidden {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	writeFSLayerAuthorizedMainObject(w, r, b, path, primary)
}

func serveFSLayerObjectPointLookup(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, store *datastore.Store, layerID, path string, tipMax *int64) {
	var (
		entry *datastore.FSLayerEntry
		err   error
	)
	if tipMax != nil {
		entry, err = store.GetFSLayerEntryAtSeq(r.Context(), layerID, path, *tipMax)
	} else {
		entry, err = store.GetFSLayerEntry(r.Context(), layerID, path)
	}
	if err != nil && !errors.Is(err, datastore.ErrNotFound) {
		writeFSLayerStoreError(w, r, err)
		return
	}
	if errors.Is(err, datastore.ErrNotFound) || entry == nil {
		writeFSLayerMainObject(w, r, b, path)
		return
	}
	if entry.Op == datastore.FSLayerEntryOpWhiteout {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	if entry.Op == datastore.FSLayerEntryOpUpsert && entry.Kind == datastore.FSLayerEntryKindFile {
		writeFSLayerEntryObject(w, r, b, entry)
		return
	}
	if entry.Op == datastore.FSLayerEntryOpUpsert || entry.Op == datastore.FSLayerEntryOpMkdir || entry.Op == datastore.FSLayerEntryOpSymlink {
		errJSON(w, http.StatusBadRequest, "fs layer object path is not a file upsert")
		return
	}
	writeFSLayerMainObject(w, r, b, path)
}

func writeFSLayerEntryObject(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, entry *datastore.FSLayerEntry) {
	if entry.StorageType == string(datastore.StorageS3) {
		presignedURL, err := b.PresignFSLayerEntryData(r.Context(), entry)
		if err != nil {
			logger.Error(r.Context(), "fs_layer_object_presign_failed", eventFields(r.Context(), "fs_layer_object_presign_failed", "path", entry.Path, "error", err)...)
			writeBackendError(w, r, err)
			return
		}
		recordTenantFileUsage(r.Context(), "read", "s3", entry.SizeBytes, 1)
		http.Redirect(w, r, presignedURL, http.StatusTemporaryRedirect)
		return
	}
	rc, err := b.OpenFSLayerEntryData(r.Context(), entry)
	if err != nil {
		logger.Error(r.Context(), "fs_layer_object_read_failed", eventFields(r.Context(), "fs_layer_object_read_failed", "path", entry.Path, "error", err)...)
		writeBackendError(w, r, err)
		return
	}
	defer func() { _ = rc.Close() }()
	w.Header().Set("Content-Type", "application/octet-stream")
	if entry.SizeBytes > 0 || (entry.SizeBytes == 0 && len(entry.ContentBlob) == 0) {
		w.Header().Set("Content-Length", strconv.FormatInt(entry.SizeBytes, 10))
	}
	readBytes, err := io.Copy(w, rc)
	// Bill the op only when the copy completes; transferred bytes are recorded
	// either way (legacy parity with partial reads).
	recordTenantFileUsage(r.Context(), "read", "db9", readBytes, fileOpsForErr(err))
	if err != nil {
		logger.Error(r.Context(), "fs_layer_object_copy_failed", eventFields(r.Context(), "fs_layer_object_copy_failed", "path", entry.Path, "error", err)...)
	}
}

func fsLayerSameRootLayers(ctx context.Context, store *datastore.Store, layer *datastore.FSLayer) (map[string]*datastore.FSLayer, error) {
	out := make(map[string]*datastore.FSLayer)
	if store == nil || layer == nil {
		return out, nil
	}
	root := layer.RootLayerID
	if root == "" {
		root = layer.LayerID
	}
	layers, err := store.ListFSLayersByRootMeta(ctx, root)
	if err != nil {
		return nil, err
	}
	for i := range layers {
		out[layers[i].LayerID] = &layers[i]
	}
	return out, nil
}

func fsLayerHopLandedFn(layers map[string]*datastore.FSLayer) func(datastore.FSLayerRenameHop) bool {
	if layers == nil {
		return nil
	}
	return func(hop datastore.FSLayerRenameHop) bool {
		return fsLayerHopCoveredByLineage(hop, layers)
	}
}

func fsLayerApplied(l *datastore.FSLayer) bool {
	return l != nil && (l.State == datastore.FSLayerStateCommitted || l.CommitApplied)
}

// fsLayerHopCoveredByLineage reports whether destHop has already been applied
// to live main: the recording layer itself applied, or some applied
// descendant was pinned through that recording layer at origin_seq >= hop.EntrySeq.
// OriginSeq is only compared in the parent it pins; unrelated same-root
// siblings are not a cover. Applied means state==committed or commit_applied
// (survives D17 abandon).
func fsLayerHopCoveredByLineage(hop datastore.FSLayerRenameHop, layers map[string]*datastore.FSLayer) bool {
	if hop.LayerID == "" || layers == nil {
		return false
	}
	if rec, ok := layers[hop.LayerID]; ok && fsLayerApplied(rec) {
		return true
	}
	if hop.EntrySeq <= 0 {
		return false
	}
	for _, cand := range layers {
		if !fsLayerApplied(cand) {
			continue
		}
		if fsLayerLineagePinsHop(cand, hop, layers) {
			return true
		}
	}
	return false
}

func fsLayerLineagePinsHop(committed *datastore.FSLayer, hop datastore.FSLayerRenameHop, layers map[string]*datastore.FSLayer) bool {
	cur := committed
	for i := 0; i < datastore.FSLayerHardMaxDepth+2 && cur != nil && cur.ParentLayerID != ""; i++ {
		if cur.ParentLayerID == hop.LayerID {
			return cur.OriginSeq >= hop.EntrySeq
		}
		parent, ok := layers[cur.ParentLayerID]
		if !ok {
			return false
		}
		cur = parent
	}
	return false
}

func fsLayerObjectMainCandidates(ctx context.Context, store *datastore.Store, layer *datastore.FSLayer, tree map[string]datastore.FSLayerOverlayNode, path string, n datastore.FSLayerOverlayNode, hasNode bool) (hidden bool, primary string, err error) {
	layers, err := fsLayerSameRootLayers(ctx, store, layer)
	if err != nil {
		return false, "", err
	}
	landed := fsLayerHopLandedFn(layers)
	var remapped string
	if hasNode {
		hidden, remapped = datastore.OverlayMainReadPathIf(tree, path, n, landed)
	} else {
		hidden, remapped = datastore.OverlayHideOrRemapIf(tree, path, landed)
	}
	if hidden {
		return true, "", nil
	}
	if remapped == "" {
		remapped = path
	}
	return false, remapped, nil
}

func writeFSLayerAuthorizedMainObject(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, requestPath, mainPath string) {
	if mainPath != "" && mainPath != requestPath && !authorizeFS(w, r, FSOpRead, mainPath) {
		return
	}
	writeFSLayerMainObject(w, r, b, mainPath)
}

func writeFSLayerMainObject(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, path string) {
	nf, err := b.StatNodeCtx(r.Context(), path)
	if errors.Is(err, datastore.ErrNotFound) {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	if err != nil {
		writeBackendError(w, r, err)
		return
	}
	if nf.Node.IsDirectory || nf.File == nil {
		errJSON(w, http.StatusBadRequest, "fs layer object path is not a file")
		return
	}
	if nf.File.ContentLayout == datastore.ContentLayoutAppendLog {
		errJSONCode(w, http.StatusBadRequest, "append_log_unsupported", "fs layer operations are unsupported for append-log files")
		return
	}
	rc, size, err := b.OpenFileDataCtx(r.Context(), path)
	if errors.Is(err, datastore.ErrNotFound) {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	if err != nil {
		writeBackendError(w, r, err)
		return
	}
	defer func() { _ = rc.Close() }()
	w.Header().Set("Content-Type", "application/octet-stream")
	if size >= 0 {
		w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
	}
	readBytes, err := io.Copy(w, rc)
	// Bill the op only when the copy completes; transferred bytes are recorded
	// either way (legacy parity with partial reads).
	recordTenantFileUsage(r.Context(), "read", "db9", readBytes, fileOpsForErr(err))
	if err != nil {
		logger.Error(r.Context(), "fs_layer_object_copy_failed", eventFields(r.Context(), "fs_layer_object_copy_failed", "path", path, "error", err)...)
	}
}

func (s *Server) handleFSLayerObjectUpload(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, store *datastore.Store, layer *datastore.FSLayer) {
	if layer.State != datastore.FSLayerStateActive {
		errJSON(w, http.StatusConflict, "fs layer is not active")
		return
	}
	defer func() { _ = r.Body.Close() }()
	rawPath := fsLayerPathQuery(r)
	if rawPath == "" {
		errJSON(w, http.StatusBadRequest, "path is required")
		return
	}
	size := r.ContentLength
	if rawSize := strings.TrimSpace(r.URL.Query().Get("size")); rawSize != "" {
		parsed, err := strconv.ParseInt(rawSize, 10, 64)
		if err != nil || parsed < 0 {
			errJSON(w, http.StatusBadRequest, "invalid size")
			return
		}
		size = parsed
	}
	if size < 0 {
		errJSON(w, http.StatusBadRequest, "content length is required")
		return
	}
	if size > s.maxUploadBytes {
		errJSON(w, http.StatusRequestEntityTooLarge, fmt.Sprintf("upload too large: max %d bytes", s.maxUploadBytes))
		return
	}
	prepared := datastore.FSLayerEntry{
		LayerID:      layer.LayerID,
		Path:         rawPath,
		Op:           datastore.FSLayerEntryOpUpsert,
		Kind:         datastore.FSLayerEntryKindFile,
		BaseRevision: parseFSLayerInt64Query(r, "base_revision"),
	}
	if mode, ok := parseFSLayerModeQuery(r, "mode"); ok {
		prepared.Mode = mode
	}
	if prepared.Mode == 0 {
		prepared.Mode = 0o644
	}
	if err := normalizeAndValidateFSLayerServerEntry(r.Context(), layer, &prepared); err != nil {
		errJSON(w, http.StatusBadRequest, err.Error())
		return
	}
	if !authorizeFSLayerEntryMutation(w, r, &prepared) {
		return
	}
	if err := fillFSLayerBaseSnapshot(r.Context(), b, store, layer, &prepared, nil); err != nil {
		if errors.Is(err, backendpkg.ErrAppendLogUnsupported) {
			errJSONCode(w, http.StatusBadRequest, "append_log_unsupported", "fs layer operations are unsupported for append-log files")
			return
		}
		writeFSLayerStoreError(w, r, err)
		return
	}
	body := http.MaxBytesReader(w, r.Body, s.maxUploadBytes)
	publishCtx, cancelPublish := context.WithTimeout(r.Context(), backendpkg.FailedWriteObjectGCPublishTimeout)
	defer cancelPublish()
	entry, err := b.PutFSLayerObject(publishCtx, layer.LayerID, prepared.Path, body, size)
	if err != nil {
		logger.Error(r.Context(), "fs_layer_object_upload_failed", eventFields(r.Context(), "fs_layer_object_upload_failed", "path", prepared.Path, "error", err)...)
		writeBackendError(w, r, err)
		return
	}
	entry.LayerID = layer.LayerID
	entry.Path = prepared.Path
	entry.Op = prepared.Op
	entry.Kind = prepared.Kind
	entry.Mode = prepared.Mode
	entry.BaseRevision = prepared.BaseRevision
	entry.BaseInodeID = prepared.BaseInodeID
	if err := store.UpsertFSLayerEntry(publishCtx, entry); err != nil {
		if errors.Is(err, datastore.ErrCommitOutcomeUnknown) {
			resolved, reconcileErr := reconcileFSLayerEntryCommit(r.Context(), store, entry)
			if reconcileErr != nil {
				writeFSLayerStoreError(w, r, err)
				return
			}
			entry = resolved
		} else {
			b.CleanupFailedWriteObjectGC(r.Context(), entry.StorageRef)
			writeFSLayerStoreError(w, r, err)
			return
		}
	}
	b.CompleteFailedWriteObjectGC(r.Context(), entry.StorageRef)
	if err := b.RecordFSLayerStorage(r.Context(), entry); err != nil {
		writeBackendError(w, r, err)
		return
	}
	stored, err := store.GetFSLayerEntry(r.Context(), layer.LayerID, entry.Path)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	recordTenantFileUsage(r.Context(), "write", "db9", entry.SizeBytes, 1)
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(toFSLayerEntryResponse(stored, false))
}

func (s *Server) handleFSLayerEvents(w http.ResponseWriter, r *http.Request, store *datastore.Store, layer *datastore.FSLayer) {
	since := parseFSLayerInt64Query(r, "since")
	events, err := store.ListFSLayerEvents(r.Context(), layer.LayerID, since, 1000)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]any{"events": toFSLayerEventResponses(events)})
}

func (s *Server) handleFSLayerEntryUpsert(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, store *datastore.Store, layer *datastore.FSLayer) {
	if layer.State != datastore.FSLayerStateActive {
		errJSON(w, http.StatusConflict, "fs layer is not active")
		return
	}
	defer func() { _ = r.Body.Close() }()
	var req fsLayerEntryRequest
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerEntryBodyBytes)).Decode(&req); err != nil {
		errJSON(w, http.StatusBadRequest, "malformed JSON")
		return
	}
	entry := datastore.FSLayerEntry{
		LayerID:                layer.LayerID,
		Path:                   req.Path,
		Op:                     datastore.FSLayerEntryOp(strings.TrimSpace(req.Op)),
		Kind:                   datastore.FSLayerEntryKind(strings.TrimSpace(req.Kind)),
		BaseInodeID:            strings.TrimSpace(req.BaseInodeID),
		BaseRevision:           req.BaseRevision,
		StorageType:            strings.TrimSpace(req.StorageType),
		StorageRef:             strings.TrimSpace(req.StorageRef),
		StorageRefHash:         strings.TrimSpace(req.StorageRefHash),
		StorageEncryptionMode:  datastore.StorageEncryptionMode(strings.TrimSpace(req.StorageEncryptionMode)),
		StorageEncryptionKeyID: strings.TrimSpace(req.StorageEncryptionKeyID),
		ContentBlob:            req.Content,
		ContentType:            strings.TrimSpace(req.ContentType),
		ContentText:            req.ContentText,
		ChecksumSHA256:         strings.TrimSpace(req.ChecksumSHA256),
		SizeBytes:              req.SizeBytes,
		Mode:                   req.Mode,
	}
	if err := normalizeAndValidateFSLayerServerEntry(r.Context(), layer, &entry); err != nil {
		errJSON(w, http.StatusBadRequest, err.Error())
		return
	}
	if err := normalizeFSLayerInlineStorage(&entry); err != nil {
		errJSON(w, http.StatusBadRequest, err.Error())
		return
	}
	if !authorizeFSLayerEntryMutation(w, r, &entry) {
		return
	}
	var overlay map[string]datastore.FSLayerOverlayNode
	if fsLayerEntryStampsDirBody(&entry) {
		var treeErr error
		overlay, treeErr = fsLayerOverlayTreeForWrite(r.Context(), store, layer)
		if treeErr != nil {
			writeFSLayerStoreError(w, r, treeErr)
			return
		}
		exists, existsErr := fsLayerMkdirConflictsWithVisibleMain(r.Context(), b, store, layer, entry.Path, overlay)
		if existsErr != nil {
			writeBackendError(w, r, existsErr)
			return
		}
		if exists {
			errJSON(w, http.StatusConflict, "fs layer mkdir path already exists")
			return
		}
	}
	if len(entry.ContentBlob) > 0 {
		if entry.ChecksumSHA256 == "" {
			sum := sha256.Sum256(entry.ContentBlob)
			entry.ChecksumSHA256 = hex.EncodeToString(sum[:])
		}
		if entry.ContentType == "" {
			entry.ContentType = http.DetectContentType(entry.ContentBlob)
		}
	}
	if err := fillFSLayerBaseSnapshot(r.Context(), b, store, layer, &entry, overlay); err != nil {
		if errors.Is(err, backendpkg.ErrAppendLogUnsupported) {
			errJSONCode(w, http.StatusBadRequest, "append_log_unsupported", "fs layer operations are unsupported for append-log files")
			return
		}
		writeFSLayerStoreError(w, r, err)
		return
	}
	if err := store.UpsertFSLayerEntry(r.Context(), &entry); err != nil {
		if errors.Is(err, datastore.ErrCommitOutcomeUnknown) {
			resolved, reconcileErr := reconcileFSLayerEntryCommit(r.Context(), store, &entry)
			if reconcileErr != nil {
				writeFSLayerStoreError(w, r, err)
				return
			}
			entry = *resolved
		} else {
			writeFSLayerStoreError(w, r, err)
			return
		}
	}
	if err := b.RecordFSLayerStorage(r.Context(), &entry); err != nil {
		writeBackendError(w, r, err)
		return
	}
	stored, err := store.GetFSLayerEntry(r.Context(), layer.LayerID, entry.Path)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	if entry.Op == datastore.FSLayerEntryOpUpsert && entry.Kind == datastore.FSLayerEntryKindFile {
		recordTenantFileUsage(r.Context(), "write", "db9", int64(len(entry.ContentBlob)), 1)
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(toFSLayerEntryResponse(stored, true))
}

func reconcileFSLayerEntryCommit(ctx context.Context, store *datastore.Store, entry *datastore.FSLayerEntry) (*datastore.FSLayerEntry, error) {
	if entry == nil || entry.EntrySeq <= 0 {
		return nil, fmt.Errorf("committed fs layer entry identity is incomplete")
	}
	resolved, err := store.GetFSLayerEntryAtSeq(ctx, entry.LayerID, entry.Path, entry.EntrySeq)
	if err != nil {
		return nil, err
	}
	if resolved.Op != entry.Op || resolved.Kind != entry.Kind || resolved.StorageRef != entry.StorageRef || resolved.SizeBytes != entry.SizeBytes {
		return nil, fmt.Errorf("committed fs layer entry metadata mismatch")
	}
	return resolved, nil
}

// normalizeFSLayerInlineStorage makes the entry body, rather than client
// metadata, authoritative for inline file storage. Object-backed layer files
// must use the object upload endpoint, which generates and verifies these
// fields server-side.
func normalizeFSLayerInlineStorage(entry *datastore.FSLayerEntry) error {
	if entry == nil || (entry.Op != "" && entry.Op != datastore.FSLayerEntryOpUpsert) ||
		(entry.Kind != "" && entry.Kind != datastore.FSLayerEntryKindFile) {
		return nil
	}
	if entry.StorageRef != "" && len(entry.ContentBlob) == 0 {
		return fmt.Errorf("client-supplied fs layer object metadata is not supported; use the object upload endpoint")
	}
	entry.StorageType = string(datastore.StorageDB9)
	entry.StorageRef = ""
	entry.StorageRefHash = ""
	entry.StorageEncryptionMode = ""
	entry.StorageEncryptionKeyID = ""
	entry.SizeBytes = int64(len(entry.ContentBlob))
	return nil
}

func parseFSLayerInt64Query(r *http.Request, key string) int64 {
	raw := strings.TrimSpace(r.URL.Query().Get(key))
	if raw == "" {
		return 0
	}
	n, err := strconv.ParseInt(raw, 10, 64)
	if err != nil {
		return 0
	}
	return n
}

func parseFSLayerModeQuery(r *http.Request, key string) (uint32, bool) {
	raw := strings.TrimSpace(r.URL.Query().Get(key))
	if raw == "" {
		return 0, false
	}
	n, err := strconv.ParseUint(raw, 8, 32)
	if err != nil {
		n, err = strconv.ParseUint(raw, 10, 32)
	}
	if err != nil {
		return 0, false
	}
	return uint32(n) & 0o777, true
}

func (s *Server) handleFSLayerEntryGet(w http.ResponseWriter, r *http.Request, store *datastore.Store, layer *datastore.FSLayer) {
	path := fsLayerPathQuery(r)
	if path == "" {
		errJSON(w, http.StatusBadRequest, "path is required")
		return
	}
	path, err := canonicalFSLayerServerPath(path, "", "")
	if err != nil {
		if errors.Is(err, pathutil.ErrInvalidPath) {
			metricEvent(r.Context(), "fs_invalid_path")
		}
		errJSON(w, http.StatusBadRequest, fmt.Sprintf("invalid fs layer entry path: %v", err))
		return
	}
	if !fsLayerPathWithinBase(path, layer.BaseRootPath) {
		errJSON(w, http.StatusBadRequest, "fs layer entry path is outside base root")
		return
	}
	if !authorizeFS(w, r, FSOpRead, path) {
		return
	}
	maxSeq, hasMaxSeq, err := fsLayerMaxSeqFromRequest(r)
	if err != nil {
		errJSON(w, http.StatusBadRequest, err.Error())
		return
	}
	var (
		entry *datastore.FSLayerEntry
		tree  map[string]datastore.FSLayerOverlayNode
	)
	if layer.HasParent() {
		var tipMax *int64
		if hasMaxSeq {
			tipMax = &maxSeq
		}
		tree, err = store.MaterializeFSLayerOverlay(r.Context(), layer.LayerID, tipMax)
		if err != nil {
			writeFSLayerStoreError(w, r, err)
			return
		}
		if n, ok := datastore.OverlayLookup(tree, path); ok {
			resolved := datastore.OverlayNodeEntry(n)
			entry = &resolved
		} else {
			layers, layerErr := fsLayerSameRootLayers(r.Context(), store, layer)
			if layerErr != nil {
				writeFSLayerStoreError(w, r, layerErr)
				return
			}
			hidden, remapped := datastore.OverlayHideOrRemapIf(tree, path, fsLayerHopLandedFn(layers))
			if hidden {
				entry = &datastore.FSLayerEntry{Path: path, Op: datastore.FSLayerEntryOpWhiteout}
			} else if remapped != "" && remapped != path {
				entry = &datastore.FSLayerEntry{
					Path:        remapped,
					Op:          datastore.FSLayerEntryOpRename,
					Kind:        datastore.FSLayerEntryKindFile,
					ContentText: path,
				}
			} else {
				errJSON(w, http.StatusNotFound, "not found")
				return
			}
		}
	} else if hasMaxSeq {
		entry, err = store.GetFSLayerEntryAtSeq(r.Context(), layer.LayerID, path, maxSeq)
		if err != nil {
			writeFSLayerStoreError(w, r, err)
			return
		}
	} else {
		entry, err = store.GetFSLayerEntry(r.Context(), layer.LayerID, path)
		if err != nil {
			writeFSLayerStoreError(w, r, err)
			return
		}
	}
	if !authorizeOrRedactFSLayerEntryRead(w, r, store, layer, path, entry, tree) {
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(toFSLayerEntryResponse(entry, true))
}

func (s *Server) handleFSLayerFork(w http.ResponseWriter, r *http.Request, store *datastore.Store, parent *datastore.FSLayer) {
	defer func() { _ = r.Body.Close() }()
	var req fsLayerForkRequest
	if r.Body != nil {
		if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerBodyBytes)).Decode(&req); err != nil && !errors.Is(err, io.EOF) {
			errJSON(w, http.StatusBadRequest, "malformed JSON")
			return
		}
	}
	childID := strings.TrimSpace(req.LayerID)
	if childID == "" {
		childID = journal.NewID("layer")
	}
	actorID := strings.TrimSpace(req.ActorID)
	if actorID == "" {
		actorID = strings.TrimSpace(r.Header.Get("X-Dat9-Actor"))
	}
	child, err := store.ForkFSLayer(r.Context(), datastore.FSLayerForkOptions{
		ChildLayerID:  childID,
		ParentLayerID: parent.LayerID,
		Name:          req.Name,
		ActorID:       actorID,
		CheckpointID:  req.CheckpointID,
	})
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	_ = json.NewEncoder(w).Encode(toFSLayerResponse(child))
}

func (s *Server) handleFSLayerChain(w http.ResponseWriter, r *http.Request, store *datastore.Store, layer *datastore.FSLayer) {
	frames, err := store.ListFSLayerChain(r.Context(), layer.LayerID)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	out := make([]map[string]any, 0, len(frames))
	for _, f := range frames {
		item := map[string]any{
			"layer_id":             f.Layer.LayerID,
			"name":                 f.Layer.Name,
			"state":                string(f.Layer.State),
			"parent_layer_id":      f.Layer.ParentLayerID,
			"origin_seq":           f.Layer.OriginSeq,
			"origin_checkpoint_id": f.Layer.OriginCheckpointID,
			"depth":                f.Layer.Depth,
			"root_layer_id":        f.Layer.RootLayerID,
			"base_root_path":       f.Layer.BaseRootPath,
			"created_at":           f.Layer.CreatedAt,
			"limit_seq":            f.LimitSeq,
		}
		out = append(out, item)
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]any{"chain": out})
}

func (s *Server) handleFSLayerCommit(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, store *datastore.Store, layer *datastore.FSLayer) {
	if layer.State == datastore.FSLayerStateCommitted {
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(fsLayerCommitResponse{
			Status:  "committed",
			LayerID: layer.LayerID,
		})
		return
	}
	if layer.State != datastore.FSLayerStateActive && layer.State != datastore.FSLayerStateSealed {
		errJSON(w, http.StatusConflict, "fs layer is not active or sealed")
		return
	}
	if err := store.BeginFSLayerCommit(r.Context(), layer.LayerID); err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	// Any layer: flatten overlay then diff vs live main (D18/D19).
	// After a child has already applied the overlapping view, the parent
	// apply set is empty instead of replaying the raw log into a conflict.
	var (
		entries []datastore.FSLayerEntry
		err     error
	)
	var overlay map[string]datastore.FSLayerOverlayNode
	overlay, err = store.MaterializeFSLayerOverlay(r.Context(), layer.LayerID, nil)
	if err == nil {
		var layers map[string]*datastore.FSLayer
		layers, err = fsLayerSameRootLayers(r.Context(), store, layer)
		if err == nil {
			entries, err = planFSLayerCommitVsMain(r.Context(), b, overlay, fsLayerHopLandedFn(layers))
		}
	}
	if err != nil {
		_ = store.SetFSLayerStateIf(r.Context(), layer.LayerID, []datastore.FSLayerState{datastore.FSLayerStateCommitting}, datastore.FSLayerStateConflicted)
		writeFSLayerStoreError(w, r, err)
		return
	}
	if conflicts := validateFSLayerCommitScope(layer, entries); len(conflicts) > 0 {
		_ = store.SetFSLayerStateIf(r.Context(), layer.LayerID, []datastore.FSLayerState{datastore.FSLayerStateCommitting}, datastore.FSLayerStateConflicted)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusConflict)
		_ = json.NewEncoder(w).Encode(fsLayerCommitResponse{
			Status:    "conflicted",
			LayerID:   layer.LayerID,
			Conflicts: conflicts,
		})
		return
	}
	conflicts := preflightFSLayerCommit(r.Context(), b, entries, false)
	if len(conflicts) > 0 {
		_ = store.SetFSLayerStateIf(r.Context(), layer.LayerID, []datastore.FSLayerState{datastore.FSLayerStateCommitting}, datastore.FSLayerStateConflicted)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusConflict)
		_ = json.NewEncoder(w).Encode(fsLayerCommitResponse{
			Status:    "conflicted",
			LayerID:   layer.LayerID,
			Conflicts: conflicts,
		})
		return
	}
	snapshots, err := snapshotFSLayerCommit(r.Context(), b, entries)
	if err == nil {
		err = validateFSLayerCommitSnapshots(snapshots)
	}
	if err != nil {
		_ = store.SetFSLayerStateIf(r.Context(), layer.LayerID, []datastore.FSLayerState{datastore.FSLayerStateCommitting}, datastore.FSLayerStateConflicted)
		if errors.Is(err, backendpkg.ErrAppendLogUnsupported) {
			errJSONCode(w, http.StatusBadRequest, "append_log_unsupported", "fs layer operations are unsupported for append-log files")
			return
		}
		logger.Error(r.Context(), "fs_layer_commit_validate_failed", eventFields(r.Context(), "fs_layer_commit_validate_failed", "layer_id", layer.LayerID, "error", err)...)
		writeBackendError(w, r, err)
		return
	}
	failpoint.InjectCall("fsLayerCommitAfterPreflight", layer.LayerID)
	rollbackCtx, rollbackCancel := context.WithTimeout(context.WithoutCancel(r.Context()), fsLayerRollbackTimeout)
	defer rollbackCancel()
	touchedPaths := make(map[string]struct{})
	ours := fsLayerSnapshotOurs(snapshots)
	created := make(map[string]struct{})
	for i := range entries {
		modeMayBeSuperseded := fsLayerEntryModeSuperseded(entries, i)
		createdIDs, err := applyFSLayerEntry(r.Context(), b, &entries[i], false, touchedPaths, modeMayBeSuperseded)
		if err != nil {
			applied := entries[:i]
			if fsLayerApplyMutationCommitted(err) {
				applied = entries[:i+1]
				fsLayerRememberCreated(ours, created, createdIDs)
			}
			rollbackErr := rollbackFSLayerCommit(rollbackCtx, b, filterFSLayerSnapshotsForEntries(snapshots, applied), ours, created)
			_ = store.SetFSLayerStateIf(rollbackCtx, layer.LayerID, []datastore.FSLayerState{datastore.FSLayerStateCommitting}, datastore.FSLayerStateConflicted)
			if rollbackErr != nil {
				writeFSLayerCommitRollbackFailed(w, r, entries[i].Path, "fs_layer_commit_apply_failed", err, rollbackErr)
				return
			}
			if conflict, ok := fsLayerApplyOCCConflict(entries[i].Path, err); ok {
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusConflict)
				_ = json.NewEncoder(w).Encode(fsLayerCommitResponse{
					Status:    "conflicted",
					LayerID:   layer.LayerID,
					Conflicts: []fsLayerCommitConflict{conflict},
				})
				return
			}
			if errors.Is(err, backendpkg.ErrAppendLogUnsupported) {
				errJSONCode(w, http.StatusBadRequest, "append_log_unsupported", "fs layer operations are unsupported for append-log files")
				return
			}
			logger.Error(r.Context(), "fs_layer_commit_apply_failed", eventFields(r.Context(), "fs_layer_commit_apply_failed", "path", entries[i].Path, "error", err)...)
			writeBackendError(w, r, err)
			return
		}
		fsLayerRememberCreated(ours, created, createdIDs)
		failpoint.InjectCall("fsLayerApplyAfterSuccess", entries[i].Path)
		markFSLayerEntryTouched(touchedPaths, &entries[i])
	}
	var finalizeErr error
	failpoint.InjectCall("fsLayerCommitFinalizeFail", &finalizeErr)
	if finalizeErr == nil {
		finalizeErr = store.FinalizeFSLayerCommit(r.Context(), layer.LayerID)
	}
	if finalizeErr != nil {
		rollbackErr := rollbackFSLayerCommit(rollbackCtx, b, snapshots, ours, created)
		_ = store.SetFSLayerStateIf(rollbackCtx, layer.LayerID, []datastore.FSLayerState{datastore.FSLayerStateCommitting}, datastore.FSLayerStateConflicted)
		if rollbackErr != nil {
			writeFSLayerCommitRollbackFailed(w, r, layer.LayerID, "fs_layer_commit_finalize_failed", finalizeErr, rollbackErr)
			return
		}
		logger.Error(r.Context(), "fs_layer_commit_finalize_failed", eventFields(r.Context(), "fs_layer_commit_finalize_failed", "layer_id", layer.LayerID, "error", finalizeErr)...)
		writeBackendError(w, r, finalizeErr)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(fsLayerCommitResponse{
		Status:  "committed",
		LayerID: layer.LayerID,
		Applied: len(entries),
	})
}

func (s *Server) handleFSLayerCheckpoint(w http.ResponseWriter, r *http.Request, store *datastore.Store, layerID string) {
	defer func() { _ = r.Body.Close() }()
	var req fsLayerCheckpointRequest
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerBodyBytes)).Decode(&req); err != nil {
		errJSON(w, http.StatusBadRequest, "malformed JSON")
		return
	}
	checkpointID := strings.TrimSpace(req.CheckpointID)
	if checkpointID == "" {
		checkpointID = journal.NewID("ckpt")
	}
	checkpoint := datastore.FSLayerCheckpoint{
		CheckpointID: checkpointID,
		LayerID:      layerID,
		Label:        strings.TrimSpace(req.Label),
	}
	if err := store.CreateFSLayerCheckpoint(r.Context(), &checkpoint); err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	stored, err := store.GetFSLayerCheckpoint(r.Context(), checkpointID)
	if err != nil {
		writeFSLayerStoreError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(toFSLayerCheckpointResponse(stored))
}

func fsLayerMaxSeqFromRequest(r *http.Request) (int64, bool, error) {
	values, present := r.URL.Query()["max_seq"]
	if !present || len(values) == 0 {
		return 0, false, nil
	}
	raw := strings.TrimSpace(values[0])
	if raw == "" {
		return 0, false, fmt.Errorf("invalid max_seq")
	}
	seq, err := strconv.ParseInt(raw, 10, 64)
	if err != nil || seq < 0 {
		return 0, false, fmt.Errorf("invalid max_seq")
	}
	return seq, true, nil
}

func fsLayerReplayLogFromRequest(r *http.Request) bool {
	raw := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("replay")))
	if raw == "1" || raw == "true" || raw == "yes" {
		return true
	}
	mode := strings.ToLower(strings.TrimSpace(r.URL.Query().Get("mode")))
	return mode == "log" || mode == "replay"
}

func normalizeAndValidateFSLayerServerEntry(ctx context.Context, layer *datastore.FSLayer, entry *datastore.FSLayerEntry) error {
	if layer == nil || entry == nil {
		return fmt.Errorf("fs layer entry is required")
	}
	path, err := canonicalFSLayerServerPath(entry.Path, string(entry.Op), string(entry.Kind))
	if err != nil {
		if errors.Is(err, pathutil.ErrInvalidPath) {
			metricEvent(ctx, "fs_invalid_path")
		}
		return fmt.Errorf("invalid fs layer entry path: %w", err)
	}
	if !fsLayerPathWithinBase(path, layer.BaseRootPath) {
		return fmt.Errorf("fs layer entry path is outside base root")
	}
	entry.Path = path
	if entry.Op == datastore.FSLayerEntryOpRename {
		target := fsLayerRenameTarget(entry)
		targetPath, err := canonicalFSLayerServerPath(target, "", string(entry.Kind))
		if err != nil {
			if errors.Is(err, pathutil.ErrInvalidPath) {
				metricEvent(ctx, "fs_invalid_path")
			}
			return fmt.Errorf("invalid fs layer rename target: %w", err)
		}
		if !fsLayerPathWithinBase(targetPath, layer.BaseRootPath) {
			return fmt.Errorf("fs layer rename target is outside base root")
		}
		if datastore.OverlayRenameDestInsideSrc(path, targetPath) {
			return fmt.Errorf("fs layer rename destination is inside source")
		}
		entry.ContentText = targetPath
	}
	return nil
}

func canonicalFSLayerServerPath(rawPath, op, kind string) (string, error) {
	if rawPath == "" {
		return "", fmt.Errorf("path is required")
	}
	if kind == string(datastore.FSLayerEntryKindDir) || op == string(datastore.FSLayerEntryOpMkdir) || strings.HasSuffix(rawPath, "/") {
		return pathutil.CanonicalizeDir(rawPath)
	}
	return pathutil.Canonicalize(rawPath)
}

func fsLayerSamePath(a, b string) bool {
	if a == b {
		return true
	}
	return strings.TrimSuffix(a, "/") == strings.TrimSuffix(b, "/")
}

func fsLayerPathWithinBase(path, baseRoot string) bool {
	if baseRoot == "/" {
		return strings.HasPrefix(path, "/")
	}
	baseRoot = strings.TrimRight(baseRoot, "/")
	if path == baseRoot {
		return true
	}
	return strings.HasPrefix(path, baseRoot+"/")
}

func fsLayerEntryStampsDirBody(entry *datastore.FSLayerEntry) bool {
	if entry == nil {
		return false
	}
	if entry.Op == datastore.FSLayerEntryOpMkdir {
		return true
	}
	return entry.Op == datastore.FSLayerEntryOpUpsert && (entry.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entry.Path, "/"))
}

// fsLayerMkdirConflictsWithVisibleMain is true when mkdir would stamp an
// overlay-local dir body over a path that already exists in the layer view
// (overlay node or live main) and is not hidden. That sequence later folds
// into mkdir+whiteout and cannot commit a non-empty directory.
func fsLayerMkdirConflictsWithVisibleMain(ctx context.Context, b *backendpkg.Dat9Backend, store *datastore.Store, layer *datastore.FSLayer, path string, tree map[string]datastore.FSLayerOverlayNode) (bool, error) {
	if b == nil || store == nil || layer == nil || path == "" {
		return false, nil
	}
	if tree == nil {
		var err error
		tree, err = fsLayerOverlayTreeForWrite(ctx, store, layer)
		if err != nil {
			return false, err
		}
	}
	if n, ok := datastore.OverlayLookup(tree, path); ok && !n.Whiteout {
		return true, nil
	}
	if n, ok := datastore.OverlayLookup(tree, path); ok && n.Whiteout {
		return false, nil
	}
	layers, err := fsLayerSameRootLayers(ctx, store, layer)
	if err != nil {
		return false, err
	}
	hidden, remapped := datastore.OverlayHideOrRemapIf(tree, path, fsLayerHopLandedFn(layers))
	if hidden {
		return false, nil
	}
	ok, statErr := fsLayerMainPathExistsAnyAlias(ctx, b, path)
	if statErr != nil {
		return false, statErr
	}
	if ok {
		return true, nil
	}
	if remapped != "" && remapped != path {
		return fsLayerMainPathExistsAnyAlias(ctx, b, remapped)
	}
	return false, nil
}

func fsLayerMainPathExistsAnyAlias(ctx context.Context, b *backendpkg.Dat9Backend, path string) (bool, error) {
	ok, err := fsLayerMainPathExistsErr(ctx, b, path)
	if err != nil || ok {
		return ok, err
	}
	if strings.HasSuffix(path, "/") {
		return fsLayerMainPathExistsErr(ctx, b, strings.TrimSuffix(path, "/"))
	}
	return fsLayerMainPathExistsErr(ctx, b, path+"/")
}

func fsLayerMainPathExistsErr(ctx context.Context, b *backendpkg.Dat9Backend, path string) (bool, error) {
	_, err := b.StatNodeCtx(ctx, path)
	if errors.Is(err, datastore.ErrNotFound) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return true, nil
}

func fsLayerOverlayTreeForWrite(ctx context.Context, store *datastore.Store, layer *datastore.FSLayer) (map[string]datastore.FSLayerOverlayNode, error) {
	if store == nil || layer == nil {
		return map[string]datastore.FSLayerOverlayNode{}, nil
	}
	// Always materialize. The parentless GET fast path (no rename/whiteout/chmod)
	// must not be reused here: a prior mkdir/upsert-dir is a view hit.
	return store.MaterializeFSLayerOverlay(ctx, layer.LayerID, nil)
}

func authorizeOrRedactFSLayerEntryRead(w http.ResponseWriter, r *http.Request, store *datastore.Store, layer *datastore.FSLayer, requestPath string, entry *datastore.FSLayerEntry, tree map[string]datastore.FSLayerOverlayNode) bool {
	if entry == nil {
		return true
	}
	if entry.Op == datastore.FSLayerEntryOpRename && entry.Path != "" && entry.Path != requestPath {
		if !fsLayerRenameHopsLanded(r.Context(), store, layer, tree, requestPath) {
			if !authorizeFS(w, r, FSOpRead, entry.Path) {
				return false
			}
		}
	}
	if entry.Op == datastore.FSLayerEntryOpRename || entry.Op == datastore.FSLayerEntryOpWhiteout {
		dest := fsLayerRenameTarget(entry)
		if dest != "" && dest != requestPath && strings.HasPrefix(dest, "/") {
			scope := ScopeFromContext(r.Context())
			if scope == nil {
				errJSON(w, http.StatusUnauthorized, "missing tenant scope")
				return false
			}
			if err := scope.AuthorizeFS(FSOpRead, dest); err != nil {
				entry.ContentText = ""
			}
		}
	}
	return true
}

func fsLayerRenameHopsLanded(ctx context.Context, store *datastore.Store, layer *datastore.FSLayer, tree map[string]datastore.FSLayerOverlayNode, path string) bool {
	if store == nil || layer == nil || tree == nil {
		return false
	}
	n, ok := datastore.OverlayLookup(tree, path)
	if !ok {
		return false
	}
	layers, err := fsLayerSameRootLayers(ctx, store, layer)
	if err != nil {
		return false
	}
	return datastore.OverlayRenameHopsAllLanded(n, fsLayerHopLandedFn(layers))
}

func validateFSLayerCommitScope(layer *datastore.FSLayer, entries []datastore.FSLayerEntry) []fsLayerCommitConflict {
	var conflicts []fsLayerCommitConflict
	for i := range entries {
		if !fsLayerPathWithinBase(entries[i].Path, layer.BaseRootPath) {
			conflicts = append(conflicts, fsLayerCommitConflict{Path: entries[i].Path, Reason: "entry outside base root"})
		}
		if entries[i].Op == datastore.FSLayerEntryOpRename {
			target := fsLayerRenameTarget(&entries[i])
			targetPath, err := canonicalFSLayerServerPath(target, "", string(entries[i].Kind))
			if err != nil {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entries[i].Path, Reason: "invalid rename target"})
				continue
			}
			if !fsLayerPathWithinBase(targetPath, layer.BaseRootPath) {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entries[i].Path, Reason: "rename target outside base root"})
			}
		}
	}
	return conflicts
}

type fsLayerBaseSnapshot struct {
	Path        string
	Exists      bool
	IsDir       bool
	IsSymlink   bool
	CleanupOnly bool
	PruneExtras bool
	Target      string
	Data        []byte
	BodyOK      bool
	FileID      string
	Mode        uint32
	ModeIsSet   bool
	SnapshotOK  bool
	SnapshotErr error
}

func snapshotFSLayerCommit(ctx context.Context, b *backendpkg.Dat9Backend, entries []datastore.FSLayerEntry) ([]fsLayerBaseSnapshot, error) {
	seen := make(map[string]struct{})
	var paths []string
	cleanupOnly := make(map[string]bool)
	pruneExtras := make(map[string]bool)
	bodyPaths := make(map[string]struct{})
	addTrackedPath := func(p string, cleanup bool) {
		if p == "" {
			return
		}
		if _, ok := seen[p]; ok {
			if !cleanup {
				delete(cleanupOnly, p)
			}
			return
		}
		seen[p] = struct{}{}
		paths = append(paths, p)
		if cleanup {
			cleanupOnly[p] = true
		}
	}
	addParentCleanupPaths := func(p string) {
		for parent := pathutil.ParentPath(p); parent != "/" && parent != ""; parent = pathutil.ParentPath(parent) {
			addTrackedPath(parent, true)
		}
	}
	var addDirTreePaths func(string) error
	addDirTreePaths = func(dir string) error {
		if dir != "/" && !strings.HasSuffix(dir, "/") {
			dir += "/"
		}
		children, err := b.ReadDirCtx(ctx, dir)
		if err != nil {
			if errors.Is(err, datastore.ErrNotFound) {
				return nil
			}
			return fmt.Errorf("snapshot fs layer dir tree %s: %w", dir, err)
		}
		pruneExtras[dir] = true
		for _, child := range children {
			childPath := dir + child.Name
			if child.IsDir {
				childPath += "/"
			}
			addTrackedPath(childPath, false)
			if child.IsDir {
				if err := addDirTreePaths(childPath); err != nil {
					return err
				}
			}
		}
		return nil
	}
	for i := range entries {
		addParentCleanupPaths(entries[i].Path)
		addTrackedPath(entries[i].Path, false)
		if fsLayerSnapshotNeedsBody(&entries[i]) {
			bodyPaths[entries[i].Path] = struct{}{}
			if live := fsLayerSnapshotBodyLivePath(entries, entries[i].Path); live != "" && live != entries[i].Path {
				bodyPaths[live] = struct{}{}
			}
		}
		if entries[i].Op == datastore.FSLayerEntryOpRename {
			if entries[i].Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entries[i].Path, "/") {
				if err := addDirTreePaths(entries[i].Path); err != nil {
					return nil, err
				}
			}
			target := fsLayerRenameTarget(&entries[i])
			if targetPath, err := canonicalFSLayerServerPath(target, "", string(entries[i].Kind)); err == nil {
				addParentCleanupPaths(targetPath)
				addTrackedPath(targetPath, false)
			}
		}
	}
	out := make([]fsLayerBaseSnapshot, 0, len(paths))
	for _, p := range paths {
		_, captureBody := bodyPaths[p]
		snap := snapshotFSLayerBasePath(ctx, b, p, captureBody)
		snap.CleanupOnly = cleanupOnly[p]
		snap.PruneExtras = pruneExtras[p]
		out = append(out, snap)
	}
	return out, nil
}

func fsLayerSnapshotBodyLivePath(entries []datastore.FSLayerEntry, path string) string {
	type hop struct{ src, dest string }
	var hops []hop
	for i := range entries {
		e := &entries[i]
		if e.Op != datastore.FSLayerEntryOpRename {
			continue
		}
		if e.Kind != datastore.FSLayerEntryKindDir && !strings.HasSuffix(e.Path, "/") {
			continue
		}
		dest := fsLayerRenameTarget(e)
		destPath, err := canonicalFSLayerServerPath(dest, "", string(e.Kind))
		if err != nil || destPath == "" {
			continue
		}
		if !strings.HasSuffix(destPath, "/") {
			destPath += "/"
		}
		src := e.Path
		if !strings.HasSuffix(src, "/") {
			src += "/"
		}
		hops = append(hops, hop{src: src, dest: destPath})
	}
	sort.Slice(hops, func(i, j int) bool {
		if len(hops[i].dest) != len(hops[j].dest) {
			return len(hops[i].dest) > len(hops[j].dest)
		}
		return hops[i].dest < hops[j].dest
	})
	live := path
	seen := map[string]struct{}{live: {}}
	for {
		next := live
		for _, h := range hops {
			if live == strings.TrimSuffix(h.dest, "/") {
				next = strings.TrimSuffix(h.src, "/")
				break
			}
			if strings.HasPrefix(live, h.dest) {
				next = h.src + strings.TrimPrefix(live, h.dest)
				break
			}
		}
		if next == live {
			return live
		}
		if _, ok := seen[next]; ok {
			return live
		}
		seen[next] = struct{}{}
		live = next
	}
}

func fsLayerSnapshotNeedsBody(entry *datastore.FSLayerEntry) bool {
	if entry == nil {
		return false
	}
	if entry.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entry.Path, "/") {
		return false
	}
	switch entry.Op {
	case datastore.FSLayerEntryOpUpsert, datastore.FSLayerEntryOpWhiteout, datastore.FSLayerEntryOpSymlink:
		return true
	default:
		return false
	}
}

func filterFSLayerSnapshotsForEntries(snapshots []fsLayerBaseSnapshot, entries []datastore.FSLayerEntry) []fsLayerBaseSnapshot {
	if len(snapshots) == 0 || len(entries) == 0 {
		return nil
	}
	needed := make(map[string]struct{})
	addPath := func(p string) {
		if p != "" {
			needed[p] = struct{}{}
		}
	}
	addParentCleanupPaths := func(p string) {
		for parent := pathutil.ParentPath(p); parent != "/" && parent != ""; parent = pathutil.ParentPath(parent) {
			addPath(parent)
		}
	}
	addDirTreePaths := func(dir string) {
		for i := range snapshots {
			if snapshots[i].Path == "" || snapshots[i].Path == dir {
				continue
			}
			if fsLayerPathDescendsFrom(snapshots[i].Path, dir) {
				addPath(snapshots[i].Path)
			}
		}
	}
	for i := range entries {
		entry := &entries[i]
		addParentCleanupPaths(entry.Path)
		addPath(entry.Path)
		if entry.Op != datastore.FSLayerEntryOpRename {
			continue
		}
		if entry.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entry.Path, "/") {
			addDirTreePaths(entry.Path)
		}
		target := fsLayerRenameTarget(entry)
		if targetPath, err := canonicalFSLayerServerPath(target, "", string(entry.Kind)); err == nil {
			addParentCleanupPaths(targetPath)
			addPath(targetPath)
		}
	}
	out := make([]fsLayerBaseSnapshot, 0, len(snapshots))
	for i := range snapshots {
		if _, ok := needed[snapshots[i].Path]; ok {
			out = append(out, snapshots[i])
		}
	}
	return out
}

func fsLayerPathDescendsFrom(path, dir string) bool {
	if dir == "" || dir == "/" {
		return strings.HasPrefix(path, "/")
	}
	if !strings.HasSuffix(dir, "/") {
		dir += "/"
	}
	return strings.HasPrefix(path, dir)
}

func validateFSLayerCommitSnapshots(snapshots []fsLayerBaseSnapshot) error {
	for i := range snapshots {
		if !snapshots[i].SnapshotOK {
			if snapshots[i].SnapshotErr != nil {
				return fmt.Errorf("snapshot fs layer base path %s: %w", snapshots[i].Path, snapshots[i].SnapshotErr)
			}
			return fmt.Errorf("snapshot fs layer base path %s failed", snapshots[i].Path)
		}
	}
	return nil
}

func snapshotFSLayerBasePath(ctx context.Context, b *backendpkg.Dat9Backend, path string, captureBody bool) fsLayerBaseSnapshot {
	snap := fsLayerBaseSnapshot{Path: path, SnapshotOK: true}
	nf, err := b.StatNodeCtx(ctx, path)
	if errors.Is(err, datastore.ErrNotFound) {
		return snap
	}
	if err != nil {
		snap.SnapshotOK = false
		snap.SnapshotErr = err
		return snap
	}
	snap.Exists = true
	snap.IsDir = nf.Node.IsDirectory
	snap.FileID = datastore.NodeFileID(nf)
	if nf.Mode != 0 {
		snap.Mode = nf.Mode
		snap.ModeIsSet = true
	}
	if nf.File != nil {
		if nf.File.ContentLayout == datastore.ContentLayoutAppendLog {
			snap.SnapshotOK = false
			snap.SnapshotErr = backendpkg.ErrAppendLogUnsupported
			return snap
		}
		if nf.File.Mode != 0 {
			snap.Mode = nf.File.Mode
			snap.ModeIsSet = true
		}
		snap.IsSymlink = isFSLayerSymlinkMode(nf.File.Mode)
		if !captureBody && !snap.IsSymlink {
			return snap
		}
		data, err := b.ReadCtx(ctx, path, 0, -1)
		if err != nil {
			snap.SnapshotOK = false
			snap.SnapshotErr = err
			return snap
		}
		snap.BodyOK = true
		if snap.IsSymlink {
			snap.Target = string(data)
		} else {
			snap.Data = append([]byte(nil), data...)
		}
	}
	return snap
}

func rollbackFSLayerCommit(ctx context.Context, b *backendpkg.Dat9Backend, snapshots []fsLayerBaseSnapshot, ours, created map[string]struct{}) error {
	var injected error
	failpoint.InjectCall("fsLayerCommitRollbackFail", &injected)
	if injected != nil {
		return injected
	}
	if ours == nil {
		ours = fsLayerSnapshotOurs(snapshots)
	}
	if created == nil {
		created = map[string]struct{}{}
	}
	if err := relocateFSLayerRollbackInodes(ctx, b, snapshots, ours, created); err != nil {
		return err
	}
	for i := len(snapshots) - 1; i >= 0; i-- {
		if err := restoreFSLayerBaseSnapshot(ctx, b, &snapshots[i], snapshots, ours, created); err != nil {
			return err
		}
	}
	return nil
}

func fsLayerSnapshotOurs(snapshots []fsLayerBaseSnapshot) map[string]struct{} {
	ours := make(map[string]struct{})
	for i := range snapshots {
		if snapshots[i].FileID != "" {
			ours[snapshots[i].FileID] = struct{}{}
		}
	}
	return ours
}

func fsLayerApplyPathExists(err error) error {
	if err == nil {
		return nil
	}
	if errors.Is(err, datastore.ErrPathConflict) {
		return fmt.Errorf("%w: %w", datastore.ErrRevisionConflict, datastore.ErrFSLayerBasePathExists)
	}
	return err
}

func fsLayerDropCreatedAncestors(ctx context.Context, b *backendpkg.Dat9Backend, path string, ids []string) {
	if len(ids) == 0 || path == "" {
		return
	}
	want := make(map[string]struct{}, len(ids))
	for _, id := range ids {
		if id != "" {
			want[id] = struct{}{}
		}
	}
	for p := pathutil.ParentPath(path); p != "" && p != "/"; p = pathutil.ParentPath(p) {
		nf, err := b.StatNodeCtx(ctx, p)
		if err != nil {
			continue
		}
		id := datastore.NodeFileID(nf)
		if _, ok := want[id]; !ok {
			continue
		}
		_ = b.RemoveDirFencedCtx(ctx, p, id)
	}
}

func fsLayerRememberCreated(ours, created map[string]struct{}, ids []string) {
	if created == nil {
		return
	}
	for _, id := range ids {
		if id == "" {
			continue
		}
		if _, ok := ours[id]; ok {
			continue
		}
		created[id] = struct{}{}
	}
}

type fsLayerApplyCommittedError struct {
	err error
}

func (e *fsLayerApplyCommittedError) Error() string {
	if e == nil || e.err == nil {
		return "fs layer apply mutation committed"
	}
	return e.err.Error()
}

func (e *fsLayerApplyCommittedError) Unwrap() error { return e.err }

func fsLayerMarkApplyCommitted(err error) error {
	if err == nil {
		return nil
	}
	if fsLayerApplyMutationCommitted(err) {
		return err
	}
	return &fsLayerApplyCommittedError{err: err}
}

func fsLayerApplyMutationCommitted(err error) bool {
	var postCommit *backendpkg.PostCommitQuotaMutationError
	if errors.As(err, &postCommit) {
		return true
	}
	var committed *fsLayerApplyCommittedError
	return errors.As(err, &committed)
}

func writeFSLayerCommitRollbackFailed(w http.ResponseWriter, r *http.Request, path, event string, applyErr, rollbackErr error) {
	if event == "" {
		event = "fs_layer_commit_apply_failed"
	}
	logger.Error(r.Context(), event, eventFields(r.Context(), event, "path", path, "error", applyErr, "rollback_error", rollbackErr)...)
	errJSON(w, http.StatusInternalServerError, "backend unavailable")
}

func restoreFSLayerBaseSnapshot(ctx context.Context, b *backendpkg.Dat9Backend, snap *fsLayerBaseSnapshot, snapshots []fsLayerBaseSnapshot, ours, created map[string]struct{}) error {
	if snap == nil || snap.Path == "" {
		return nil
	}
	if !snap.SnapshotOK {
		return fmt.Errorf("no rollback snapshot for %s", snap.Path)
	}
	if snap.CleanupOnly {
		if snap.Exists {
			return nil
		}
		return restoreVacatedFSLayerPath(ctx, b, snap, snapshots, ours, created)
	}
	if !snap.Exists {
		return restoreVacatedFSLayerPath(ctx, b, snap, snapshots, ours, created)
	}
	if snap.IsDir {
		return restoreFSLayerDirSnapshot(ctx, b, snap, snapshots, ours, created)
	}
	if snap.IsSymlink {
		nf, err := b.StatNodeCtx(ctx, snap.Path)
		if err != nil && !errors.Is(err, datastore.ErrNotFound) {
			return err
		}
		if err == nil && fsLayerRestoreLeaveReplacement(nf, snap, ours) {
			return nil
		}
		if err == nil {
			if err := fsLayerRemovePathForRollback(ctx, b, snap.Path, nf); err != nil {
				return err
			}
		}
		return fsLayerRestoreIgnoreCreateRace(b.CreateSymlinkCtx(ctx, snap.Path, snap.Target))
	}
	nf, err := b.StatNodeCtx(ctx, snap.Path)
	if err != nil && !errors.Is(err, datastore.ErrNotFound) {
		return err
	}
	if err == nil && fsLayerRestoreLeaveReplacement(nf, snap, ours) {
		return nil
	}
	recreate := fsLayerRestoreMustRecreate(nf, err)
	sameInode := !recreate && snap.FileID != "" && datastore.NodeFileID(nf) == snap.FileID
	if sameInode {
		if !snap.BodyOK {
			return restoreFSLayerSnapshotMode(ctx, b, snap, false)
		}
		data, readErr := b.ReadCtx(ctx, snap.Path, 0, -1)
		if readErr == nil && bytes.Equal(data, snap.Data) {
			return restoreFSLayerSnapshotMode(ctx, b, snap, false)
		}
		_, _, err := b.WriteCtxIfRevisionAndFileID(ctx, snap.Path, snap.Data, 0, filesystem.WriteFlagCreate|filesystem.WriteFlagTruncate, -1, snap.FileID, nil, "")
		if errors.Is(err, datastore.ErrFSLayerBaseInodeChanged) {
			return nil
		}
		created := false
		if errors.Is(err, datastore.ErrFSLayerBasePathMissing) || errors.Is(err, datastore.ErrNotFound) {
			_, _, err = b.WriteCtxIfRevisionWithTagsResult(ctx, snap.Path, snap.Data, 0, filesystem.WriteFlagCreate|filesystem.WriteFlagTruncate, 0, nil, "")
			if fsLayerRestoreLeaveCreateRace(err) {
				return nil
			}
			created = true
		}
		if err != nil {
			return err
		}
		return restoreFSLayerSnapshotMode(ctx, b, snap, created)
	}
	if !snap.BodyOK {
		return fmt.Errorf("fs layer rollback missing body snapshot for %s", snap.Path)
	}
	if errors.Is(err, datastore.ErrNotFound) {
		_, _, err = b.WriteCtxIfRevisionWithTagsResult(ctx, snap.Path, snap.Data, 0, filesystem.WriteFlagCreate|filesystem.WriteFlagTruncate, 0, nil, "")
		if fsLayerRestoreLeaveCreateRace(err) {
			return nil
		}
		if err != nil {
			return err
		}
		return restoreFSLayerSnapshotMode(ctx, b, snap, true)
	}
	if err := fsLayerRemovePathForRollback(ctx, b, snap.Path, nf); err != nil {
		return err
	}
	if _, _, err := b.WriteCtxIfRevisionWithTagsResult(ctx, snap.Path, snap.Data, 0, filesystem.WriteFlagCreate|filesystem.WriteFlagTruncate, 0, nil, ""); err != nil {
		if fsLayerRestoreLeaveCreateRace(err) {
			return nil
		}
		return err
	}
	return restoreFSLayerSnapshotMode(ctx, b, snap, true)
}

func restoreFSLayerDirSnapshot(ctx context.Context, b *backendpkg.Dat9Backend, snap *fsLayerBaseSnapshot, snapshots []fsLayerBaseSnapshot, ours, created map[string]struct{}) error {
	nf, err := b.StatNodeCtx(ctx, snap.Path)
	if err != nil && !errors.Is(err, datastore.ErrNotFound) {
		return err
	}
	if err == nil && nf.Node.IsDirectory {
		got := datastore.NodeFileID(nf)
		if got == snap.FileID || snap.FileID == "" {
			if snap.PruneExtras {
				if err := pruneFSLayerDirExtras(ctx, b, snap.Path, snapshots, ours, created); err != nil {
					return err
				}
			}
			return restoreFSLayerSnapshotMode(ctx, b, snap, false)
		}
		if _, isOurs := ours[got]; isOurs {
			home := fsLayerRollbackHomePath(snapshots, got)
			if home != "" && home != snap.Path {
				_, err := fsLayerMoveOursInode(ctx, b, snap.Path, home, got, ours, created)
				return err
			}
			return restoreFSLayerSnapshotMode(ctx, b, snap, false)
		}
	}
	if err == nil && fsLayerRestoreLeaveReplacement(nf, snap, ours) {
		return nil
	}
	mode := snap.Mode & 0o777
	if mode == 0 {
		mode = 0o755
	}
	if errors.Is(err, datastore.ErrNotFound) {
		return fsLayerRestoreIgnoreCreateRace(b.MkdirCtx(ctx, snap.Path, mode))
	}
	if err := fsLayerRemovePathForRollback(ctx, b, snap.Path, nf); err != nil {
		return err
	}
	return fsLayerRestoreIgnoreCreateRace(b.MkdirCtx(ctx, snap.Path, mode))
}

func pruneFSLayerDirExtras(ctx context.Context, b *backendpkg.Dat9Backend, dir string, snapshots []fsLayerBaseSnapshot, ours, created map[string]struct{}) error {
	if dir == "" {
		return nil
	}
	allowed := make(map[string]struct{}, len(snapshots))
	for i := range snapshots {
		p := snapshots[i].Path
		if !snapshots[i].Exists || p == "" || p == dir {
			continue
		}
		if fsLayerPathDescendsFrom(p, dir) {
			allowed[p] = struct{}{}
		}
	}
	children, err := b.ReadDirCtx(ctx, dir)
	if errors.Is(err, datastore.ErrNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	if !strings.HasSuffix(dir, "/") {
		dir += "/"
	}
	for _, child := range children {
		path := dir + child.Name
		if child.IsDir {
			path += "/"
		}
		if _, ok := allowed[path]; ok {
			if child.IsDir {
				if err := pruneFSLayerDirExtras(ctx, b, path, snapshots, ours, created); err != nil {
					return err
				}
			}
			continue
		}
		nf, statErr := b.StatNodeCtx(ctx, path)
		if errors.Is(statErr, datastore.ErrNotFound) {
			continue
		}
		if statErr != nil {
			return statErr
		}
		got := datastore.NodeFileID(nf)
		if _, ok := created[got]; !ok || got == "" {
			continue
		}
		if err := fsLayerRemoveOwnedPath(ctx, b, path, got, ours, created); err != nil {
			return err
		}
	}
	return nil
}

func restoreVacatedFSLayerPath(ctx context.Context, b *backendpkg.Dat9Backend, snap *fsLayerBaseSnapshot, snapshots []fsLayerBaseSnapshot, ours, created map[string]struct{}) error {
	nf, err := b.StatNodeCtx(ctx, snap.Path)
	if errors.Is(err, datastore.ErrNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	got := datastore.NodeFileID(nf)
	if got != "" {
		if _, isOurs := ours[got]; isOurs {
			home := fsLayerRollbackHomePath(snapshots, got)
			if home == "" || home == snap.Path {
				return nil
			}
			_, err := fsLayerMoveOursInode(ctx, b, snap.Path, home, got, ours, created)
			return err
		}
		if _, isCreated := created[got]; isCreated {
			return fsLayerRemoveOwnedPath(ctx, b, snap.Path, got, ours, created)
		}
	}
	return nil
}

func restoreFSLayerSnapshotMode(ctx context.Context, b *backendpkg.Dat9Backend, snap *fsLayerBaseSnapshot, justCreated bool) error {
	if snap == nil || !snap.ModeIsSet {
		return nil
	}
	if snap.FileID != "" && !justCreated {
		nf, err := b.StatNodeCtx(ctx, snap.Path)
		if errors.Is(err, datastore.ErrNotFound) {
			return nil
		}
		if err != nil {
			return err
		}
		if datastore.NodeFileID(nf) != snap.FileID {
			return nil
		}
	}
	if err := b.ChmodCtx(ctx, snap.Path, snap.Mode&0o777); err != nil && !errors.Is(err, datastore.ErrNotFound) {
		return err
	}
	return nil
}

func relocateFSLayerRollbackInodes(ctx context.Context, b *backendpkg.Dat9Backend, snapshots []fsLayerBaseSnapshot, ours, created map[string]struct{}) error {
	home := make(map[string]string, len(ours))
	for i := range snapshots {
		if snapshots[i].FileID != "" && snapshots[i].Exists && !snapshots[i].CleanupOnly {
			home[snapshots[i].FileID] = snapshots[i].Path
		}
	}
	if len(home) == 0 {
		return nil
	}
	order := make([]string, 0, len(home))
	for fileID := range home {
		order = append(order, fileID)
	}
	sort.Slice(order, func(i, j int) bool {
		pi, pj := home[order[i]], home[order[j]]
		di, dj := fsLayerRollbackPathDepth(pi), fsLayerRollbackPathDepth(pj)
		if di != dj {
			return di < dj
		}
		return pi < pj
	})
	var extras []string
	for n := 0; n < len(home)*2+4; n++ {
		current := fsLayerScanOursPaths(ctx, b, snapshots, extras, ours)
		moved := false
		for _, fileID := range order {
			homePath := home[fileID]
			from := current[fileID]
			if from == "" || from == homePath {
				continue
			}
			tmp, err := fsLayerMoveOursInode(ctx, b, from, homePath, fileID, ours, created)
			if tmp != "" {
				extras = append(extras, tmp)
			}
			if err != nil {
				return err
			}
			moved = true
			break
		}
		if !moved {
			return fsLayerRemoveEmptyRollbackTemps(ctx, b, extras)
		}
	}
	return fmt.Errorf("fs layer rollback relocate did not settle")
}

func fsLayerRollbackPathDepth(path string) int {
	n := 0
	for i := 0; i < len(path); i++ {
		if path[i] == '/' {
			n++
		}
	}
	return n
}

func fsLayerScanOursPaths(ctx context.Context, b *backendpkg.Dat9Backend, snapshots []fsLayerBaseSnapshot, extras []string, ours map[string]struct{}) map[string]string {
	out := make(map[string]string, len(ours))
	add := func(path string) {
		if path == "" {
			return
		}
		nf, err := b.StatNodeCtx(ctx, path)
		if err != nil {
			return
		}
		got := datastore.NodeFileID(nf)
		if got == "" {
			return
		}
		if _, ok := ours[got]; ok {
			out[got] = path
		}
	}
	for i := range snapshots {
		add(snapshots[i].Path)
	}
	for _, path := range extras {
		add(path)
	}
	return out
}

func fsLayerRollbackHomePath(snapshots []fsLayerBaseSnapshot, fileID string) string {
	for i := range snapshots {
		if snapshots[i].FileID == fileID && snapshots[i].Exists && !snapshots[i].CleanupOnly {
			return snapshots[i].Path
		}
	}
	return ""
}

func fsLayerMoveOursInode(ctx context.Context, b *backendpkg.Dat9Backend, from, to, expectedFileID string, ours, created map[string]struct{}) (string, error) {
	if from == "" || to == "" || from == to {
		return "", nil
	}
	tnf, err := b.StatNodeCtx(ctx, to)
	if errors.Is(err, datastore.ErrNotFound) {
		return "", fsLayerRenameNoReplace(ctx, b, from, to, expectedFileID)
	}
	if err != nil {
		return "", err
	}
	tgot := datastore.NodeFileID(tnf)
	if tgot != "" {
		if _, isOurs := ours[tgot]; isOurs {
			tmp := fsLayerRollbackTempPath(to)
			if err := fsLayerRenameNoReplace(ctx, b, to, tmp, tgot); err != nil {
				return "", err
			}
			if err := fsLayerRenameNoReplace(ctx, b, from, to, expectedFileID); err != nil {
				return tmp, err
			}
			return tmp, nil
		}
		if _, isCreated := created[tgot]; isCreated {
			if err := fsLayerRemoveOwnedPath(ctx, b, to, tgot, ours, created); err != nil {
				return "", err
			}
			return "", fsLayerRenameNoReplace(ctx, b, from, to, expectedFileID)
		}
	}
	return "", fmt.Errorf("fs layer rollback: %s occupied by foreign inode", to)
}

func fsLayerRollbackTempPath(path string) string {
	parent := pathutil.ParentPath(path)
	name := pathutil.BaseName(path)
	sum := sha256.Sum256([]byte(path))
	tag := hex.EncodeToString(sum[:4])
	if strings.HasSuffix(path, "/") {
		return parent + ".fs-layer-rb-" + tag + "-" + name + "/"
	}
	return parent + ".fs-layer-rb-" + tag + "-" + name
}

func fsLayerRemoveEmptyRollbackTemps(ctx context.Context, b *backendpkg.Dat9Backend, extras []string) error {
	for i := len(extras) - 1; i >= 0; i-- {
		nf, err := b.StatNodeCtx(ctx, extras[i])
		if errors.Is(err, datastore.ErrNotFound) {
			continue
		}
		if err != nil {
			return err
		}
		if nf.Node.IsDirectory {
			children, readErr := b.ReadDirCtx(ctx, extras[i])
			if readErr != nil && !errors.Is(readErr, datastore.ErrNotFound) {
				return readErr
			}
			if len(children) > 0 {
				continue
			}
		}
		if err := fsLayerRemovePathForRollback(ctx, b, extras[i], nf); err != nil && !errors.Is(err, datastore.ErrDirectoryNotEmpty) {
			return err
		}
	}
	return nil
}

func fsLayerRestoreMustRecreate(nf *datastore.NodeWithFile, err error) bool {
	if errors.Is(err, datastore.ErrNotFound) || nf == nil {
		return true
	}
	if nf.Node.IsDirectory || nf.File == nil {
		return true
	}
	return isFSLayerSymlinkMode(nf.File.Mode)
}

func fsLayerRestoreLeaveReplacement(nf *datastore.NodeWithFile, snap *fsLayerBaseSnapshot, ours map[string]struct{}) bool {
	if snap == nil || nf == nil {
		return false
	}
	got := datastore.NodeFileID(nf)
	if got == "" || got == snap.FileID {
		return false
	}
	if _, ok := ours[got]; ok {
		return false
	}
	return true
}

func fsLayerRestoreLeaveCreateRace(err error) bool {
	return errors.Is(err, datastore.ErrFSLayerBasePathExists) ||
		errors.Is(err, datastore.ErrPathConflict) ||
		errors.Is(err, datastore.ErrRevisionConflict)
}

func fsLayerRestoreIgnoreCreateRace(err error) error {
	if fsLayerRestoreLeaveCreateRace(err) {
		return nil
	}
	return err
}

func fsLayerRemovePathForRollback(ctx context.Context, b *backendpkg.Dat9Backend, path string, nf *datastore.NodeWithFile) error {
	if nf == nil {
		var err error
		nf, err = b.StatNodeCtx(ctx, path)
		if errors.Is(err, datastore.ErrNotFound) {
			return nil
		}
		if err != nil {
			return err
		}
	}
	expectedFileID := datastore.NodeFileID(nf)
	if expectedFileID == "" {
		return nil
	}
	failpoint.InjectCall("fsLayerRestoreRemoveBeforeDelete", path, expectedFileID)
	if nf.Node.IsDirectory {
		return b.RemoveDirFencedCtx(ctx, path, expectedFileID)
	}
	return b.RemoveCtxIfFileID(ctx, path, expectedFileID)
}

func fsLayerRemoveOwnedPath(ctx context.Context, b *backendpkg.Dat9Backend, path, expectedFileID string, ours, created map[string]struct{}) error {
	if expectedFileID == "" {
		return nil
	}
	failpoint.InjectCall("fsLayerRemoveOwnedBeforeDelete", path, expectedFileID)
	if strings.HasSuffix(path, "/") {
		return fsLayerRemoveCreatedTree(ctx, b, path, expectedFileID, ours, created)
	}
	return b.RemoveCtxIfFileID(ctx, path, expectedFileID)
}

func fsLayerRemoveCreatedTree(ctx context.Context, b *backendpkg.Dat9Backend, dir, expectedFileID string, ours, created map[string]struct{}) error {
	children, err := b.ReadDirCtx(ctx, dir)
	if errors.Is(err, datastore.ErrNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	prefix := dir
	if prefix != "/" && !strings.HasSuffix(prefix, "/") {
		prefix += "/"
	}
	for _, child := range children {
		path := prefix + child.Name
		if child.IsDir {
			path += "/"
		}
		nf, err := b.StatNodeCtx(ctx, path)
		if errors.Is(err, datastore.ErrNotFound) {
			continue
		}
		if err != nil {
			return err
		}
		got := datastore.NodeFileID(nf)
		if got == "" {
			continue
		}
		if _, ok := created[got]; !ok {
			continue
		}
		if err := fsLayerRemoveOwnedPath(ctx, b, path, got, ours, created); err != nil {
			return err
		}
	}
	left, err := b.ReadDirCtx(ctx, dir)
	if errors.Is(err, datastore.ErrNotFound) {
		return nil
	}
	if err != nil {
		return err
	}
	if len(left) > 0 {
		return nil
	}
	if expectedFileID == "" {
		return nil
	}
	if _, ok := created[expectedFileID]; !ok {
		return nil
	}
	return b.RemoveDirFencedCtx(ctx, dir, expectedFileID)
}

func fsLayerRenameNoReplace(ctx context.Context, b *backendpkg.Dat9Backend, from, to, expectedFileID string) error {
	if _, err := b.StatNodeCtx(ctx, to); err == nil {
		return fmt.Errorf("%w: %s", datastore.ErrPathConflict, to)
	} else if !errors.Is(err, datastore.ErrNotFound) {
		return err
	}
	failpoint.InjectCall("fsLayerRenameNoReplaceAfterDestMissing", from, to)
	fnf, err := b.StatNodeCtx(ctx, from)
	if err != nil {
		return err
	}
	if expectedFileID != "" && datastore.NodeFileID(fnf) != expectedFileID {
		return fmt.Errorf("fs layer rollback: %s occupied by foreign inode", from)
	}
	failpoint.InjectCall("fsLayerRenameNoReplaceAfterSourceCheck", from, to)
	if fnf.Node.IsDirectory {
		return b.RenameDirNoReplaceIfInodeIDCtx(ctx, from, to, expectedFileID)
	}
	return b.RenameFileNoReplaceIfFileIDCtx(ctx, from, to, expectedFileID)
}

func isFSLayerSymlinkMode(mode uint32) bool {
	return mode&0o170000 == 0o120000
}

// planFSLayerCommitVsMain implements D18: apply set ≡ overlay − live main.
// Paths that already match live main are skipped. Remaining drafts keep the
// copy-up snapshot from OverlayCommitDrafts so preflight can return
// "base revision changed" instead of last-write-wins.
func planFSLayerCommitVsMain(ctx context.Context, b *backendpkg.Dat9Backend, overlay map[string]datastore.FSLayerOverlayNode, landed func(datastore.FSLayerRenameHop) bool) ([]datastore.FSLayerEntry, error) {
	drafts := datastore.OverlayCommitDrafts(overlay)
	out := make([]datastore.FSLayerEntry, 0, len(drafts))
	for i := range drafts {
		e := drafts[i]
		nf, err := b.StatNodeCtx(ctx, e.Path)
		if err != nil && !errors.Is(err, datastore.ErrNotFound) {
			return nil, err
		}
		missing := errors.Is(err, datastore.ErrNotFound)
		if e.Op == datastore.FSLayerEntryOpRename {
			if dest := fsLayerRenameTarget(&e); dest != "" {
				if n, ok := datastore.OverlayLookup(overlay, dest); ok {
					if datastore.OverlayRenameHopsAllLanded(n, landed) {
						continue
					}
					if live := datastore.OverlayLiveRenameSourceIf(n, landed); live != "" {
						if live != e.Path {
							if ln, ok := datastore.OverlayLookup(overlay, live); ok {
								// Live path is a different inode only when
								// that hop copied up an existing occupant.
								// A claim-less live node is an overlay dest
								// that was not on main; after the hop lands
								// the file there is still the first source.
								if ln.Entry.BaseInodeID != "" || ln.Entry.BaseRevision != 0 {
									e.BaseInodeID = ln.Entry.BaseInodeID
									e.BaseRevision = ln.Entry.BaseRevision
								}
							}
						}
						e.Path = live
						_, err = b.StatNodeCtx(ctx, e.Path)
						if err != nil && !errors.Is(err, datastore.ErrNotFound) {
							return nil, err
						}
						missing = errors.Is(err, datastore.ErrNotFound)
					}
				}
				if dest := fsLayerRenameTarget(&e); dest != "" && fsLayerSamePath(e.Path, dest) {
					// Dest-space hop rewrite can make a landed file hop look
					// like dest→dest; the uncommitted ancestor dir move applies it.
					continue
				}
				already, cycleErr := fsLayerRenameCycleAlreadyOnMain(ctx, b, overlay, &e, landed)
				if cycleErr != nil {
					return nil, cycleErr
				}
				if already {
					continue
				}
			}
			if fsLayerRenameSettledOnMain(ctx, b, overlay, &e, landed) {
				continue
			}
			if missing {
				dest := fsLayerRenameTarget(&e)
				if dest != "" {
					_, destErr := b.StatNodeCtx(ctx, dest)
					if destErr != nil && !errors.Is(destErr, datastore.ErrNotFound) {
						return nil, destErr
					}
					if destErr == nil {
						if n, ok := datastore.OverlayLookup(overlay, dest); ok && datastore.OverlayDestHopLanded(n, landed) {
							// Source gone, dest present, and this dest hop
							// already applied — skip. Without a landed
							// signal keep the draft for preflight.
							continue
						}
					}
				}
			}
			out = append(out, e)
			continue
		}
		if e.Op == datastore.FSLayerEntryOpWhiteout {
			if fsLayerDraftRenameVacates(out, e.Path) {
				// Intermediate hop already vacated by a kept rename in this set.
				continue
			}
			if missing {
				// Dir/file will exist after a kept rename in this apply set.
				if fsLayerDraftRenameCreates(out, e.Path) {
					out = append(out, e)
				}
				continue
			}
			out = append(out, e)
			continue
		}
		if e.Op == datastore.FSLayerEntryOpChmod {
			if fsLayerDraftRenameCreates(out, e.Path) {
				// Dest chmod after a rename still in this set. Do not
				// skip-if-equal against the live occupant (swap dest is
				// a different file).
				out = append(out, e)
				continue
			}
			if missing {
				continue
			}
			if overlayNodeModeMatches(nf, e.Mode) {
				continue
			}
			out = append(out, e)
			continue
		}
		if e.Kind == datastore.FSLayerEntryKindDir || e.Op == datastore.FSLayerEntryOpMkdir {
			if fsLayerDraftRenameVacates(drafts, e.Path) {
				// mv dir new; mkdir dir — live main still has the old dir.
				out = append(out, e)
				continue
			}
			if !missing && nf.Node.IsDirectory {
				if overlayNodeModeMatches(nf, e.Mode) {
					continue
				}
				e.Op = datastore.FSLayerEntryOpChmod
				out = append(out, e)
				continue
			}
			out = append(out, e)
			continue
		}
		if e.Op == datastore.FSLayerEntryOpSymlink || e.Kind == datastore.FSLayerEntryKindSymlink {
			if !missing && !fsLayerDraftRenameVacates(drafts, e.Path) {
				same, serr := fsLayerSymlinkMatchesMain(ctx, b, &e)
				if serr != nil {
					return nil, serr
				}
				if same {
					continue
				}
			}
			out = append(out, e)
			continue
		}
		if !missing && nf.File != nil && !fsLayerDraftRenameVacates(drafts, e.Path) {
			if fsLayerOverlayMatchesMain(&e, nf) {
				continue
			}
			same, cmpErr := fsLayerInlineBlobMatchesMain(ctx, b, &e, nf)
			if cmpErr != nil {
				return nil, cmpErr
			}
			if same {
				continue
			}
		}
		// Do not restamp BaseRevision from live main. fillFSLayerBaseSnapshot
		// would adopt the post-copy-up identity and preflight would pass.
		out = append(out, e)
	}
	return datastore.OverlayBreakRenameCycles(out), nil
}

func fsLayerRenameCycleAlreadyOnMain(ctx context.Context, b *backendpkg.Dat9Backend, overlay map[string]datastore.FSLayerOverlayNode, e *datastore.FSLayerEntry, landed func(datastore.FSLayerRenameHop) bool) (bool, error) {
	if e == nil || landed == nil {
		return false, nil
	}
	dest := fsLayerRenameTarget(e)
	if dest == "" {
		return false, nil
	}
	_, destErr := b.StatNodeCtx(ctx, dest)
	if destErr != nil {
		if errors.Is(destErr, datastore.ErrNotFound) {
			return false, nil
		}
		return false, destErr
	}
	n, ok := datastore.OverlayLookup(overlay, dest)
	if !ok {
		return false, nil
	}
	live := e.Path
	// Cycle existence is about the folded original source, not the
	// landed-aware live pointer. A partially landed chain must not skip.
	if src := datastore.OverlayLiveRenameSourceIf(n, nil); src != "" {
		live = src
	}
	ln, liveOK := datastore.OverlayLookup(overlay, live)
	if !liveOK || ln.RenameFrom == "" || ln.Whiteout {
		return false, nil
	}
	_, srcErr := b.StatNodeCtx(ctx, live)
	if srcErr != nil {
		if errors.Is(srcErr, datastore.ErrNotFound) {
			return false, nil
		}
		return false, srcErr
	}
	return datastore.OverlayDestHopLanded(n, landed), nil
}

func fsLayerRenameSettledOnMain(ctx context.Context, b *backendpkg.Dat9Backend, overlay map[string]datastore.FSLayerOverlayNode, e *datastore.FSLayerEntry, landed func(datastore.FSLayerRenameHop) bool) bool {
	if e == nil || landed == nil {
		return false
	}
	dest := fsLayerRenameTarget(e)
	if dest == "" {
		return false
	}
	n, ok := datastore.OverlayLookup(overlay, dest)
	if !ok || n.RenameFrom == "" || n.Whiteout {
		return false
	}
	if !datastore.OverlayDestHopLanded(n, landed) {
		return false
	}
	_, err := b.StatNodeCtx(ctx, dest)
	return err == nil
}

func fsLayerDraftRenameVacates(drafts []datastore.FSLayerEntry, path string) bool {
	if path == "" {
		return false
	}
	for i := range drafts {
		if drafts[i].Op == datastore.FSLayerEntryOpRename && drafts[i].Path == path {
			return true
		}
	}
	return false
}

func fsLayerDraftRenameCreates(drafts []datastore.FSLayerEntry, path string) bool {
	if path == "" {
		return false
	}
	for i := range drafts {
		if drafts[i].Op != datastore.FSLayerEntryOpRename {
			continue
		}
		dest := fsLayerRenameTarget(&drafts[i])
		if dest == path {
			return true
		}
		destPath, err := canonicalFSLayerServerPath(dest, "", string(drafts[i].Kind))
		if err == nil && destPath == path {
			return true
		}
		if destPath == "" {
			destPath = dest
		}
		if drafts[i].Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(destPath, "/") {
			prefix := destPath
			if !strings.HasSuffix(prefix, "/") {
				prefix += "/"
			}
			if strings.HasPrefix(path, prefix) {
				return true
			}
		}
	}
	return false
}

func overlayNodeModeMatches(nf *datastore.NodeWithFile, mode uint32) bool {
	if nf == nil || mode == 0 {
		return true
	}
	cur := nf.Mode
	if nf.File != nil && nf.File.Mode != 0 {
		cur = nf.File.Mode
	}
	return cur&0o777 == mode&0o777
}

func fsLayerOverlayMatchesMain(e *datastore.FSLayerEntry, nf *datastore.NodeWithFile) bool {
	if e == nil || nf == nil || nf.File == nil {
		return false
	}
	if e.Kind == datastore.FSLayerEntryKindDir || nf.Node.IsDirectory {
		return false
	}
	if e.Op == datastore.FSLayerEntryOpSymlink || e.Kind == datastore.FSLayerEntryKindSymlink {
		return false
	}
	if e.ChecksumSHA256 != "" && nf.File.ChecksumSHA256 != "" && e.ChecksumSHA256 != nf.File.ChecksumSHA256 {
		return false
	}
	if e.SizeBytes > 0 && nf.File.SizeBytes != e.SizeBytes {
		return false
	}
	if e.StorageRef != "" && nf.File.StorageRef != "" && e.StorageRef != nf.File.StorageRef {
		return false
	}
	if e.ChecksumSHA256 == "" && e.StorageRef == "" && len(e.ContentBlob) > 0 {
		if int64(len(e.ContentBlob)) != nf.File.SizeBytes {
			return false
		}
	}
	if e.Mode != 0 && !overlayNodeModeMatches(nf, e.Mode) {
		return false
	}
	// Require at least one positive identity signal so empty overlay
	// entries are not treated as equal to an existing main file.
	if e.ChecksumSHA256 == "" && e.StorageRef == "" && len(e.ContentBlob) == 0 {
		return false
	}
	if e.ChecksumSHA256 != "" && nf.File.ChecksumSHA256 == e.ChecksumSHA256 {
		return true
	}
	if e.StorageRef != "" && nf.File.StorageRef == e.StorageRef && e.SizeBytes == nf.File.SizeBytes {
		return true
	}
	return false
}

func fsLayerInlineBlobMatchesMain(ctx context.Context, b *backendpkg.Dat9Backend, e *datastore.FSLayerEntry, nf *datastore.NodeWithFile) (bool, error) {
	if b == nil || e == nil || nf == nil || nf.File == nil || len(e.ContentBlob) == 0 {
		return false, nil
	}
	if e.ChecksumSHA256 != "" || e.StorageRef != "" {
		return false, nil
	}
	if !overlayNodeModeMatches(nf, e.Mode) {
		return false, nil
	}
	want := int64(len(e.ContentBlob))
	if nf.File.SizeBytes != want {
		return false, nil
	}
	data, err := b.ReadCtx(ctx, e.Path, 0, want+1)
	if errors.Is(err, datastore.ErrNotFound) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return bytes.Equal(data, e.ContentBlob), nil
}

func fsLayerSymlinkMatchesMain(ctx context.Context, b *backendpkg.Dat9Backend, e *datastore.FSLayerEntry) (bool, error) {
	if b == nil || e == nil {
		return false, nil
	}
	nf, err := b.StatNodeCtx(ctx, e.Path)
	if errors.Is(err, datastore.ErrNotFound) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	if nf.File == nil || !isFSLayerSymlinkMode(nf.File.Mode) {
		return false, nil
	}
	want := e.ContentText
	if want == "" && len(e.ContentBlob) > 0 {
		want = string(e.ContentBlob)
	}
	if want == "" {
		return false, nil
	}
	data, err := b.ReadCtx(ctx, e.Path, 0, -1)
	if errors.Is(err, datastore.ErrNotFound) {
		return false, nil
	}
	if err != nil {
		return false, err
	}
	return string(data) == want, nil
}

// fillFSLayerBaseSnapshot records live main identity at copy-up / write time.
// Dest-space paths (under an uncommitted ancestor rename dest) stamp the
// remapped live-main path, same as GET. Commit planning must not call this:
// restamping from live main at commit time turns V1 stale-base conflicts
// into last-write-wins.
func fillFSLayerBaseSnapshot(ctx context.Context, b *backendpkg.Dat9Backend, store *datastore.Store, layer *datastore.FSLayer, entry *datastore.FSLayerEntry, overlay map[string]datastore.FSLayerOverlayNode) error {
	if b == nil || entry == nil {
		return nil
	}
	nf, err := b.StatNodeCtx(ctx, entry.Path)
	if err == nil {
		if nf.File != nil && nf.File.ContentLayout == datastore.ContentLayoutAppendLog {
			return backendpkg.ErrAppendLogUnsupported
		}
		return fillFSLayerClaimAgainstNode(entry, nf)
	}
	if !errors.Is(err, datastore.ErrNotFound) {
		return err
	}
	if store == nil || layer == nil {
		return nil
	}
	// Raw path is missing. Remap dest-space writes onto live main, except
	// an exact uncommitted rename dest (mv a b; write b): a dest-path claim
	// would 409 "base path missing". OCC stays on the source whiteout.
	claimed := entry.BaseRevision > 0 || entry.BaseInodeID != ""
	if overlay == nil {
		hasRemap, derr := store.FSLayerChainHasRemapDrafts(ctx, layer.LayerID, nil)
		if derr != nil {
			return derr
		}
		if !hasRemap {
			if claimed {
				return fmt.Errorf("%w: %w for %s (base_revision=%d)", datastore.ErrRevisionConflict, datastore.ErrFSLayerBasePathMissing, entry.Path, entry.BaseRevision)
			}
			return nil
		}
		var oerr error
		overlay, oerr = store.MaterializeFSLayerOverlay(ctx, layer.LayerID, nil)
		if oerr != nil {
			return oerr
		}
	}
	if n, hasNode := datastore.OverlayLookup(overlay, entry.Path); hasNode && n.RenameFrom != "" {
		entry.BaseInodeID = ""
		entry.BaseRevision = 0
		return nil
	}
	hidden, remapped, rerr := fsLayerObjectMainCandidates(ctx, store, layer, overlay, entry.Path, datastore.FSLayerOverlayNode{}, false)
	if rerr != nil {
		return rerr
	}
	if hidden || remapped == "" || remapped == entry.Path {
		if claimed {
			return fmt.Errorf("%w: %w for %s (base_revision=%d)", datastore.ErrRevisionConflict, datastore.ErrFSLayerBasePathMissing, entry.Path, entry.BaseRevision)
		}
		return nil
	}
	nf, err = b.StatNodeCtx(ctx, remapped)
	if err != nil {
		if errors.Is(err, datastore.ErrNotFound) {
			if claimed {
				return fmt.Errorf("%w: %w for %s (base_revision=%d)", datastore.ErrRevisionConflict, datastore.ErrFSLayerBasePathMissing, remapped, entry.BaseRevision)
			}
			return nil
		}
		return err
	}
	if nf.File != nil && nf.File.ContentLayout == datastore.ContentLayoutAppendLog {
		return backendpkg.ErrAppendLogUnsupported
	}
	return fillFSLayerClaimAgainstNode(entry, nf)
}

func fillFSLayerClaimAgainstNode(entry *datastore.FSLayerEntry, nf *datastore.NodeWithFile) error {
	if entry == nil || nf == nil {
		return nil
	}
	if nf.File != nil {
		if entry.BaseRevision > 0 && nf.File.Revision != entry.BaseRevision {
			return fmt.Errorf("%w: %w for %s (current=%d, want=%d)", datastore.ErrRevisionConflict, datastore.ErrFSLayerBaseRevisionChanged, entry.Path, nf.File.Revision, entry.BaseRevision)
		}
		if entry.BaseInodeID != "" && datastore.NodeFileID(nf) != entry.BaseInodeID {
			return fmt.Errorf("%w: %w for %s", datastore.ErrRevisionConflict, datastore.ErrFSLayerBaseInodeChanged, entry.Path)
		}
		if entry.BaseInodeID == "" {
			entry.BaseInodeID = nf.File.FileID
		}
		if entry.BaseRevision == 0 {
			entry.BaseRevision = nf.File.Revision
		}
		return nil
	}
	if entry.BaseInodeID == "" && nf.Node.InodeID != "" {
		entry.BaseInodeID = nf.Node.InodeID
	}
	return nil
}

func preflightFSLayerCommit(ctx context.Context, b *backendpkg.Dat9Backend, entries []datastore.FSLayerEntry, recoveringCommit bool) []fsLayerCommitConflict {
	var conflicts []fsLayerCommitConflict
	for i := range entries {
		entry := &entries[i]
		if recoveringCommit {
			applied, err := fsLayerEntryAlreadyApplied(ctx, b, entry)
			if err != nil {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: err.Error()})
				continue
			}
			if applied {
				continue
			}
		}
		if entry.Op == datastore.FSLayerEntryOpRename {
			conflicts = append(conflicts, preflightFSLayerRename(ctx, b, entry, entries)...)
		}
		if fsLayerDraftRenameCreates(entries, entry.Path) {
			// Dest chmod (including swap dest) must not compare against the
			// live occupant. Other dest-space ops skip only while the dest
			// is still missing; if it already exists, fall through to OCC.
			if entry.Op == datastore.FSLayerEntryOpChmod {
				continue
			}
			_, destErr := b.StatNodeCtx(ctx, entry.Path)
			if errors.Is(destErr, datastore.ErrNotFound) {
				continue
			}
			if destErr != nil {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: destErr.Error()})
				continue
			}
		}
		if entry.Op == datastore.FSLayerEntryOpWhiteout && (entry.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entry.Path, "/")) {
			children, err := b.ReadDirCtx(ctx, entry.Path)
			if err == nil && len(children) > 0 {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: "directory whiteout requires empty directory"})
				continue
			}
			if err != nil && !errors.Is(err, datastore.ErrNotFound) {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: err.Error()})
				continue
			}
		}
		if entry.BaseRevision == 0 && entry.BaseInodeID == "" {
			if _, err := b.StatNodeCtx(ctx, entry.Path); err == nil {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: "base path exists"})
			} else if !errors.Is(err, datastore.ErrNotFound) {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: err.Error()})
			}
			continue
		}
		if entry.BaseRevision <= 0 {
			nf, err := b.StatNodeCtx(ctx, entry.Path)
			if errors.Is(err, datastore.ErrNotFound) {
				conflicts = append(conflicts, fsLayerCommitConflict{
					Path:   entry.Path,
					Reason: "base path missing",
				})
				continue
			}
			if err != nil {
				conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: err.Error()})
				continue
			}
			if datastore.NodeFileID(nf) != entry.BaseInodeID {
				conflicts = append(conflicts, fsLayerCommitConflict{
					Path:   entry.Path,
					Reason: "base inode changed",
				})
			}
			continue
		}
		nf, err := b.StatNodeCtx(ctx, entry.Path)
		if errors.Is(err, datastore.ErrNotFound) {
			conflicts = append(conflicts, fsLayerCommitConflict{
				Path:         entry.Path,
				Reason:       "base path missing",
				WantRevision: entry.BaseRevision,
			})
			continue
		}
		if err != nil {
			conflicts = append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: err.Error()})
			continue
		}
		if nf.File == nil {
			conflicts = append(conflicts, fsLayerCommitConflict{
				Path:   entry.Path,
				Reason: "base inode changed",
			})
			continue
		}
		if entry.BaseInodeID != "" && datastore.NodeFileID(nf) != entry.BaseInodeID {
			conflicts = append(conflicts, fsLayerCommitConflict{
				Path:   entry.Path,
				Reason: "base inode changed",
			})
			continue
		}
		if nf.File.Revision != entry.BaseRevision {
			conflicts = append(conflicts, fsLayerCommitConflict{
				Path:         entry.Path,
				Reason:       "base revision changed",
				BaseRevision: nf.File.Revision,
				WantRevision: entry.BaseRevision,
			})
		}
	}
	return conflicts
}

func fsLayerEntryAlreadyApplied(ctx context.Context, b *backendpkg.Dat9Backend, entry *datastore.FSLayerEntry) (bool, error) {
	if entry == nil {
		return false, nil
	}
	switch entry.Op {
	case datastore.FSLayerEntryOpUpsert:
		nf, err := b.StatNodeCtx(ctx, entry.Path)
		if errors.Is(err, datastore.ErrNotFound) {
			return false, nil
		}
		if err != nil {
			return false, err
		}
		if nf.File == nil || nf.Node.IsDirectory {
			return false, nil
		}
		if nf.File.SizeBytes != entry.SizeBytes {
			return false, nil
		}
		if entry.ChecksumSHA256 != "" && nf.File.ChecksumSHA256 != entry.ChecksumSHA256 {
			return false, nil
		}
		if entry.Mode != 0 && nf.File.Mode != 0 && nf.File.Mode&0o777 != entry.Mode&0o777 {
			return false, nil
		}
		return true, nil
	case datastore.FSLayerEntryOpMkdir:
		nf, err := b.StatNodeCtx(ctx, entry.Path)
		if errors.Is(err, datastore.ErrNotFound) {
			return false, nil
		}
		if err != nil {
			return false, err
		}
		return nf.Node.IsDirectory, nil
	case datastore.FSLayerEntryOpWhiteout:
		_, err := b.StatNodeCtx(ctx, entry.Path)
		if errors.Is(err, datastore.ErrNotFound) {
			return true, nil
		}
		if err != nil {
			return false, err
		}
		return false, nil
	case datastore.FSLayerEntryOpChmod:
		if entry.Mode == 0 {
			return true, nil
		}
		nf, err := b.StatNodeCtx(ctx, entry.Path)
		if errors.Is(err, datastore.ErrNotFound) {
			return false, nil
		}
		if err != nil {
			return false, err
		}
		mode := nf.Mode
		if nf.File != nil && nf.File.Mode != 0 {
			mode = nf.File.Mode
		}
		return mode&0o777 == entry.Mode&0o777, nil
	case datastore.FSLayerEntryOpSymlink:
		nf, err := b.StatNodeCtx(ctx, entry.Path)
		if errors.Is(err, datastore.ErrNotFound) {
			return false, nil
		}
		if err != nil {
			return false, err
		}
		if nf.File == nil || !isFSLayerSymlinkMode(nf.File.Mode) {
			return false, nil
		}
		target := entry.ContentText
		if target == "" && len(entry.ContentBlob) > 0 {
			target = string(entry.ContentBlob)
		}
		data, err := b.ReadCtx(ctx, entry.Path, 0, -1)
		if err != nil {
			return false, err
		}
		return string(data) == target, nil
	case datastore.FSLayerEntryOpRename:
		target := fsLayerRenameTarget(entry)
		if target == "" {
			return false, nil
		}
		targetPath, err := canonicalFSLayerServerPath(target, "", "")
		if err != nil {
			return false, err
		}
		_, srcErr := b.StatNodeCtx(ctx, entry.Path)
		_, dstErr := b.StatNodeCtx(ctx, targetPath)
		if errors.Is(srcErr, datastore.ErrNotFound) && dstErr == nil {
			return true, nil
		}
		if srcErr != nil && !errors.Is(srcErr, datastore.ErrNotFound) {
			return false, srcErr
		}
		if dstErr != nil && !errors.Is(dstErr, datastore.ErrNotFound) {
			return false, dstErr
		}
		return false, nil
	default:
		return false, nil
	}
}

func preflightFSLayerRename(ctx context.Context, b *backendpkg.Dat9Backend, entry *datastore.FSLayerEntry, applySet []datastore.FSLayerEntry) []fsLayerCommitConflict {
	var conflicts []fsLayerCommitConflict
	source, err := b.StatNodeCtx(ctx, entry.Path)
	sourceMissing := errors.Is(err, datastore.ErrNotFound)
	if sourceMissing && !fsLayerDraftRenameCreates(applySet, entry.Path) {
		return append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: "base path missing"})
	}
	if err != nil && !sourceMissing {
		return append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: err.Error()})
	}
	target := fsLayerRenameTarget(entry)
	if target == "" {
		return append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: "rename target is required"})
	}
	targetKind := string(entry.Kind)
	if source != nil && source.Node.IsDirectory {
		targetKind = string(datastore.FSLayerEntryKindDir)
	}
	targetPath, err := canonicalFSLayerServerPath(target, "", targetKind)
	if err != nil {
		return append(conflicts, fsLayerCommitConflict{Path: entry.Path, Reason: "invalid rename target"})
	}
	if targetPath == entry.Path {
		return conflicts
	}
	if _, err := b.StatNodeCtx(ctx, targetPath); err == nil {
		if !fsLayerDraftRenameVacates(applySet, targetPath) {
			return append(conflicts, fsLayerCommitConflict{Path: targetPath, Reason: "rename target exists"})
		}
	} else if !errors.Is(err, datastore.ErrNotFound) {
		return append(conflicts, fsLayerCommitConflict{Path: targetPath, Reason: err.Error()})
	}
	return conflicts
}

func ensureFSLayerEntryBaseStillMatches(ctx context.Context, b *backendpkg.Dat9Backend, entry *datastore.FSLayerEntry, touched map[string]struct{}) error {
	if entry == nil || b == nil {
		return nil
	}
	switch entry.Op {
	case datastore.FSLayerEntryOpWhiteout, datastore.FSLayerEntryOpChmod, datastore.FSLayerEntryOpRename:
	default:
		return nil
	}
	if _, ok := touched[entry.Path]; ok {
		return nil
	}
	if entry.BaseRevision == 0 && entry.BaseInodeID == "" {
		return nil
	}
	nf, err := b.StatNodeCtx(ctx, entry.Path)
	if errors.Is(err, datastore.ErrNotFound) {
		return fmt.Errorf("%w: %w", datastore.ErrRevisionConflict, datastore.ErrFSLayerBasePathMissing)
	}
	if err != nil {
		return err
	}
	if entry.BaseInodeID != "" && datastore.NodeFileID(nf) != entry.BaseInodeID {
		return fmt.Errorf("%w: %w", datastore.ErrRevisionConflict, datastore.ErrFSLayerBaseInodeChanged)
	}
	if entry.BaseRevision > 0 && nf.File != nil && nf.File.Revision != entry.BaseRevision {
		return fmt.Errorf("%w: %w", datastore.ErrRevisionConflict, datastore.ErrFSLayerBaseRevisionChanged)
	}
	return nil
}

func markFSLayerEntryTouched(touched map[string]struct{}, entry *datastore.FSLayerEntry) {
	if touched == nil || entry == nil {
		return
	}
	if entry.Path != "" {
		touched[entry.Path] = struct{}{}
	}
	if entry.Op != datastore.FSLayerEntryOpRename {
		return
	}
	target := fsLayerRenameTarget(entry)
	if target != "" {
		touched[target] = struct{}{}
	}
}

func fsLayerPathTouched(touched map[string]struct{}, path string) bool {
	if touched == nil || path == "" {
		return false
	}
	if _, ok := touched[path]; ok {
		return true
	}
	for prefix := range touched {
		if fsLayerPathDescendsFrom(path, prefix) {
			return true
		}
	}
	return false
}

func fsLayerApplyExpectedRevision(entry *datastore.FSLayerEntry, touched map[string]struct{}) int64 {
	if entry == nil {
		return -1
	}
	if fsLayerPathTouched(touched, entry.Path) {
		return -1
	}
	return entry.BaseRevision
}

func fsLayerApplyExpectedFileID(entry *datastore.FSLayerEntry, touched map[string]struct{}) string {
	if entry == nil || fsLayerPathTouched(touched, entry.Path) {
		return ""
	}
	return strings.TrimSpace(entry.BaseInodeID)
}

func fsLayerApplyOCCConflict(path string, err error) (fsLayerCommitConflict, bool) {
	if err == nil {
		return fsLayerCommitConflict{}, false
	}
	if !errors.Is(err, datastore.ErrRevisionConflict) {
		return fsLayerCommitConflict{}, false
	}
	reason := "base revision changed"
	switch {
	case errors.Is(err, datastore.ErrFSLayerBaseInodeChanged):
		reason = "base inode changed"
	case errors.Is(err, datastore.ErrFSLayerBasePathMissing):
		reason = "base path missing"
	case errors.Is(err, datastore.ErrFSLayerBasePathExists):
		reason = "base path exists"
	case errors.Is(err, datastore.ErrFSLayerBaseRevisionChanged):
		reason = "base revision changed"
	}
	return fsLayerCommitConflict{Path: path, Reason: reason}, true
}

func fsLayerEntryModeSuperseded(entries []datastore.FSLayerEntry, idx int) bool {
	if idx < 0 || idx >= len(entries) {
		return false
	}
	path := entries[idx].Path
	if path == "" {
		return false
	}
	for i := idx + 1; i < len(entries); i++ {
		next := &entries[i]
		if next.Path == path {
			return true
		}
		if next.Op == datastore.FSLayerEntryOpWhiteout && (next.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(next.Path, "/")) && fsLayerPathDescendsFrom(path, next.Path) {
			return true
		}
		if next.Op == datastore.FSLayerEntryOpRename && (next.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(next.Path, "/")) && fsLayerPathDescendsFrom(path, next.Path) {
			return true
		}
	}
	return false
}

func applyFSLayerEntry(ctx context.Context, b *backendpkg.Dat9Backend, entry *datastore.FSLayerEntry, recoveringCommit bool, touched map[string]struct{}, modeMayBeSuperseded bool) ([]string, error) {
	if recoveringCommit {
		applied, err := fsLayerEntryAlreadyApplied(ctx, b, entry)
		if err != nil {
			return nil, err
		}
		if applied {
			return nil, nil
		}
	}
	if err := ensureFSLayerEntryBaseStillMatches(ctx, b, entry, touched); err != nil {
		return nil, err
	}
	failpoint.InjectCall("fsLayerApplyAfterEnsure", entry.Path, string(entry.Op))
	switch entry.Op {
	case datastore.FSLayerEntryOpUpsert:
		expectedRevision := fsLayerApplyExpectedRevision(entry, touched)
		if entry.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entry.Path, "/") {
			mode := entry.Mode
			if mode == 0 {
				mode = 0o755
			}
			createdIDs, err := b.MkdirCreatedCtx(ctx, entry.Path, mode&0o777)
			if errors.Is(err, datastore.ErrPathConflict) {
				nf, statErr := b.StatNodeCtx(ctx, entry.Path)
				if statErr != nil {
					return nil, statErr
				}
				if !nf.Node.IsDirectory {
					return nil, fsLayerApplyPathExists(err)
				}
				expectedFileID := fsLayerApplyExpectedFileID(entry, touched)
				if expectedFileID == "" || datastore.NodeFileID(nf) != expectedFileID {
					return nil, fsLayerApplyPathExists(err)
				}
				if entry.Mode != 0 {
					return nil, fsLayerMarkApplyCommitted(applyFSLayerEntryMode(ctx, b, entry.Path, entry.Mode&0o777, modeMayBeSuperseded))
				}
				return nil, nil
			}
			if err != nil {
				return nil, err
			}
			return createdIDs, nil
		}
		expectedFileID := fsLayerApplyExpectedFileID(entry, touched)
		var createdIDs []string
		var err error
		if entry.StorageRef != "" || entry.StorageType == string(datastore.StorageS3) {
			writeEntry := *entry
			writeEntry.BaseRevision = expectedRevision
			writeEntry.BaseInodeID = expectedFileID
			_, createdIDs, err = b.WriteStoredObjectCreatedCtxIfRevision(ctx, entry.Path, &writeEntry)
		} else {
			createdIDs, err = b.WriteCtxIfRevisionWithCreatedID(
				ctx,
				entry.Path,
				entry.ContentBlob,
				0,
				filesystem.WriteFlagCreate|filesystem.WriteFlagTruncate,
				expectedRevision,
				expectedFileID,
				nil,
				"",
			)
		}
		if err != nil {
			return createdIDs, err
		}
		if entry.Mode != 0 {
			return createdIDs, fsLayerMarkApplyCommitted(applyFSLayerEntryMode(ctx, b, entry.Path, entry.Mode&0o777, modeMayBeSuperseded))
		}
		return createdIDs, nil
	case datastore.FSLayerEntryOpMkdir:
		mode := entry.Mode
		if mode == 0 {
			mode = 0o755
		}
		createdIDs, err := b.MkdirCreatedCtx(ctx, entry.Path, mode)
		if errors.Is(err, datastore.ErrPathConflict) {
			return nil, fsLayerApplyPathExists(err)
		}
		return createdIDs, err
	case datastore.FSLayerEntryOpWhiteout:
		var err error
		if entry.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entry.Path, "/") {
			children, readErr := b.ReadDirCtx(ctx, entry.Path)
			if errors.Is(readErr, datastore.ErrNotFound) {
				return nil, nil
			}
			if readErr != nil {
				return nil, readErr
			}
			if len(children) > 0 {
				return nil, fmt.Errorf("%w: %s", datastore.ErrDirectoryNotEmpty, entry.Path)
			}
			err = b.RemoveDirCtx(ctx, entry.Path)
		} else {
			err = b.RemoveCtx(ctx, entry.Path)
		}
		if errors.Is(err, datastore.ErrNotFound) {
			return nil, nil
		}
		return nil, err
	case datastore.FSLayerEntryOpChmod:
		mode := entry.Mode
		if mode == 0 {
			return nil, nil
		}
		return nil, applyFSLayerEntryMode(ctx, b, entry.Path, mode&0o777, modeMayBeSuperseded)
	case datastore.FSLayerEntryOpSymlink:
		target := entry.ContentText
		if target == "" && len(entry.ContentBlob) > 0 {
			target = string(entry.ContentBlob)
		}
		if target == "" {
			return nil, fmt.Errorf("symlink target is required")
		}
		createdIDs, err := b.CreateSymlinkCreatedCtx(ctx, entry.Path, target)
		if errors.Is(err, datastore.ErrPathConflict) {
			return nil, fsLayerApplyPathExists(err)
		}
		return createdIDs, err
	case datastore.FSLayerEntryOpRename:
		target := fsLayerRenameTarget(entry)
		if target == "" {
			return nil, fmt.Errorf("rename target is required")
		}
		createdParents, err := b.EnsureParentDirsCreatedCtx(ctx, target)
		if err != nil {
			return nil, err
		}
		var renameErr error
		if entry.Kind == datastore.FSLayerEntryKindDir || strings.HasSuffix(entry.Path, "/") {
			renameErr = b.RenameCtx(ctx, entry.Path, target)
		} else {
			renameErr = b.RenameFileNoReplaceCtx(ctx, entry.Path, target)
		}
		if renameErr != nil {
			fsLayerDropCreatedAncestors(ctx, b, target, createdParents)
			return nil, fsLayerApplyPathExists(renameErr)
		}
		return createdParents, nil
	default:
		return nil, fmt.Errorf("unsupported fs layer op %q", entry.Op)
	}
}

func applyFSLayerEntryMode(ctx context.Context, b *backendpkg.Dat9Backend, path string, mode uint32, allowMissing bool) error {
	if mode == 0 {
		return nil
	}
	if err := b.ChmodCtx(ctx, path, mode); err != nil {
		if allowMissing && errors.Is(err, datastore.ErrNotFound) {
			return nil
		}
		if !errors.Is(err, datastore.ErrNotFound) {
			return fmt.Errorf("chmod fs layer path %s: %w", path, err)
		}
		nf, statErr := b.StatNodeCtx(ctx, path)
		if statErr == nil && nf.File != nil && nf.Node.InodeID == "" {
			return nil
		}
		if statErr != nil && !errors.Is(statErr, datastore.ErrNotFound) {
			return fmt.Errorf("stat fs layer chmod path %s: %w", path, statErr)
		}
		return fmt.Errorf("chmod fs layer path %s: %w", path, err)
	}
	return nil
}

func writeFSLayerStoreError(w http.ResponseWriter, r *http.Request, err error) {
	if errors.Is(err, datastore.ErrFSLayerInvalidArgument) {
		errJSON(w, http.StatusBadRequest, err.Error())
		return
	}
	if errors.Is(err, datastore.ErrNotFound) {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	if errors.Is(err, datastore.ErrRevisionConflict) {
		errJSON(w, http.StatusConflict, err.Error())
		return
	}
	if errors.Is(err, datastore.ErrFSLayerRefAmbiguous) {
		errJSON(w, http.StatusConflict, err.Error())
		return
	}
	if errors.Is(err, datastore.ErrFSLayerStateConflict) {
		errJSON(w, http.StatusConflict, err.Error())
		return
	}
	if errors.Is(err, datastore.ErrFSLayerDepthExceeded) {
		errJSON(w, http.StatusConflict, err.Error())
		return
	}
	if errors.Is(err, datastore.ErrFSLayerHasChildren) {
		errJSON(w, http.StatusConflict, err.Error())
		return
	}
	logger.Error(r.Context(), "fs_layer_store_error", eventFields(r.Context(), "fs_layer_store_error", "error", err)...)
	writeBackendError(w, r, err)
}

func toFSLayerResponse(layer *datastore.FSLayer) fsLayerResponse {
	return fsLayerResponse{
		LayerID:            layer.LayerID,
		BaseRootPath:       layer.BaseRootPath,
		Name:               layer.Name,
		Tags:               layer.Tags,
		State:              string(layer.State),
		DurabilityMode:     string(layer.DurabilityMode),
		ActorID:            layer.ActorID,
		DurableSeq:         layer.DurableSeq,
		ParentLayerID:      layer.ParentLayerID,
		OriginSeq:          layer.OriginSeq,
		OriginCheckpointID: layer.OriginCheckpointID,
		RootLayerID:        layer.RootLayerID,
		Depth:              layer.Depth,
		Origin:             layer.Origin,
		CreatedAt:          layer.CreatedAt,
		UpdatedAt:          layer.UpdatedAt,
		SealedAt:           layer.SealedAt,
	}
}

func toFSLayerEntryResponse(entry *datastore.FSLayerEntry, includeContent bool) fsLayerEntryResponse {
	resp := fsLayerEntryResponse{
		LayerID:                entry.LayerID,
		Path:                   entry.Path,
		ParentPath:             entry.ParentPath,
		Name:                   entry.Name,
		Op:                     string(entry.Op),
		Kind:                   string(entry.Kind),
		BaseInodeID:            entry.BaseInodeID,
		BaseRevision:           entry.BaseRevision,
		StorageType:            entry.StorageType,
		StorageRef:             entry.StorageRef,
		StorageRefHash:         entry.StorageRefHash,
		StorageEncryptionMode:  string(entry.StorageEncryptionMode),
		StorageEncryptionKeyID: entry.StorageEncryptionKeyID,
		ChecksumSHA256:         entry.ChecksumSHA256,
		SizeBytes:              entry.SizeBytes,
		Mode:                   entry.Mode,
		ContentText:            entry.ContentText,
		EntrySeq:               entry.EntrySeq,
		CreatedAt:              entry.CreatedAt,
		UpdatedAt:              entry.UpdatedAt,
	}
	if includeContent {
		resp.Content = entry.ContentBlob
	}
	return resp
}

func toFSLayerCheckpointResponse(checkpoint *datastore.FSLayerCheckpoint) fsLayerCheckpointResponse {
	return fsLayerCheckpointResponse{
		CheckpointID: checkpoint.CheckpointID,
		LayerID:      checkpoint.LayerID,
		DurableSeq:   checkpoint.DurableSeq,
		Label:        checkpoint.Label,
		CreatedAt:    checkpoint.CreatedAt,
	}
}

func toFSLayerEventResponses(events []datastore.FSLayerEvent) []fsLayerEventResponse {
	out := make([]fsLayerEventResponse, 0, len(events))
	for i := range events {
		out = append(out, fsLayerEventResponse{
			EventID:   events[i].EventID,
			LayerID:   events[i].LayerID,
			Seq:       events[i].Seq,
			ActorID:   events[i].ActorID,
			Op:        events[i].Op,
			Path:      events[i].Path,
			CreatedAt: events[i].CreatedAt,
		})
	}
	return out
}
