package runtime

// Tests for the prompt-template expansion engine (US-002, #332). Covers the
// positional/default/slice syntax from the acceptance criteria, the
// ${...}-before-bare-$ ordering invariant, and the no-placeholder append
// behavior that preserves ParseUserCommand's existing semantics.

import "testing"

func TestExpandTemplateNoPlaceholder(t *testing.T) {
	// No args: verbatim.
	if got := ExpandTemplate("Take a note", nil); got != "Take a note" {
		t.Errorf("no args: got %q", got)
	}
	if got := ExpandTemplate("Take a note", []string{}); got != "Take a note" {
		t.Errorf("empty args: got %q", got)
	}
	// With args: appended after a blank line, joined by spaces.
	if got := ExpandTemplate("Take a note", []string{"buy", "milk"}); got != "Take a note\n\nbuy milk" {
		t.Errorf("with args: got %q", got)
	}
}

func TestExpandTemplatePositional(t *testing.T) {
	args := []string{"Button", "click", "handler"}
	if got := ExpandTemplate("$1", args); got != "Button" {
		t.Errorf("$1 = %q, want Button", got)
	}
	if got := ExpandTemplate("$3", args); got != "handler" {
		t.Errorf("$3 = %q, want handler", got)
	}
	// Out of range -> empty.
	if got := ExpandTemplate("$5", args); got != "" {
		t.Errorf("$5 = %q, want empty", got)
	}
	// Multi-digit.
	if got := ExpandTemplate("$10", []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j"}); got != "j" {
		t.Errorf("$10 = %q, want j", got)
	}
}

func TestExpandTemplateAllArgs(t *testing.T) {
	args := []string{"Button", "click", "handler"}
	if got := ExpandTemplate("$@", args); got != "Button click handler" {
		t.Errorf("$@ = %q", got)
	}
	if got := ExpandTemplate("$ARGUMENTS", args); got != "Button click handler" {
		t.Errorf("$ARGUMENTS = %q", got)
	}
}

func TestExpandTemplateDefaultPositional(t *testing.T) {
	// Present and non-empty -> the arg.
	if got := ExpandTemplate("${1:-default}", []string{"a"}); got != "a" {
		t.Errorf("present: got %q", got)
	}
	// Absent -> default.
	if got := ExpandTemplate("${1:-default}", nil); got != "default" {
		t.Errorf("absent: got %q", got)
	}
	// Present but empty -> default.
	if got := ExpandTemplate("${1:-default}", []string{""}); got != "default" {
		t.Errorf("empty: got %q", got)
	}
	// Default containing a $ is not re-expanded.
	if got := ExpandTemplate("${1:-pay $5}", nil); got != "pay $5" {
		t.Errorf("default literal $: got %q", got)
	}
}

func TestExpandTemplateDefaultAllArgs(t *testing.T) {
	if got := ExpandTemplate("${@:-default}", []string{"a", "b"}); got != "a b" {
		t.Errorf("present: got %q", got)
	}
	if got := ExpandTemplate("${@:-default}", nil); got != "default" {
		t.Errorf("absent: got %q", got)
	}
	if got := ExpandTemplate("${ARGUMENTS:-default}", []string{"x"}); got != "x" {
		t.Errorf("ARGUMENTS present: got %q", got)
	}
	if got := ExpandTemplate("${ARGUMENTS:-default}", nil); got != "default" {
		t.Errorf("ARGUMENTS absent: got %q", got)
	}
}

