// Package workspace sets up an isolated temp directory for each Cursor CLI /
// ACP child. It pre-populates a minimal .cursor config so the agent does not
// load the real user's global rules from ~/.cursor, and returns a set of
// environment overrides (HOME, CURSOR_CONFIG_DIR, XDG_CONFIG_HOME, APPDATA…)
// so the child cannot escape back to the real profile.
//
// Ported from cursor-api-proxy/src/lib/workspace.ts.
package workspace

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
)

// ChatOnly prepares an isolated temp workspace and returns:
//   - dir: the absolute path of the new temp directory (caller is responsible
//     for removing it when the child process exits).
//   - env: a map of environment variables to override on the child.
//
// Auth is the tricky part. The Cursor CLI resolves login tokens from the
// real user profile (macOS keychain on darwin, ~/.cursor/agent-cli-state.json
// elsewhere); if we override HOME to the temp dir, `agent --print` dies with
// "Authentication required. Please run 'agent login' first…". So we keep
// HOME untouched unless either:
//   - CURSOR_API_KEY is set (the CLI uses the env var and doesn't need HOME), or
//   - authConfigDir is non-empty (account-pool mode, not used here yet).
//
// We *do* always override CURSOR_CONFIG_DIR → tempDir/.cursor. That's the
// setting Cursor uses to locate rules/, cli-config.json, and per-project
// state, so this single override is enough to stop the agent from loading
// the user's real ~/.cursor/rules/* into the prompt.
func ChatOnly(authConfigDir string) (dir string, env map[string]string, err error) {
	tempDir, err := os.MkdirTemp("", "cursor-adapter-ws-*")
	if err != nil {
		return "", nil, fmt.Errorf("mkdtemp: %w", err)
	}

	cursorDir := filepath.Join(tempDir, ".cursor")
	if err := os.MkdirAll(filepath.Join(cursorDir, "rules"), 0o755); err != nil {
		_ = os.RemoveAll(tempDir)
		return "", nil, fmt.Errorf("mkdir .cursor/rules: %w", err)
	}

	minimalConfig := map[string]any{
		"version": 1,
		"editor":  map[string]any{"vimMode": false},
		"permissions": map[string]any{
			"allow": []any{},
			"deny":  []any{},
		},
	}
	cfgBytes, _ := json.Marshal(minimalConfig)
	if err := os.WriteFile(filepath.Join(cursorDir, "cli-config.json"), cfgBytes, 0o644); err != nil {
		_ = os.RemoveAll(tempDir)
		return "", nil, fmt.Errorf("write cli-config.json: %w", err)
	}

	env = map[string]string{}
	if authConfigDir != "" {
		env["CURSOR_CONFIG_DIR"] = authConfigDir
		return tempDir, env, nil
	}

	env["CURSOR_CONFIG_DIR"] = cursorDir

	// Only fully isolate HOME if the child will auth via CURSOR_API_KEY.
	// With keychain/home-based auth, replacing HOME makes agent exit 1.
	if os.Getenv("CURSOR_API_KEY") != "" {
		env["HOME"] = tempDir
		env["USERPROFILE"] = tempDir

		if runtime.GOOS == "windows" {
			appDataRoaming := filepath.Join(tempDir, "AppData", "Roaming")
			appDataLocal := filepath.Join(tempDir, "AppData", "Local")
			_ = os.MkdirAll(appDataRoaming, 0o755)
			_ = os.MkdirAll(appDataLocal, 0o755)
			env["APPDATA"] = appDataRoaming
			env["LOCALAPPDATA"] = appDataLocal
		} else {
			xdg := filepath.Join(tempDir, ".config")
			_ = os.MkdirAll(xdg, 0o755)
			env["XDG_CONFIG_HOME"] = xdg
		}
	}

	return tempDir, env, nil
}

// MergeEnv takes the current process env (as "KEY=VALUE" strings) and
// overlays overrides on top, returning a new slice suitable for exec.Cmd.Env.
// Keys from overrides replace any existing entries with the same key.
func MergeEnv(base []string, overrides map[string]string) []string {
	if len(overrides) == 0 {
		return base
	}

	out := make([]string, 0, len(base)+len(overrides))
	seen := make(map[string]bool, len(overrides))

	for _, kv := range base {
		eq := indexOf(kv, '=')
		if eq < 0 {
			out = append(out, kv)
			continue
		}
		key := kv[:eq]
		if v, ok := overrides[key]; ok {
			out = append(out, key+"="+v)
			seen[key] = true
		} else {
			out = append(out, kv)
		}
	}
	for k, v := range overrides {
		if !seen[k] {
			out = append(out, k+"="+v)
		}
	}
	return out
}

func indexOf(s string, c byte) int {
	for i := 0; i < len(s); i++ {
		if s[i] == c {
			return i
		}
	}
	return -1
}
