package services

// proceeding_mapping bridges the two proceeding-type vocabularies in the
// codebase: the **litigation** conceptual category (INF / REV / APP /
// CCR / AMD / APM / ZPO_CIVIL) used by the historical project-binding
// + Pipeline-A rules, and the **fristenrechner** code category
// (upc.inf.cfi / de.inf.lg / epa.opp.opd / …) used by the Determinator
// cascade + rule engine. Post-Phase-3-Slice-5 (t-paliad-186) projects
// bind to fristenrechner codes directly, but the litigation→fristenrechner
// mapping is still needed for the ~40 Pipeline-A rules that remain on
// litigation proceedings and for any other surface that thinks in
// litigation terms.
//
// The mapping table here is the single source of truth — see
// docs/design-determinator-row-cascade-2026-05-13.md §4.2 for the
// design rationale + ambiguity notes, and
// docs/design-proceeding-code-taxonomy-2026-05-18.md for the
// lowercase dot-separated naming convention applied by mig 096
// (t-paliad-206). **Never silent FK promotion**: every ambiguous case
// returns ok=false so callers can degrade gracefully ("no narrowing")
// instead of guessing.

// Stable code constants — the strings landed by mig 096. Use these
// throughout the codebase so a future rename only needs to touch this
// file. The id-anchored FKs (deadline_rules.proceeding_type_id,
// projects.proceeding_type_id) are unaffected by the rename.
const (
	CodeUPCInfringement       = "upc.inf.cfi"
	CodeUPCRevocation         = "upc.rev.cfi"
	CodeUPCCounterclaim       = "upc.ccr.cfi"
	CodeUPCPreliminary        = "upc.pi.cfi"
	CodeUPCDamages            = "upc.dmgs.cfi"
	CodeUPCDiscovery          = "upc.disc.cfi"
	CodeUPCAppealMerits       = "upc.apl.merits"
	CodeUPCAppealOrder        = "upc.apl.order"
	CodeUPCAppealCost         = "upc.apl.cost"
	CodeDEInfringementLG      = "de.inf.lg"
	CodeDEInfringementOLG     = "de.inf.olg"
	CodeDEInfringementBGH     = "de.inf.bgh"
	CodeDENullityBPatG        = "de.null.bpatg"
	CodeDENullityBGH          = "de.null.bgh"
	CodeEPAGrant              = "epa.grant.exa"
	CodeEPAOpposition         = "epa.opp.opd"
	CodeEPAOppositionAppeal   = "epa.opp.boa"
	CodeDPMAOpposition        = "dpma.opp.dpma"
	CodeDPMAAppealBPatG       = "dpma.appeal.bpatg"
	CodeDPMAAppealBGH         = "dpma.appeal.bgh"
)

// MapLitigationToFristenrechner returns the fristenrechner code +
// condition flags implied by a (litigationCode, jurisdiction) pair.
//
// Inputs are case-sensitive — pass the canonical upper-snake form
// (e.g. "INF", "UPC"). Unrecognised codes or genuinely ambiguous
// combinations (APP+DE, ZPO_CIVIL+DE) return ok=false with a zero
// fristenrechner code; callers should treat that as "no narrowing"
// and leave the cascade wide-open rather than auto-pick.
//
// Condition flags are returned as a slice so callers can apply them
// alongside the fristenrechner code (CCR+UPC → upc.inf.cfi + with_ccr,
// AMD+UPC → upc.inf.cfi + with_amend). An empty slice means no flag
// context applies.
func MapLitigationToFristenrechner(litigationCode, jurisdiction string) (fristenrechnerCode string, conditionFlags []string, ok bool) {
	switch litigationCode {
	case "INF":
		switch jurisdiction {
		case "UPC":
			return CodeUPCInfringement, nil, true
		case "DE":
			return CodeDEInfringementLG, nil, true
		}
	case "REV":
		switch jurisdiction {
		case "UPC":
			return CodeUPCRevocation, nil, true
		case "DE":
			return CodeDENullityBPatG, nil, true
		}
	case "CCR":
		// Counterclaim revocation — UPC fold-in is structural (the
		// counterclaim lives inside an upc.inf.cfi proceeding with the
		// with_ccr flag). DE Nichtigkeit is conceptually the same
		// adversarial-validity test, no separate flag.
		switch jurisdiction {
		case "UPC":
			return CodeUPCInfringement, []string{"with_ccr"}, true
		case "DE":
			return CodeDENullityBPatG, nil, true
		}
	case "AMD":
		// Amendment-application bundled into upc.inf.cfi via with_amend.
		// No DE / EPA / DPMA analogue today.
		if jurisdiction == "UPC" {
			return CodeUPCInfringement, []string{"with_amend"}, true
		}
	case "APP":
		// Appeal is ambiguous in DE (OLG vs BGH) and the project
		// model doesn't carry the instance hint we'd need to
		// disambiguate. UPC is unambiguous — upc.apl.merits covers
		// the merits appeal track for inf/rev/ccr/damages.
		if jurisdiction == "UPC" {
			return CodeUPCAppealMerits, nil, true
		}
	case "APM":
		// Preliminary injunction / urgency procedure — UPC-only
		// concept in the fristenrechner taxonomy.
		if jurisdiction == "UPC" {
			return CodeUPCPreliminary, nil, true
		}
	case "OPP":
		// Opposition — primarily EPA. DPMA has dpma.opp.dpma but it
		// doesn't surface from the litigation vocabulary today.
		if jurisdiction == "EPA" {
			return CodeEPAOpposition, nil, true
		}
	}
	return "", nil, false
}

// ResolveCounterclaimRouting handles the determinator's
// upc.ccr.cfi illustrative-peer route: the code exists in the dropdown
// for taxonomic completeness, but no rules are attached to it. When the
// cascade resolves to upc.ccr.cfi we route the rule lookup back to
// upc.inf.cfi with a default with_ccr=true flag — see
// docs/design-proceeding-code-taxonomy-2026-05-18.md §0.3 sub-decision S1.
//
// `code` is the proceeding code the cascade resolved to. If it's
// upc.ccr.cfi, the function returns (CodeUPCInfringement,
// []string{"with_ccr"}, true). For any other code the function returns
// (code, nil, false) and callers proceed with the code unchanged. The
// boolean signals "routing was applied"; the caller can surface the hint
// "Regeln liegen auf upc.inf.cfi (with_ccr=true); wir leiten Sie dorthin
// weiter." in the UI.
func ResolveCounterclaimRouting(code string) (effectiveCode string, defaultFlags []string, routed bool) {
	if code == CodeUPCCounterclaim {
		return CodeUPCInfringement, []string{"with_ccr"}, true
	}
	return code, nil, false
}
