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"`
	// OperationalStates are the active operational states of the machine.
	// Used to filter patterns that only apply in specific states (e.g. teach_mode, maintenance).
	OperationalStates []string `json:"operational_states,omitempty"`
	// StateTransitions are active state transitions (format: "from→to").
	// Used to detect transition-specific hazards like unexpected restart.
	StateTransitions []string `json:"state_transitions,omitempty"`
	// HumanRoles are the human roles interacting with the machine in this project.
	// Used to filter patterns that only apply to specific roles (e.g. programmer, maintenance_tech).
	HumanRoles []string `json:"human_roles,omitempty"`
	// FailureModes are the active failure mode IDs relevant for this project.
	// Used to filter patterns that require specific failure modes.
	FailureModes []string `json:"failure_modes,omitempty"`
	// MachineTypes are the industry sectors / machine types for this project.
	// Patterns with MachineTypes filter only fire if at least one matches.
	// Empty = all patterns fire (backwards compatible).
	MachineTypes []string `json:"machine_types,omitempty"`
}

// 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"`
}

// MatchReason explains why a specific check passed or was relevant for a pattern match.
type MatchReason struct {
	Type string `json:"type"` // "required_component_tag", "required_energy_tag", "lifecycle_match", "no_exclusion"
	Tag  string `json:"tag"`
	Met  bool   `json:"met"`
}

// 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"`
	// Explainability: structured reasons why this pattern fired
	MatchReasons []MatchReason `json:"match_reasons,omitempty"`
	// Detail fields from the pattern definition
	ScenarioDE       string   `json:"scenario_de,omitempty"`
	TriggerDE        string   `json:"trigger_de,omitempty"`
	HarmDE           string   `json:"harm_de,omitempty"`
	AffectedDE       string   `json:"affected_de,omitempty"`
	ZoneDE           string   `json:"zone_de,omitempty"`
	DefaultSeverity  int      `json:"default_severity,omitempty"`
	DefaultExposure  int      `json:"default_exposure,omitempty"`
	HazardCats        []string `json:"hazard_categories,omitempty"`
	ExpertHintDE      string   `json:"expert_hint_de,omitempty"`
	RequiresExpert    bool     `json:"requires_expert,omitempty"`
	OperationalStates []string `json:"operational_states,omitempty"`
	StateTransitions  []string `json:"state_transitions,omitempty"`
	HumanRoles           []string `json:"human_roles,omitempty"`
	GeneratedHazardType  string   `json:"generated_hazard_type,omitempty"`
	MatchedFailureModes  []string `json:"matched_failure_modes,omitempty"`
	ApplicableLifecycles []string `json:"applicable_lifecycles,omitempty"`
	SuggestedMeasureIDs      []string `json:"suggested_measure_ids,omitempty"`
	ClarificationQuestionsDE []string `json:"clarification_questions_de,omitempty"`
	ISO12100Section          string   `json:"iso_12100_section,omitempty"`
	DefaultAvoidability      int      `json:"default_avoidability,omitempty"`
}

// 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.
// Pattern registration is in pattern_registry.go (collectAllPatterns).
func NewPatternEngine() *PatternEngine {
	return &PatternEngine{
		resolver: NewTagResolver(),
		patterns: collectAllPatterns(),
	}
}

// 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) {
			continue
		}

		// Collect the tags that contributed + build explainability reasons
		var matchedTags []string
		var reasons []MatchReason
		for _, t := range p.RequiredComponentTags {
			if tagSet[t] {
				matchedTags = append(matchedTags, t)
				reasons = append(reasons, MatchReason{Type: "required_component_tag", Tag: t, Met: true})
			}
		}
		for _, t := range p.RequiredEnergyTags {
			if tagSet[t] {
				matchedTags = append(matchedTags, t)
				reasons = append(reasons, MatchReason{Type: "required_energy_tag", Tag: t, Met: true})
			}
		}
		for _, t := range p.ExcludedComponentTags {
			reasons = append(reasons, MatchReason{Type: "no_exclusion", Tag: t, Met: !tagSet[t]})
		}
		if len(p.RequiredLifecycles) > 0 {
			for _, lc := range p.RequiredLifecycles {
				found := false
				for _, ilc := range input.LifecyclePhases {
					if ilc == lc {
						found = true
						break
					}
				}
				reasons = append(reasons, MatchReason{Type: "lifecycle_match", Tag: lc, Met: found})
			}
		}
		if len(p.OperationalStates) > 0 {
			stateSet := toSet(input.OperationalStates)
			for _, s := range p.OperationalStates {
				reasons = append(reasons, MatchReason{Type: "operational_state", Tag: s, Met: stateSet[s]})
			}
		}
		if len(p.StateTransitions) > 0 {
			transSet := toSet(input.StateTransitions)
			for _, t := range p.StateTransitions {
				reasons = append(reasons, MatchReason{Type: "state_transition", Tag: t, Met: transSet[t]})
			}
		}
		if len(p.HumanRoles) > 0 {
			roleSet := toSet(input.HumanRoles)
			for _, r := range p.HumanRoles {
				reasons = append(reasons, MatchReason{Type: "human_role", Tag: r, Met: roleSet[r]})
			}
		}
		var matchedFMs []string
		if len(p.RequiredFailureModes) > 0 {
			fmSet := toSet(input.FailureModes)
			for _, fm := range p.RequiredFailureModes {
				met := fmSet[fm]
				reasons = append(reasons, MatchReason{Type: "failure_mode", Tag: fm, Met: met})
				if met {
					matchedFMs = append(matchedFMs, fm)
				}
			}
		}

		matchedPatterns = append(matchedPatterns, PatternMatch{
			PatternID:       p.ID,
			PatternName:     p.NameDE,
			Priority:        p.Priority,
			MatchedTags:     matchedTags,
			MatchReasons:    reasons,
			ScenarioDE:      p.ScenarioDE,
			TriggerDE:       p.TriggerDE,
			HarmDE:          p.HarmDE,
			AffectedDE:      p.AffectedDE,
			ZoneDE:          p.ZoneDE,
			DefaultSeverity: p.DefaultSeverity,
			DefaultExposure: p.DefaultExposure,
			HazardCats:      p.GeneratedHazardCats,
			ExpertHintDE:      p.ExpertHintDE,
			RequiresExpert:    p.RequiresExpertCalculation,
			OperationalStates:   p.OperationalStates,
			StateTransitions:    p.StateTransitions,
			HumanRoles:          p.HumanRoles,
			GeneratedHazardType: p.GeneratedHazardType,
			MatchedFailureModes:  matchedFMs,
			ApplicableLifecycles: guardedLifecycles(p, tagSet),
			SuggestedMeasureIDs:      p.SuggestedMeasureIDs,
			ClarificationQuestionsDE: p.ClarificationQuestionsDE,
			ISO12100Section:          p.ISO12100Section,
			DefaultAvoidability:      p.DefaultAvoidability,
		})

		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, lifecycle phases,
