package topdata

import (
	"fmt"
	"strings"
)

type familyIdentity struct {
	Parent string
	Child  string
}

type familyExpansionSource struct {
	Dataset   string
	Column    string
	Predicate string
}

type familyExpansionSpec struct {
	Family            string
	FamilyKey         string
	Template          string
	ChildSource       familyExpansionSource
	NamePrefix        string
	LabelPrefix       string
	ConstantPrefix    string
	TemplateFields    []string
	DefaultFields     map[string]any
	ApplyAfterModules bool
	ChildRefField     string
	IdentitySource    string
	AllowExistingOnly bool
	AutoPrereqFields  map[string]string
	LegacyFamilyKeys  []string
}

func splitFamilyExpansionIdentity(text string) familyIdentity {
	text = strings.TrimSpace(text)
	if text == "" {
		return familyIdentity{}
	}
	if idx := strings.Index(text, "_"); idx > 0 && idx < len(text)-1 {
		return familyIdentity{
			Parent: text[:idx],
			Child:  text[idx+1:],
		}
	}
	return familyIdentity{Parent: text}
}

func parseFamilyExpansionSource(raw any) (familyExpansionSource, error) {
	if raw == nil {
		return familyExpansionSource{}, fmt.Errorf("child_source is required")
	}
	obj, ok := raw.(map[string]any)
	if !ok {
		return familyExpansionSource{}, fmt.Errorf("child_source must be an object")
	}
	dataset, ok := obj["dataset"].(string)
	if !ok || strings.TrimSpace(dataset) == "" {
		return familyExpansionSource{}, fmt.Errorf("child_source.dataset must be a non-empty string")
	}
	source := familyExpansionSource{
		Dataset: strings.TrimSpace(dataset),
	}
	if column, ok := obj["column"].(string); ok && strings.TrimSpace(column) != "" {
		source.Column = strings.TrimSpace(column)
	}
	if predicate, ok := obj["predicate"].(string); ok && strings.TrimSpace(predicate) != "" {
		source.Predicate = strings.TrimSpace(predicate)
	}
	return source, nil
}

func parseFamilyExpansionSpec(path string, obj map[string]any) (familyExpansionSpec, error) {
	family, ok := obj["family"].(string)
	if !ok || strings.TrimSpace(family) == "" {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: family must be a non-empty string", path)
	}
	familyKey, ok := obj["family_key"].(string)
	if !ok || strings.TrimSpace(familyKey) == "" {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: family_key must be a non-empty string", path)
	}
	template, ok := obj["template"].(string)
	if !ok || strings.TrimSpace(template) == "" {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: template must be a non-empty string", path)
	}
	source, err := parseFamilyExpansionSource(obj["child_source"])
	if err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	namePrefix, _ := optionalTrimmedString(obj, "name_prefix")
	labelPrefix, _ := optionalTrimmedString(obj, "label_prefix")
	constantPrefix, _ := optionalTrimmedString(obj, "constant_prefix")
	templateFields, err := parseOptionalStringArray(obj["template_fields"], "template_fields")
	if err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	defaultFields, err := parseOptionalObject(obj["default_fields"], "default_fields")
	if err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	applyAfterModules, err := parseOptionalBoolField(obj["apply_after_modules"], "apply_after_modules")
	if err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	childRefField, _ := optionalTrimmedString(obj, "child_ref_field")
	identitySource, _ := optionalTrimmedString(obj, "identity_source")
	switch identitySource {
	case "", "child_source_value":
	default:
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: identity_source must be empty or child_source_value", path)
	}
	autoPrereqFields, err := parseOptionalStringMap(obj["auto_prereq_fields"], "auto_prereq_fields")
	if err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	legacyFamilyKeys, err := parseOptionalStringArray(obj["legacy_family_keys"], "legacy_family_keys")
	if err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	if err := validateLegacyFamilyKeys(familyKey, legacyFamilyKeys); err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	allowExistingOnly, err := parseOptionalBoolField(obj["allow_existing_only"], "allow_existing_only")
	if err != nil {
		return familyExpansionSpec{}, fmt.Errorf("generated file %s: %w", path, err)
	}
	return familyExpansionSpec{
		Family:            strings.TrimSpace(family),
		FamilyKey:         strings.TrimSpace(familyKey),
		Template:          strings.TrimSpace(template),
		ChildSource:       source,
		NamePrefix:        namePrefix,
		LabelPrefix:       labelPrefix,
		ConstantPrefix:    constantPrefix,
		TemplateFields:    templateFields,
		DefaultFields:     defaultFields,
		ApplyAfterModules: applyAfterModules,
		ChildRefField:     childRefField,
		IdentitySource:    identitySource,
		AllowExistingOnly: allowExistingOnly,
		AutoPrereqFields:  autoPrereqFields,
		LegacyFamilyKeys:  legacyFamilyKeys,
	}, nil
}

func validateLegacyFamilyKeys(familyKey string, legacyFamilyKeys []string) error {
	seen := map[string]string{}
	normalizedFamilyKey := normalizeKeyIdentity(familyKey)
	for _, legacyKey := range legacyFamilyKeys {
		normalizedLegacyKey := normalizeKeyIdentity(legacyKey)
		if normalizedLegacyKey == normalizedFamilyKey {
			return fmt.Errorf("legacy_family_keys must not include family_key %q", familyKey)
		}
		if previous, ok := seen[normalizedLegacyKey]; ok {
			return fmt.Errorf("legacy_family_keys contains duplicate-equivalent keys %q and %q", previous, legacyKey)
		}
		seen[normalizedLegacyKey] = legacyKey
	}
	return nil
}

