// Copyright 2026 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.

package xstrings

import (
	"testing"
	"unicode/utf8"
)

// The tests in this file are regression tests for defects which were found by
// an audit of this package. Each test asserts the behaviour which the package
// document promises.

// Successor: the carry generated by '9' must be '1' instead of '0'.
//
// Regression test for #62.
func TestIssueSuccessorCarryFromNine(t *testing.T) {
	runTestCases(t, Successor, _M{
		"9":    "10",
		"99":   "100",
		"999":  "1000",
		"9z":   "10a",
		"9zz":  "10aa",
		"9*":   "10*",
		"*9":   "*10",
		"9-9":  "10-0",
		"-9z":  "-10a",
		"9 9":  "10 0",
		"a9 9": "b0 0",
	})
}

// Successor: the prefix in front of the inserted carry rune is sliced with a
// rune index used as a byte offset, which cuts multibyte runes.
//
// Regression test for #63.
func TestIssueSuccessorSlicesMultibytePrefixByRune(t *testing.T) {
	cases := _M{
		"中z":  "中aa",
		"üZ":  "üAA",
		"中中z": "中中aa",
		"😀Z":  "😀AA",
	}

	for str, want := range cases {
		got := Successor(str)

		if !utf8.ValidString(got) {
			t.Fatalf("Successor(%q) = %q, result is not a valid utf8 string", str, got)
		}

		if got != want {
			t.Fatalf("Successor(%q) = %q, want %q", str, got, want)
		}
	}
}

// ToCamelCase/ToPascalCase: an input which contains connectors only returns an
// extra copy of its last rune.
//
// Regression test for #64.
func TestIssueCamelCaseDuplicatesTrailingConnector(t *testing.T) {
	cases := _M{
		"_":    "_",
		"__":   "__",
		"___":  "___",
		"-":    "-",
		"--":   "--",
		" ":    " ",
		"  ":   "  ",
		"\t":   "\t",
		"-_-":  "-_-",
		" _- ": " _- ",
		"\n ":  "\n ",
	}

	runTestCases(t, ToCamelCase, cases)
	runTestCases(t, ToPascalCase, cases)
}

// Translate/Delete: once one rune has been translated, every remaining rune
// which decodes to utf8.RuneError is dropped, even if it is a valid U+FFFD rune
// which does not match the pattern.
//
// Regression test for #65.
func TestIssueTranslateDropsUnmatchedRuneError(t *testing.T) {
	cases := []struct {
		name string
		got  string
		want string
	}{
		{`Translate("a\uFFFDb", "a", "x")`, Translate("a\uFFFDb", "a", "x"), "x\uFFFDb"},
		{`Translate("a\uFFFDb", "a-z", "X")`, Translate("a\uFFFDb", "a-z", "X"), "X\uFFFDX"},
		{`Translate("a\uFFFD\uFFFDb", "a", "x")`, Translate("a\uFFFD\uFFFDb", "a", "x"), "x\uFFFD\uFFFDb"},
		{`Delete("a\uFFFDb", "a")`, Delete("a\uFFFDb", "a"), "\uFFFDb"},
	}

	for _, c := range cases {
		if c.got != c.want {
			t.Fatalf("%s = %q, want %q", c.name, c.got, c.want)
		}
	}
}

// Translate: a translated rune whose value is 0 (NUL) is silently ignored,
// because 0 is used as the "no mapping" marker of the quick dictionary.
//
// Regression test for #66.
func TestIssueTranslateToNulIsIgnored(t *testing.T) {
	cases := []struct {
		name string
		got  string
		want string
	}{
		{`Translate("hello", "h", "\x00")`, Translate("hello", "h", "\x00"), "\x00ello"},
		{`Translate("hello", "l", "\x00")`, Translate("hello", "l", "\x00"), "he\x00\x00o"},
		{`Translate("hello", "l", "\x00x")`, Translate("hello", "l", "\x00x"), "he\x00\x00o"},
	}

	for _, c := range cases {
		if c.got != c.want {
			t.Fatalf("%s = %q, want %q", c.name, c.got, c.want)
		}
	}
}

// Successor: a carry rune which crossed a non-alphanumeric rune is not allowed
// to continue over a rune of a different kind (letter vs. digit), which is what
// Ruby's String#succ does. xstrings keeps carrying it to the left instead.
//
// Regression test for #67.
func TestIssueSuccessorCarryAcrossRuneKind(t *testing.T) {
	runTestCases(t, Successor, _M{
		"1*z":  "1*aa",
		"8a_9": "8a_10",
		"1 Z":  "1 AA",
		"0.Z":  "0.AA",
		"z*9":  "z*10",
		"9-Z":  "9-AA",
		"99 z": "99 aa",
		"09 z": "09 aa",
		"1*z9": "1*aa0",
		"Z.9 ": "Z.10 ",
		"9.z9": "9.aa0",
		"a1-Z": "a1-AA",
		"80.z": "80.aa",
		"z0*z": "z0*aa",
		"-1_z": "-1_aa",
		"0*-Z": "0*-AA",
	})
}
