package validation

import (
	"fmt"

	"github.com/steveyegge/beads/internal/types"
)

// IssueValidator validates an issue and returns an error if validation fails.
// Validators can be composed using Chain() for complex validation logic.
type IssueValidator func(id string, issue *types.Issue) error

// Chain composes multiple validators into a single validator.
// Validators are executed in order and the first error stops the chain.
func Chain(validators ...IssueValidator) IssueValidator {
	return func(id string, issue *types.Issue) error {
		for _, v := range validators {
			if err := v(id, issue); err != nil {
				return err
			}
		}
		return nil
	}
}

// Exists validates that an issue is not nil.
func Exists() IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return fmt.Errorf("issue %s not found", id)
		}
		return nil
	}
}

// NotTemplate validates that an issue is not a template.
// Templates are read-only and cannot be modified.
func NotTemplate() IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return nil // Let Exists() handle nil check if needed
		}
		if issue.IsTemplate {
			return fmt.Errorf("cannot modify template %s: templates are read-only; use 'bd mol pour' to create a work item", id)
		}
		return nil
	}
}

// NotPinned validates that an issue is not pinned.
// Returns an error if the issue is pinned, unless force is true.
func NotPinned(force bool) IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return nil // Let Exists() handle nil check if needed
		}
		if !force && issue.Status == types.StatusPinned {
			return fmt.Errorf("cannot modify pinned issue %s (use --force to override)", id)
		}
		return nil
	}
}

// NotClosed validates that an issue is not already closed.
func NotClosed() IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return nil
		}
		if issue.Status == types.StatusClosed {
			return fmt.Errorf("issue %s is already closed", id)
		}
		return nil
	}
}

// NotHooked validates that an issue is not in hooked status.
func NotHooked(force bool) IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return nil
		}
		if !force && issue.Status == types.StatusHooked {
			return fmt.Errorf("cannot modify hooked issue %s (use --force to override)", id)
		}
		return nil
	}
}

// HasStatus validates that an issue has one of the allowed statuses.
func HasStatus(allowed ...types.Status) IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return nil
		}
		for _, status := range allowed {
			if issue.Status == status {
				return nil
			}
		}
		return fmt.Errorf("issue %s has status %s, expected one of: %v", id, issue.Status, allowed)
	}
}

// HasType validates that an issue has one of the allowed types.
func HasType(allowed ...types.IssueType) IssueValidator {
	return func(id string, issue *types.Issue) error {
		if issue == nil {
			return nil
		}
		for _, t := range allowed {
			if issue.IssueType == t {
				return nil
			}
		}
		return fmt.Errorf("issue %s has type %s, expected one of: %v", id, issue.IssueType, allowed)
	}
}

// ForUpdate returns a validator chain for update operations.
// Validates: issue exists and is not a template.
func ForUpdate() IssueValidator {
	return Chain(
		Exists(),
		NotTemplate(),
	)
}

// ForClose returns a validator chain for close operations.
// Validates: issue exists, is not a template, and is not pinned (unless force).
func ForClose(force bool) IssueValidator {
	return Chain(
		Exists(),
		NotTemplate(),
		NotPinned(force),
	)
}

// ForDelete returns a validator chain for delete operations.
// Validates: issue exists and is not a template.
func ForDelete() IssueValidator {
	return Chain(
		Exists(),
		NotTemplate(),
	)
}

// ForReopen returns a validator chain for reopen operations.
// Validates: issue exists, is not a template, and is closed.
func ForReopen() IssueValidator {
	return Chain(
		Exists(),
		NotTemplate(),
		HasStatus(types.StatusClosed),
	)
}
