//go:build go1.23

package mapsloop

import (
	"iter"
	"maps"
)

var _ = maps.Clone[M] // force "maps" import so that each diagnostic doesn't add one

type M map[int]string

// -- src is map --

func useCopy(dst, src map[int]string) {
	// Replace loop by maps.Copy.
	for key, value := range src {
		// A
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
}

func useCopyRetrieveMap(x map[int]int) {
	getMap := func(int) map[int]int { return nil }
	for i, v := range x {
		// TODO(yuchen): don't assume that getMap returns the same map each time and has no effects.
		//
		// So, to avoid changing the cardinality of side effects,
		// the limit expression must not involve function calls (e.g. seq.Len()) or channel receives.
		getMap(0)[i] = v // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
}

func useCopyGeneric[K comparable, V any, M ~map[K]V](dst, src M) {
	// Replace loop by maps.Copy.
	for key, value := range src {
		// A
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
}

func useCopyNotClone(src map[int]string) {
	// Clone is tempting but wrong when src may be nil; see #71844.

	// Replace make(...) by maps.Copy.
	dst := make(map[int]string, len(src))
	// A
	for key, value := range src {
		// B
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
		// C
	}

	// A
	dst = map[int]string{}
	// B
	for key, value := range src {
		// C
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
	println(dst)
}

func useCopyParen(src map[int]string) {
	// Clone is tempting but wrong when src may be nil; see #71844.

	// Replace (make)(...) by maps.Clone.
	dst := (make)(map[int]string, len(src))
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}

	dst = (map[int]string{})
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
	println(dst)
}

func useCopy_typesDiffer(src M) {
	// Replace loop but not make(...) as maps.Copy(src) would return wrong type M.
	dst := make(map[int]string, len(src))
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
	println(dst)
}

func useCopy_typesDiffer2(src map[int]string) {
	// Replace loop but not make(...) as maps.Copy(src) would return wrong type map[int]string.
	dst := make(M, len(src))
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
	println(dst)
}

func useClone_typesDiffer3(src map[int]string) {
	// Clone is tempting but wrong when src may be nil; see #71844.

	// Replace loop and make(...) as maps.Clone(src) returns map[int]string
	// which is assignable to M.
	var dst M
	dst = make(M, len(src))
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
	println(dst)
}

func useClone_typesDiffer4(src map[int]string) {
	// Clone is tempting but wrong when src may be nil; see #71844.

	// Replace loop and make(...) as maps.Clone(src) returns map[int]string
	// which is assignable to M.
	var dst M
	dst = make(M, len(src))
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
	println(dst)
}

func useClone_generic[Map ~map[K]V, K comparable, V any](src Map) {
	// Clone is tempting but wrong when src may be nil; see #71844.

	// Replace loop and make(...) by maps.Clone
	dst := make(Map, len(src))
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Copy"
	}
	println(dst)
}

// -- src is iter.Seq2 --

func useInsert_assignableToSeq2(dst map[int]string, src func(yield func(int, string) bool)) {
	// Replace loop by maps.Insert because src is assignable to iter.Seq2.
	for k, v := range src {
		dst[k] = v // want "Replace m\\[k\\]=v loop with maps.Insert"
	}
}

func useCollect(src iter.Seq2[int, string]) {
	// Replace loop and make(...) by maps.Collect.
	var dst map[int]string
	dst = make(map[int]string) // A
	// B
	for key, value := range src {
		// C
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Collect"
	}
}

func useInsert_typesDifferAssign(src iter.Seq2[int, string]) {
	// Replace loop and make(...): maps.Collect returns an unnamed map type
	// that is assignable to M.
	var dst M
	dst = make(M)
	// A
	for key, value := range src {
		// B
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Collect"
	}
}

func useInsert_typesDifferDeclare(src iter.Seq2[int, string]) {
	// Replace loop but not make(...) as maps.Collect would return an
	// unnamed map type that would change the type of dst.
	dst := make(M)
	for key, value := range src {
		dst[key] = value // want "Replace m\\[k\\]=v loop with maps.Insert"
	}
}

// Regression test for go.dev/issues/78322
func useCollect_mapDefine(it iter.Seq2[string, int]) map[string]int {
	m := map[string]int{}
	for k, v := range it {
		m[k] = v // want "Replace m\\[k\\]=v loop with maps.Collect"
	}
	return m
}

// -- non-matches --

type isomerOfSeq2 func(yield func(int, string) bool)

func nopeInsertRequiresAssignableToSeq2(dst map[int]string, src isomerOfSeq2) {
	for k, v := range src { // nope: src is not assignable to maps.Insert's iter.Seq2 parameter
		dst[k] = v
	}
}

func nopeSingleVarRange(dst map[int]bool, src map[int]string) {
	for key := range src { // nope: must be "for k, v"
		dst[key] = true
	}
}

func nopeBodyNotASingleton(src map[int]string) {
	var dst map[int]string
	for key, value := range src {
		dst[key] = value
		println() // nope: other things in the loop body
	}
}

// Regression test for https://github.com/golang/go/issues/70815#issuecomment-2581999787.
func nopeAssignmentHasIncrementOperator(src map[int]int) {
	dst := make(map[int]int)
	for k, v := range src {
		dst[k] += v
	}
}

func nopeNotAMap(src map[int]string) {
	var dst []string
	for k, v := range src {
		dst[k] = v
	}
}

func nopeNotAMapGeneric[E any, M ~map[int]E, S ~[]E](src M) {
	var dst S
	for k, v := range src {
		dst[k] = v
	}
}

func nopeHasImplicitValueWidening(src map[string]int) {
	dst := make(map[string]any)
	for k, v := range src {
		dst[k] = v
	}
}

func nopeHasImplicitKeyWidening(src map[string]string) {
	dst := make(map[any]string)
	for k, v := range src {
		dst[k] = v
	}
}

// The expression for the map (y[v]) must not itself refer to loop variables.
// See https://go.dev/issue/77008.
func nopeMapExprUsesLoopVars(x map[int]int, y []map[int]int) {
	for i, v := range x {
		y[v][i] = v
	}
}

func nopeExtraKeyValueUsage(x map[int]int, y []map[int]int) {
	for i, v := range x {
		y[i][i] = v
	}

	getMap := func(int) map[int]int { return nil }
	for i, v := range x {
		getMap(i)[i] = v
	}

	for i, v := range x {
		getMap(v)[i] = v
	}
}

func nope2LhsAssigment(x map[int]int, y map[int]int) {
	for i, v := range x {
		y[i], _ = v, 0
	}
}
