// Package filters — pre-completion filter chain.
//
// v0.29.0: Reference implementation for the server-side filter model.
// Filters run before every completion request, injecting context
// (system messages, knowledge base results, external data) into the
// conversation. The chain is composable: Go built-in filters register
// at startup; Starlark extension filters register at package install.
//
// Architecture:
//
//	user message → [filter chain] → LLM
//	                 │
//	                 ├─ KB auto-inject (built-in, CS0)
//	                 ├─ memory hint   (future migration)
//	                 └─ ext filters   (Starlark, CS3)
//
// Each filter receives a CompletionContext and returns zero or more
// system messages to inject. Filters are executed in registration
// order. A filter failure is logged and skipped — it never aborts
// the completion.
package filters

import (
	"context"
	"log"
	"sort"
	"time"
)

// ─── Context ─────────────────────────────────

// CompletionContext carries the state available to pre-completion filters.
// Filters should treat this as read-only.
type CompletionContext struct {
	ChannelID       string
	UserID          string
	PersonaID       string
	LastUserMessage string // current user message content (for semantic search)
	TeamIDs         []string
}

// ─── Contribution ────────────────────────────

// InjectedMessage is a role + content pair that a filter wants to inject
// into the conversation. Kept simple to avoid coupling to provider types.
type InjectedMessage struct {
	Role    string // "system" (most common), "user", or "assistant"
	Content string
}

// Contribution holds what a filter wants to inject into the completion.
type Contribution struct {
	// Messages are injected into the conversation in order, after the
	// persona/project system prompts and before the message history.
	Messages []InjectedMessage
}

// ─── Filter Interface ────────────────────────

// PreCompletionFilter processes context before a completion request.
// Implementations must be safe for concurrent use.
type PreCompletionFilter interface {
	// Name returns a human-readable identifier (e.g., "kb-auto-inject").
	// Used in logs and admin UI.
	Name() string

	// Order returns the execution priority. Lower values run first.
	// Built-in filters use 0–99; extension filters use 100+.
	Order() int

	// Execute runs the filter and returns messages to inject.
	// Returning nil or an empty Contribution is valid (no injection).
	// Errors are logged and skipped — they never abort the completion.
	Execute(ctx context.Context, cc *CompletionContext) (*Contribution, error)
}

// ─── Chain ───────────────────────────────────

// Chain holds an ordered set of pre-completion filters.
type Chain struct {
	filters []PreCompletionFilter
	sorted  bool
}

// NewChain creates an empty filter chain.
func NewChain() *Chain {
	return &Chain{}
}

// Register adds a filter to the chain. Filters are sorted by Order()
// before the first execution.
func (c *Chain) Register(f PreCompletionFilter) {
	c.filters = append(c.filters, f)
	c.sorted = false
	log.Printf("  🔗 Pre-completion filter registered: %s (order=%d)", f.Name(), f.Order())
}

// Len returns the number of registered filters.
func (c *Chain) Len() int {
	return len(c.filters)
}

// Execute runs all filters in order, collecting their contributions.
// Returns the combined list of messages to inject. Individual filter
// errors are logged and skipped.
func (c *Chain) Execute(ctx context.Context, cc *CompletionContext) []InjectedMessage {
	if len(c.filters) == 0 {
		return nil
	}

	if !c.sorted {
		sort.Slice(c.filters, func(i, j int) bool {
			return c.filters[i].Order() < c.filters[j].Order()
		})
		c.sorted = true
	}

	var messages []InjectedMessage
	for _, f := range c.filters {
		start := time.Now()
		contrib, err := f.Execute(ctx, cc)
		elapsed := time.Since(start)

		if err != nil {
			log.Printf("⚠ filter %s failed (%.0fms): %v", f.Name(), elapsed.Seconds()*1000, err)
			continue
		}
		if contrib != nil && len(contrib.Messages) > 0 {
			messages = append(messages, contrib.Messages...)
			log.Printf("  🔗 filter %s: injected %d messages (%.0fms)", f.Name(), len(contrib.Messages), elapsed.Seconds()*1000)
		}
	}
	return messages
}
