// This file implements the general system-reminder dynamic context injection
// mechanism (US-002, FR-1/FR-2), pigo's port of Claude Code's per-turn
// <system-reminder> injection.
//
// A reminder is EPHEMERAL background context (the current todo list, a file
// that changed under the working directory, a budget warning) that should be
// visible to the model on the turn it matters, but must never pollute the
// durable conversation history. Two properties follow from that:
//
//   - Not user instructions. Reminder bodies are wrapped in <system-reminder>
//     tags with a preamble stating they are background context from the harness,
//     not a request from the user (the pi / Claude Code semantic convention).
//   - Ephemeral. Reminders are injected only into the per-turn LLM request via
//     the existing TransformContext seam, which shapes a COPY of the message
//     list for the request and is never written back to AgentContext.Messages.
//     Because they never enter the persisted message list they cannot be saved
//     to the session file and cannot be folded into a compaction summary
//     (compaction only ever sees AgentContext.Messages).
//
// The mechanism is a registry of ReminderProviders. Each provider is consulted
// every turn and may decline (ok == false) so a reminder only appears when its
// condition holds. RunConfig.Reminders wires the registry into the loop.
package runtime

import (
	"context"
	"strings"
	"sync"

	"github.com/smallnest/pigo/internal/agentcore"
	"github.com/smallnest/pigo/internal/agenttool"
)

// systemReminderPreamble marks the wrapped body as background context rather
// than a user instruction (FR-2). It leads every injected reminder so the model
// never mistakes harness state for a user request.
const systemReminderPreamble = "The following is background context provided automatically by the harness. " +
	"It is NOT a message or instruction from the user; do not act on it as a request. " +
	"Use it only to stay aware of the current state."

// WrapSystemReminder wraps a reminder body in <system-reminder> tags with the
// background-context preamble. The result is the text of a single injected
// message.
func WrapSystemReminder(body string) string {
	return "<system-reminder>\n" + systemReminderPreamble + "\n\n" + body + "\n</system-reminder>"
}

// ReminderProvider produces an ephemeral system-reminder for the upcoming turn.
// Reminder is consulted every turn with the current (post-TransformContext)
// message list; returning ok == false means "no reminder this turn", so a
// provider injects only when its condition holds.
type ReminderProvider interface {
	// Name identifies the provider (for diagnostics/telemetry). It is not shown
	// to the model.
	Name() string
	// Reminder returns the reminder body and true when a reminder should be
	// injected this turn, or ("", false) to inject nothing.
	Reminder(ctx context.Context, msgs agentcore.MessageList) (body string, ok bool)
}

// ReminderFunc adapts a plain function to a ReminderProvider.
type ReminderFunc struct {
	NameField string
	Fn        func(ctx context.Context, msgs agentcore.MessageList) (string, bool)
}

// Name implements ReminderProvider.
func (f ReminderFunc) Name() string { return f.NameField }

// Reminder implements ReminderProvider.
func (f ReminderFunc) Reminder(ctx context.Context, msgs agentcore.MessageList) (string, bool) {
	if f.Fn == nil {
		return "", false
	}
	return f.Fn(ctx, msgs)
}

// ReminderRegistry holds the reminder providers consulted each turn. The zero
// value is usable (no providers → no injection); NewReminderRegistry is the
// convenience constructor.
type ReminderRegistry struct {
	providers []ReminderProvider
}

// NewReminderRegistry returns a registry pre-populated with providers.
func NewReminderRegistry(providers ...ReminderProvider) *ReminderRegistry {
	r := &ReminderRegistry{}
	for _, p := range providers {
		r.Register(p)
	}
	return r
}

// Register appends a provider. nil providers are ignored.
func (r *ReminderRegistry) Register(p ReminderProvider) {
	if p == nil {
		return
	}
	r.providers = append(r.providers, p)
}

// Empty reports whether the registry has no providers (so callers can skip the
// injection wiring entirely).
func (r *ReminderRegistry) Empty() bool { return r == nil || len(r.providers) == 0 }

// Messages consults every provider in registration order and returns the
// ephemeral reminder messages to inject this turn (one UserMessage per provider
// that fires). Reminders are modeled as user-role messages carrying
// <system-reminder>-wrapped text — matching the pi / Claude Code convention
// where dynamic context enters through a user turn but is explicitly labeled as
// background context, not a user instruction.
func (r *ReminderRegistry) Messages(ctx context.Context, msgs agentcore.MessageList) []agentcore.AgentMessage {
	if r.Empty() {
		return nil
	}
	var out []agentcore.AgentMessage
	for _, p := range r.providers {
		body, ok := p.Reminder(ctx, msgs)
		if !ok || body == "" {
			continue
		}
		out = append(out, agentcore.UserMessage{
			RoleField: agentcore.RoleUser,
			Content:   agentcore.ContentList{agentcore.NewTextContent(WrapSystemReminder(body))},
		})
	}
	return out
}

