package web

import (
	"bytes"
	"html/template"
	"net/url"
	"strings"

	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/extension"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/text"
	"github.com/yuin/goldmark/util"
)

type markdownLinkTransformer struct {
	linkBaseURL  *url.URL
	imageBaseURL *url.URL
}

func renderMarkdown(source, linkBaseURL, imageBaseURL string) (template.HTML, error) {
	linkBase, err := url.Parse(linkBaseURL)
	if err != nil {
		return "", err
	}
	imageBase, err := url.Parse(imageBaseURL)
	if err != nil {
		return "", err
	}
	renderer := goldmark.New(
		goldmark.WithExtensions(extension.GFM, &markdownMathExtension{}),
		goldmark.WithParserOptions(
			parser.WithAutoHeadingID(),
			parser.WithASTTransformers(
				util.Prioritized(&markdownLinkTransformer{
					linkBaseURL:  linkBase,
					imageBaseURL: imageBase,
				}, 100),
			),
		),
	)

	var rendered bytes.Buffer
	if err := renderer.Convert([]byte(source), &rendered); err != nil {
		return "", err
	}
	return template.HTML(rendered.String()), nil // #nosec G203 -- Goldmark's safe renderer produced this HTML.
}

func (t *markdownLinkTransformer) Transform(document *ast.Document, _ text.Reader, _ parser.Context) {
	_ = ast.Walk(document, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
		if !entering {
			return ast.WalkContinue, nil
		}
		switch node := node.(type) {
		case *ast.Link:
			node.Destination = resolveMarkdownDestination(t.linkBaseURL, node.Destination)
		case *ast.Image:
			node.Destination = resolveMarkdownDestination(t.imageBaseURL, node.Destination)
		}
		return ast.WalkContinue, nil
	})
}

func resolveMarkdownDestination(baseURL *url.URL, destination []byte) []byte {
	raw := string(destination)
	if raw == "" || strings.HasPrefix(raw, "/") || strings.HasPrefix(raw, "#") || strings.HasPrefix(raw, "//") {
		return destination
	}
	reference, err := url.Parse(raw)
	if err != nil || reference.IsAbs() {
		return destination
	}
	return []byte(baseURL.ResolveReference(reference).String())
}