package packs

import (
	"fmt"
	"sort"
	"strings"
)

func sourceBackedComponentPayload(source *ResolvedPackSource, component ManifestComponent) ([]byte, error) {
	if strings.TrimSpace(component.Path) != "" {
		normalized, err := normalizeManifestPath(component.Path)
		if err != nil {
			return nil, err
		}
		payload, ok := source.Files[normalized]
		if !ok {
			return nil, fmt.Errorf("payload path %q was not loaded", normalized)
		}
		return append([]byte(nil), payload...), nil
	}
	if len(component.Payload) == 0 {
		return nil, fmt.Errorf("component has no payload")
	}
	return append([]byte(nil), component.Payload...), nil
}

func componentPayloadLabel(component ManifestComponent) string {
	if strings.TrimSpace(component.Path) != "" {
		return component.Path
	}
	return fmt.Sprintf("components[%s/%s].payload", component.Kind, strings.TrimSpace(component.BusinessKey))
}

func topologicallySortManifestComponents(source *ResolvedPackSource, components []ManifestComponent) ([]ManifestComponent, error) {
	byKey := map[string]ManifestComponent{}
	indegree := map[string]int{}
	dependents := map[string][]string{}
	explicitDependencies := map[string]map[string]struct{}{}
	defaultPageLayoutKeyByObject := map[string]string{}
	defaultsOnlyObjectSpecListFieldsKeyByObject := map[string]string{}
	addEdge := func(fromKey, toKey string) {
		for _, existing := range dependents[fromKey] {
			if existing == toKey {
				return
			}
		}
		dependents[fromKey] = append(dependents[fromKey], toKey)
		indegree[toKey]++
	}
	for _, component := range components {
		key := sourceBackedComponentKey(component.Kind, component.BusinessKey)
		byKey[key] = component
		if _, ok := indegree[key]; !ok {
			indegree[key] = 0
		}
		if component.Kind == ComponentKindPageLayout {
			if objectAPIName, layoutKey, ok := splitSourceBackedPageLayoutBusinessKey(component.BusinessKey); ok && strings.EqualFold(layoutKey, "default") {
				defaultPageLayoutKeyByObject[strings.ToLower(objectAPIName)] = key
			}
		}
		if component.Kind == ComponentKindObjectSpec && component.Manageability == ManageabilityDefaultsOnly {
			payload, err := sourceBackedComponentPayload(source, component)
			if err != nil {
				return nil, fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
			}
			specs, err := parseObjectSpecsJSON(payload, componentPayloadLabel(component))
			if err != nil {
				return nil, fmt.Errorf("%s %s: %w", component.Kind, component.BusinessKey, err)
			}
			for _, spec := range specs {
				if spec.ListFields == nil {
					continue
				}
				objectAPIName := strings.ToLower(strings.TrimSpace(spec.APIName))
				if objectAPIName == "" {
					continue
				}
				defaultsOnlyObjectSpecListFieldsKeyByObject[objectAPIName] = key
			}
		}
	}
	for _, component := range components {
		key := sourceBackedComponentKey(component.Kind, component.BusinessKey)
		deps := append([]ComponentReference(nil), component.DependsOn...)
		if component.Kind == ComponentKindPageLayout {
			if objectAPIName, layoutKey, ok := splitSourceBackedPageLayoutBusinessKey(component.BusinessKey); ok && !strings.EqualFold(layoutKey, "default") {
				if defaultKey, ok := defaultPageLayoutKeyByObject[strings.ToLower(objectAPIName)]; ok && defaultKey != key {
					defaultComponent := byKey[defaultKey]
					if !hasComponentDependency(deps, defaultComponent.Kind, defaultComponent.BusinessKey) {
						deps = append(deps, ComponentReference{
							Kind:        defaultComponent.Kind,
							BusinessKey: defaultComponent.BusinessKey,
						})
					}
				}
			}
		}
		if component.Kind == ComponentKindListFields {
			payload, err := sourceBackedComponentPayload(source, component)
			if err != nil {
				return nil, fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
			}
			listFieldRows, err := sourceBackedListFieldsProjection(payload, componentPayloadLabel(component))
			if err != nil {
				return nil, err
			}
			for _, row := range listFieldRows {
				objectAPIName := strings.ToLower(strings.TrimSpace(row.ObjectAPIName))
				if objectAPIName == "" {
					continue
				}
				if objectSpecKey, ok := defaultsOnlyObjectSpecListFieldsKeyByObject[objectAPIName]; ok && objectSpecKey != key {
					objectSpecComponent := byKey[objectSpecKey]
					if !hasComponentDependency(deps, objectSpecComponent.Kind, objectSpecComponent.BusinessKey) {
						deps = append(deps, ComponentReference{
							Kind:        objectSpecComponent.Kind,
							BusinessKey: objectSpecComponent.BusinessKey,
						})
					}
				}
			}
		}
		for _, dep := range deps {
			depKey := sourceBackedComponentKey(dep.Kind, dep.BusinessKey)
			if explicitDependencies[key] == nil {
				explicitDependencies[key] = map[string]struct{}{}
			}
			explicitDependencies[key][depKey] = struct{}{}
			addEdge(depKey, key)
		}
	}

	layoutByIdentity := map[string]string{}
	layoutByTuple := map[string]string{}
	for _, component := range components {
		if component.Kind != ComponentKindPageLayout {
			continue
		}
		payload, err := sourceBackedComponentPayload(source, component)
		if err != nil {
			return nil, fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
		}
		layout, err := sourceBackedPageLayoutFromPayload(
			payload,
			component.BusinessKey,
			componentPayloadLabel(component),
		)
		if err != nil {
			return nil, err
		}
		identityKey := normalizedSourceBackedPageLayoutInstallIdentity(layout)
		if existing, ok := layoutByIdentity[identityKey]; ok {
			return nil, fmt.Errorf(
				"source-backed page_layout identity %s already declared by %s (conflict: %s)",
				layout.ID,
				existing,
				component.BusinessKey,
			)
		}
		layoutByIdentity[identityKey] = component.BusinessKey
		tupleKey := normalizedSourceBackedPageLayoutTupleKey(layout.ObjectAPIName, layout.LayoutName)
		if existing, ok := layoutByTuple[tupleKey]; ok {
			return nil, fmt.Errorf(
				"source-backed page_layout business key %s/%s already declared by %s (conflict: %s)",
				layout.ObjectAPIName,
				layout.LayoutName,
				existing,
				component.BusinessKey,
			)
		}
		layoutByTuple[tupleKey] = component.BusinessKey
	}

	ready := make([]string, 0, len(indegree))
	for key, degree := range indegree {
		if degree == 0 {
			ready = append(ready, key)
		}
	}
	sort.Strings(ready)
	out := make([]ManifestComponent, 0, len(components))
	for len(ready) > 0 {
		key := ready[0]
		ready = ready[1:]
		out = append(out, byKey[key])
		next := append([]string(nil), dependents[key]...)
		sort.Strings(next)
		for _, dependent := range next {
			indegree[dependent]--
			if indegree[dependent] == 0 {
				ready = append(ready, dependent)
			}
		}
		sort.Strings(ready)
	}
	if len(out) != len(components) {
		return nil, fmt.Errorf("component dependency cycle detected")
	}
	return out, nil
}

func sourceBackedComponentKey(kind ComponentKind, businessKey string) string {
	return string(kind) + "\x00" + strings.TrimSpace(businessKey)
}
