package fieldprovenance

// ObjectDeclaration is a neutral shape for one object's L1 declaration,
// consumed by BuildClassifier. Callers translate their own JSON shape
// (system_tables.json / standard_objects_spec.json / tenant_objects_spec.json)
// into this common format so fieldprovenance stays decoupled from any
// specific on-disk schema.
type ObjectDeclaration struct {
	APIName string
	Fields  []FieldDeclaration
}

// FieldDeclaration is a neutral shape for one field's L1 declaration.
// Only attributes consulted by the classifier are represented; callers
// may ignore everything else on the source side.
type FieldDeclaration struct {
	APIName      string
	IsCompound   bool
	ComposedFrom []string
}

// BuildClassifier wires three L1 manifest slices + the platform standard-
// field template into a ready-to-use Classifier. Any nil slice is treated
// as an empty manifest. Safe for concurrent reads after return.
func BuildClassifier(
	systemTables []ObjectDeclaration,
	standardSpec []ObjectDeclaration,
	tenantSpec []ObjectDeclaration,
	platformStandardFields []string,
) *Classifier {
	return NewClassifier(
		indexManifest(systemTables),
		indexManifest(standardSpec),
		indexManifest(tenantSpec),
		platformStandardFields,
	)
}

// indexManifest converts a slice of ObjectDeclaration into a *Manifest
// ready for Lookup. Duplicate (object, field) entries: last wins. A nil or
// empty input returns an empty (but non-nil) manifest.
func indexManifest(objects []ObjectDeclaration) *Manifest {
	m := NewManifest()
	for _, obj := range objects {
		if obj.APIName == "" {
			continue
		}
		for _, f := range obj.Fields {
			if f.APIName == "" {
				continue
			}
			decl := &FieldDecl{
				ObjectAPIName: obj.APIName,
				FieldAPIName:  f.APIName,
				IsCompound:    f.IsCompound,
			}
			if len(f.ComposedFrom) > 0 {
				decl.ComposedFrom = append([]string(nil), f.ComposedFrom...)
			}
			m.Add(obj.APIName, f.APIName, decl)
		}
	}
	return m
}
