//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 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"
	}
}

// -- 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
	}
}
