package stringscut

import (
	"bytes"
	"strings"
)

func basic(s string) bool {
	s = "reassigned"
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	if i >= 0 {
		print(s[:i])
	}
	return i >= 0
}

func basic_contains(s string) bool {
	s = "reassigned"
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Contains"
	return i >= 0
}

func contains_variety(s, sub string) {
	i := strings.Index(s, sub) // want "strings.Index can be simplified using strings.Contains"
	if i >= 0 {
		print("found")
	}
	if i < 0 {
		print("not found")
	}
	if i <= -1 {
		print("not found")
	}
}

func basic_contains_bytes(s string) bool {
	i := strings.IndexByte(s, '=') // want "strings.IndexByte can be simplified using strings.Contains"
	return i < 0
}

func basic_contains_bytes_byte(s []byte) bool {
	i := bytes.IndexByte(s, 22) // want "bytes.IndexByte can be simplified using bytes.Contains"
	return i < 0
}

func skip_var_decl(s string) bool {
	var i int
	i = strings.Index(s, "=") // don't modernize - i might be reassigned
	print(s[:i])
	return i >= 0
}

func basic_substr_arg(s string, substr string) bool {
	i := strings.Index(s, substr) // want "strings.Index can be simplified using strings.Cut"
	if i >= 0 {
		print(s[i+len(substr):])
	}
	return i >= 0
}

func wrong_len_arg(s string, substr string) bool {
	i := strings.Index(s, substr) // don't modernize since i+len(s) is not valid
	print(s[i+len(s):])
	return i >= 0
}

func basic_strings_byte(s string) bool {
	i := strings.IndexByte(s, '+') // want "strings.IndexByte can be simplified using strings.Cut"
	if i >= 0 {
		print(s[:i])
	}
	return i >= 0
}

func basic_strings_byte_int(s string) bool {
	i := strings.IndexByte(s, 55) // want "strings.IndexByte can be simplified using strings.Cut"
	if i >= 0 {
		print(s[:i])
	}
	return i >= 0
}

func basic_strings_byte_var(s string) bool {
	b := byte('b')
	i := strings.IndexByte(s, b) // want "strings.IndexByte can be simplified using strings.Cut"
	if i >= 0 {
		print(s[:i])
	}
	return i >= 0
}

func basic_bytes(b []byte) []byte {
	i := bytes.Index(b, []byte("str")) // want "bytes.Index can be simplified using bytes.Cut"
	if i >= 0 {
		return b[:i]
	} else {
		return b[i+3:]
	}
}

func basic_index_bytes(b []byte) string {
	i := bytes.IndexByte(b, 's') // don't modernize: b[i+1:] in else is not guarded
	if i >= 0 {
		return string(b[:i])
	} else {
		return string(b[i+1:])
	}
}

func const_substr_len(s string) bool {
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	if i >= 0 {
		r := s[i+len("="):]
		return len(r) > 0
	}
	return false
}

func const_for_len(s string) bool {
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	if i >= 0 {
		r := s[i+1:]
		return len(r) > 0
	}
	return false
}

func index(s string) bool {
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	if i < 0 {
		return false
	}
	if i >= 0 {
		return true
	}
	print(s[:i])
	return true
}

func index_flipped(s string) bool {
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	if 0 > i {
		return false
	}
	if 0 <= i {
		return true
	}
	print(s[:i])
	return true
}

func invalid_index(s string) bool {
	i := strings.Index(s, "=") // don't modernize since i is used in an "invalid" binaryexpr
	if 0 > i {
		return false
	}
	if i < 10 {
		return true
	}
	return true
}

func invalid_slice(s string) string {
	i := strings.Index(s, "=") // don't modernize since i is used in an "invalid" slice index
	if i >= 0 {
		return s[i+4:]
	}
	return ""
}

func index_and_before_after(s string) string {
	substr := "="
	i := strings.Index(s, substr) // don't modernize: s[i+len(substr):] and s[len(substr)+i:] not nonneg-guarded
	if i == -1 {
		print("test")
	}
	if i < 0 {
		return ""
	} else {
		if i >= 0 {
			return s[:i]
		} else {
			return s[i+len(substr):]
		}
	}
	if -1 == i {
		return s[len(substr)+i:]
	}
	return "final"
}

