package terminal

import (
	"github.com/posener/complete"

	"github.com/block/ftl/common/log"
	"github.com/block/ftl/common/schema"
	"github.com/block/ftl/common/slices"
	"github.com/block/ftl/internal/schema/schemaeventsource"
)

func Predictors(lazy func() schemaeventsource.View) map[string]complete.Predictor {
	view := &lazyView{init: lazy}
	return map[string]complete.Predictor{
		"decls":         &declPredictor[schema.Decl]{view: view},
		"verbs":         &declPredictor[*schema.Verb]{view: view},
		"configs":       &declPredictor[*schema.Config]{view: view},
		"secrets":       &declPredictor[*schema.Secret]{view: view},
		"databases":     &declPredictor[*schema.Database]{view: view},
		"topics":        &declPredictor[*schema.Topic]{view: view},
		"subscriptions": &subscriptionsPredictor{view: view},
		"modules":       &modulePredictor{view: view},
		"deployments":   &deploymentsPredictor{view: view},
		"log-level":     &logLevelPredictor{},
	}
}

type lazyView struct {
	view schemaeventsource.View
	init func() schemaeventsource.View
}

func (l *lazyView) GetCanonical() *schema.Schema {
	if l.view == nil {
		l.view = l.init()
	}
	return l.view.GetCanonical()
}

type declPredictor[d schema.Decl] struct {
	view schemaeventsource.View
}

func (p *declPredictor[d]) Predict(args complete.Args) []string {
	sch := p.view.GetCanonical()
	ret := []string{}
	for _, module := range sch.InternalModules() {
		for _, dec := range module.Decls {
			if _, ok := dec.(d); ok {
				ref := schema.Ref{Module: module.Name, Name: dec.GetName()}
				ret = append(ret, ref.String())
			}
		}
	}
	return ret
}

type subscriptionsPredictor struct {
	view schemaeventsource.View
}

func (p *subscriptionsPredictor) Predict(args complete.Args) []string {
	sch := p.view.GetCanonical()
	ret := []string{}
	for _, module := range sch.InternalModules() {
		for _, dec := range module.Decls {
			verb, ok := dec.(*schema.Verb)
			if !ok {
				continue
			}
			_, ok = slices.FindVariant[*schema.MetadataSubscriber](verb.Metadata)
			if !ok {
				continue
			}
			ref := schema.Ref{Module: module.Name, Name: dec.GetName()}
			ret = append(ret, ref.String())
		}
	}
	return ret
}

type modulePredictor struct {
	view schemaeventsource.View
}

func (v *modulePredictor) Predict(args complete.Args) []string {
	sch := v.view.GetCanonical()
	ret := []string{}
	for _, module := range sch.InternalModules() {
		ret = append(ret, module.Name)
	}
	return ret
}

type deploymentsPredictor struct {
	view schemaeventsource.View
}

func (v *deploymentsPredictor) Predict(args complete.Args) []string {
	sch := v.view.GetCanonical()
	ret := []string{}
	for _, module := range sch.InternalModules() {
		if module.Runtime == nil || module.Runtime.Deployment == nil {
			continue
		}
		ret = append(ret, module.Runtime.Deployment.DeploymentKey.String())
	}
	return ret
}

type logLevelPredictor struct {
}

func (v *logLevelPredictor) Predict(args complete.Args) []string {
	return log.LevelStrings()
}