func isFamilyExpansionObject(obj map[string]any) bool {
	_, hasFamilyKey := obj["family_key"]
	_, hasTemplate := obj["template"]
	_, hasChildSource := obj["child_source"]
	_, hasNamePrefix := obj["name_prefix"]
	_, hasLabelPrefix := obj["label_prefix"]
	_, hasConstantPrefix := obj["constant_prefix"]
	_, hasTemplateFields := obj["template_fields"]
	_, hasDefaultFields := obj["default_fields"]
	_, hasApplyAfterModules := obj["apply_after_modules"]
	_, hasChildRefField := obj["child_ref_field"]
	_, hasIdentitySource := obj["identity_source"]
	_, hasAllowExistingOnly := obj["allow_existing_only"]
	_, hasAutoPrereqFields := obj["auto_prereq_fields"]
	_, hasLegacyFamilyKeys := obj["legacy_family_keys"]
	return hasFamilyKey || hasTemplate || hasChildSource || hasNamePrefix || hasLabelPrefix ||
		hasConstantPrefix || hasTemplateFields || hasDefaultFields || hasApplyAfterModules || hasChildRefField ||
		hasIdentitySource || hasAllowExistingOnly || hasAutoPrereqFields || hasLegacyFamilyKeys
}

func optionalTrimmedString(obj map[string]any, field string) (string, bool) {
	text, ok := obj[field].(string)
	if !ok || strings.TrimSpace(text) == "" {
		return "", false
	}
	return strings.TrimSpace(text), true
}

func parseOptionalStringArray(raw any, field string) ([]string, error) {
	if raw == nil {
		return nil, nil
	}
	items, ok := raw.([]any)
	if !ok {
		return nil, fmt.Errorf("%s must be an array of strings", field)
	}
	out := make([]string, 0, len(items))
	for _, item := range items {
		text, ok := item.(string)
		if !ok || strings.TrimSpace(text) == "" {
			return nil, fmt.Errorf("%s must contain only non-empty strings", field)
		}
		out = append(out, strings.TrimSpace(text))
	}
	return out, nil
}

func parseOptionalObject(raw any, field string) (map[string]any, error) {
	if raw == nil {
		return nil, nil
	}
	obj, ok := raw.(map[string]any)
	if !ok {
		return nil, fmt.Errorf("%s must be an object", field)
	}
	return obj, nil
}

func parseOptionalStringMap(raw any, field string) (map[string]string, error) {
	if raw == nil {
		return nil, nil
	}
	obj, ok := raw.(map[string]any)
	if !ok {
		return nil, fmt.Errorf("%s must be an object of strings", field)
	}
	out := make(map[string]string, len(obj))
	for key, value := range obj {
		text, ok := value.(string)
		if !ok || strings.TrimSpace(text) == "" {
			return nil, fmt.Errorf("%s must contain only non-empty string values", field)
		}
		out[key] = strings.TrimSpace(text)
	}
	return out, nil
}

func parseOptionalBoolField(raw any, field string) (bool, error) {
	if raw == nil {
		return false, nil
	}
	value, ok := raw.(bool)
	if !ok {
		return false, fmt.Errorf("%s must be a boolean", field)
	}
	return value, nil
}

func familyMetadata(parent, child, source, template string) map[string]any {
	meta := map[string]any{
		"parent": strings.TrimSpace(parent),
	}
	if strings.TrimSpace(child) != "" {
		meta["child"] = strings.TrimSpace(child)
	}
	if strings.TrimSpace(source) != "" {
		meta["source"] = strings.TrimSpace(source)
	}
	if strings.TrimSpace(template) != "" {
		meta["template"] = strings.TrimSpace(template)
	}
	return map[string]any{"family": meta}
}

func parseFamilyMetadata(raw any) (map[string]any, error) {
	obj, ok := raw.(map[string]any)
	if !ok {
		return nil, fmt.Errorf("must be an object")
	}
	parent, ok := obj["parent"].(string)
	if !ok || strings.TrimSpace(parent) == "" {
		return nil, fmt.Errorf("parent must be a non-empty string")
	}
	meta := map[string]any{
		"parent": strings.TrimSpace(parent),
	}
	if child, ok := obj["child"].(string); ok && strings.TrimSpace(child) != "" {
		meta["child"] = strings.TrimSpace(child)
	}
	if source, ok := obj["source"].(string); ok && strings.TrimSpace(source) != "" {
		meta["source"] = strings.TrimSpace(source)
	}
	if template, ok := obj["template"].(string); ok && strings.TrimSpace(template) != "" {
		meta["template"] = strings.TrimSpace(template)
	}
	return meta, nil
}

func childTokenFromExpandedKey(key, parent string) string {
	key = strings.TrimSpace(key)
	key = strings.TrimPrefix(key, "feat:")
	if strings.HasPrefix(key, parent+"_") {
		return strings.TrimPrefix(key, parent+"_")
	}
	if strings.HasPrefix(key, parent) {
		return strings.TrimPrefix(key, parent)
	}
	return ""
}
