package registry

import (
	"fmt"
	"strings"
)

// ValidationError represents one or more integrity violations.
type ValidationError struct {
	ID     string
	Errors []string
}

func (v *ValidationError) Error() string {
	return fmt.Sprintf("%s: %s", v.ID, strings.Join(v.Errors, "; "))
}

// ValidateFunction checks integrity rules from docs/integrity.md.
// knownFunctions and knownTypes are sets of IDs that exist in the registry
// (including the current indexing batch).
func ValidateFunction(f *Function, knownFunctions, knownTypes map[string]bool) *ValidationError {
	var errs []string

	// Required fields
	if f.Name == "" {
		errs = append(errs, "name is required")
	}
	if f.Kind == "" {
		errs = append(errs, "kind is required")
	}
	if f.Lang == "" {
		errs = append(errs, "lang is required")
	}
	if f.Domain == "" {
		errs = append(errs, "domain is required")
	}
	if f.Description == "" {
		errs = append(errs, "description is required")
	}

	// Pipeline rules
	if f.Kind == KindPipeline {
		if f.Purity != PurityImpure {
			errs = append(errs, "pipeline must be impure")
		}
		if len(f.UsesFunctions) == 0 {
			errs = append(errs, "pipeline uses_functions cannot be empty")
		}
	}

	// Purity rules
	if f.Purity == PurityPure {
		if f.ReturnsOptional {
			errs = append(errs, "pure function cannot have returns_optional: true (model as sum type)")
		}
		if f.ErrorType != "" {
			errs = append(errs, "pure function cannot have error_type")
		}
	}
	if f.Purity == PurityImpure && f.Kind != KindComponent {
		if f.ErrorType == "" {
			errs = append(errs, "impure function must declare error_type")
		}
	}

	// Tested rules
	if f.Tested {
		if f.TestFilePath == "" {
			errs = append(errs, "tested: true requires test_file_path")
		}
		if len(f.Tests) == 0 {
			errs = append(errs, "tested: true requires non-empty tests")
		}
	} else {
		if len(f.Tests) > 0 {
			errs = append(errs, "tested: false but tests is not empty")
		}
		if f.TestFilePath != "" {
			errs = append(errs, "tested: false but test_file_path is set")
		}
	}

	// Component rules
	if f.Kind == KindComponent {
		if f.Framework == "" {
			errs = append(errs, "component must declare framework")
		}
		if len(f.Returns) > 0 {
			errs = append(errs, "component returns must be empty (use emits)")
		}
		if f.HasState != nil && *f.HasState && f.Purity != PurityImpure {
			errs = append(errs, "component with has_state: true must be impure")
		}
	}

	// File path must be relative
	if f.FilePath != "" && strings.HasPrefix(f.FilePath, "/") {
		errs = append(errs, "file_path must be relative to registry root")
	}

	// Reference validation
	for _, ref := range f.UsesFunctions {
		if !knownFunctions[ref] {
			errs = append(errs, fmt.Sprintf("uses_functions references unknown function: %s", ref))
		}
	}
	for _, ref := range f.UsesTypes {
		if !knownTypes[ref] {
			errs = append(errs, fmt.Sprintf("uses_types references unknown type: %s", ref))
		}
	}
	for _, ref := range f.Returns {
		if !knownTypes[ref] {
			errs = append(errs, fmt.Sprintf("returns references unknown type: %s", ref))
		}
	}
	if f.ErrorType != "" {
		if !knownTypes[f.ErrorType] {
			errs = append(errs, fmt.Sprintf("error_type references unknown type: %s", f.ErrorType))
		}
	}

	if len(errs) > 0 {
		return &ValidationError{ID: f.ID, Errors: errs}
	}
	return nil
}

// ValidateProposal checks integrity rules for proposals.
func ValidateProposal(p *Proposal) *ValidationError {
	var errs []string

	if p.ID == "" {
		errs = append(errs, "id is required")
	}
	if p.Title == "" {
		errs = append(errs, "title is required")
	}

	switch p.Kind {
	case ProposalNewFunction, ProposalNewType, ProposalImproveFunction, ProposalImproveType, ProposalNewPipeline:
	case "":
		errs = append(errs, "kind is required")
	default:
		errs = append(errs, fmt.Sprintf("invalid kind: %s", p.Kind))
	}

	switch p.Status {
	case ProposalPending, ProposalApproved, ProposalRejected, ProposalImplemented, "":
	default:
		errs = append(errs, fmt.Sprintf("invalid status: %s", p.Status))
	}

	if (p.Kind == ProposalImproveFunction || p.Kind == ProposalImproveType) && p.TargetID == "" {
		errs = append(errs, "target_id is required for improve_* proposals")
	}

	if len(errs) > 0 {
		return &ValidationError{ID: p.ID, Errors: errs}
	}
	return nil
}

