package usecase

import (
	"context"
	"fmt"
	"strings"

	"apps/backend/internal/module/radar/domain"
)

func (s *Service) BuildProductQueryPlan(ctx context.Context, ownerUID int64, productID string) (*domain.QueryPlan, error) {
	m, err := s.GetDemandMap(ctx, ownerUID, productID)
	if err != nil {
		return nil, err
	}
	label := ""
	if s.ProductSource != nil {
		if product, perr := s.ProductSource.GetProduct(ctx, ownerUID, productID); perr != nil {
			return nil, perr
		} else if product != nil {
			label = product.Label
		}
	}
	return BuildQueryPlan(m, label)
}

// BuildQueryPlan compiles only demand language into short, independent
// searches. Product/brand names are context and can never become a query by
// themselves. The result is deterministic, which makes a plan safe to cache
// and easy to audit alongside its DemandMap versions.
func BuildQueryPlan(m *domain.DemandMap, productLabel string) (*domain.QueryPlan, error) {
	if m == nil {
		return nil, fmt.Errorf("%w: demand map required", domain.ErrValidation)
	}
	if m.State != "ready" {
		return nil, fmt.Errorf("%w: demand map is %s", domain.ErrValidation, m.State)
	}
	productLabel = strings.ToLower(domain.NormalizeSearchTerm(productLabel))
	include := func(list []domain.DemandMapPhrase) []domain.DemandMapPhrase {
		out := make([]domain.DemandMapPhrase, 0, len(list))
		for _, p := range list {
			if !p.Enabled {
				continue
			}
			variants := domain.SearchableTermVariants(p.Text, true)
			if len(variants) > domain.MaxSearchableVariantsPerTerm {
				variants = variants[:domain.MaxSearchableVariantsPerTerm]
			}
			basis := strings.TrimSpace(p.BasisText)
			if basis == "" {
				basis = strings.TrimSpace(p.Text)
			}
			for _, text := range variants {
				if text == "" || strings.ToLower(text) == productLabel {
					continue
				}
				cp := p
				cp.Text = text
				cp.BasisText = basis
				out = append(out, cp)
			}
		}
		return out
	}
	excludePhrases := func(list []domain.DemandMapPhrase) []domain.DemandMapPhrase {
		out := make([]domain.DemandMapPhrase, 0, len(list))
		for _, p := range list {
			if !p.Enabled {
				continue
			}
			text := domain.NormalizeSearchTerm(p.Text)
			if text == "" {
				continue
			}
			p.Text = text
			out = append(out, p)
		}
		return out
	}
	pains, scenarios := include(m.PainPhrases), include(m.ScenarioPhrases)
	outcomes, solutions := include(m.DesiredOutcomes), include(m.SolutionSignals)
	exclusions := excludePhrases(m.ExclusionSignals)

	groups := make([]domain.QueryPlanGroup, 0, domain.MaxExploreTerms)
	seen := map[string]bool{}
	add := func(parts ...domain.DemandMapPhrase) {
		if len(groups) >= domain.MaxExploreTerms {
			return
		}
		terms := make([]string, 0, len(parts))
		basisKinds := make([]string, 0, len(parts))
		basisTexts := make([]string, 0, len(parts))
		for _, p := range parts {
			if p.Text == "" {
				continue
			}
			terms = append(terms, p.Text)
			basisKinds = append(basisKinds, p.Kind)
			basisTexts = append(basisTexts, p.BasisText)
		}
		query := domain.NormalizeSearchTerm(strings.Join(terms, " "))
		if !domain.IsThreadsSearchable(query) || seen[strings.ToLower(query)] {
			return
		}
		seen[strings.ToLower(query)] = true
		exclude := make([]string, 0, len(exclusions))
		for _, p := range exclusions {
			exclude = append(exclude, p.Text)
		}
		groups = append(groups, domain.QueryPlanGroup{Query: query, Include: append([]string(nil), terms...), Exclude: exclude, BasisKinds: basisKinds, BasisTexts: basisTexts})
	}
	for _, pain := range pains {
		add(pain)
		if len(groups) >= domain.MaxExploreTerms {
			break
		}
		for _, scenario := range scenarios {
			add(pain, scenario)
			if len(groups) >= domain.MaxExploreTerms {
				break
			}
		}
		if len(groups) >= domain.MaxExploreTerms {
			break
		}
		for _, outcome := range outcomes {
			add(pain, outcome)
			if len(groups) >= domain.MaxExploreTerms {
				break
			}
		}
		if len(groups) >= domain.MaxExploreTerms {
			break
		}
	}
	if len(groups) == 0 {
		for _, scenario := range scenarios {
			for _, solution := range solutions {
				add(scenario, solution)
				if len(groups) >= domain.MaxExploreTerms {
					break
				}
			}
		}
	}
	if len(groups) == 0 {
		return nil, fmt.Errorf("%w: demand map has no searchable phrases", domain.ErrValidation)
	}
	return &domain.QueryPlan{DemandInputVersion: m.DemandInputVersion, MapVersion: m.MapVersion, Groups: groups}, nil
}
