package nativepath

import (
	"path/filepath"
	"sync"
	"unsafe"

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

// On macOS, we use open + fcntl(F_GETPATH) to resolve the canonical path in
// O(1) syscalls instead of Go's filepath.EvalSymlinks which does an lstat per
// path component — O(depth).
//
// How it works:
//   - open(path, O_EVTONLY|O_NONBLOCK|O_CLOEXEC) follows all symlinks and gives
//     us a lightweight fd. O_EVTONLY is macOS's event-only descriptor — it
//     doesn't require read permission (similar to Linux's O_PATH) but still
//     references the vnode. O_NONBLOCK prevents blocking on FIFOs.
//   - fcntl(fd, F_GETPATH, buf) asks the kernel for the canonical path of the
//     open file descriptor, written into a MAXPATHLEN buffer.
//
// unix.FcntlInt takes an int arg, so call it through a uintptr-escaping wrapper
// to keep the buffer pointer valid until fcntl returns.

var hasFGetPath = sync.OnceValue(func() bool {
	// Verify that F_GETPATH is supported by this kernel version.
	var buf [unix.PathMax]byte
	fd, err := unix.Open(".", unix.O_EVTONLY|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
	if err != nil {
		return false
	}
	defer unix.Close(fd)
	_, err = fcntlGetPath(fd, &buf)
	return err == nil
})

func fcntlGetPath(fd int, buf *[unix.PathMax]byte) (int, error) {
	return ignoringEINTR(func() (int, error) {
		return fcntlGetPathPtr(uintptr(fd), uintptr(unsafe.Pointer(&buf[0])))
	})
}

//go:uintptrescapes
func fcntlGetPathPtr(fd uintptr, buf uintptr) (int, error) {
	return unix.FcntlInt(fd, unix.F_GETPATH, int(buf))
}

func Realpath(path string) (string, error) {
	if !hasFGetPath() {
		return filepath.EvalSymlinks(path)
	}

	fd, err := unix.Open(path, unix.O_EVTONLY|unix.O_NONBLOCK|unix.O_CLOEXEC, 0)
	if err != nil {
		return "", err
	}
	defer unix.Close(fd)

	var buf [unix.PathMax]byte
	if _, err := fcntlGetPath(fd, &buf); err != nil {
		return "", err
	}

	return unix.ByteSliceToString(buf[:]), nil
}
