package nomen

import (
	"strings"
	"testing"
)

// mapResolver builds a VarResolver from a map: a present key (even empty) is
// reported present only when its value is non-blank, matching the engine's
// blank-is-absent contract.
func mapResolver(m map[string]string) VarResolver {
	return func(key string) (string, bool) {
		v, ok := m[key]
		return v, ok
	}
}

// upperSanitiser is a stand-in per-value transform used to prove SanitiseValue
// runs on values but not on separators or wraps.
func upperSanitiser(s string) string { return strings.ToUpper(s) }

func TestRender_TrailingSeparators(t *testing.T) {
	// date joins with " ", parties join with " ./. " — the draft-title shape.
	comp := Composition{Version: Version, Segments: []Segment{
		{Var: "date", Sep: " ", Missing: Omit()},
		{Var: "client", Sep: " ./. ", Missing: Omit()},
		{Var: "forum", Sep: " ./. ", Missing: Omit()},
		{Var: "opponent", Sep: "", Missing: Omit()},
	}}
	cases := []struct {
		name string
		vars map[string]string
		want string
	}{
		{"all present", map[string]string{"date": "2026-05-31", "client": "Bayer AG", "forum": "UPC", "opponent": "Novartis"}, "2026-05-31 Bayer AG ./. UPC ./. Novartis"},
		{"client absent — date joins forum with a space", map[string]string{"date": "2026-05-31", "forum": "UPC", "opponent": "Novartis"}, "2026-05-31 UPC ./. Novartis"},
		{"only opponent absent", map[string]string{"date": "2026-05-31", "client": "Bayer AG", "forum": "UPC"}, "2026-05-31 Bayer AG ./. UPC"},
		{"forum absent", map[string]string{"date": "2026-05-31", "client": "Bayer AG", "opponent": "Acme"}, "2026-05-31 Bayer AG ./. Acme"},
		{"date only", map[string]string{"date": "2026-05-31"}, "2026-05-31"},
		{"blank value treated as absent", map[string]string{"date": "2026-05-31", "client": "  ", "forum": "UPC"}, "2026-05-31 UPC"},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := comp.Render(mapResolver(c.vars), PlainTarget("title"))
			if got != c.want {
				t.Errorf("Render = %q, want %q", got, c.want)
			}
		})
	}
}

func TestRender_MissingRulesAndTargets(t *testing.T) {
	// The filename shape: keyword literal fallback, case placeholder + wrap,
	// a sanitiser + suffix target.
	comp := Composition{Version: Version, Segments: []Segment{
		{Var: "date", Sep: " ", Missing: Omit()},
		{Var: "keyword", Sep: " ", Missing: Literal("submission")},
		{Var: "case_number", Sep: "", Wrap: [2]string{"(", ")"}, Missing: Placeholder("Az. folgt")},
	}}
	target := FuncTarget{NameVal: "filename", Sanitiser: upperSanitiser, Suffix: ".docx"}

	cases := []struct {
		name string
		vars map[string]string
		want string
	}{
		{"all present — value sanitised, frame preserved", map[string]string{"date": "2026-05-31", "keyword": "Replik", "case_number": "x/y"}, "2026-05-31 REPLIK (X/Y).docx"},
		{"keyword empty → literal fallback (also sanitised)", map[string]string{"date": "2026-05-31", "case_number": "abc"}, "2026-05-31 SUBMISSION (ABC).docx"},
		{"case empty → placeholder, wrapped", map[string]string{"date": "2026-05-31", "keyword": "Replik"}, "2026-05-31 REPLIK (AZ. FOLGT).docx"},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := comp.Render(mapResolver(c.vars), target)
			if got != c.want {
				t.Errorf("Render = %q, want %q", got, c.want)
			}
		})
	}
}

func TestRender_EmptyPlaceholderOmits(t *testing.T) {
	// A placeholder/literal whose value is itself blank contributes nothing
	// (and suppresses its trailing separator).
	comp := Composition{Version: Version, Segments: []Segment{
		{Var: "a", Sep: "-", Missing: Placeholder("  ")},
		{Var: "b", Sep: "", Missing: Omit()},
	}}
	got := comp.Render(mapResolver(map[string]string{"b": "tail"}), PlainTarget("x"))
	if got != "tail" {
		t.Errorf("Render = %q, want %q", got, "tail")
	}
}

func TestValidate(t *testing.T) {
	cat := VarCatalog{"date": {Key: "date"}, "client": {Key: "client"}}
	ok := Composition{Version: Version, Segments: []Segment{{Var: "date"}, {Var: "client"}}}
	if err := ok.Validate(cat); err != nil {
		t.Fatalf("valid composition rejected: %v", err)
	}
	bad := []struct {
		name string
		comp Composition
	}{
		{"wrong version", Composition{Version: 0, Segments: []Segment{{Var: "date"}}}},
		{"unknown var", Composition{Version: Version, Segments: []Segment{{Var: "nope"}}}},
		{"empty var", Composition{Version: Version, Segments: []Segment{{Var: " "}}}},
	}
	for _, c := range bad {
		t.Run(c.name, func(t *testing.T) {
			if err := c.comp.Validate(cat); err == nil {
				t.Errorf("expected validation error, got nil")
			}
		})
	}
}
