// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright (c) 2024 Matthew Penner

package ufs

import (
	"errors"
	iofs "io/fs"
	"os"

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

var (
	// ErrIsDirectory is an error for when an operation that operates only on
	// files is given a path to a directory.
	ErrIsDirectory = errors.New("is a directory")
	// ErrNotDirectory is an error for when an operation that operates only on
	// directories is given a path to a file.
	ErrNotDirectory = errors.New("not a directory")
	// ErrBadPathResolution is an error for when a sand-boxed filesystem
	// resolves a given path to a forbidden location.
	ErrBadPathResolution = errors.New("bad path resolution")
	// ErrNotRegular is an error for when an operation that operates only on
	// regular files is passed something other than a regular file.
	ErrNotRegular = errors.New("not a regular file")

	// ErrClosed is an error for when an entry was accessed after being closed.
	ErrClosed = iofs.ErrClosed
	// ErrInvalid is an error for when an invalid argument was used.
	ErrInvalid = iofs.ErrInvalid
	// ErrExist is an error for when an entry already exists.
	ErrExist = iofs.ErrExist
	// ErrNotExist is an error for when an entry does not exist.
	ErrNotExist = iofs.ErrNotExist
	// ErrPermission is an error for when the required permissions to perform an
	// operation are missing.
	ErrPermission = iofs.ErrPermission
)

// LinkError records an error during a link or symlink or rename
// system call and the paths that caused it.
type LinkError = os.LinkError

// PathError records an error and the operation and file path that caused it.
type PathError = iofs.PathError

// SyscallError records an error from a specific system call.
type SyscallError = os.SyscallError

// NewSyscallError returns, as an error, a new SyscallError
// with the given system call name and error details.
// As a convenience, if err is nil, NewSyscallError returns nil.
func NewSyscallError(syscall string, err error) error {
	return os.NewSyscallError(syscall, err)
}

// convertErrorType converts errors into our custom errors to ensure consistent
// error values.
func convertErrorType(err error) error {
	if err == nil {
		return nil
	}
	var pErr *PathError
	switch {
	case errors.As(err, &pErr):
		switch {
		// File exists
		case errors.Is(pErr.Err, unix.EEXIST):
			return &PathError{
				Op:   pErr.Op,
				Path: pErr.Path,
				Err:  ErrExist,
			}
		// Is a directory
		case errors.Is(pErr.Err, unix.EISDIR):
			return &PathError{
				Op:   pErr.Op,
				Path: pErr.Path,
				Err:  ErrIsDirectory,
			}
		// Not a directory
		case errors.Is(pErr.Err, unix.ENOTDIR):
			return &PathError{
				Op:   pErr.Op,
				Path: pErr.Path,
				Err:  ErrNotDirectory,
			}
		// No such file or directory
		case errors.Is(pErr.Err, unix.ENOENT):
			return &PathError{
				Op:   pErr.Op,
				Path: pErr.Path,
				Err:  ErrNotExist,
			}
		// Operation not permitted
		case errors.Is(pErr.Err, unix.EPERM):
			return &PathError{
				Op:   pErr.Op,
				Path: pErr.Path,
				Err:  ErrPermission,
			}
		// Invalid cross-device link
		case errors.Is(pErr.Err, unix.EXDEV):
			return &PathError{
				Op:   pErr.Op,
				Path: pErr.Path,
				Err:  ErrBadPathResolution,
			}
		// Too many levels of symbolic links
		case errors.Is(pErr.Err, unix.ELOOP):
			return &PathError{
				Op:   pErr.Op,
				Path: pErr.Path,
				Err:  ErrBadPathResolution,
			}
		}
	}
	return err
}