package querycodec

import (
	"fmt"
	"strings"
)

func lowerSemiJoins(request *QueryRequest) error {
	if request == nil || len(request.SemiJoins) == 0 {
		return nil
	}
	filter := request.Filter
	for _, clause := range request.SemiJoins {
		var ok bool
		filter, ok = replaceSemiJoin(filter, request.ScanAll, clause)
		if !ok {
			return fmt.Errorf("semi-join placeholder for index %d is missing or malformed", clause.Index)
		}
	}
	request.Filter = filter
	request.SemiJoins = nil
	return nil
}

func replaceSemiJoin(predicate Predicate, scanAll bool, clause SemiJoinClause) (Predicate, bool) {
	if isSemiJoinPlaceholder(predicate, clause) {
		inner := clause.InnerQuery
		if inner == nil || len(inner.SelectFields) != 1 || inner.ObjectAPIName == "" {
			return Predicate{}, false
		}
		return Predicate{Kind: PredicateCorrelatedMembership, CorrelatedMembership: &CorrelatedMembership{
			ID: fmt.Sprintf("semi_%d", clause.Index), OuterField: clause.OuterField, Negate: clause.Negate,
			InnerObjectAPIName: inner.ObjectAPIName, InnerSelectField: inner.SelectFields[0],
			InnerFilter: inner.Filter, InnerLimit: inner.Limit, InnerScanAll: scanAll,
		}}, true
	}
	switch predicate.Kind {
	case PredicateAnd, PredicateOr:
		children := make([]Predicate, len(predicate.Children))
		replaced := false
		for i, child := range predicate.Children {
			mapped, ok := replaceSemiJoin(child, scanAll, clause)
			if ok {
				children[i], replaced = mapped, true
			} else {
				children[i] = child
			}
		}
		if !replaced {
			return predicate, false
		}
		predicate.Children = children
		return predicate, true
	case PredicateNot:
		if len(predicate.Children) != 1 {
			return predicate, false
		}
		mapped, ok := replaceSemiJoin(predicate.Children[0], scanAll, clause)
		if !ok {
			return predicate, false
		}
		predicate.Children = []Predicate{mapped}
		return predicate, true
	default:
		return predicate, false
	}
}

func isSemiJoinPlaceholder(predicate Predicate, clause SemiJoinClause) bool {
	if predicate.Kind != PredicateComparison || predicate.Left.Kind != OperandField || !strings.EqualFold(predicate.Left.Field, clause.OuterField) {
		return false
	}
	op := strings.ToLower(strings.Join(strings.Fields(predicate.Op), " "))
	if clause.Negate && op != "not in" || !clause.Negate && op != "in" {
		return false
	}
	if predicate.Right.Kind != OperandList || len(predicate.Right.Elements) != 1 {
		return false
	}
	token, ok := predicate.Right.Elements[0].Value.(string)
	return ok && token == SemiJoinPlaceholderToken(clause.Index)
}
