//go:build windows

package memorysearch

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"golang.org/x/sys/windows"
)

// ownerLockFileName is the cross-process lock the Reconciler acquires at
// Start. A second Desktop instance that cannot acquire it must report
// degraded rather than queue or write concurrently — the spec §5.2 single-
// writer + cross-process file lock requirement.
const ownerLockFileName = "owner.lock"

// OwnerLock is the cross-process lock the Reconciler holds for the
// lifetime of a single Desktop instance owning a binding's derivative.
// The Windows implementation uses LockFileEx with LOCKFILE_EXCLUSIVE_LOCK
// and LOCKFILE_FAIL_IMMEDIATELY so a second instance fails fast with
// ERROR_LOCK_VIOLATION rather than blocking.
type OwnerLock struct {
	path string
	file *os.File
}

// TryAcquireOwnerLock opens (creating if absent) the lock file under
// derivativeRoot and attempts a non-blocking exclusive LockFileEx.
// Returns ErrOwnerLockBusy if another Desktop instance already holds
// the lock; the caller must report degraded and never block waiting.
//
// The lock file path is fixed under the derivative root; the caller
// cannot supply or override it. This is the trust boundary: a runtime-
// supplied path must never land in the lock file location.
func TryAcquireOwnerLock(derivativeRoot string) (*OwnerLock, error) {
	if derivativeRoot == "" {
		return nil, fmt.Errorf("%w: empty derivative root", ErrInvalidBuildOptions)
	}
	if err := os.MkdirAll(derivativeRoot, 0o755); err != nil {
		return nil, fmt.Errorf("memory search: mkdir derivative root: %w", err)
	}
	lockPath := filepath.Join(derivativeRoot, ownerLockFileName)
	f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0o600)
	if err != nil {
		return nil, fmt.Errorf("memory search: open owner lock %s: %w", lockPath, err)
	}
	var overlapped windows.Overlapped
	flags := uint32(windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY)
	if err := windows.LockFileEx(windows.Handle(f.Fd()), flags, 0, 1, 0, &overlapped); err != nil {
		_ = f.Close()
		if errors.Is(err, windows.ERROR_LOCK_VIOLATION) || err == windows.ERROR_LOCK_VIOLATION {
			return nil, fmt.Errorf("%w: %s", ErrOwnerLockBusy, lockPath)
		}
		return nil, fmt.Errorf("memory search: acquire owner lock %s: %w", lockPath, err)
	}
	return &OwnerLock{path: lockPath, file: f}, nil
}

// Release drops the file-region lock and closes the lock file. Safe to
// call on a nil pointer or call multiple times.
func (l *OwnerLock) Release() {
	if l == nil || l.file == nil {
		return
	}
	var overlapped windows.Overlapped
	_ = windows.UnlockFileEx(windows.Handle(l.file.Fd()), 0, 1, 0, &overlapped)
	_ = l.file.Close()
	l.file = nil
}

// ErrOwnerLockBusy signals that another Desktop instance holds the
// per-agent owner lock. The caller reports degraded and does not write.
var ErrOwnerLockBusy = errors.New("memory search: owner lock held by another process")
