package dispatch

import (
	"errors"
	"regexp"
	"sort"

	"github.com/lukaszraczylo/go-telegram/api"
)

// ErrEndGroups stops dispatch from running any further handlers in any
// group for this update when returned by a handler. Use it to indicate
// the update has been definitively handled.
//
// errors.Is(err, ErrEndGroups) is the canonical check, though dispatch
// itself recognises it by exact identity.
var ErrEndGroups = errors.New("dispatch: end groups")

// ErrContinueGroups signals that this group's handler should be treated
// as not-matching when returned by a handler: dispatch moves on to the
// next handler in the same group, then to subsequent groups.
//
// Without ErrContinueGroups, a non-error return from a matched handler
// stops dispatch (default first-match-wins semantics).
var ErrContinueGroups = errors.New("dispatch: continue groups")

// RouterScope registers handlers into a specific priority group on its parent
// Router. Group 0 runs first, then group 1, etc. Within a group, handlers run
// in registration order; the first non-skipped match terminates dispatch
// unless the handler returns ErrContinueGroups.
type RouterScope struct {
	router *Router
	group  int
}

// Group returns a RouterScope that registers handlers in the given group.
// Group 0 (the default) runs first, then group 1, etc. Within a group,
// handlers run in registration order; the first non-skipped match
// terminates dispatch unless the handler returns ErrContinueGroups.
func (r *Router) Group(group int) *RouterScope {
	return &RouterScope{router: r, group: group}
}

// OnCommand registers a command handler in this group.
func (s *RouterScope) OnCommand(cmd string, h Handler[*api.Message]) {
	s.router.groupCommands = append(s.router.groupCommands, groupCommandRoute{
		cmd: cmd, group: s.group, handler: h,
	})
}

// OnText registers a regex text handler in this group.
// Panics at registration time if pattern is not a valid regular expression.
func (s *RouterScope) OnText(pattern string, h Handler[*api.Message]) {
	s.router.groupTexts = append(s.router.groupTexts, groupTextRoute{
		re: regexp.MustCompile(pattern), group: s.group, handler: h,
	})
}

// OnMessageFilter registers a filter-based message handler in this group.
func (s *RouterScope) OnMessageFilter(f Filter[*api.Message], h Handler[*api.Message]) {
	s.router.groupMessageFilters = append(s.router.groupMessageFilters, groupMessageFilterRoute{
		filter: f, group: s.group, handler: h,
	})
}

// group-aware route types

type groupCommandRoute struct {
	cmd     string
	group   int
	handler Handler[*api.Message]
}

type groupTextRoute struct {
	re      *regexp.Regexp
	group   int
	handler Handler[*api.Message]
}

type groupMessageFilterRoute struct {
	filter  Filter[*api.Message]
	group   int
	handler Handler[*api.Message]
}

// dispatchGroups runs message handlers registered via RouterScope.Group().
// It collects all matching groups, sorts by group number, and applies
// first-match-wins semantics within each group. Handlers may return
// ErrContinueGroups (skip to next handler/group) or ErrEndGroups (stop all groups).
// A non-sentinel error stops dispatch and is returned to the caller.
func (r *Router) dispatchGroups(c *Context, m *api.Message) error {
	// Collect group numbers present.
	groupSet := map[int]struct{}{}
	for _, gr := range r.groupCommands {
		groupSet[gr.group] = struct{}{}
	}
	for _, gr := range r.groupTexts {
		groupSet[gr.group] = struct{}{}
	}
	for _, gr := range r.groupMessageFilters {
		groupSet[gr.group] = struct{}{}
	}
	if len(groupSet) == 0 {
		return nil
	}

	groups := make([]int, 0, len(groupSet))
	for g := range groupSet {
		groups = append(groups, g)
	}
	sort.Ints(groups)

	for _, g := range groups {
		matched, err := r.runGroupHandlers(c, m, g)
		if err != nil {
			if errors.Is(err, ErrEndGroups) {
				return nil
			}
			return err
		}
		if matched {
			// First-match-wins: stop further groups.
			return nil
		}
		// No match or ErrContinueGroups from all handlers: try next group.
	}
	return nil
}

// runGroupHandlers runs all handlers in group g against m, in registration
// order. Returns (true, nil) when a handler matched (returned nil). Returns
// (false, nil) when all handlers returned ErrContinueGroups. Returns
// (false, err) for ErrEndGroups or any non-sentinel error.
func (r *Router) runGroupHandlers(c *Context, m *api.Message, g int) (matched bool, err error) {
	// Commands.
	if cmd, args, ok := extractCommand(m); ok {
		for _, route := range r.groupCommands {
			if route.group != g || route.cmd != cmd {
				continue
			}
			c.Command = cmd
			c.CommandArgs = args
			if err := route.handler(c, m); err != nil {
				if errors.Is(err, ErrContinueGroups) {
					continue
				}
				return false, err
			}
			return true, nil
		}
	}
	// Text regex.
	if m.Text != "" {
		for _, route := range r.groupTexts {
			if route.group != g {
				continue
			}
			subs := route.re.FindStringSubmatch(m.Text)
			if subs == nil {
				continue
			}
			c.RegexMatch = subs
			if err := route.handler(c, m); err != nil {
				if errors.Is(err, ErrContinueGroups) {
					continue
				}
				return false, err
			}
			return true, nil
		}
	}
	// Filter-based.
	for _, route := range r.groupMessageFilters {
		if route.group != g || !route.filter(m) {
			continue
		}
		if err := route.handler(c, m); err != nil {
			if errors.Is(err, ErrContinueGroups) {
				continue
			}
			return false, err
		}
		return true, nil
	}
	return false, nil
}
