package skills

import (
	"context"
	"encoding/json"
	"fmt"
	"sync"

	"github.com/patriceckhart/zot/packages/core"
	"github.com/patriceckhart/zot/packages/provider"
)

// Tool implements core.Tool, exposing a `skill` tool the LLM can call
// to load the body of a discovered skill on demand. The system-prompt
// addendum lists the available names; this tool returns the full
// markdown for the requested one.
//
// The list of skills is held behind a mutex so tests / future
// /reload-skills wiring can swap in a fresh set without races.
type Tool struct {
	mu     sync.RWMutex
	skills []*Skill
}

// NewTool returns a skill loader tool seeded with the given skills.
// Pass the slice from Discover().
func NewTool(skills []*Skill) *Tool { return &Tool{skills: skills} }

// SetSkills atomically replaces the underlying skill set. Used when
// the user re-runs discovery (e.g. after editing a SKILL.md).
func (t *Tool) SetSkills(s []*Skill) {
	t.mu.Lock()
	t.skills = s
	t.mu.Unlock()
}

// Skills returns a snapshot for callers that need to render the
// current set (e.g. the /skills picker).
func (t *Tool) Skills() []*Skill {
	t.mu.RLock()
	defer t.mu.RUnlock()
	out := make([]*Skill, len(t.skills))
	copy(out, t.skills)
	return out
}

// ---- core.Tool implementation ----

// Name is the LLM-facing tool name.
func (*Tool) Name() string { return "skill" }

// Description tells the model what this tool does. Kept blunt so the
// model reliably uses it instead of guessing what a "skill" is.
func (*Tool) Description() string {
	return "Load a named skill's instructions. Use when the user's request matches a skill listed above."
}

// Schema is one required string parameter: the skill name.
func (*Tool) Schema() json.RawMessage {
	return json.RawMessage(`{"type":"object","properties":{"name":{"type":"string"}},"required":["name"]}`)
}

// Execute returns the markdown body of the requested skill.
func (t *Tool) Execute(ctx context.Context, args json.RawMessage, progress func(string)) (core.ToolResult, error) {
	var in struct {
		Name string `json:"name"`
	}
	if err := json.Unmarshal(args, &in); err != nil {
		return core.ToolResult{
			IsError: true,
			Content: []provider.Content{provider.TextBlock{Text: "skill: invalid args: " + err.Error()}},
		}, nil
	}
	if in.Name == "" {
		return core.ToolResult{
			IsError: true,
			Content: []provider.Content{provider.TextBlock{Text: "skill: name is required"}},
		}, nil
	}

	t.mu.RLock()
	s := FindByName(t.skills, in.Name)
	t.mu.RUnlock()
	if s == nil {
		return core.ToolResult{
			IsError: true,
			Content: []provider.Content{provider.TextBlock{Text: fmt.Sprintf("skill: no skill named %q (run /skills in zot to see what's available)", in.Name)}},
		}, nil
	}

	header := fmt.Sprintf("# Skill: %s\n\n%s\n\n---\n\n", s.Name, s.Description)
	return core.ToolResult{
		Content: []provider.Content{provider.TextBlock{Text: header + s.Body}},
		Details: map[string]any{
			"skill": s.Name,
			"path":  s.Path,
		},
	}, nil
}