func TestExpandTemplateSlice(t *testing.T) {
	args := []string{"a", "b", "c", "d", "e"}
	// ${@:N}: from Nth onward.
	if got := ExpandTemplate("${@:2}", args); got != "b c d e" {
		t.Errorf("${@:2} = %q", got)
	}
	if got := ExpandTemplate("${@:1}", args); got != "a b c d e" {
		t.Errorf("${@:1} = %q", got)
	}
	// ${@:N:L}: L args starting at N.
	if got := ExpandTemplate("${@:2:2}", args); got != "b c" {
		t.Errorf("${@:2:2} = %q", got)
	}
	if got := ExpandTemplate("${@:1:3}", args); got != "a b c" {
		t.Errorf("${@:1:3} = %q", got)
	}
	// N beyond args -> empty.
	if got := ExpandTemplate("${@:9}", args); got != "" {
		t.Errorf("${@:9} = %q, want empty", got)
	}
	// L clamps to available.
	if got := ExpandTemplate("${@:3:100}", args); got != "c d e" {
		t.Errorf("${@:3:100} = %q, want c d e", got)
	}
	// N<1 -> empty.
	if got := ExpandTemplate("${@:0}", args); got != "" {
		t.Errorf("${@:0} = %q, want empty", got)
	}
}

func TestExpandTemplateOrderingNoDoubleMatch(t *testing.T) {
	// A bare $1 must NOT match inside ${1:-...}. With args present, ${1:-$2}
	// uses arg1 ("a"); the $2 default is never consulted nor expanded.
	if got := ExpandTemplate("${1:-$2}", []string{"a"}); got != "a" {
		t.Errorf("${1:-$2} with arg1=a: got %q, want a", got)
	}
	// With arg1 absent, the default "$2" is taken literally (not expanded to "").
	if got := ExpandTemplate("${1:-$2}", nil); got != "$2" {
		t.Errorf("${1:-$2} absent: got %q, want literal $2", got)
	}
	// Braced and bare coexist in one pass.
	if got := ExpandTemplate("${1:-x} and $2", []string{"a", "b"}); got != "a and b" {
		t.Errorf("coexist: got %q", got)
	}
}

func TestExpandTemplateExampleFromSpec(t *testing.T) {
	// $@ expands to ALL args joined (AC: "all args joined by a single space").
	if got := ExpandTemplate("echo $@", []string{"a", "b", "c"}); got != "echo a b c" {
		t.Errorf("$@ all-args: got %q", got)
	}
	// The pi "component" example: $1 for the name, features are the remaining
	// args expressed with the slice form ${@:2} (the right tool for "rest").
	tmpl := "Create a React component named $1 with features: ${@:2}"
	if got := ExpandTemplate(tmpl, []string{"Button", "click", "handler"}); got != "Create a React component named Button with features: click handler" {
		t.Errorf("component example: got %q", got)
	}
	// "Summarize the current state in ${1:-7} bullet points."
	bullets := "Summarize the current state in ${1:-7} bullet points."
	if got := ExpandTemplate(bullets, nil); got != "Summarize the current state in 7 bullet points." {
		t.Errorf("default bullets: got %q", got)
	}
	if got := ExpandTemplate(bullets, []string{"5"}); got != "Summarize the current state in 5 bullet points." {
		t.Errorf("explicit bullets: got %q", got)
	}
}

func TestExpandTemplateLiteralDollar(t *testing.T) {
	// A '$' not forming a placeholder is emitted literally. Such a template has
	// no placeholder, so args (if any) are appended per the no-placeholder rule.
	if got := ExpandTemplate("100$ off", nil); got != "100$ off" {
		t.Errorf("literal $ mid-string: got %q", got)
	}
	if got := ExpandTemplate("trailing$", nil); got != "trailing$" {
		t.Errorf("trailing $ no args: got %q", got)
	}
	// No placeholder + args -> append after a blank line.
	if got := ExpandTemplate("trailing$", []string{"x"}); got != "trailing$\n\nx" {
		t.Errorf("trailing $ with args: got %q", got)
	}
	// A literal '$' alongside a real placeholder: '$' before a space stays '$'.
	if got := ExpandTemplate("cost $ and $1", []string{"five"}); got != "cost $ and five" {
		t.Errorf("literal $ next to placeholder: got %q", got)
	}
}
