// Package cline implements the Gortex init integration for Cline
// (formerly Claude Dev — extension ID saoudrizwan.claude-dev). Cline
// stores its MCP config under the host editor's globalStorage
// directory; we probe both the VS Code and Cursor variants on each
// OS. The Step 3 audit will add JetBrains support and verify whether
// Cline has renamed alwaysAllow → autoApprove.
package cline

import (
	"os"
	"path/filepath"

	"github.com/zzet/gortex/internal/agents"
	"github.com/zzet/gortex/internal/agents/internalutil"
)

const Name = "cline"
const DocsURL = "https://docs.cline.bot/mcp/mcp-overview"

type Adapter struct{}

func New() *Adapter                { return &Adapter{} }
func (a *Adapter) Name() string    { return Name }
func (a *Adapter) DocsURL() string { return DocsURL }

// settingsPaths returns candidate cline_mcp_settings.json locations
// across OS/editor combos. Order doesn't matter — we write to every
// path whose parent directory exists.
func settingsPaths(home string) []string {
	var paths []string
	vscodeBases := []string{
		filepath.Join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
		filepath.Join(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
	}
	cursorBases := []string{
		filepath.Join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
		filepath.Join(home, ".config", "Cursor", "User", "globalStorage", "saoudrizwan.claude-dev", "settings"),
	}
	for _, dir := range append(vscodeBases, cursorBases...) {
		paths = append(paths, filepath.Join(dir, "cline_mcp_settings.json"))
	}
	return paths
}

// alwaysAllow is Cline's auto-approve allow-list. Matches the
// Gortex tool surface so Cline doesn't ask the user on every query.
var alwaysAllow = []string{
	"graph_stats", "search_symbols", "winnow_symbols", "get_symbol", "get_file_summary",
	"get_editing_context", "get_dependencies", "get_dependents",
	"get_call_chain", "get_callers", "find_implementations", "find_usages",
	"get_cluster", "get_symbol_signature", "get_symbol_source", "batch_symbols",
	"find_import_path", "explain_change_impact", "get_recent_changes",
	"smart_context", "get_edit_plan", "get_test_targets", "suggest_pattern",
	"get_communities", "get_community", "get_processes", "get_process",
	"detect_changes", "index_repository", "reindex_repository",
	"verify_change", "check_guards", "prefetch_context",
	"find_dead_code", "find_hotspots", "find_cycles", "would_create_cycle",
	"diff_context", "index_health", "get_symbol_history",
	"scaffold", "batch_edit",
	"flow_between", "taint_paths",
	"find_clones",
}

func (a *Adapter) Detect(env agents.Env) (bool, error) {
	if env.Home == "" {
		return false, nil
	}
	for _, p := range settingsPaths(env.Home) {
		if _, err := os.Stat(filepath.Dir(p)); err == nil {
			return true, nil
		}
	}
	return false, nil
}

func (a *Adapter) Plan(env agents.Env) (*agents.Plan, error) {
	p := &agents.Plan{}
	if env.Home == "" {
		return p, nil
	}
	for _, path := range settingsPaths(env.Home) {
		if _, err := os.Stat(filepath.Dir(path)); err == nil {
			p.Files = append(p.Files, agents.FileAction{
				Path:   path,
				Action: agents.ActionWouldMerge,
				Keys:   []string{"mcpServers"},
			})
		}
	}
	if env.Mode != agents.ModeGlobal && env.SkillsRouting != "" {
		p.Files = append(p.Files, agents.FileAction{
			Path:   filepath.Join(env.Root, ".clinerules", "gortex-communities.md"),
			Action: agents.ActionWouldCreate,
			Keys:   []string{"communities-rule"},
		})
	}
	return p, nil
}

func (a *Adapter) Apply(env agents.Env, opts agents.ApplyOpts) (*agents.Result, error) {
	res := &agents.Result{Name: Name, DocsURL: DocsURL}
	detected, _ := a.Detect(env)
	res.Detected = detected
	if !detected {
		internalutil.Logf(env.Stderr, "[gortex init] skip Cline setup (Cline not detected)")
		return res, nil
	}
	internalutil.Logf(env.Stderr, "[gortex init] setting up Cline integration...")

	for _, path := range settingsPaths(env.Home) {
		if _, err := os.Stat(filepath.Dir(path)); err != nil {
			continue
		}
		entry := agents.DefaultGortexMCPEntry()
		entry["alwaysAllow"] = alwaysAllow
		action, err := agents.MergeJSON(env.Stderr, path, func(root map[string]any, _ bool) (bool, error) {
			return agents.UpsertMCPServer(root, "gortex", entry, opts), nil
		}, opts)
		if err != nil {
			internalutil.Warnf(env.Stderr, "could not configure Cline at %s: %v", path, err)
			continue
		}
		res.Files = append(res.Files, action)
	}
	// Cline reads .clinerules/*.md as project-scoped instructions
	// on every chat turn. The community-routing file is ours
	// end-to-end — regenerated each `gortex init` run so the
	// listing tracks the current graph. Skipped in global mode
	// (file is per-repo) and when --no-skills / no communities
	// qualify.
	if env.Mode != agents.ModeGlobal && env.SkillsRouting != "" {
		rulesPath := filepath.Join(env.Root, ".clinerules", "gortex-communities.md")
		body := agents.CommunitiesStartMarker + "\n" + env.SkillsRouting + "\n" + agents.CommunitiesEndMarker + "\n"
		ruleAction, err := agents.WriteOwnedFile(env.Stderr, rulesPath, body, opts)
		if err != nil {
			internalutil.Warnf(env.Stderr, "could not write Cline community rules at %s: %v", rulesPath, err)
		} else {
			res.Files = append(res.Files, ruleAction)
		}
	}

	res.Configured = len(res.Files) > 0
	return res, nil
}
