package analytics

import "regexp"

// syntheticShape classifies the encoded form of a Report.SourceObject that is
// not a stored Custom Report Type. See docs/design/crt-report-support.md §4.6
// and docs/design/crt-research-data.md Q3 for the observed forms.
type syntheticShape int

const (
	shapeUnknown syntheticShape = iota
	shapeCustomEntity
	shapeCustomEntityScoped
	shapeOrderCustomEntity
	shapeNestedCustomEntity
)

type parsedSynthetic struct {
	shape        syntheticShape
	baseObject   string
	anchorObject string
	anchorField  string
	secondary    string
}

// syntheticParser converts Report.SourceObject strings into a parsedSynthetic.
// It is a pure string parser with no dependencies; the zero value is usable.
type syntheticParser struct{}

// ident matches a Salesforce-style identifier segment, including namespace
// prefixes (`pqcrush__Account_Plan__c`) and standard fields.
const ident = `[A-Za-z0-9_]+`

// Ordering matters: the nested-with-tail pattern must be tried before the plain
// nested one, and the scoped variants before their bare counterparts, so the
// most specific shape wins.
var (
	reNestedScoped = regexp.MustCompile(
		`^CustomEntityCustomEntity\$(` + ident + `)\$(` + ident + `)@(` + ident + `)\.(` + ident + `)$`,
	)
	reNested = regexp.MustCompile(
		`^CustomEntityCustomEntity\$(` + ident + `)\$(` + ident + `)$`,
	)
	reOrderScoped = regexp.MustCompile(
		`^OrderCustomEntity\$(` + ident + `)@(` + ident + `)\.(` + ident + `)$`,
	)
	reOrderBare = regexp.MustCompile(
		`^OrderCustomEntity\$(` + ident + `)$`,
	)
	reScoped = regexp.MustCompile(
		`^CustomEntity\$(` + ident + `)@(` + ident + `)\.(` + ident + `)$`,
	)
	reBare = regexp.MustCompile(
		`^CustomEntity\$(` + ident + `)$`,
	)
)

// Parse classifies sourceObject into a parsedSynthetic. Never panics, never
// returns an error; unrecognised input yields parsedSynthetic{shape: shapeUnknown}.
func (p *syntheticParser) Parse(sourceObject string) parsedSynthetic {
	if sourceObject == "" {
		return parsedSynthetic{}
	}

	if m := reNestedScoped.FindStringSubmatch(sourceObject); m != nil {
		// Nested-with-anchor is downgraded to shapeNestedCustomEntity; the synthesizer
		// degrades further to base-object-only per design §4.10 C5.
		return parsedSynthetic{
			shape:        shapeNestedCustomEntity,
			baseObject:   m[1],
			secondary:    m[2],
			anchorObject: m[3],
			anchorField:  m[4],
		}
	}
	if m := reNested.FindStringSubmatch(sourceObject); m != nil {
		return parsedSynthetic{
			shape:      shapeNestedCustomEntity,
			baseObject: m[1],
			secondary:  m[2],
		}
	}
	if m := reOrderScoped.FindStringSubmatch(sourceObject); m != nil {
		return parsedSynthetic{
			shape:        shapeOrderCustomEntity,
			baseObject:   m[1],
			anchorObject: m[2],
			anchorField:  m[3],
		}
	}
	if m := reOrderBare.FindStringSubmatch(sourceObject); m != nil {
		return parsedSynthetic{
			shape:      shapeOrderCustomEntity,
			baseObject: m[1],
		}
	}
	if m := reScoped.FindStringSubmatch(sourceObject); m != nil {
		return parsedSynthetic{
			shape:        shapeCustomEntityScoped,
			baseObject:   m[1],
			anchorObject: m[2],
			anchorField:  m[3],
		}
	}
	if m := reBare.FindStringSubmatch(sourceObject); m != nil {
		return parsedSynthetic{
			shape:      shapeCustomEntity,
			baseObject: m[1],
		}
	}
	return parsedSynthetic{}
}
