/*
 * Copyright 2025 CloudWeGo Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Package deep provides a prebuilt agent with deep task orchestration.
package deep

import (
	"context"
	"fmt"

	"github.com/bytedance/sonic"

	"github.com/cloudwego/eino/adk"
	"github.com/cloudwego/eino/adk/filesystem"
	"github.com/cloudwego/eino/adk/internal"
	filesystem2 "github.com/cloudwego/eino/adk/middlewares/filesystem"
	"github.com/cloudwego/eino/components/model"
	"github.com/cloudwego/eino/components/tool/utils"
	"github.com/cloudwego/eino/schema"
)

func init() {
	schema.RegisterName[TODO]("_eino_adk_prebuilt_deep_todo")
	schema.RegisterName[[]TODO]("_eino_adk_prebuilt_deep_todo_slice")
}

// TypedConfig defines the configuration for creating a DeepAgent parameterized by message type.
// An Agentic DeepAgent (M = *schema.AgenticMessage) only supports Agentic sub-agents,
// and a standard DeepAgent (M = *schema.Message) only supports standard sub-agents.
// This is enforced by the type system through the SubAgents field.
type TypedConfig[M adk.MessageType] struct {
	// Name is the identifier for the Deep agent.
	Name string
	// Description provides a brief explanation of the agent's purpose.
	Description string

	// ChatModel is the model used by DeepAgent for reasoning and task execution.
	// If the agent uses any tools, this model must support the model.WithTools call option,
	// as that's how the agent configures the model with tool information.
	ChatModel model.BaseModel[M]
	// Instruction contains the system prompt that guides the agent's behavior.
	// When empty, a built-in default system prompt will be used, which includes general assistant
	// behavior guidelines, security policies, coding style guidelines, and tool usage policies.
	Instruction string
	// SubAgents are specialized agents that can be invoked by the agent.
	// For M = *schema.AgenticMessage, only agentic sub-agents are accepted.
	SubAgents []adk.TypedAgent[M]
	// ToolsConfig provides the tools and tool-calling configurations available for the agent to invoke.
	ToolsConfig adk.ToolsConfig
	// MaxIteration limits the maximum number of reasoning iterations the agent can perform.
	MaxIteration int

	// Backend provides filesystem operations used by tools and offloading.
	// If set, filesystem tools (read_file, write_file, edit_file, glob, grep) will be registered.
	// Optional.
	Backend filesystem.Backend
	// Shell provides shell command execution capability.
	// If set, an execute tool will be registered to support shell command execution.
	// Optional. Mutually exclusive with StreamingShell.
	Shell filesystem.Shell
	// StreamingShell provides streaming shell command execution capability.
	// If set, a streaming execute tool will be registered to support streaming shell command execution.
	// Optional. Mutually exclusive with Shell.
	StreamingShell filesystem.StreamingShell

	// WithoutWriteTodos disables the built-in write_todos tool when set to true.
	WithoutWriteTodos bool
	// WithoutGeneralSubAgent disables the general-purpose subagent when set to true.
	WithoutGeneralSubAgent bool
	// TaskToolDescriptionGenerator allows customizing the description for the task tool.
	// If provided, this function generates the tool description based on available subagents.
	TaskToolDescriptionGenerator func(ctx context.Context, availableAgents []adk.TypedAgent[M]) (string, error)

	Middlewares []adk.AgentMiddleware

	// Handlers configures interface-based handlers for extending agent behavior.
	// Unlike Middlewares (struct-based), Handlers allow users to:
	//   - Add custom methods to their handler implementations
	//   - Return modified context from handler methods
	//   - Centralize configuration in struct fields instead of closures
	//
	// Handlers are processed after Middlewares, in registration order.
	// See adk.ChatModelAgentMiddleware documentation for when to use Handlers vs Middlewares.
	Handlers []adk.TypedChatModelAgentMiddleware[M]

	ModelRetryConfig *adk.TypedModelRetryConfig[M]
	// ModelFailoverConfig configures failover behavior for the ChatModel.
	// When set, the agent will automatically fail over to alternative models on errors.
	// This config is also propagated to the general sub-agent.
	ModelFailoverConfig *adk.ModelFailoverConfig[M]

	// OutputKey stores the agent's response in the session.
	// Optional. When set, stores output via AddSessionValue(ctx, outputKey, msg.Content).
	OutputKey string
}

// Config defines the configuration for creating a standard DeepAgent.
type Config = TypedConfig[*schema.Message]

// NewTyped creates a new typed Deep agent instance with the provided configuration.
// This function initializes built-in tools, creates a task tool for subagent orchestration,
// and returns a fully configured TypedChatModelAgent ready for execution.
func NewTyped[M adk.MessageType](ctx context.Context, cfg *TypedConfig[M]) (adk.TypedResumableAgent[M], error) {
	handlers, err := buildTypedBuiltinAgentMiddlewares(ctx, cfg)
	if err != nil {
		return nil, err
	}

	instruction := cfg.Instruction
	if len(instruction) == 0 {
		instruction = internal.SelectPrompt(internal.I18nPrompts{
			English: baseAgentInstruction,
			Chinese: baseAgentInstructionChinese,
		})
	}

	if !cfg.WithoutGeneralSubAgent || len(cfg.SubAgents) > 0 {
		tt, err := typedTaskToolMiddleware(
			ctx,
			cfg.TaskToolDescriptionGenerator,
			cfg.SubAgents,

			cfg.WithoutGeneralSubAgent,
			cfg.ChatModel,
			instruction,
			cfg.ToolsConfig,
			cfg.MaxIteration,
			cfg.Middlewares,
			append(handlers, cfg.Handlers...),
			cfg.ModelFailoverConfig,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to new task tool: %w", err)
		}
		handlers = append(handlers, tt)
	}

	return adk.NewTypedChatModelAgent(ctx, &adk.TypedChatModelAgentConfig[M]{
		Name:          cfg.Name,
		Description:   cfg.Description,
		Instruction:   instruction,
		Model:         cfg.ChatModel,
		ToolsConfig:   cfg.ToolsConfig,
		MaxIterations: cfg.MaxIteration,
		Middlewares:   cfg.Middlewares,
		Handlers:      append(handlers, cfg.Handlers...),

		GenModelInput:       typedGenModelInput[M],
		ModelRetryConfig:    cfg.ModelRetryConfig,
		ModelFailoverConfig: cfg.ModelFailoverConfig,
		OutputKey:           cfg.OutputKey,
	})
}

// New creates a new Deep agent instance with the provided configuration.
// This function initializes built-in tools, creates a task tool for subagent orchestration,
// and returns a fully configured ChatModelAgent ready for execution.
func New(ctx context.Context, cfg *Config) (adk.ResumableAgent, error) {
	return NewTyped(ctx, cfg)
}

func typedGenModelInput[M adk.MessageType](_ context.Context, instruction string, input *adk.TypedAgentInput[M]) ([]M, error) {
	var zero M
	switch any(zero).(type) {
	case *schema.Message:
		msgs := make([]*schema.Message, 0, len(input.Messages)+1)
		if instruction != "" {
			msgs = append(msgs, schema.SystemMessage(instruction))
		}
		// Type assertion is safe here because M = *schema.Message.
		for _, m := range input.Messages {
			msgs = append(msgs, any(m).(*schema.Message))
		}
		result := make([]M, len(msgs))
		for i, m := range msgs {
			result[i] = any(m).(M)
		}
		return result, nil
	case *schema.AgenticMessage:
		msgs := make([]*schema.AgenticMessage, 0, len(input.Messages)+1)
		if instruction != "" {
			msgs = append(msgs, schema.SystemAgenticMessage(instruction))
		}
		for _, m := range input.Messages {
			msgs = append(msgs, any(m).(*schema.AgenticMessage))
		}
		result := make([]M, len(msgs))
		for i, m := range msgs {
			result[i] = any(m).(M)
		}
		return result, nil
	}
	panic("unreachable")
}

func buildTypedBuiltinAgentMiddlewares[M adk.MessageType](ctx context.Context, cfg *TypedConfig[M]) ([]adk.TypedChatModelAgentMiddleware[M], error) {
	var ms []adk.TypedChatModelAgentMiddleware[M]
	if !cfg.WithoutWriteTodos {
		t, err := typedNewWriteTodos[M]()
		if err != nil {
			return nil, err
		}
		ms = append(ms, t)
	}

	if cfg.Backend != nil || cfg.Shell != nil || cfg.StreamingShell != nil {
		fm, err := filesystem2.NewTyped[M](ctx, &filesystem2.MiddlewareConfig{
			Backend:        cfg.Backend,
			Shell:          cfg.Shell,
			StreamingShell: cfg.StreamingShell,
		})
		if err != nil {
			return nil, err
		}
		ms = append(ms, fm)
	}

	return ms, nil
}

type TODO struct {
	Content    string `json:"content"`
	ActiveForm string `json:"activeForm"`
	Status     string `json:"status" jsonschema:"enum=pending,enum=in_progress,enum=completed"`
}

type writeTodosArguments struct {
	Todos []TODO `json:"todos"`
}

func typedNewWriteTodos[M adk.MessageType]() (adk.TypedChatModelAgentMiddleware[M], error) {
	toolDesc := internal.SelectPrompt(internal.I18nPrompts{
		English: writeTodosToolDescription,
		Chinese: writeTodosToolDescriptionChinese,
	})
	resultMsg := internal.SelectPrompt(internal.I18nPrompts{
		English: "Updated todo list to %s",
		Chinese: "已更新待办列表为 %s",
	})

	t, err := utils.InferTool("write_todos", toolDesc, func(ctx context.Context, input writeTodosArguments) (output string, err error) {
		adk.AddSessionValue(ctx, SessionKeyTodos, input.Todos)
		todos, err := sonic.MarshalString(input.Todos)
		if err != nil {
			return "", err
		}
		return fmt.Sprintf(resultMsg, todos), nil
	})
	if err != nil {
		return nil, err
	}

	return typedBuildAppendPromptTool[M]("", t), nil
}
