// This file implements the generic `task` tool (US-002, #454): a general-purpose
// sub-agent the model can dispatch with a free-form prompt to fan out work in a
// single assistant message. It is a thin specialization of SubAgentTool - it
// reuses executeGoroutine's child-loop driving - configured with a generic
// system prompt (the delegated task comes from the call arguments at runtime),
// a shared concurrency semaphore, and a child tool set from which `task` itself
// is excluded (the nesting guard, wired in internal/cli/run).
package runtime

import (
	"encoding/json"
	"os"
	"strconv"
	"strings"

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

// DefaultMaxSubagents is the concurrency cap applied when PIGO_MAX_SUBAGENTS is
// unset or invalid. It bounds how many task sub-agents run at once so a fan-out
// cannot overwhelm the provider rate limit.
const DefaultMaxSubagents = 4

// taskDescription is advertised to the parent model so it knows when to delegate
// work to a generic sub-agent.
const taskDescription = "Dispatch a general-purpose sub-agent to autonomously complete a delegated task. " +
	"The sub-agent runs its own agent loop with a fresh context and the standard tool set, then returns its final report. " +
	"Provide a complete, self-contained prompt since the sub-agent shares none of this conversation's context. " +
	"Multiple task calls in one message run in parallel."

// taskSystemPrompt seeds every generic sub-agent's context. It is intentionally
// generic (the actual work arrives as the runtime prompt) and mirrors the
// parent agent's operating posture so a delegated task is carried out the same
// way the parent would.
const taskSystemPrompt = "You are a focused sub-agent working on one delegated task. " +
	"You have your own fresh context and the standard tool set, but you cannot spawn further sub-agents. " +
	"Complete the task fully using the tools available, then respond with a concise final report of what you did and any key findings. " +
	"Your final message is returned verbatim to the agent that dispatched you, so make it self-contained."

// taskSchema is the JSON Schema for a task invocation: a required self-contained
// prompt plus an optional short description used for status display.
var taskSchema = json.RawMessage(`{
  "type": "object",
  "properties": {
    "description": {
      "type": "string",
      "description": "A short (3-5 word) description of the task, for status display."
    },
    "prompt": {
      "type": "string",
      "description": "The full task for the sub-agent to perform. It must be self-contained since the sub-agent runs with a fresh context and shares none of this conversation."
    }
  },
  "required": ["prompt"],
  "additionalProperties": false
}`)

// NewTaskTool builds the generic `task` sub-agent tool. factory produces a fresh
// child RunConfig per spawn (reusing the parent's provider stream/model and a
// child tool registry that must exclude `task` for the nesting guard); sem is a
// shared buffered channel bounding concurrent task runs (nil disables limiting).
// The child prompt comes from the call arguments at runtime, so a single generic
// spec serves every delegated task.
func NewTaskTool(factory func() RunConfig, sem chan struct{}) *SubAgentTool {
	return NewSubAgentTool(SubAgentSpec{
		Name:         "task",
		Description:  taskDescription,
		SystemPrompt: taskSystemPrompt,
		Schema:       taskSchema,
		NewRunConfig: factory,
		Sem:          sem,
	})
}

// MaxSubagents resolves the concurrency cap for task sub-agents from
// PIGO_MAX_SUBAGENTS: absent or unparseable yields DefaultMaxSubagents (4), and
// a parsed value below 1 is floored to 1 so the semaphore always admits at least
// one runner.
func MaxSubagents() int {
	v := strings.TrimSpace(os.Getenv("PIGO_MAX_SUBAGENTS"))
	if v == "" {
		return DefaultMaxSubagents
	}
	n, err := strconv.Atoi(v)
	if err != nil {
		return DefaultMaxSubagents
	}
	if n < 1 {
		return 1
	}
	return n
}

// NewSubagentSemaphore builds the shared concurrency semaphore for task
// sub-agents, sized by MaxSubagents. One instance per run is created and shared
// across every task call so the cap is enforced run-wide.
func NewSubagentSemaphore() chan struct{} {
	return make(chan struct{}, MaxSubagents())
}

// activityOf maps a child sub-agent event to the display verb surfaced in a
// SubAgentProgressEvent (D-8: tool name / phase, no argument summary). A child
// ToolExecutionStartEvent maps by tool name; a TurnStartEvent (a fresh turn with
// no tool in progress) maps to "Thinking". Every other event maps to "" so the
// caller emits nothing — progress is reported only at these activity boundaries,
// keeping event volume proportional to the child's tool calls rather than its
// text deltas (D-7).
func activityOf(ev agentcore.AgentEvent) string {
	switch e := ev.(type) {
	case agentcore.ToolExecutionStartEvent:
		switch e.ToolName {
		case "read":
			return "Reading"
		case "edit", "write":
			return "Editing"
		case "bash":
			return "Running bash"
		case "grep", "find", "ls":
			return "Searching"
		case "webfetch":
			return "Fetching"
		default:
			return ""
		}
	case agentcore.TurnStartEvent:
		return "Thinking"
	default:
		return ""
	}
}

// estimateTokens gives a coarse output-token estimate from a running character
// count of the child's streamed text (~4 chars per token). It rides along on
// each progress event as a rough "↓ tokens" figure; 0 means unknown (no text
// streamed yet).
func estimateTokens(chars int) int {
	if chars <= 0 {
		return 0
	}
	return chars / 4
}
