package markdown

import (
	"bytes"
	"strings"

	markdown "github.com/teekennedy/goldmark-markdown"
	"github.com/yuin/goldmark"
	gast "github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/text"
	"github.com/yuin/goldmark/util"

	"github.com/apricote/releaser-pleaser/internal/markdown/extensions"
	"github.com/apricote/releaser-pleaser/internal/markdown/extensions/ast"
)

func New() goldmark.Markdown {
	return goldmark.New(
		goldmark.WithExtensions(extensions.Section),
		goldmark.WithParserOptions(parser.WithASTTransformers(
			util.Prioritized(&newLineTransformer{}, 1),
		)),
		goldmark.WithRenderer(markdown.NewRenderer()),
	)
}

// Format the Markdown document in a style mimicking Prettier. This is done for compatibility with other tools
// users might have installed in their IDE. This does not guarantee that the output matches Prettier exactly.
func Format(input string) (string, error) {
	var buf bytes.Buffer
	buf.Grow(len(input))

	err := New().Convert([]byte(input), &buf)
	if err != nil {
		return "", err
	}

	return buf.String(), nil
}

func GetCodeBlockText(source []byte, language string, output *string, found *bool) gast.Walker {
	return func(n gast.Node, entering bool) (gast.WalkStatus, error) {
		if !entering {
			return gast.WalkContinue, nil
		}

		if n.Kind() != gast.KindFencedCodeBlock {
			return gast.WalkContinue, nil
		}

		codeBlock := n.(*gast.FencedCodeBlock)

		if string(codeBlock.Language(source)) != language {
			return gast.WalkContinue, nil
		}

		*output = textFromLines(source, codeBlock)
		if found != nil {
			*found = true
		}
		// Stop looking after we find the first result
		return gast.WalkStop, nil
	}
}

func GetSectionText(source []byte, name string, output *string) gast.Walker {
	return func(n gast.Node, entering bool) (gast.WalkStatus, error) {
		if !entering {
			return gast.WalkContinue, nil
		}

		if n.Kind() != ast.KindSection {
			return gast.WalkContinue, nil
		}

		section := n.(*ast.Section)

		if section.Name != name {
			return gast.WalkContinue, nil
		}

		// Do not show section markings in output, we only care about the content
		section.HideInOutput()

		// Found the right section
		outputBuffer := new(bytes.Buffer)
		err := New().Renderer().Render(outputBuffer, source, section)
		if err != nil {
			return gast.WalkStop, err
		}

		*output = outputBuffer.String()
		// Stop looking after we find the first result
		return gast.WalkStop, nil
	}
}

func textFromLines(source []byte, n gast.Node) string {
	content := make([]byte, 0)

	l := n.Lines().Len()
	for i := 0; i < l; i++ {
		line := n.Lines().At(i)
		content = append(content, line.Value(source)...)
	}

	return strings.TrimSpace(string(content))
}

func WalkAST(source []byte, walkers ...gast.Walker) (err error) {
	doc := New().Parser().Parse(text.NewReader(source))

	for _, walker := range walkers {
		err = gast.Walk(doc, walker)
		if err != nil {
			return err
		}
	}

	return nil
}
