package container

import (
	"errors"
	"os"
	"syscall"

	"hakurei.app/check"
	"hakurei.app/message"
	"hakurei.app/vfs"
)

// messageFromError returns a printable error message for a supported concrete type.
func messageFromError(err error) (m string, ok bool) {
	if m, ok = messagePrefixP[MountError]("cannot ", err); ok {
		return
	}
	if m, ok = messagePrefixP[os.PathError]("cannot ", err); ok {
		return
	}
	if m, ok = messagePrefix[check.AbsoluteError](zeroString, err); ok {
		return
	}
	if m, ok = messagePrefix[OpRepeatError](zeroString, err); ok {
		return
	}
	if m, ok = messagePrefix[OpStateError](zeroString, err); ok {
		return
	}

	if m, ok = messagePrefixP[vfs.DecoderError]("cannot ", err); ok {
		return
	}
	if m, ok = messagePrefix[TmpfsSizeError](zeroString, err); ok {
		return
	}

	if m, ok = message.GetMessage(err); ok {
		return
	}

	return zeroString, false
}

// messagePrefix checks and prefixes the error message of a non-pointer error.
// While this is usable for pointer errors, such use should be avoided as nil
// check is omitted.
func messagePrefix[T error](prefix string, err error) (string, bool) {
	if e, ok := errors.AsType[T](err); ok {
		return prefix + e.Error(), true
	}
	return zeroString, false
}

// messagePrefixP checks and prefixes the error message of a pointer error.
func messagePrefixP[V any, T interface {
	*V
	error
}](prefix string, err error) (string, bool) {
	if e, ok := errors.AsType[T](err); ok && e != nil {
		return prefix + e.Error(), true
	}
	return zeroString, false
}

// MountError wraps errors returned by syscall.Mount.
type MountError struct {
	Source, Target, Fstype string

	Flags uintptr
	Data  string
	syscall.Errno
}

func (e *MountError) Unwrap() error {
	if e.Errno == 0 {
		return nil
	}
	return e.Errno
}

func (e *MountError) Message() string { return "cannot " + e.Error() }
func (e *MountError) Error() string {
	if e.Flags&syscall.MS_BIND != 0 {
		if e.Flags&syscall.MS_REMOUNT != 0 {
			return "remount " + e.Target + ": " + e.Errno.Error()
		}
		return "bind " + e.Source + " on " + e.Target + ": " + e.Errno.Error()
	}

	if e.Fstype != FstypeNULL {
		return "mount " + e.Fstype + " on " + e.Target + ": " + e.Errno.Error()
	}

	// fallback case: if this is reached, the conditions for it to occur should be handled above
	return "mount " + e.Target + ": " + e.Errno.Error()
}

// optionalErrorUnwrap calls [errors.Unwrap] and returns the resulting value
// if it is not nil, or the original value if it is.
func optionalErrorUnwrap(err error) error {
	if underlyingErr := errors.Unwrap(err); underlyingErr != nil {
		return underlyingErr
	}
	return err
}

// errnoFallback returns the concrete errno from an error, or a [os.PathError] fallback.
func errnoFallback(op, path string, err error) (syscall.Errno, *os.PathError) {
	errno, ok := errors.AsType[syscall.Errno](err)
	if !ok {
		return 0, &os.PathError{Op: op, Path: path, Err: err}
	}
	return errno, nil
}

// mount wraps syscall.Mount for error handling.
func mount(source, target, fstype string, flags uintptr, data string) error {
	if max(len(source), len(target), len(data))+1 > os.Getpagesize() {
		return &MountError{source, target, fstype, flags, data, syscall.ENOMEM}
	}

	err := syscall.Mount(source, target, fstype, flags, data)
	if err == nil {
		return nil
	}
	if errno, pathError := errnoFallback("mount", target, err); pathError != nil {
		return pathError
	} else {
		return &MountError{source, target, fstype, flags, data, errno}
	}
}
