package iace

import "sort"

// MatchInput defines the inputs for the pattern-matching engine.
type MatchInput struct {
	ComponentLibraryIDs []string `json:"component_library_ids"`
	EnergySourceIDs     []string `json:"energy_source_ids"`
	LifecyclePhases     []string `json:"lifecycle_phases"`
	CustomTags          []string `json:"custom_tags"`
}

// MatchOutput contains the results of pattern matching.
type MatchOutput struct {
	MatchedPatterns   []PatternMatch       `json:"matched_patterns"`
	SuggestedHazards  []HazardSuggestion   `json:"suggested_hazards"`
	SuggestedMeasures []MeasureSuggestion  `json:"suggested_measures"`
	SuggestedEvidence []EvidenceSuggestion `json:"suggested_evidence"`
	ResolvedTags      []string             `json:"resolved_tags"`
}

// PatternMatch records which pattern fired and why.
type PatternMatch struct {
	PatternID   string   `json:"pattern_id"`
	PatternName string   `json:"pattern_name"`
	Priority    int      `json:"priority"`
	MatchedTags []string `json:"matched_tags"`
}

// HazardSuggestion is a suggested hazard from pattern matching.
type HazardSuggestion struct {
	Category       string   `json:"category"`
	SourcePatterns []string `json:"source_patterns"`
	Confidence     float64  `json:"confidence"`
}

// MeasureSuggestion is a suggested protective measure from pattern matching.
type MeasureSuggestion struct {
	MeasureID      string   `json:"measure_id"`
	SourcePatterns []string `json:"source_patterns"`
}

// EvidenceSuggestion is a suggested evidence type from pattern matching.
type EvidenceSuggestion struct {
	EvidenceID     string   `json:"evidence_id"`
	SourcePatterns []string `json:"source_patterns"`
}

// PatternEngine evaluates hazard patterns against resolved tags.
type PatternEngine struct {
	resolver *TagResolver
	patterns []HazardPattern
}

// NewPatternEngine creates a PatternEngine with all pattern sources and resolver.
func NewPatternEngine() *PatternEngine {
	// Combine all pattern sources
	patterns := GetBuiltinHazardPatterns()          // HP001-HP044
	patterns = append(patterns, GetExtendedHazardPatterns()...) // HP045+ from rule library
	patterns = append(patterns, GetPressHazardPatterns()...)    // HP045-HP058 press-specific
	patterns = append(patterns, GetCobotHazardPatterns()...)    // HP059-HP065 cobot-specific
	return &PatternEngine{
		resolver: NewTagResolver(),
		patterns: patterns,
	}
}

