package definitions

import (
	"fmt"

	"github.com/Xuanwo/gg"
	log "github.com/sirupsen/logrus"
)

func GenerateIterator(path string) {
	data := []string{
		"Block",
		"Object",
		"Part",
		"Storager",
	}

	g := gg.New()
	f := g.NewGroup()
	f.AddLineComment("Code generated by go generate cmd/definitions; DO NOT EDIT.")
	f.AddPackage("types")
	f.NewImport().
		AddPath("context").
		AddPath("errors").
		AddPath("fmt")

	f.NewInterface("Continuable").
		NewFunction("ContinuationToken").
		AddResult("", "string")

	for _, name := range data {
		typ := name
		// Add a `*` if the type is not an interface.
		if name != "Storager" {
			typ = "*" + typ
		}

		nextFuncName := fmt.Sprintf("Next%sFunc", name)
		pageStructName := fmt.Sprintf("%sPage", name)
		iteratorStructName := fmt.Sprintf("%sIterator", name)

		f.AddLineComment(`%s is the func used in iterator.

Notes
- ErrDone should be return while there are no items any more.
- Input %s slice should be set every time.
`, nextFuncName, name)
		f.AddType(
			nextFuncName,
			gg.Function("").
				AddParameter("ctx", "context.Context").
				AddParameter("page", "*"+pageStructName).
				AddResult("", "error"))

		f.NewStruct(pageStructName).
			AddField("Status", "Continuable").
			AddField("Data", "[]"+typ)

		f.NewStruct(iteratorStructName).
			AddField("ctx", "context.Context").
			AddField("next", nextFuncName).
			AddLine().
			AddField("index", "int").
			AddField("done", "bool").
			AddLine().
			AddField("o", pageStructName)

		f.NewFunction("New"+iteratorStructName).
			AddParameter("ctx", "context.Context").
			AddParameter("next", nextFuncName).
			AddParameter("status", "Continuable").
			AddResult("", "*"+iteratorStructName).
			AddBody(gg.Return(
				gg.Value("&"+iteratorStructName).
					AddField("ctx", "ctx").
					AddField("next", "next").
					AddField("o", gg.Value(pageStructName).
						AddField("Status", "status"))))

		f.NewFunction("ContinuationToken").
			WithReceiver("it", "*"+iteratorStructName).
			AddResult("", "string").
			AddBody(gg.Return(
				gg.Call("ContinuationToken").WithOwner("it.o.Status")))

		f.NewFunction("Next").
			WithReceiver("it", "*"+iteratorStructName).
			AddResult("object", typ).
			AddResult("err", "error").
			AddBody(
				gg.LineComment("Consume Data via index."),
				gg.S(`// Consume Data via index.
if it.index < len(it.o.Data) {
	it.index++
	return it.o.Data[it.index-1], nil
}
// Return IterateDone if iterator is already done.
if it.done {
	return nil, IterateDone
}

// Reset buf before call next.
it.o.Data = it.o.Data[:0]

err = it.next(it.ctx ,&it.o)
if err != nil && !errors.Is(err, IterateDone) {
	return nil, fmt.Errorf("iterator next failed: %w", err)
}
// Make iterator to done so that we will not fetch from upstream anymore.
if err != nil {
	it.done = true
}
// Return IterateDone directly if we don't have any data.
if len(it.o.Data) == 0 {
	return nil, IterateDone
}
// Return the first object.
it.index = 1
return it.o.Data[0], nil`))
	}

	err := g.WriteFile(path)
	if err != nil {
		log.Fatalf("generate to %s: %v", path, err)
	}
}
