package iace

import "sort"

// NormSuggestion represents a single norm matched to a project context.
type NormSuggestion struct {
	Norm       NormReference `json:"norm"`
	Reason     string        `json:"reason"`     // Why this norm was suggested
	Confidence float64       `json:"confidence"` // 0.0-1.0 relevance score
	Sources    []string      `json:"sources"`    // What triggered the match
}

// NormSuggestionResult groups suggested norms by hierarchy type.
type NormSuggestionResult struct {
	ANorms  []NormSuggestion `json:"a_norms"`
	B1Norms []NormSuggestion `json:"b1_norms"`
	B2Norms []NormSuggestion `json:"b2_norms"`
	CNorms  []NormSuggestion `json:"c_norms"`
	Total   int              `json:"total"`
}

// SuggestNorms matches relevant norms for a project based on its machine type,
// identified hazard categories, and component/energy tags.
// A-norms are always included (they apply universally). B/C norms are matched
// by machine type (confidence 0.9), hazard category (0.8), or tag (0.7).
// collectAllNorms aggregates every A/B/C norm source into one slice. Single
// source of truth used by SuggestNorms and the architecture self-description.
func collectAllNorms() []NormReference {
	allNorms := GetNormsLibrary()
	allNorms = append(allNorms, GetExtendedB2Norms()...)
	allNorms = append(allNorms, GetCNormsLibrary()...)
	allNorms = append(allNorms, GetExtendedCNormsLibrary()...)
	allNorms = append(allNorms, GetWoodMetalCNorms()...)
	allNorms = append(allNorms, GetFoodPkgCNorms()...)
	allNorms = append(allNorms, GetLiftMiscCNorms()...)
	allNorms = append(allNorms, GetMachiningCNorms()...)
	allNorms = append(allNorms, GetConveyorAutoCNorms()...)
	allNorms = append(allNorms, GetProcessCNorms()...)
	allNorms = append(allNorms, GetConstructionCNorms()...)
	allNorms = append(allNorms, GetNiche1CNorms()...)
	allNorms = append(allNorms, GetNiche2CNorms()...)
	allNorms = append(allNorms, GetNiche3CNorms()...)
	allNorms = append(allNorms, GetExtendedB2Norms2()...)
	allNorms = append(allNorms, GetWave3aCNorms()...)
	allNorms = append(allNorms, GetWave3a2CNorms()...)
	allNorms = append(allNorms, GetWave3bCNorms()...)
	allNorms = append(allNorms, GetWave3cCNorms()...)
	allNorms = append(allNorms, GetWave3c2CNorms()...)
	allNorms = append(allNorms, GetWave3dCNorms()...)
	allNorms = append(allNorms, GetWave3dExtCNorms()...)
	allNorms = append(allNorms, GetWave3dHvacCNorms()...)
	allNorms = append(allNorms, GetFinalCNorms()...)
	return allNorms
}

func SuggestNorms(machineType string, hazardCategories []string, tags []string) *NormSuggestionResult {
	allNorms := collectAllNorms()

	// Build lookup sets for efficient matching
	hazardSet := toSet(hazardCategories)
	tagSet := toSet(tags)

	seen := make(map[string]bool)
	var suggestions []NormSuggestion

	for _, norm := range allNorms {
		if seen[norm.ID] {
			continue
		}

		suggestion := matchNorm(norm, machineType, hazardSet, tagSet)
		if suggestion != nil {
			seen[norm.ID] = true
			suggestions = append(suggestions, *suggestion)
		}
	}

	return groupByType(suggestions)
}

