package sources

import (
	"errors"
	"strings"
	"sync"
)

// ScanErrors is used to collect errors encountered while scanning.
// It ensures that errors are collected in a thread-safe manner.
type ScanErrors struct {
	mu     sync.RWMutex
	errors []error
}

// NewScanErrors creates a new thread safe error collector.
func NewScanErrors() *ScanErrors {
	return &ScanErrors{errors: make([]error, 0)}
}

// Add an error to the collection in a thread-safe manner.
func (s *ScanErrors) Add(err error) {
	if err == nil {
		return
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.errors = append(s.errors, err)
}

// Count returns the number of errors collected.
func (s *ScanErrors) Count() uint64 {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return uint64(len(s.errors))
}

func (s *ScanErrors) String() string {
	s.mu.RLock()
	defer s.mu.RUnlock()

	var sb strings.Builder
	sb.WriteString("[")
	for i, err := range s.errors {
		sb.WriteString(`"` + err.Error() + `"`)
		if i < len(s.errors)-1 {
			sb.WriteString(", ")
		}
	}
	sb.WriteString("]")
	return sb.String()
}

func (s *ScanErrors) Errors() error {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return errors.Join(s.errors...)
}

// TargetedScanError is an error with a secret ID attached. Collections of them can be returned by targeted scans that
// scan multiple targets in order to associate individual errors with individual scan targets.
type TargetedScanError struct {
	Err      error
	SecretID int64
}

var _ error = (*TargetedScanError)(nil)

func (t TargetedScanError) Error() string {
	return t.Err.Error()
}

func (t TargetedScanError) Unwrap() error {
	return t.Err
}