// operational states, and state transitions.
func patternMatches(p HazardPattern, tagSet map[string]bool, input MatchInput) bool {
	// If pattern requires specific machine types, project must match at least one.
	// Patterns without MachineTypes fire for ALL projects (backwards compatible).
	if len(p.MachineTypes) > 0 && len(input.MachineTypes) > 0 {
		found := false
		mtSet := toSet(input.MachineTypes)
		for _, mt := range p.MachineTypes {
			if mtSet[mt] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	// 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(input.LifecyclePhases) > 0 {
		found := false
		phaseSet := toSet(input.LifecyclePhases)
		for _, lp := range p.RequiredLifecycles {
			if phaseSet[lp] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	// If pattern requires specific operational states, at least one must match.
	// nil/empty OperationalStates on pattern = fires in ALL states (backwards compatible).
	if len(p.OperationalStates) > 0 && len(input.OperationalStates) > 0 {
		found := false
		stateSet := toSet(input.OperationalStates)
		for _, s := range p.OperationalStates {
			if stateSet[s] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	// If pattern requires specific state transitions, at least one must match.
	if len(p.StateTransitions) > 0 && len(input.StateTransitions) > 0 {
		found := false
		transSet := toSet(input.StateTransitions)
		for _, t := range p.StateTransitions {
			if transSet[t] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	// If pattern requires specific human roles, at least one must match.
	if len(p.HumanRoles) > 0 && len(input.HumanRoles) > 0 {
		found := false
		roleSet := toSet(input.HumanRoles)
		for _, r := range p.HumanRoles {
			if roleSet[r] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	// If pattern requires specific failure modes, at least one must match.
	if len(p.RequiredFailureModes) > 0 && len(input.FailureModes) > 0 {
		found := false
		fmSet := toSet(input.FailureModes)
		for _, fm := range p.RequiredFailureModes {
			if fmSet[fm] {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}

	// Interlocked-enclosure gate (guardable contact/entanglement). See pattern_enclosure.go.
	if suppressedByEnclosure(p, tagSet, input.LifecyclePhases) {
		return false
	}

	// Domain-specific supersession (generic duplicate replaced by a precise one).
	if supersededByDomainSpecific(p, tagSet) {
		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
}

// ── Operational State Graph constants ──────────────────────────────

// Standard operational states for machinery (ISO 12100 + BetrSichV).
const (
	StateStartup            = "startup"
	StateHoming             = "homing"
	StateAutomaticOperation = "automatic_operation"
	StateManualOperation    = "manual_operation"
	StateTeachMode          = "teach_mode"
	StateMaintenance        = "maintenance"
	StateCleaning           = "cleaning"
	StateEmergencyStop      = "emergency_stop"
	StateRecoveryMode       = "recovery_mode"
)

// AllOperationalStates returns the 9 standard operational states.
func AllOperationalStates() []string {
	return []string{
		StateStartup, StateHoming, StateAutomaticOperation,
		StateManualOperation, StateTeachMode, StateMaintenance,
		StateCleaning, StateEmergencyStop, StateRecoveryMode,
	}
}

// StandardStateTransitions returns the valid transitions between states.
// Format: "from→to". These represent the directed edges of the state graph.
func StandardStateTransitions() []string {
	return []string{
		"startup→homing",
		"homing→automatic_operation",
		"automatic_operation→manual_operation",
		"manual_operation→automatic_operation",
		"automatic_operation→teach_mode",
		"teach_mode→automatic_operation",
		"automatic_operation→maintenance",
		"manual_operation→maintenance",
		"maintenance→automatic_operation",
		"maintenance→manual_operation",
		"automatic_operation→cleaning",
		"cleaning→automatic_operation",
		"automatic_operation→emergency_stop",
		"manual_operation→emergency_stop",
		"teach_mode→emergency_stop",
		"maintenance→emergency_stop",
		"cleaning→emergency_stop",
		"emergency_stop→recovery_mode",
		"recovery_mode→homing",
		"recovery_mode→manual_operation",
	}
}