// matchNorm checks a single norm against the project context and returns
// a suggestion if it matches, or nil otherwise.
func matchNorm(norm NormReference, machineType string, hazardSet, tagSet map[string]bool) *NormSuggestion {
	// A-norms always apply to all machines
	if norm.NormType == "A" {
		return &NormSuggestion{
			Norm:       norm,
			Reason:     "Grundnorm — gilt fuer alle Maschinen",
			Confidence: 1.0,
			Sources:    []string{"norm_type:A"},
		}
	}

	var bestConfidence float64
	var reasons []string
	var sources []string

	// Machine type match — compared on the CANONICAL family so the fine-grained
	// norm vocabulary (welding_machine, band_saw, mobile_crane ...) reconciles
	// with the 68 canonical dropdown keys (welding, circular_saw, crane ...).
	// Without this fold, exact "==" left most C-norms unreachable by machine type.
	machineTypeMatched := false
	if machineType != "" && len(norm.MachineTypes) > 0 {
		projCanon := canonicalMachineType(machineType)
		for _, mt := range norm.MachineTypes {
			if canonicalMachineType(mt) == projCanon {
				bestConfidence = 0.9
				reasons = append(reasons, "Maschinentyp: "+machineType)
				sources = append(sources, "machine_type:"+machineType)
				machineTypeMatched = true
				break
			}
		}
	}

	// C-norms with machine_types ONLY match via machine type — skip tag/hazard matching
	// to avoid suggesting e.g. lathe norms for a press just because both have "rotating_part"
	if norm.NormType == "C" && len(norm.MachineTypes) > 0 && !machineTypeMatched {
		return nil
	}

	// Hazard category match (B-norms and C-norms without machine_types)
	for _, hc := range norm.HazardCats {
		if hazardSet[hc] {
			if 0.8 > bestConfidence {
				bestConfidence = 0.8
			}
			reasons = append(reasons, "Gefaehrdung: "+hc)
			sources = append(sources, "hazard_cat:"+hc)
		}
	}

	// Tag match
	for _, t := range norm.Tags {
		if tagSet[t] {
			if 0.7 > bestConfidence {
				bestConfidence = 0.7
			}
			reasons = append(reasons, "Tag: "+t)
			sources = append(sources, "tag:"+t)
		}
	}

	if bestConfidence == 0 {
		return nil
	}

	return &NormSuggestion{
		Norm:       norm,
		Reason:     joinReasons(reasons),
		Confidence: bestConfidence,
		Sources:    sources,
	}
}

// Per-type caps for norm suggestions to avoid overwhelming the user.
// A professional typically references 3-5 A-norms, 5-10 B-norms, and 3-8 C-norms.
const (
	maxANorms  = 5
	maxB1Norms = 8
	maxB2Norms = 10
	maxCNorms  = 10
)

// groupByType sorts suggestions by confidence, groups them by norm type,
// and applies per-type caps to keep the list manageable.
func groupByType(suggestions []NormSuggestion) *NormSuggestionResult {
	sort.Slice(suggestions, func(i, j int) bool {
		return suggestions[i].Confidence > suggestions[j].Confidence
	})

	result := &NormSuggestionResult{
		ANorms:  []NormSuggestion{},
		B1Norms: []NormSuggestion{},
		B2Norms: []NormSuggestion{},
		CNorms:  []NormSuggestion{},
	}

	for _, s := range suggestions {
		switch s.Norm.NormType {
		case "A":
			if len(result.ANorms) < maxANorms {
				result.ANorms = append(result.ANorms, s)
			}
		case "B1":
			if len(result.B1Norms) < maxB1Norms {
				result.B1Norms = append(result.B1Norms, s)
			}
		case "B2":
			if len(result.B2Norms) < maxB2Norms {
				result.B2Norms = append(result.B2Norms, s)
			}
		case "C":
			if len(result.CNorms) < maxCNorms {
				result.CNorms = append(result.CNorms, s)
			}
		}
	}

	result.Total = len(result.ANorms) + len(result.B1Norms) + len(result.B2Norms) + len(result.CNorms)
	return result
}

// joinReasons combines multiple reason strings with "; ".
func joinReasons(reasons []string) string {
	if len(reasons) == 0 {
		return ""
	}
	result := reasons[0]
	for i := 1; i < len(reasons); i++ {
		result += "; " + reasons[i]
	}
	return result
}