// ValidateApp checks integrity rules for apps.
func ValidateApp(a *App, knownFunctions, knownTypes map[string]bool) *ValidationError {
	var errs []string

	if a.Name == "" {
		errs = append(errs, "name is required")
	}
	if a.Lang == "" {
		errs = append(errs, "lang is required")
	}
	if a.Domain == "" {
		errs = append(errs, "domain is required")
	}
	if a.Description == "" {
		errs = append(errs, "description is required")
	}

	if a.DirPath != "" && strings.HasPrefix(a.DirPath, "/") {
		errs = append(errs, "dir_path must be relative to registry root")
	}

	for _, ref := range a.UsesFunctions {
		if !knownFunctions[ref] {
			errs = append(errs, fmt.Sprintf("uses_functions references unknown function: %s", ref))
		}
	}
	for _, ref := range a.UsesTypes {
		if !knownTypes[ref] {
			errs = append(errs, fmt.Sprintf("uses_types references unknown type: %s", ref))
		}
	}

	if len(errs) > 0 {
		return &ValidationError{ID: a.ID, Errors: errs}
	}
	return nil
}

// ValidateAnalysis checks integrity rules for analysis entries.
func ValidateAnalysis(a *Analysis, knownFunctions, knownTypes map[string]bool) *ValidationError {
	var errs []string

	if a.Name == "" {
		errs = append(errs, "name is required")
	}
	if a.Lang == "" {
		errs = append(errs, "lang is required")
	}
	if a.Domain == "" {
		errs = append(errs, "domain is required")
	}
	if a.Description == "" {
		errs = append(errs, "description is required")
	}

	if a.DirPath != "" && strings.HasPrefix(a.DirPath, "/") {
		errs = append(errs, "dir_path must be relative to registry root")
	}

	for _, ref := range a.UsesFunctions {
		if !knownFunctions[ref] {
			errs = append(errs, fmt.Sprintf("uses_functions references unknown function: %s", ref))
		}
	}
	for _, ref := range a.UsesTypes {
		if !knownTypes[ref] {
			errs = append(errs, fmt.Sprintf("uses_types references unknown type: %s", ref))
		}
	}

	if len(errs) > 0 {
		return &ValidationError{ID: a.ID, Errors: errs}
	}
	return nil
}

// ValidateProject checks integrity rules for projects.
func ValidateProject(p *Project) *ValidationError {
	var errs []string

	if p.ID == "" {
		errs = append(errs, "id is required")
	}
	if p.Name == "" {
		errs = append(errs, "name is required")
	}
	if p.Description == "" {
		errs = append(errs, "description is required")
	}
	if p.DirPath != "" && strings.HasPrefix(p.DirPath, "/") {
		errs = append(errs, "dir_path must be relative to registry root")
	}

	if len(errs) > 0 {
		return &ValidationError{ID: p.ID, Errors: errs}
	}
	return nil
}

// ValidateType checks integrity rules for types.
func ValidateType(t *Type, knownTypes map[string]bool) *ValidationError {
	var errs []string

	if t.Name == "" {
		errs = append(errs, "name is required")
	}
	if t.Lang == "" {
		errs = append(errs, "lang is required")
	}
	if t.Domain == "" {
		errs = append(errs, "domain is required")
	}
	if t.Description == "" {
		errs = append(errs, "description is required")
	}
	if t.Algebraic != AlgebraicProduct && t.Algebraic != AlgebraicSum {
		errs = append(errs, fmt.Sprintf("algebraic must be 'product' or 'sum', got %q", t.Algebraic))
	}

	if t.FilePath != "" && strings.HasPrefix(t.FilePath, "/") {
		errs = append(errs, "file_path must be relative to registry root")
	}

	// Self-reference check
	for _, ref := range t.UsesTypes {
		if ref == t.ID {
			errs = append(errs, "type cannot reference itself in uses_types")
		}
		if !knownTypes[ref] {
			errs = append(errs, fmt.Sprintf("uses_types references unknown type: %s", ref))
		}
	}

	if len(errs) > 0 {
		return &ValidationError{ID: t.ID, Errors: errs}
	}
	return nil
}
