package iace

// P3: pin accepted proposer decisions into the GT gate.
//
// When a human accepts a proposal from the offline proposer (a dedup
// supersession, a foreign-framing gate, a vocab→tag mapping, a coverage hazard),
// they record an AcceptedPin. A pin is a tiny, machine-scoped invariant — "this
// pattern MUST (or must NOT) fire for this machine" — that a test re-checks on
// every run. This is what makes the library's growth COMPOUND into the gate
// instead of silently eroding it: a future change that re-introduces a dropped
// duplicate, un-gates a foreign pattern, or removes a coverage hazard breaks the
// pin and fails CI.
//
// A single boolean covers all four proposal types:
//   - dedup supersession accepted → DropPattern MustFire=false
//   - foreign-framing gate accepted → foreign pattern MustFire=false
//   - vocab→tag / coverage hazard accepted → the enabled pattern MustFire=true

// AcceptedPin is one regression invariant for an accepted proposal.
type AcceptedPin struct {
	Pattern      string `json:"pattern"`
	MustFire     bool   `json:"must_fire"`
	Reason       string `json:"reason"`
	FromProposal string `json:"from_proposal,omitempty"`
}

// PinSet is the accepted-pin registry for one machine (testdata/accepted_pins_*.json).
type PinSet struct {
	Machine string        `json:"machine"`
	Pins    []AcceptedPin `json:"pins"`
}

// PinResult is the verdict for one pin against an engine run.
type PinResult struct {
	Pin    AcceptedPin
	OK     bool
	Detail string
}

// VerifyPins checks every pin against the set of pattern IDs the engine actually
// fired for the machine. A pin holds iff the pattern's presence equals MustFire.
func VerifyPins(pins []AcceptedPin, firedPatternIDs []string) []PinResult {
	fired := make(map[string]bool, len(firedPatternIDs))
	for _, id := range firedPatternIDs {
		fired[id] = true
	}
	out := make([]PinResult, 0, len(pins))
	for _, p := range pins {
		got := fired[p.Pattern]
		ok := got == p.MustFire
		detail := "ok"
		if !ok {
			if p.MustFire {
				detail = "expected to fire but did NOT — coverage/mapping regressed"
			} else {
				detail = "expected to be suppressed but FIRED — gate/supersession regressed"
			}
		}
		out = append(out, PinResult{Pin: p, OK: ok, Detail: detail})
	}
	return out
}

// GenerateDedupPin turns an accepted (verdict=duplicate) dedup candidate into the
// pin that protects the supersession: the dropped pattern must no longer fire.
func GenerateDedupPin(c DedupCandidate) AcceptedPin {
	return AcceptedPin{
		Pattern:      c.DropPattern,
		MustFire:     false,
		Reason:       "accepted duplicate of " + c.KeepPattern + " (" + c.Category + ")",
		FromProposal: "dedup " + c.DropPattern + " -> " + c.KeepPattern,
	}
}
