package deterministic

import (
	"context"
	"fmt"

	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/envelope"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/event"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/orchestrator"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/prompt"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/store"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/transport"
)

// Coordinator is the deterministic dispatch coordinator. It owns the
// hardcoded routing path that runs when an org opts in via
// `dispatch_mode: deterministic`. Construct one per run and call
// Dispatch with the user prompt.
//
// The Coordinator deliberately holds plain pointers to the harness
// primitives it needs (store, queue, runner, classifier) rather than
// taking a *Master — this keeps orchestrator/master.go free of any
// deterministic-mode pollution.
type Coordinator struct {
	Orch          *orchestrator.Orchestrator
	Queue         *transport.Queue
	Runner        *orchestrator.OrgRunner
	Classer       prompt.Classifier
	RunID         string
	MasterRole    string
	AutoEvaluator orchestrator.AutoEvaluator

	// MasterHelpers gives the Coordinator access to Master's small set
	// of orchestration utilities (notifyUser, recordPrompt, openTasks)
	// without forcing them to be relocated. Wired by runlive.
	NotifyUser   func(ctx context.Context, taskID, kind, body string) error
	RecordPrompt func(ctx context.Context, text string, res prompt.Result) error
	OpenTasks    func(ctx context.Context) ([]prompt.TaskSummary, error)
}

// Dispatch is the deterministic-mode entry point. Classifies the prompt
// via the rule classifier and dispatches via the appropriate hardcoded
// path: steer (existing task), fanOut (org-wide introspection), delegate
// (single worker), or no-dispatch (unrouted).
//
// Returns (kind, taskID, error) for the caller. Mirrors the old
// Master.Dispatch signature so runlive can swap between this and the
// LLM-driven shovel-to-inbox path without changing callers.
func (c *Coordinator) Dispatch(ctx context.Context, userText string) (prompt.Kind, string, error) {
	open, err := c.OpenTasks(ctx)
	if err != nil {
		return "", "", err
	}
	res, err := c.Classer.Classify(ctx, prompt.Input{Text: userText, OpenTasks: open})
	if err != nil {
		return "", "", fmt.Errorf("deterministic: classify: %w", err)
	}

	_, _ = c.Orch.Bus.Emit(ctx, event.Event{
		Kind: "master.classified", RunID: c.RunID,
		Payload: map[string]any{
			"kind":           string(res.Kind),
			"confidence":     res.Confidence,
			"rationale":      res.Rationale,
			"target_task_id": res.TargetTaskID,
			"impl":           prompt.ImplName(c.Classer),
			"prompt":         trunc(userText, 120),
		},
	})

	switch res.Kind {
	case prompt.KindSteering:
		return res.Kind, res.TargetTaskID, c.steer(ctx, res.TargetTaskID, userText)
	case prompt.KindIntrospection:
		return res.Kind, "", c.fanOut(ctx, userText, res)
	case prompt.KindImplementNew, prompt.KindImplementSub, prompt.KindImplementCrossBound,
		prompt.KindDebugDiscovery, prompt.KindDebugOwned:
		if existingTask := c.findReuseCandidate(ctx, userText); existingTask != "" {
			_, _ = c.Orch.Bus.Emit(ctx, event.Event{
				Kind: "master.handoff_reuse", RunID: c.RunID, TaskID: existingTask,
				Payload: map[string]any{"prompt": trunc(userText, 120), "rationale": "shared keyword overlap with active task"},
			})
			return res.Kind, existingTask, c.steer(ctx, existingTask, userText)
		}
		return res.Kind, "", c.delegate(ctx, userText, res)
	case prompt.KindConversational, prompt.KindEvaluation, prompt.KindUnknown,
		prompt.KindInfoUnblock:
		_ = c.RecordPrompt(ctx, userText, res)
		_, _ = c.Orch.Bus.Emit(ctx, event.Event{
			Kind: "master.no_dispatch", RunID: c.RunID,
			Payload: map[string]any{
				"kind":       string(res.Kind),
				"confidence": res.Confidence,
				"rationale":  res.Rationale,
				"hint":       "prompt classified as " + string(res.Kind) + " — no worker dispatched. Rephrase as a concrete change or reference an in-flight task by id.",
			},
		})
		_ = c.NotifyUser(ctx, "", "unrouted",
			"I couldn't route this prompt — classified as "+string(res.Kind)+
				" (confidence "+fmt.Sprintf("%.2f", res.Confidence)+"). Rephrase as a concrete change, or reference an in-flight task by id to steer it. Rationale: "+res.Rationale,
		)
		_ = c.Orch.EndRun(ctx, c.RunID, orchestrator.RunOutcome{
			Status:          orchestrator.RunUnrouted,
			FailureCategory: orchestrator.FailUnrouted,
			FailureDetail:   "prompt classified as " + string(res.Kind) + "; rationale: " + res.Rationale,
		})
		return res.Kind, "", nil
	}
	return res.Kind, "", nil
}

// genID, trunc — kept package-local rather than imported from
// orchestrator so this package has no return-dependency.
func genID(prefix string) string {
	return fmt.Sprintf("%s_%d_%d", prefix, store.Now().UnixNano(), nextSeq())
}

var seqCounter uint64

func nextSeq() uint64 {
	seqCounter++
	return seqCounter
}

func trunc(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n]
}

// envelope shorthand — keeps the moved bodies readable.
var (
	_ = envelope.TypeDelegate
)
