package main

import (
	"errors"
	"io/fs"
	"os"
	"syscall"
	"time"
)

const maxRetries = 3
const retryDelay = 100 * time.Millisecond

type SyncError struct {
	Path string
	Op   string
	Err  error
}

func (e *SyncError) Error() string {
	return e.Op + " " + e.Path + ": " + e.Err.Error()
}

func (e *SyncError) Unwrap() error {
	return e.Err
}

func retry(name string, fn func() error) error {
	var lastErr error
	for i := 0; i < maxRetries; i++ {
		lastErr = fn()
		if lastErr == nil {
			return nil
		}
		if !isRetryable(lastErr) {
			return lastErr
		}
		time.Sleep(retryDelay * time.Duration(i+1))
	}
	return lastErr
}

func isRetryable(err error) bool {
	if errors.Is(err, fs.ErrNotExist) {
		return true
	}
	if errors.Is(err, syscall.ENOENT) {
		return true
	}
	return false
}

func logSyncError(op, relPath string, err error) {
	if err == nil {
		return
	}
	if errors.Is(err, fs.ErrNotExist) {
		return
	}
	var pathErr *fs.PathError
	if errors.As(err, &pathErr) {
		err = pathErr.Err
	}

	switch {
	case errors.Is(err, syscall.ENOSPC):
		logErrorf("DISK FULL: %s %s failed - no space left on device", op, relPath)
	case errors.Is(err, syscall.EACCES), errors.Is(err, syscall.EPERM):
		logErrorf("PERMISSION DENIED: %s %s - check volume mount permissions", op, relPath)
	case errors.Is(err, syscall.ELOOP):
		logErrorf("SYMLINK LOOP: %s %s - too many symbolic links", op, relPath)
	default:
		logErrorf("%s %s: %v", op, relPath, err)
	}
}

func copyFileWithRetry(src, dst string, mode os.FileMode) error {
	return retry("copy", func() error {
		return copyFile(src, dst, mode)
	})
}
