package packs

import (
	"context"
	"fmt"
	"strings"
)

func (p *SourceBackedPack) replayListFieldsComponentsForObjects(ctx context.Context, deps ApplyDeps, objectSet map[string]bool) error {
	if len(objectSet) == 0 {
		return nil
	}
	for _, component := range p.components {
		if component.Kind() != ComponentKindListFields {
			continue
		}
		touches, err := sourceBackedListFieldsComponentTouchesSet(component, objectSet)
		if err != nil {
			return err
		}
		if !touches {
			continue
		}
		if err := component.Apply(ctx, deps); err != nil {
			return fmt.Errorf("%s %s: %w", component.Kind(), component.BusinessKey(), err)
		}
	}
	return nil
}

func (p *SourceBackedPack) appendObjectSpecListFieldsStartupShallowDeclarations(specs []ObjectSpec) error {
	for _, spec := range specs {
		objectAPIName := strings.TrimSpace(spec.APIName)
		if objectAPIName == "" || spec.ListFields == nil {
			continue
		}
		projection, err := marshalRuntimeStartupShallowProjection(runtimeStartupShallowListFieldsProjection{
			ObjectAPIName: objectAPIName,
			Fields:        append([]string{}, sourceBackedNormalizeStringSlice(spec.ListFields)...),
		})
		if err != nil {
			return fmt.Errorf("%s object_spec %s list_fields startup shallow projection: %w", p.Name(), objectAPIName, err)
		}
		p.appendRuntimeStartupShallowDeclaration(ComponentKindListFields, objectAPIName, projection)
	}
	return nil
}

func lockedObjectSpecListFieldsObjects(parsed map[string][]ObjectSpec, components []ManifestComponent) map[string]struct{} {
	byBusinessKey := make(map[string]Manageability, len(components))
	for _, component := range components {
		if component.Kind != ComponentKindObjectSpec {
			continue
		}
		byBusinessKey[strings.TrimSpace(component.BusinessKey)] = component.Manageability
	}
	locked := map[string]struct{}{}
	for businessKey, specs := range parsed {
		if byBusinessKey[strings.TrimSpace(businessKey)] != ManageabilityLocked {
			continue
		}
		for _, spec := range specs {
			if spec.ListFields == nil {
				continue
			}
			objectAPIName := strings.ToLower(strings.TrimSpace(spec.APIName))
			if objectAPIName == "" {
				continue
			}
			locked[objectAPIName] = struct{}{}
		}
	}
	return locked
}

func sourceBackedStandaloneListFieldsObjects(source *ResolvedPackSource, components []ManifestComponent) (map[string]struct{}, error) {
	objects := map[string]struct{}{}
	for _, component := range components {
		if component.Kind != ComponentKindListFields {
			continue
		}
		payload, err := sourceBackedComponentPayload(source, component)
		if err != nil {
			return nil, fmt.Errorf("%s %s payload: %w", component.Kind, component.BusinessKey, err)
		}
		rows, err := sourceBackedListFieldsProjection(payload, componentPayloadLabel(component))
		if err != nil {
			return nil, err
		}
		for _, row := range rows {
			objectAPIName := strings.ToLower(strings.TrimSpace(row.ObjectAPIName))
			if objectAPIName != "" {
				objects[objectAPIName] = struct{}{}
			}
		}
	}
	return objects, nil
}

func sourceBackedListFieldsComponentTouchesSet(component PackComponent, objectSet map[string]bool) (bool, error) {
	payload, err := component.ExpectedPayload()
	if err != nil {
		return false, fmt.Errorf("%s %s payload: %w", component.Kind(), component.BusinessKey(), err)
	}
	rows, err := sourceBackedListFieldsProjection(payload, fmt.Sprintf("%s %s", component.Kind(), component.BusinessKey()))
	if err != nil {
		return false, err
	}
	for _, row := range rows {
		if objectSet[strings.ToLower(strings.TrimSpace(row.ObjectAPIName))] {
			return true, nil
		}
	}
	return false, nil
}

func objectSpecsForListFieldsVerify(specs []ObjectSpec, manageability Manageability, standaloneListFieldsObjects map[string]struct{}) []ObjectSpec {
	if manageability != ManageabilityDefaultsOnly || len(standaloneListFieldsObjects) == 0 {
		return specs
	}
	out := make([]ObjectSpec, 0, len(specs))
	for _, spec := range specs {
		objectAPIName := strings.ToLower(strings.TrimSpace(spec.APIName))
		if objectAPIName != "" && spec.ListFields != nil {
			if _, ok := standaloneListFieldsObjects[objectAPIName]; ok {
				copied := spec
				copied.ListFields = nil
				out = append(out, copied)
				continue
			}
		}
		out = append(out, spec)
	}
	return out
}