// Match executes the pattern-matching algorithm:
// 1. Resolve component + energy IDs → tags
// 2. Merge with custom tags
// 3. Sort patterns by priority (descending)
// 4. For each pattern: check required_tags (AND) and excluded_tags (NOT)
// 5. Collect hazards, measures, evidence from fired patterns (deduplicated)
// 6. Calculate confidence scores
func (e *PatternEngine) Match(input MatchInput) *MatchOutput {
	// Step 1+2: resolve all tags
	allTags := e.resolver.ResolveTags(input.ComponentLibraryIDs, input.EnergySourceIDs, input.CustomTags)

	if len(allTags) == 0 {
		return &MatchOutput{ResolvedTags: []string{}}
	}

	tagSet := toSet(allTags)

	// Step 3: sort patterns by priority descending
	patterns := make([]HazardPattern, len(e.patterns))
	copy(patterns, e.patterns)
	sort.Slice(patterns, func(i, j int) bool {
		return patterns[i].Priority > patterns[j].Priority
	})

	// Step 4+5: evaluate each pattern
	var matchedPatterns []PatternMatch
	hazardCatSources := make(map[string][]string)  // category → pattern IDs
	measureSources := make(map[string][]string)     // measure ID → pattern IDs
	evidenceSources := make(map[string][]string)     // evidence ID → pattern IDs

	for _, p := range patterns {
		if !patternMatches(p, tagSet, input.LifecyclePhases) {
			continue
		}

		// Collect the tags that contributed to this match
		var matchedTags []string
		for _, t := range p.RequiredComponentTags {
			if tagSet[t] {
				matchedTags = append(matchedTags, t)
			}
		}
		for _, t := range p.RequiredEnergyTags {
			if tagSet[t] {
				matchedTags = append(matchedTags, t)
			}
		}

		matchedPatterns = append(matchedPatterns, PatternMatch{
			PatternID:   p.ID,
			PatternName: p.NameDE,
			Priority:    p.Priority,
			MatchedTags: matchedTags,
		})

		for _, cat := range p.GeneratedHazardCats {
			hazardCatSources[cat] = appendUnique(hazardCatSources[cat], p.ID)
		}
		for _, mid := range p.SuggestedMeasureIDs {
			measureSources[mid] = appendUnique(measureSources[mid], p.ID)
		}
		for _, eid := range p.SuggestedEvidenceIDs {
			evidenceSources[eid] = appendUnique(evidenceSources[eid], p.ID)
		}
	}

	// Step 6: build output with confidence scores
	totalPatterns := len(patterns)
	firedCount := len(matchedPatterns)

	var suggestedHazards []HazardSuggestion
	for cat, sources := range hazardCatSources {
		confidence := float64(len(sources)) / float64(maxInt(firedCount, 1))
		if confidence > 1.0 {
			confidence = 1.0
		}
		suggestedHazards = append(suggestedHazards, HazardSuggestion{
			Category:       cat,
			SourcePatterns: sources,
			Confidence:     confidence,
		})
	}
	// Sort by confidence descending
	sort.Slice(suggestedHazards, func(i, j int) bool {
		return suggestedHazards[i].Confidence > suggestedHazards[j].Confidence
	})

	var suggestedMeasures []MeasureSuggestion
	for mid, sources := range measureSources {
		suggestedMeasures = append(suggestedMeasures, MeasureSuggestion{
			MeasureID:      mid,
			SourcePatterns: sources,
		})
	}
	sort.Slice(suggestedMeasures, func(i, j int) bool {
		return suggestedMeasures[i].MeasureID < suggestedMeasures[j].MeasureID
	})

	var suggestedEvidence []EvidenceSuggestion
	for eid, sources := range evidenceSources {
		suggestedEvidence = append(suggestedEvidence, EvidenceSuggestion{
			EvidenceID:     eid,
			SourcePatterns: sources,
		})
	}
	sort.Slice(suggestedEvidence, func(i, j int) bool {
		return suggestedEvidence[i].EvidenceID < suggestedEvidence[j].EvidenceID
	})

	_ = totalPatterns // used conceptually for confidence normalization

	return &MatchOutput{
		MatchedPatterns:   matchedPatterns,
		SuggestedHazards:  suggestedHazards,
		SuggestedMeasures: suggestedMeasures,
		SuggestedEvidence: suggestedEvidence,
		ResolvedTags:      allTags,
	}
}

// patternMatches checks if a pattern fires given the resolved tag set and lifecycle phases.
func patternMatches(p HazardPattern, tagSet map[string]bool, lifecyclePhases []string) bool {
	// All required component tags must be present (AND)
	for _, t := range p.RequiredComponentTags {
		if !tagSet[t] {
			return false
		}
	}

	// All required energy tags must be present (AND)
	for _, t := range p.RequiredEnergyTags {
		if !tagSet[t] {
			return false
		}
	}

	// None of the excluded tags may be present (NOT)
	for _, t := range p.ExcludedComponentTags {
		if tagSet[t] {
			return false
		}
	}

	// If pattern requires specific lifecycle phases, at least one must match
	if len(p.RequiredLifecycles) > 0 && len(lifecyclePhases) > 0 {
		found := false
		phaseSet := toSet(lifecyclePhases)
		for _, lp := range p.RequiredLifecycles {
			if phaseSet[lp] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	return true
}

// appendUnique appends val to slice if not already present.
func appendUnique(slice []string, val string) []string {
	for _, s := range slice {
		if s == val {
			return slice
		}
	}
	return append(slice, val)
}

// maxInt returns the larger of a and b.
func maxInt(a, b int) int {
	if a > b {
		return a
	}
	return b
}
