package analytics

import "testing"

// TestSyntheticParser_Parse is the first direct coverage of the synthetic-CRT
// SourceObject grammar. It pins every shape, the ordering precedence
// (nested-before-single, scoped-before-bare), the generalized object-name
// prefix, and the documented newly-matched edge cases.
func TestSyntheticParser_Parse(t *testing.T) {
	p := &syntheticParser{}
	tests := []struct {
		name string
		in   string
		want parsedSynthetic
	}{
		// --- non-matches → shapeUnknown ---
		{"empty", "", parsedSynthetic{}},
		{"garbage", "RandomGarbage", parsedSynthetic{}},
		{"no dollar", "CustomEntity", parsedSynthetic{}},

		// --- plain single-entity forms ---
		{"bare", "CustomEntity$Account",
			parsedSynthetic{shape: shapeCustomEntity, baseObject: "Account"}},
		{"scoped", "CustomEntity$Contact@Account.AccountId",
			parsedSynthetic{shape: shapeCustomEntityScoped, baseObject: "Contact", anchorObject: "Account", anchorField: "AccountId"}},
		{"namespaced base", "CustomEntity$pqcrush__Account_Plan__c",
			parsedSynthetic{shape: shapeCustomEntity, baseObject: "pqcrush__Account_Plan__c"}},

		// --- generalized prefix (was the hardcoded "Order" arm) ---
		// The prefix is matched structurally and discarded, so a prefixed form
		// yields the SAME shape+fields as its unprefixed counterpart.
		{"order prefix bare", "OrderCustomEntity$Account",
			parsedSynthetic{shape: shapeCustomEntity, baseObject: "Account"}},
		{"order prefix scoped", "OrderCustomEntity$Contact@Account.AccountId",
			parsedSynthetic{shape: shapeCustomEntityScoped, baseObject: "Contact", anchorObject: "Account", anchorField: "AccountId"}},
		// Any other app object's prefix generalizes identically — proof the
		// grammar no longer recognizes a register of CRM object names.
		{"opportunity prefix bare", "OpportunityCustomEntity$Account",
			parsedSynthetic{shape: shapeCustomEntity, baseObject: "Account"}},
		{"jira-like prefix scoped", "IssueCustomEntity$Project@Sprint.SprintId",
			parsedSynthetic{shape: shapeCustomEntityScoped, baseObject: "Project", anchorObject: "Sprint", anchorField: "SprintId"}},

		// --- nested forms: tried first, never swallowed by the single family ---
		{"nested bare", "CustomEntityCustomEntity$A$B",
			parsedSynthetic{shape: shapeNestedCustomEntity, baseObject: "A", secondary: "B"}},
		{"nested scoped", "CustomEntityCustomEntity$A$B@C.D",
			parsedSynthetic{shape: shapeNestedCustomEntity, baseObject: "A", secondary: "B", anchorObject: "C", anchorField: "D"}},

		// --- documented edge: single-segment doubled prefix ---
		// Was shapeUnknown before generalization; now classifies as a bare form
		// with the leading "CustomEntity" treated as the inert prefix. This does
		// not regress (see resolver test: a non-real base still degrades to the
		// single-table fallthrough via C4).
		{"doubled prefix, single segment", "CustomEntityCustomEntity$Foo",
			parsedSynthetic{shape: shapeCustomEntity, baseObject: "Foo"}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := p.Parse(tt.in); got != tt.want {
				t.Fatalf("Parse(%q) = %+v, want %+v", tt.in, got, tt.want)
			}
		})
	}
}

// TestSyntheticParser_NestedNotSwallowed makes the no-swallow guarantee explicit:
// the generalized single-entity arms match exactly one "$"-segment, so a
// two-segment nested string can only be classified as nested.
func TestSyntheticParser_NestedNotSwallowed(t *testing.T) {
	p := &syntheticParser{}
	for _, in := range []string{
		"CustomEntityCustomEntity$A$B",
		"CustomEntityCustomEntity$A$B@C.D",
	} {
		if got := p.Parse(in); got.shape != shapeNestedCustomEntity {
			t.Fatalf("Parse(%q).shape = %d, want shapeNestedCustomEntity (%d)", in, got.shape, shapeNestedCustomEntity)
		}
	}
}
