// Package recipes provides recipe-based configuration for bd setup.
// Recipes define where beads workflow instructions are written for different AI tools.
package recipes

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"github.com/BurntSushi/toml"
)

// RecipeType indicates how the recipe is installed.
type RecipeType string

const (
	// TypeFile writes template to a file path (simple case)
	TypeFile RecipeType = "file"
	// TypeHooks modifies JSON settings to add hooks (claude, gemini)
	TypeHooks RecipeType = "hooks"
	// TypeSection injects a marked section into existing file (factory)
	TypeSection RecipeType = "section"
	// TypeMultiFile writes multiple files (aider)
	TypeMultiFile RecipeType = "multifile"
)

// Recipe defines an AI tool integration.
type Recipe struct {
	Name        string     `toml:"name"`        // Display name (e.g., "Cursor IDE")
	Path        string     `toml:"path"`        // Primary file path (for TypeFile)
	Type        RecipeType `toml:"type"`        // How to install
	Description string     `toml:"description"` // Brief description
	// Optional fields for complex recipes
	GlobalPath  string   `toml:"global_path"`  // Global settings path (for hooks)
	ProjectPath string   `toml:"project_path"` // Project settings path (for hooks)
	Paths       []string `toml:"paths"`        // Multiple paths (for multifile)
}

// BuiltinRecipes contains the default recipe definitions.
// These are compiled into the binary.
var BuiltinRecipes = map[string]Recipe{
	"cursor": {
		Name:        "Cursor IDE",
		Path:        ".cursor/rules/beads.mdc",
		Type:        TypeFile,
		Description: "Cursor IDE rules file",
	},
	"windsurf": {
		Name:        "Windsurf",
		Path:        ".windsurf/rules/beads.md",
		Type:        TypeFile,
		Description: "Windsurf editor rules file",
	},
	"cody": {
		Name:        "Sourcegraph Cody",
		Path:        ".cody/rules/beads.md",
		Type:        TypeFile,
		Description: "Cody AI rules file",
	},
	"kilocode": {
		Name:        "Kilo Code",
		Path:        ".kilocode/rules/beads.md",
		Type:        TypeFile,
		Description: "Kilo Code rules file",
	},
	"claude": {
		Name:        "Claude Code",
		Type:        TypeHooks,
		Description: "Claude Code hooks (SessionStart, PreCompact)",
		GlobalPath:  "~/.claude/settings.json",
		ProjectPath: ".claude/settings.local.json",
	},
	"gemini": {
		Name:        "Gemini CLI",
		Type:        TypeHooks,
		Description: "Gemini CLI hooks (SessionStart, PreCompress)",
		GlobalPath:  "~/.gemini/settings.json",
		ProjectPath: ".gemini/settings.json",
	},
	"factory": {
		Name:        "Factory.ai (Droid)",
		Path:        "AGENTS.md",
		Type:        TypeSection,
		Description: "Factory Droid AGENTS.md section",
	},
	"aider": {
		Name:        "Aider",
		Type:        TypeMultiFile,
		Description: "Aider config and instruction files",
		Paths:       []string{".aider.conf.yml", ".aider/BEADS.md", ".aider/README.md"},
	},
	"junie": {
		Name:        "Junie",
		Type:        TypeMultiFile,
		Description: "Junie guidelines and MCP configuration",
		Paths:       []string{".junie/guidelines.md", ".junie/mcp/mcp.json"},
	},
}

// UserRecipes holds recipes loaded from user config file.
type UserRecipes struct {
	Recipes map[string]Recipe `toml:"recipes"`
}