func idx_var_init(s string) (string, string) {
	var idx = strings.Index(s, "=") // don't modernize: s[0:idx] is not guarded
	return s[0:idx], s
}

func idx_reassigned(s string) string {
	idx := strings.Index(s, "=") // don't modernize since idx gets reassigned
	idx = 10
	return s[:idx]
}

func idx_printed(s string) string {
	idx := strings.Index(s, "=") // don't modernize since idx is used
	print(idx)
	return s[:idx]
}

func idx_aliased(s string) string {
	idx := strings.Index(s, "=") // don't modernize since idx gets aliased
	i := idx
	return s[:i]
}

func idx_aliased_var(s string) string {
	idx := strings.Index(s, "=") // don't modernize since idx gets aliased
	var i = idx
	print(i)
	return s[:idx]
}

// Regression test for golang/go#78643:
// when Index is not the sole RHS in an assignment, rewriting to Cut
// produces invalid Go (`x, before, _, ok := 0, strings.Cut(...)`).
func multiAssign(s, sub string) int {
	ret, idx := 0, strings.Index(s, sub) // don't modernize: Index is not the sole RHS
	if idx >= 0 {
		prefix := s[:idx]
		_ = prefix
	}
	return ret
}

func s_modified(s string) string {
	idx := strings.Index(s, "=") // don't modernize since s gets modified
	s = "newstring"
	return s[:idx]
}

func s_modified_multi_assign(s string) string {
	idx := strings.Index(s, ",") // don't modernize since s gets modified in multi-assignment
	var str string
	str, s = "str", "modified" // modifying use of s
	_ = str
	if idx >= 0 {
		return s[:idx]
	}
	return s
}

func s_modified_no_params() string {
	s := "string"
	idx := strings.Index(s, "=") // don't modernize since s gets modified
	s = "newstring"
	return s[:idx]
}

func s_in_func_call() string {
	s := "string"
	substr := "substr"
	idx := strings.Index(s, substr) // don't modernize: s[:idx] is not guarded
	function(s)
	return s[:idx]
}

func s_pointer() string {
	s := "string"
	idx := strings.Index(s, "s")
	ptr := &s // don't modernize since s may get modified
	reference_str(ptr)
	return s[:idx]
}

func s_pointer_before_call() string {
	s := "string"
	ptr := &s // don't modernize since s may get modified
	reference_str(ptr)
	idx := strings.Index(s, "s")
	return s[:idx]
}

func idx_used_before(s string, sub string) string {
	var index int
	reference_int(&index)
	index = strings.Index(s, sub) // don't modernize since index may get modified
	blank()
	if index >= 0 {
		return s[:index]
	}
	return ""
}

func idx_used_other_substr(s string, sub string) string {
	otherstr := "other"
	i := strings.Index(s, sub)
	print(otherstr[:i]) // don't modernize since i used in another slice expr
	if i >= 0 {
		return s[:i]
	} else {
		return ""
	}
}

func idx_gtr_zero_invalid(s string, sub string) string {
	i := strings.Index(s, sub)
	if i > 0 { // don't modernize since this is a stronger claim than i >= 0
		return s[:i]
	}
	return ""
}

func idx_gtreq_one_invalid(s string, sub string) string {
	i := strings.Index(s, sub)
	if i >= 1 { // don't modernize since this is a stronger claim than i >= 0
		return s[:i]
	}
	return ""
}

func idx_gtr_negone(s string, sub string) string {
	i := strings.Index(s, sub) // want "strings.Index can be simplified using strings.Cut"
	if i > -1 {
		return s[:i]
	}
	if i != -1 {
		return s
	}
	return ""
}

// Regression test for a crash (https://go.dev/issue/77208)
func idx_call() {
	i := bytes.Index(b(), []byte(""))
	_ = i
}

// Fix for golang/go#77566
func multipleCallsSameScope(s string) (bool, bool) {
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	if i != -1 {
		print(s[:i])
	}
	j := strings.Index(s, "-") // want "strings.Index can be simplified using strings.Cut"
	if j != -1 {
		print(s[:j])
	}
	return i >= 0, j >= 0
}

