package iace

import (
	"fmt"
	"sort"
	"strings"
)

// Foreign-framing proposer (P2 slice 4, type 2). DEV-TIME, propose-only.
//
// A pattern can fire for a machine yet describe its hazard with a zone text
// framed for a DIFFERENT machine (e.g. a dishwasher hazard whose zone names
// "Walzen, Transportbaender" or "Bearbeitungszone"). Such foreign framing leaks
// through terms that are NOT yet in domainGateTerms — once a term is a gate term,
// the ghost-pattern invariant already fences the pattern out. So we surface the
// candidates structurally: zone terms a fired pattern names that the machine's
// narrative never mentions (minus generic hazard-location vocabulary). A human
// (or the LLM) then decides: add a dom_* gate term, or re-frame the zone text.
//
// This OVER-surfaces by design — the human/LLM is the precision filter, not the
// detector (same contract as the dedup proposer).

// genericHazardStop are hazard-LOCATION words that legitimately appear in zones
// without being echoed in a narrative — they are not evidence of foreign framing.
var genericHazardStop = map[string]bool{
	"quetschstelle": true, "einzugstelle": true, "einzugsstelle": true, "scherstelle": true,
	"schneidstelle": true, "stossstelle": true, "fangstelle": true, "klemmstelle": true,
	"gefahrbereich": true, "gefahrenbereich": true, "gefahrstelle": true, "gefahrenstelle": true,
	"arbeitsbereich": true, "wirkbereich": true, "schutzbereich": true, "umgebung": true,
	"bereich": true, "zugang": true, "oberflaeche": true, "oberflaechen": true,
	"gehaeuse": true, "bauteil": true, "bauteile": true, "komponente": true, "maschine": true,
}

// FramingCandidate is a fired pattern whose zone text looks foreign for the machine.
type FramingCandidate struct {
	Pattern        string   `json:"pattern"`
	Name           string   `json:"name"`
	Category       string   `json:"category"`
	Zone           string   `json:"zone"`
	OrphanTerms    []string `json:"orphan_terms"`
	OrphanFraction float64  `json:"orphan_fraction"`
	Verdict        string   `json:"verdict"` // heuristic lean: foreign | plausible
	Evidence       string   `json:"evidence"`
}

// FindFramingCandidates returns fired patterns whose zone is mostly not echoed in
// the narrative, sorted by orphan fraction descending (deterministic).
func FindFramingCandidates(fired []PatternMatch, narrative string, minFraction float64) []FramingCandidate {
	nar := strings.ToLower(narrative)
	var narStems []string
	for _, w := range proposerWordSplit.Split(nar, -1) {
		if len([]rune(w)) >= 5 {
			narStems = append(narStems, w)
		}
	}
	var out []FramingCandidate
	for _, pm := range fired {
		parts := zoneParts(pm.ZoneDE)
		if len(parts) == 0 {
			continue
		}
		var orphans []string
		for _, p := range parts {
			if !partEchoed(p, nar, narStems) {
				orphans = append(orphans, p)
			}
		}
		frac := float64(len(orphans)) / float64(len(parts))
		if len(orphans) == 0 || frac < minFraction {
			continue
		}
		out = append(out, FramingCandidate{
			Pattern: pm.PatternID, Name: pm.PatternName, Category: primaryCat(pm),
			Zone: pm.ZoneDE, OrphanTerms: orphans, OrphanFraction: round2(frac),
			Verdict:  framingHeuristicVerdict(frac),
			Evidence: fmt.Sprintf("%d/%d zone terms have no narrative echo: %s", len(orphans), len(parts), strings.Join(orphans, ", ")),
		})
	}
	sort.SliceStable(out, func(i, j int) bool {
		if out[i].OrphanFraction != out[j].OrphanFraction {
			return out[i].OrphanFraction > out[j].OrphanFraction
		}
		return out[i].Pattern < out[j].Pattern
	})
	return out
}

func framingHeuristicVerdict(frac float64) string {
	if frac >= 0.99 {
		return "foreign" // nothing in the zone is echoed by the narrative
	}
	return "plausible" // partial echo — likely generic vocabulary, human to confirm
}

// zoneParts splits a zone string into significant terms on commas, slashes,
// parentheses and semicolons, lowercased, length >= 4.
func zoneParts(zone string) []string {
	fields := strings.FieldsFunc(strings.ToLower(zone), func(r rune) bool {
		return r == ',' || r == '/' || r == ';' || r == '(' || r == ')'
	})
	var out []string
	for _, f := range fields {
		if t := strings.TrimSpace(f); len([]rune(t)) >= 4 {
			out = append(out, t)
		}
	}
	return out
}

// partEchoed reports whether a zone part is reflected in the narrative. Matching
// is bidirectional to survive German compounding: a zone word echoes if it is a
// generic hazard term, if it is a substring of the narrative, OR if any narrative
// stem (>= 5 chars) is a substring of the zone word (so narrative "Steuerung"
// echoes zone "Steuerungssystem").
func partEchoed(part, narrative string, narStems []string) bool {
	for _, w := range strings.Fields(part) {
		if genericHazardStop[w] {
			return true
		}
		if len([]rune(w)) < 4 {
			continue
		}
		if strings.Contains(narrative, w) {
			return true
		}
		for _, ns := range narStems {
			if strings.Contains(w, ns) {
				return true
			}
		}
	}
	return false
}

// RenderFramingQueue renders foreign-framing candidates as a markdown review queue.
func RenderFramingQueue(machine string, candidates []FramingCandidate) string {
	var b strings.Builder
	fmt.Fprintf(&b, "# Foreign-framing review queue — %s\n\n", machine)
	fmt.Fprintf(&b, "%d fired pattern(s) name zone terms the narrative never mentions. Propose-only — a human (or the LLM) decides: add a dom_* gate term, or re-frame the zone.\n\n", len(candidates))
	for i, c := range candidates {
		fmt.Fprintf(&b, "## %d. %s — %s  [%s, orphan %.0f%%]\n", i+1, c.Pattern, c.Name, c.Verdict, c.OrphanFraction*100)
		fmt.Fprintf(&b, "- category: %s\n- zone: %s\n", c.Category, c.Zone)
		fmt.Fprintf(&b, "- orphan terms (no narrative echo): %s\n", strings.Join(c.OrphanTerms, ", "))
		fmt.Fprintf(&b, "- suggested action: %s\n\n", framingAction(c.Verdict))
	}
	return b.String()
}

func framingAction(verdict string) string {
	if verdict == "foreign" {
		return "likely foreign-framed — propose a dom_* gate term for the orphan term(s), or re-frame the zone; human confirms + commits + pins a GT case"
	}
	return "partial echo — likely generic vocabulary; human to confirm whether any orphan term is a foreign-machine component"
}