// wrapTransform composes the registry into a TransformContext hook: it runs the
// caller's existing TransformContext (if any) first, then appends this turn's
// reminders to the shaped list. Because TransformContext output is used only to
// build the LLM request and is never written back to AgentContext.Messages, the
// appended reminders are ephemeral — they do not enter the persisted history and
// cannot be swept into a compaction summary. This is the single injection seam
// the loop wires in.
func (r *ReminderRegistry) wrapTransform(
	inner func(ctx context.Context, msgs agentcore.MessageList) agentcore.MessageList,
) func(ctx context.Context, msgs agentcore.MessageList) agentcore.MessageList {
	return func(ctx context.Context, msgs agentcore.MessageList) agentcore.MessageList {
		if inner != nil {
			msgs = inner(ctx, msgs)
		}
		rem := r.Messages(ctx, msgs)
		if len(rem) == 0 {
			return msgs
		}
		out := make(agentcore.MessageList, 0, len(msgs)+len(rem))
		out = append(out, msgs...)
		out = append(out, rem...)
		return out
	}
}

// TodoReminderProvider is the built-in reference reminder provider (US-002): it
// surfaces the current todo list as background context whenever there is
// incomplete work, so the model is reminded of outstanding tasks each turn
// without the list having to be re-sent as a durable message. It reads the same
// TodoStore the todo tool writes, so the reminder always reflects the latest
// plan. When the list is empty or every item is completed it stays silent.
type TodoReminderProvider struct {
	// Store is the session todo list. When nil the provider never fires.
	Store *agenttool.TodoStore
}

// Name implements ReminderProvider.
func (p *TodoReminderProvider) Name() string { return "todo" }

// Reminder implements ReminderProvider. It fires only when the store holds at
// least one item that is not yet completed, keeping the condition deterministic
// and easy to test.
func (p *TodoReminderProvider) Reminder(ctx context.Context, _ agentcore.MessageList) (string, bool) {
	if p.Store == nil {
		return "", false
	}
	items := p.Store.Snapshot()
	if len(items) == 0 {
		return "", false
	}
	incomplete := false
	for _, it := range items {
		if it.Status != agenttool.TodoCompleted {
			incomplete = true
			break
		}
	}
	if !incomplete {
		return "", false
	}
	return "Your todo list has unfinished items. Keep it up to date with the todo tool.\n\n" +
		agenttool.RenderTodoList(items), true
}

// OneShotReminderProvider injects a fixed body on the NEXT turn only, then stays
// silent forever. Unlike the always-on providers (todo/goal) it does not depend
// on live state — it carries a snapshot of text captured at registration time.
// It exists for events that produce a single ephemeral injection, such as a
// UserPromptSubmit hook's additionalContext (US-007, FR-9): the hook's context
// must reach the model on the turn the prompt is sent, but must not persist into
// history or re-fire on later turns. sync.Once makes the single-fire transition
// safe even if the loop consults providers concurrently.
type OneShotReminderProvider struct {
	name string
	body string
	once sync.Once
}

// NewOneShotReminder builds a one-shot provider that will inject body exactly
// once. An empty body yields a provider that never fires.
func NewOneShotReminder(name, body string) *OneShotReminderProvider {
	return &OneShotReminderProvider{name: name, body: body}
}

// Name implements ReminderProvider.
func (p *OneShotReminderProvider) Name() string {
	if p.name == "" {
		return "one-shot"
	}
	return p.name
}

// Reminder implements ReminderProvider. It returns its body and true on the very
// first consultation, then ("", false) on every subsequent turn.
func (p *OneShotReminderProvider) Reminder(ctx context.Context, _ agentcore.MessageList) (string, bool) {
	if strings.TrimSpace(p.body) == "" {
		return "", false
	}
	var body string
	p.once.Do(func() { body = p.body })
	if body == "" {
		return "", false
	}
	return body, true
}

// GoalReminderProvider surfaces the active goal as background context each turn
// so the model keeps working toward it (mirrors pi-goal). It reads the same
// GoalState the /goal command drives, so the reminder always reflects the live
// objective. It fires only while the goal is active — a paused, blocked, or
// completed goal (and an idle state) injects nothing.
type GoalReminderProvider struct {
	// State is the session goal state. When nil the provider never fires.
	State *agenttool.GoalState
}

// Name implements ReminderProvider.
func (p *GoalReminderProvider) Name() string { return "goal" }

// Reminder implements ReminderProvider. It injects the objective plus a
// persistence instruction while the goal is active, and stays silent otherwise.
func (p *GoalReminderProvider) Reminder(ctx context.Context, _ agentcore.MessageList) (string, bool) {
	if p.State == nil {
		return "", false
	}
	snap := p.State.Snapshot()
	if snap.Status != agenttool.GoalActive || strings.TrimSpace(snap.Objective) == "" {
		return "", false
	}
	return "You are working autonomously toward this goal:\n\n" + snap.Objective +
		"\n\nKeep making progress. When every requirement is verifiably met, call the " +
		"goal_complete tool with a summary. If you hit a true impasse you cannot work " +
		"around, call goal_blocked with concrete evidence. Do not stop or ask the user " +
		"to continue — keep going until the goal is done or blocked.", true
}