func shadowing(s string) string {
	ok := "true"
	before := "before"
	print(before)              // declared within scope but not used after the index call, so no fresh name
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	print(ok)
	if i >= 0 {
		print(s[:i])
		print(i >= 0)
		return s[i+1:]
	}
	return ""
}

func foreshadowing(s string) string {
	i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
	if i != -1 {
		return s[i+1:]
	}
	// Generate a fresh name for ok because it is used within the scope after the Index call.
	ok, m := true, "m"
	if ok {
		return m
	}
	if i != -1 {
		return s
	}
	return ""
}

func b() []byte {
	return nil
}

func function(s string) {}

func reference_str(s *string) {}

func reference_int(i *int) {}

func blank() {}

// Regression test for unguarded slice uses (https://go.dev/issue/77737).
// The s[colon+1:] usage outside the "if" relies on -1+1=0 to return the full
// string when the separator is absent. Rewriting to "after" would return "".
func unguarded_after_slice(s string) (int, string) {
	colon := strings.Index(s, ":")
	if colon != -1 {
		print(s[:colon])
	}
	return colon, s[colon+1:] // don't modernize: s[colon+1:] not guarded
}

// Same as above but with the guard using i < 0.
func unguarded_after_slice_negcheck(s string) string {
	i := strings.Index(s, ":")
	if i < 0 {
		print("not found")
	}
	return s[i+1:] // don't modernize: s[i+1:] not guarded
}

// Safe: both slice uses are inside the nonneg guard.
func guarded_both_slices(s string) (string, string) {
	i := strings.Index(s, ":") // want "strings.Index can be simplified using strings.Cut"
	if i >= 0 {
		return s[:i], s[i+1:]
	}
	return "", s
}

// Safe: slice uses after early-return negative check.
func guarded_early_return(s string) (string, string) {
	i := strings.Index(s, ":") // want "strings.Index can be simplified using strings.Cut"
	if i < 0 {
		return "", s
	}
	return s[:i], s[i+1:]
}

// Safe: slice uses in else of negative check.
func guarded_neg_else(s string) (string, string) {
	i := strings.Index(s, ":") // want "strings.Index can be simplified using strings.Cut"
	if i == -1 {
		return "", s
	} else {
		return s[:i], s[i+1:]
	}
}

// -- strings.Split/SplitN [0] → strings.Cut --

// Should fire: SplitN(s, ",", 2)[0] in short var decl.
func splitn_zero_define(s string) string {
	x := strings.SplitN(s, ",", 2)[0] // want `strings.SplitN call can be simplified using strings.Cut`
	return x
}

// Should fire: Split(s, ",")[0] in short var decl.
func split_zero_define(s string) string {
	x := strings.Split(s, ",")[0] // want `strings.Split call can be simplified using strings.Cut`
	return x
}

// Should NOT fire: regular assignment (not :=).
func split_zero_assign(s string) string {
	var x string
	x = strings.Split(s, ",")[0]
	return x
}

// Should NOT fire: sep is a variable (value unknown at analysis time).
func split_variable_sep(s, sep string) string {
	x := strings.Split(s, sep)[0]
	return x
}

// Should NOT fire: sep is the empty string (different semantics from strings.Cut).
func split_empty_sep(s string) string {
	x := strings.Split(s, "")[0]
	return x
}

// Should NOT fire: index is 1, not 0.
func split_index_one(s, sep string) string {
	return strings.Split(s, sep)[1]
}

// Should NOT fire: SplitN with n != 2.
func splitn_n3(s, sep string) string {
	return strings.SplitN(s, sep, 3)[0]
}

// Should NOT fire: result stored in variable first (not inline).
func split_variable_first(s, sep string) string {
	parts := strings.Split(s, sep)
	return parts[0]
}

// Should NOT fire: LHS is blank identifier.
func split_blank_lhs(s, sep string) {
	_ = strings.Split(s, sep)[0]
}

// Should NOT fire: multiple values on LHS.
func split_multi_lhs(s, sep string) (string, string) {
	x, y := strings.Split(s, sep)[0], strings.Split(s, sep)[0]
	return x, y
}
