package tools

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
)

// ── Registry ────────────────────────────────

var registry = map[string]Tool{}

// Register adds a tool to the global registry. Called at init time.
func Register(t Tool) {
	def := t.Definition()
	if _, exists := registry[def.Name]; exists {
		panic("tools.Register: duplicate tool name: " + def.Name)
	}
	registry[def.Name] = t
	log.Printf("🔧 Registered tool: %s", def.Name)
}

// Get returns a tool by name, or nil if not found.
func Get(name string) Tool {
	return registry[name]
}

// AllDefinitions returns tool definitions for all registered tools.
// Used to populate the `tools` field in LLM requests.
func AllDefinitions() []ToolDef {
	defs := make([]ToolDef, 0, len(registry))
	for _, t := range registry {
		defs = append(defs, t.Definition())
	}
	return defs
}

// AvailableFor returns tool definitions available in the given context,
// excluding any names in the disabled set. Tools that implement
// ContextualTool have their Availability() predicate evaluated against
// tctx. Tools that only implement Tool (no predicate) are always included.
func AvailableFor(tctx ToolContext, disabled map[string]bool) []ToolDef {
	defs := make([]ToolDef, 0, len(registry))
	for _, t := range registry {
		def := t.Definition()
		if disabled[def.Name] {
			continue
		}
		// Check context-aware availability if the tool declares it
		if ct, ok := t.(ContextualTool); ok {
			if !ct.Availability()(tctx) {
				continue
			}
		}
		defs = append(defs, def)
	}
	return defs
}

// ── Execution ───────────────────────────────

// ExecuteCall runs a single tool call and returns a ToolResult.
// Never returns an error — failures are encoded in the result content
// so the LLM can see what went wrong and adjust.
func ExecuteCall(ctx context.Context, execCtx ExecutionContext, call ToolCall) ToolResult {
	tool := Get(call.Name)
	if tool == nil {
		return ToolResult{
			ToolCallID: call.ID,
			Name:       call.Name,
			Content:    jsonError(fmt.Sprintf("unknown tool: %s", call.Name)),
			IsError:    true,
		}
	}

	result, err := tool.Execute(ctx, execCtx, call.Arguments)
	if err != nil {
		log.Printf("Tool %s error: %v", call.Name, err)
		return ToolResult{
			ToolCallID: call.ID,
			Name:       call.Name,
			Content:    jsonError(err.Error()),
			IsError:    true,
		}
	}

	return ToolResult{
		ToolCallID: call.ID,
		Name:       call.Name,
		Content:    result,
	}
}

// ExecuteAll runs multiple tool calls and returns all results.
func ExecuteAll(ctx context.Context, execCtx ExecutionContext, calls []ToolCall) []ToolResult {
	results := make([]ToolResult, len(calls))
	for i, call := range calls {
		results[i] = ExecuteCall(ctx, execCtx, call)
	}
	return results
}

// HasTools returns true if any tools are registered.
func HasTools() bool {
	return len(registry) > 0
}

// ── Helpers ─────────────────────────────────

func jsonError(msg string) string {
	b, _ := json.Marshal(map[string]string{"error": msg})
	return string(b)
}
