package extensions

import (
	"strings"

	"github.com/yuin/goldmark"
	"github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/parser"
	"github.com/yuin/goldmark/text"
	"github.com/yuin/goldmark/util"
	"github.com/zk-org/zk/internal/core"
)

// WikiLinkExt is an extension parsing wiki links and Neuron's Folgezettel.
//
// For example, [[wiki link]], [[[legacy downlink]]], #[[uplink]], [[downlink]]#.
var WikiLinkExt = &wikiLink{}

type wikiLink struct{}

// WikiLink represents a wiki link found in a Markdown document.
type WikiLink struct {
	ast.Link
	// StartOffset is the byte offset of the start of the link.
	StartOffset int
	// EndOffset is the byte offset of the end of the link.
	EndOffset int
}

func (w *wikiLink) Extend(m goldmark.Markdown) {
	m.Parser().AddOptions(
		parser.WithInlineParsers(
			util.Prioritized(&wlParser{}, 199),
		),
	)
}

type wlParser struct{}

func (p *wlParser) Trigger() []byte {
	return []byte{'[', '#'}
}

func (p *wlParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
	line, segment := block.PeekLine()
	startOffset := segment.Start

	var (
		href  string
		label string
		rel   core.LinkRelation
	)

	var (
		opened          = false // Found at least [[
		closed          = false // Found at least ]]
		escaping        = false // Found a backslash, next character will be literal
		parsingLabel    = false // Found a | in a Wikilink, now we parse the link's label
		openerCharCount = 0     // Number of [ encountered
		closerCharCount = 0     // Number of ] encountered
		endPos          = 0     // Last position of the link in the line
	)

	appendRune := func(c rune) {
		if parsingLabel {
			label += string(c)
		} else {
			href += string(c)
		}
	}

	for i, char := range string(line) {
		endPos = i

		if closed {
			// Supports trailing hash syntax for Neuron's Folgezettel, e.g. [[id]]#
			if char == '#' {
				rel = core.LinkRelationDown
			}
			break
		}

		if !opened {
			switch char {
			// Supports leading hash syntax for Neuron's Folgezettel, e.g. #[[id]]
			case '#':
				rel = core.LinkRelationUp
				continue
			case '[':
				openerCharCount += 1
				continue
			}

			if openerCharCount < 2 || openerCharCount > 3 {
				return nil
			}
		}
		opened = true

		if !escaping {
			switch char {

			case '|': // [[href | label]]
				parsingLabel = true
				continue

			case '\\':
				escaping = true
				continue

			case ']':
				closerCharCount += 1
				if closerCharCount == openerCharCount {
					closed = true
					// Neuron's legacy [[[Folgezettel]]].
					if closerCharCount == 3 {
						rel = core.LinkRelationDown
					}
				}
				continue
			}
		}
		escaping = false

		// Found incomplete number of closing brackets to close the link.
		// We add them to the HREF and reset the count.
		if closerCharCount > 0 {
			for i := 0; i < closerCharCount; i++ {
				appendRune(']')
			}
			closerCharCount = 0
		}
		appendRune(char)
	}

	if !closed || len(href) == 0 {
		return nil
	}

	block.Advance(endPos)

	href = strings.TrimSpace(href)
	label = strings.TrimSpace(label)
	if len(label) == 0 {
		label = href
	}

	link := &WikiLink{Link: *ast.NewLink()}
	link.Destination = []byte(href)
	// Title will be parsed as the link's rel by the Markdown parser.
	link.Title = []byte(rel)
	link.AppendChild(link, ast.NewString([]byte(label)))
	link.StartOffset = startOffset
	link.EndOffset = startOffset + endPos

	return link
}
