package parser

import (
	"github.com/yuin/goldmark/ast"
	"github.com/yuin/goldmark/text"
	"github.com/yuin/goldmark/util"
)

var temporaryParagraphKey = NewContextKey()

type setextHeadingParser struct {
	HeadingConfig
}

func matchesSetextHeadingBar(line []byte) (byte, bool) {
	start := 0
	end := len(line)
	space := util.TrimLeftLength(line, []byte{' '})
	if space > 3 {
		return 0, false
	}
	start += space
	level1 := util.TrimLeftLength(line[start:end], []byte{'='})
	c := byte('=')
	var level2 int
	if level1 == 0 {
		level2 = util.TrimLeftLength(line[start:end], []byte{'-'})
		c = '-'
	}
	if util.IsSpace(line[end-1]) {
		end -= util.TrimRightSpaceLength(line[start:end])
	}
	if !((level1 > 0 && start+level1 == end) || (level2 > 0 && start+level2 == end)) {
		return 0, false
	}
	return c, true
}

// NewSetextHeadingParser return a new BlockParser that can parse Setext headings.
func NewSetextHeadingParser(opts ...HeadingOption) BlockParser {
	p := &setextHeadingParser{}
	for _, o := range opts {
		o.SetHeadingOption(&p.HeadingConfig)
	}
	return p
}

func (b *setextHeadingParser) Trigger() []byte {
	return []byte{'-', '='}
}

func (b *setextHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
	last := pc.LastOpenedBlock().Node
	if last == nil {
		return nil, NoChildren
	}
	paragraph, ok := last.(*ast.Paragraph)
	if !ok || paragraph.Parent() != parent {
		return nil, NoChildren
	}
	line, segment := reader.PeekLine()
	c, ok := matchesSetextHeadingBar(line)
	if !ok {
		return nil, NoChildren
	}
	level := 1
	if c == '-' {
		level = 2
	}
	node := ast.NewHeading(level)
	node.Lines().Append(segment)
	pc.Set(temporaryParagraphKey, last)
	return node, NoChildren | RequireParagraph
}

func (b *setextHeadingParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
	return Close
}

func (b *setextHeadingParser) Close(node ast.Node, reader text.Reader, pc Context) {
	heading := node.(*ast.Heading)
	segment := node.Lines().At(0)
	heading.Lines().Clear()
	tmp := pc.Get(temporaryParagraphKey).(*ast.Paragraph)
	pc.Set(temporaryParagraphKey, nil)
	if tmp.Lines().Len() == 0 {
		next := heading.NextSibling()
		segment = segment.TrimLeftSpace(reader.Source())
		if next == nil || !ast.IsParagraph(next) {
			para := ast.NewParagraph()
			para.Lines().Append(segment)
			heading.Parent().InsertAfter(heading.Parent(), heading, para)
		} else {
			next.Lines().Unshift(segment)
		}
		heading.Parent().RemoveChild(heading.Parent(), heading)
	} else {
		heading.SetLines(tmp.Lines())
		heading.SetBlankPreviousLines(tmp.HasBlankPreviousLines())
		tp := tmp.Parent()
		if tp != nil {
			tp.RemoveChild(tp, tmp)
		}
	}

	if b.Attribute {
		parseLastLineAttributes(node, reader, pc)
	}

	if b.AutoHeadingID {
		id, ok := node.AttributeString("id")
		if !ok {
			generateAutoHeadingID(heading, reader, pc)
		} else {
			pc.IDs().Put(id.([]byte))
		}
	}
}

func (b *setextHeadingParser) CanInterruptParagraph() bool {
	return true
}

func (b *setextHeadingParser) CanAcceptIndentedLine() bool {
	return false
}
