// yaml.go — Appends key entries to config.yaml without destroying comments.
//
// ## Why raw text append instead of yaml.Marshal?
//
// yaml.v3's Marshal destroys comments, reorders map keys, and normalises
// formatting. Users keep the default config's comments (key layout diagram,
// examples, etc.), so we must preserve them. The approach:
//
//   - Parse config.yaml with config.Load() for DATA (which keys exist, icons_dir, etc.)
//   - Write new keys by appending raw YAML text to the end of the file
//
// This works because keys: is always the last top-level block in config.yaml,
// and YAML maps are unordered, so appending a new 2-space-indented key entry
// to the end of the file is valid.
//
// ## Three cases handled by appendKeyToConfig
//
// Case A: "keys: {}" — the empty map literal from the default config generator.
//
//	Replace {} with a newline, then insert the key block. This is the most
//	common case on a fresh install.
//
// Case B: "keys:" with existing children — the normal case after keys exist.
//
//	Append the new key block to the end of the file. It lands inside the
//	keys: block because it's indented with 2 spaces.
//
// Case C: no "keys:" block at all — a malformed or minimal config.
//
//	Append "keys:\n" then the key block.
//
// ## Expanding
//
// To support toggle keys (icon_true/icon_false + poll block), add those fields
// to keyEntry and extend renderKeySnippet() to emit the extra YAML lines.
//
// To support deleting or editing existing keys, you'd need a different strategy
// (e.g. yaml.v3 node-level manipulation). That's not in scope for the current
// append-only tool.

package main

import (
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
)

// keyEntry holds the data collected from the TUI forms, ready to be rendered
// as a YAML snippet and appended to config.yaml.
//
// Two mutually exclusive modes:
//   - Module key: Module + Function + Params are set, Command is empty
//   - Command key: Command is set, Module/Function/Params are empty
type keyEntry struct {
	Index    int               // key slot (0-31)
	Icon     string            // icon filename relative to icons_dir
	Module   string            // module name from modules.yaml (e.g. "slack")
	Function string            // function name within the module (e.g. "set_status")
	Params   map[string]string // param overrides (merged with module defaults at runtime)
	Command  string            // raw shell command (non-module keys only)
}

// appendKeyToConfig reads config.yaml, appends a key entry as raw YAML text,
// and writes the file back. Preserves all existing content including comments.
func appendKeyToConfig(path string, entry keyEntry) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return fmt.Errorf("read config: %w", err)
	}

	content := string(data)
	snippet := renderKeySnippet(entry)

	// Case A: keys: {} — empty map literal from the default config generator.
	// Replace the {} with a newline so the snippet becomes the first child.
	emptyKeys := regexp.MustCompile(`(?m)^keys:\s*\{\}\s*$`)
	if emptyKeys.MatchString(content) {
		content = emptyKeys.ReplaceAllString(content, "keys:\n"+snippet)
		return os.WriteFile(path, []byte(content), 0644)
	}

	// Case B: keys: exists with children — append snippet to end of file.
	// The 2-space indent on the snippet places it inside the keys: block.
	hasKeys := regexp.MustCompile(`(?m)^keys:\s*$`)
	if hasKeys.MatchString(content) {
		if !strings.HasSuffix(content, "\n") {
			content += "\n"
		}
		content += snippet
		return os.WriteFile(path, []byte(content), 0644)
	}

	// Case C: no keys: block at all — append both the block header and snippet.
	if !strings.HasSuffix(content, "\n") {
		content += "\n"
	}
	content += "\nkeys:\n" + snippet
	return os.WriteFile(path, []byte(content), 0644)
}

// renderKeySnippet formats a keyEntry as an indented YAML block (2-space indent
// for the key index, 4-space for fields, 6-space for params).
//
// Module key output:
//
//	  3:
//	    icon: status.png
//	    module: slack
//	    function: set_status
//	    params:
//	      emoji: ":brb:"
//	      text: "BRB"
//
// Command key output:
//
//	  3:
//	    icon: firefox.png
//	    command: "open -a Firefox"
func renderKeySnippet(e keyEntry) string {
	var b strings.Builder

	fmt.Fprintf(&b, "  %d:\n", e.Index)
	fmt.Fprintf(&b, "    icon: %s\n", e.Icon)

	if e.Module != "" {
		fmt.Fprintf(&b, "    module: %s\n", e.Module)
		fmt.Fprintf(&b, "    function: %s\n", e.Function)
		if len(e.Params) > 0 {
			b.WriteString("    params:\n")
			// Sort param keys for deterministic, diffable output.
			keys := make([]string, 0, len(e.Params))
			for k := range e.Params {
				keys = append(keys, k)
			}
			sort.Strings(keys)
			for _, k := range keys {
				fmt.Fprintf(&b, "      %s: \"%s\"\n", k, e.Params[k])
			}
		}
	} else {
		fmt.Fprintf(&b, "    command: \"%s\"\n", e.Command)
	}

	return b.String()
}