// LoadUserRecipes loads recipes from .beads/recipes.toml if it exists.
func LoadUserRecipes(beadsDir string) (map[string]Recipe, error) {
	path := filepath.Join(beadsDir, "recipes.toml")
	data, err := os.ReadFile(path) // #nosec G304 -- path is constructed from validated beadsDir
	if os.IsNotExist(err) {
		return nil, nil // No user recipes, that's fine
	}
	if err != nil {
		return nil, fmt.Errorf("read recipes.toml: %w", err)
	}

	var userRecipes UserRecipes
	if err := toml.Unmarshal(data, &userRecipes); err != nil {
		return nil, fmt.Errorf("parse recipes.toml: %w", err)
	}

	// Set defaults for user recipes
	for name, recipe := range userRecipes.Recipes {
		if recipe.Type == "" {
			recipe.Type = TypeFile
		}
		if recipe.Name == "" {
			recipe.Name = name
		}
		userRecipes.Recipes[name] = recipe
	}

	return userRecipes.Recipes, nil
}

// GetAllRecipes returns merged built-in and user recipes.
// User recipes override built-in recipes with the same name.
func GetAllRecipes(beadsDir string) (map[string]Recipe, error) {
	result := make(map[string]Recipe)

	// Start with built-in recipes
	for name, recipe := range BuiltinRecipes {
		result[name] = recipe
	}

	// Load and merge user recipes
	userRecipes, err := LoadUserRecipes(beadsDir)
	if err != nil {
		return nil, err
	}
	for name, recipe := range userRecipes {
		result[name] = recipe
	}

	return result, nil
}

// GetRecipe looks up a recipe by name, checking user recipes first.
func GetRecipe(name string, beadsDir string) (*Recipe, error) {
	// Normalize name (lowercase, strip leading/trailing hyphens)
	name = strings.ToLower(strings.Trim(name, "-"))

	recipes, err := GetAllRecipes(beadsDir)
	if err != nil {
		return nil, err
	}

	recipe, ok := recipes[name]
	if !ok {
		return nil, fmt.Errorf("unknown recipe: %s", name)
	}

	return &recipe, nil
}

// SaveUserRecipe adds or updates a recipe in .beads/recipes.toml.
func SaveUserRecipe(beadsDir, name, path string) error {
	recipesPath := filepath.Join(beadsDir, "recipes.toml")

	// Load existing user recipes
	var userRecipes UserRecipes
	data, err := os.ReadFile(recipesPath) // #nosec G304 -- path is constructed from validated beadsDir
	if err == nil {
		if err := toml.Unmarshal(data, &userRecipes); err != nil {
			return fmt.Errorf("parse recipes.toml: %w", err)
		}
	} else if !os.IsNotExist(err) {
		return fmt.Errorf("read recipes.toml: %w", err)
	}

	if userRecipes.Recipes == nil {
		userRecipes.Recipes = make(map[string]Recipe)
	}

	// Add/update the recipe
	userRecipes.Recipes[name] = Recipe{
		Name: name,
		Path: path,
		Type: TypeFile,
	}

	// Ensure directory exists
	if err := os.MkdirAll(beadsDir, 0o755); err != nil {
		return fmt.Errorf("create beads dir: %w", err)
	}

	// Write back
	f, err := os.Create(recipesPath) // #nosec G304 -- path is constructed from validated beadsDir
	if err != nil {
		return fmt.Errorf("create recipes.toml: %w", err)
	}
	defer f.Close()

	encoder := toml.NewEncoder(f)
	if err := encoder.Encode(userRecipes); err != nil {
		return fmt.Errorf("encode recipes.toml: %w", err)
	}

	return nil
}

// ListRecipeNames returns sorted list of all recipe names.
func ListRecipeNames(beadsDir string) ([]string, error) {
	recipes, err := GetAllRecipes(beadsDir)
	if err != nil {
		return nil, err
	}

	names := make([]string, 0, len(recipes))
	for name := range recipes {
		names = append(names, name)
	}

	// Sort alphabetically
	for i := 0; i < len(names); i++ {
		for j := i + 1; j < len(names); j++ {
			if names[i] > names[j] {
				names[i], names[j] = names[j], names[i]
			}
		}
	}

	return names, nil
}

// IsBuiltin returns true if the recipe is a built-in (not user-defined).
func IsBuiltin(name string) bool {
	_, ok := BuiltinRecipes[name]
	return ok
}
