package extension

import (
	
	
	

	
	gast 
	
	
	
	
	
	
)

var footnoteListKey = parser.NewContextKey()
var footnoteLinkListKey = parser.NewContextKey()

type footnoteBlockParser struct {
}

var defaultFootnoteBlockParser = &footnoteBlockParser{}

// NewFootnoteBlockParser returns a new parser.BlockParser that can parse
// footnotes of the Markdown(PHP Markdown Extra) text.
func () parser.BlockParser {
	return defaultFootnoteBlockParser
}

func ( *footnoteBlockParser) () []byte {
	return []byte{'['}
}

func ( *footnoteBlockParser) ( gast.Node,  text.Reader,  parser.Context) (gast.Node, parser.State) {
	,  := .PeekLine()
	 := .BlockOffset()
	if  < 0 || [] != '[' {
		return nil, parser.NoChildren
	}
	++
	if  > len()-1 || [] != '^' {
		return nil, parser.NoChildren
	}
	 :=  + 1
	var  int
	 := util.FindClosure([+1:], '[', ']', false, false) //nolint:staticcheck
	 =  + 1 + 
	 :=  + 1
	if  > -1 {
		if  >= len() || [] != ':' {
			return nil, parser.NoChildren
		}
	} else {
		return nil, parser.NoChildren
	}
	 := .Padding
	 := .Value(text.NewSegment(.Start+-, .Start+-))
	if util.IsBlank() {
		return nil, parser.NoChildren
	}
	 := ast.NewFootnote()

	 =  + 1 - 
	if  >= len() {
		.Advance()
		return , parser.NoChildren
	}
	.AdvanceAndSetPadding(, )
	return , parser.HasChildren
}

func ( *footnoteBlockParser) ( gast.Node,  text.Reader,  parser.Context) parser.State {
	,  := .PeekLine()
	if util.IsBlank() {
		return parser.Continue | parser.HasChildren
	}
	,  := util.IndentPosition(, .LineOffset(), 4)
	if  < 0 {
		return parser.Close
	}
	.AdvanceAndSetPadding(, )
	return parser.Continue | parser.HasChildren
}

func ( *footnoteBlockParser) ( gast.Node,  text.Reader,  parser.Context) {
	var  *ast.FootnoteList
	if  := .Get(footnoteListKey);  != nil {
		 = .(*ast.FootnoteList)
	} else {
		 = ast.NewFootnoteList()
		.Set(footnoteListKey, )
		.Parent().InsertBefore(.Parent(), , )
	}
	.Parent().RemoveChild(.Parent(), )
	.AppendChild(, )
}

func ( *footnoteBlockParser) () bool {
	return true
}

func ( *footnoteBlockParser) () bool {
	return false
}

type footnoteParser struct {
}

var defaultFootnoteParser = &footnoteParser{}

// NewFootnoteParser returns a new parser.InlineParser that can parse
// footnote links of the Markdown(PHP Markdown Extra) text.
func () parser.InlineParser {
	return defaultFootnoteParser
}

func ( *footnoteParser) () []byte {
	// footnote syntax probably conflict with the image syntax.
	// So we need trigger this parser with '!'.
	return []byte{'!', '['}
}

func ( *footnoteParser) ( gast.Node,  text.Reader,  parser.Context) gast.Node {
	,  := .PeekLine()
	 := 1
	if len() > 0 && [0] == '!' {
		++
	}
	if  >= len() || [] != '^' {
		return nil
	}
	++
	if  >= len() {
		return nil
	}
	 := 
	 := util.FindClosure([:], '[', ']', false, false) //nolint:staticcheck
	if  < 0 {
		return nil
	}
	 :=  + 
	 := .Value(text.NewSegment(.Start+, .Start+))
	.Advance( + 1)

	var  *ast.FootnoteList
	if  := .Get(footnoteListKey);  != nil {
		 = .(*ast.FootnoteList)
	}
	if  == nil {
		return nil
	}
	 := 0
	for  := .FirstChild();  != nil;  = .NextSibling() {
		 := .(*ast.Footnote)
		if bytes.Equal(.Ref, ) {
			if .Index < 0 {
				.Count++
				.Index = .Count
			}
			 = .Index
			break
		}
	}
	if  == 0 {
		return nil
	}

	 := ast.NewFootnoteLink()
	var  []*ast.FootnoteLink
	if  := .Get(footnoteLinkListKey);  != nil {
		 = .([]*ast.FootnoteLink)
	} else {
		 = []*ast.FootnoteLink{}
		.Set(footnoteLinkListKey, )
	}
	.Set(footnoteLinkListKey, append(, ))
	if [0] == '!' {
		.AppendChild(, gast.NewTextSegment(text.NewSegment(.Start, .Start+1)))
	}

	return 
}

type footnoteASTTransformer struct {
}

var defaultFootnoteASTTransformer = &footnoteASTTransformer{}

// NewFootnoteASTTransformer returns a new parser.ASTTransformer that
// insert a footnote list to the last of the document.
func () parser.ASTTransformer {
	return defaultFootnoteASTTransformer
}

func ( *footnoteASTTransformer) ( *gast.Document,  text.Reader,  parser.Context) {
	var  *ast.FootnoteList
	var  []*ast.FootnoteLink
	if  := .Get(footnoteListKey);  != nil {
		 = .(*ast.FootnoteList)
	}
	if  := .Get(footnoteLinkListKey);  != nil {
		 = .([]*ast.FootnoteLink)
	}

	.Set(footnoteListKey, nil)
	.Set(footnoteLinkListKey, nil)

	if  == nil {
		return
	}

	 := map[int]int{}
	if  != nil {
		for ,  := range  {
			if .Index >= 0 {
				[.Index]++
			}
		}
		 := map[int]int{}
		for ,  := range  {
			.RefCount = [.Index]
			if ,  := [.Index]; ! {
				[.Index] = 0
			}
			.RefIndex = [.Index]
			[.Index]++
		}
	}
	for  := .FirstChild();  != nil; {
		var  gast.Node = 
		 := .NextSibling()
		if  := .LastChild();  != nil && gast.IsParagraph() {
			 = 
		}
		 := .(*ast.Footnote)
		 := .Index
		if  < 0 {
			.RemoveChild(, )
		} else {
			 := []
			 := ast.NewFootnoteBacklink()
			.RefCount = 
			.RefIndex = 0
			.AppendChild(, )
			if  > 1 {
				for  := 1;  < ; ++ {
					 := ast.NewFootnoteBacklink()
					.RefCount = 
					.RefIndex = 
					.AppendChild(, )
				}
			}
		}
		 = 
	}
	.SortChildren(func(,  gast.Node) int {
		if .(*ast.Footnote).Index < .(*ast.Footnote).Index {
			return -1
		}
		return 1
	})
	if .Count <= 0 {
		.Parent().RemoveChild(.Parent(), )
		return
	}

	.AppendChild(, )
}

// FootnoteConfig holds configuration values for the footnote extension.
//
// Link* and Backlink* configurations have some variables:
// Occurrences of “^^” in the string will be replaced by the
// corresponding footnote number in the HTML output.
// Occurrences of “%%” will be replaced by a number for the
// reference (footnotes can have multiple references).
type FootnoteConfig struct {
	html.Config

	// IDPrefix is a prefix for the id attributes generated by footnotes.
	IDPrefix []byte

	// IDPrefix is a function that determines the id attribute for given Node.
	IDPrefixFunction func(gast.Node) []byte

	// LinkTitle is an optional title attribute for footnote links.
	LinkTitle []byte

	// BacklinkTitle is an optional title attribute for footnote backlinks.
	BacklinkTitle []byte

	// LinkClass is a class for footnote links.
	LinkClass []byte

	// BacklinkClass is a class for footnote backlinks.
	BacklinkClass []byte

	// BacklinkHTML is an HTML content for footnote backlinks.
	BacklinkHTML []byte
}

// FootnoteOption interface is a functional option interface for the extension.
type FootnoteOption interface {
	renderer.Option
	// SetFootnoteOption sets given option to the extension.
	SetFootnoteOption(*FootnoteConfig)
}

// NewFootnoteConfig returns a new Config with defaults.
func () FootnoteConfig {
	return FootnoteConfig{
		Config:        html.NewConfig(),
		LinkTitle:     []byte(""),
		BacklinkTitle: []byte(""),
		LinkClass:     []byte("footnote-ref"),
		BacklinkClass: []byte("footnote-backref"),
		BacklinkHTML:  []byte("&#x21a9;&#xfe0e;"),
	}
}

// SetOption implements renderer.SetOptioner.
func ( *FootnoteConfig) ( renderer.OptionName,  interface{}) {
	switch  {
	case optFootnoteIDPrefixFunction:
		.IDPrefixFunction = .(func(gast.Node) []byte)
	case optFootnoteIDPrefix:
		.IDPrefix = .([]byte)
	case optFootnoteLinkTitle:
		.LinkTitle = .([]byte)
	case optFootnoteBacklinkTitle:
		.BacklinkTitle = .([]byte)
	case optFootnoteLinkClass:
		.LinkClass = .([]byte)
	case optFootnoteBacklinkClass:
		.BacklinkClass = .([]byte)
	case optFootnoteBacklinkHTML:
		.BacklinkHTML = .([]byte)
	default:
		.Config.SetOption(, )
	}
}

type withFootnoteHTMLOptions struct {
	value []html.Option
}

func ( *withFootnoteHTMLOptions) ( *renderer.Config) {
	if .value != nil {
		for ,  := range .value {
			.(renderer.Option).SetConfig()
		}
	}
}

func ( *withFootnoteHTMLOptions) ( *FootnoteConfig) {
	if .value != nil {
		for ,  := range .value {
			.SetHTMLOption(&.Config)
		}
	}
}

// WithFootnoteHTMLOptions is functional option that wraps goldmark HTMLRenderer options.
func ( ...html.Option) FootnoteOption {
	return &withFootnoteHTMLOptions{}
}

const optFootnoteIDPrefix renderer.OptionName = "FootnoteIDPrefix"

type withFootnoteIDPrefix struct {
	value []byte
}

func ( *withFootnoteIDPrefix) ( *renderer.Config) {
	.Options[optFootnoteIDPrefix] = .value
}

func ( *withFootnoteIDPrefix) ( *FootnoteConfig) {
	.IDPrefix = .value
}

// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
func [ []byte | string]( ) FootnoteOption {
	return &withFootnoteIDPrefix{[]byte()}
}

const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"

type withFootnoteIDPrefixFunction struct {
	value func(gast.Node) []byte
}

func ( *withFootnoteIDPrefixFunction) ( *renderer.Config) {
	.Options[optFootnoteIDPrefixFunction] = .value
}

func ( *withFootnoteIDPrefixFunction) ( *FootnoteConfig) {
	.IDPrefixFunction = .value
}

// WithFootnoteIDPrefixFunction is a functional option that is a prefix for the id attributes generated by footnotes.
func ( func(gast.Node) []byte) FootnoteOption {
	return &withFootnoteIDPrefixFunction{}
}

const optFootnoteLinkTitle renderer.OptionName = "FootnoteLinkTitle"

type withFootnoteLinkTitle struct {
	value []byte
}

func ( *withFootnoteLinkTitle) ( *renderer.Config) {
	.Options[optFootnoteLinkTitle] = .value
}

func ( *withFootnoteLinkTitle) ( *FootnoteConfig) {
	.LinkTitle = .value
}

// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
func [ []byte | string]( ) FootnoteOption {
	return &withFootnoteLinkTitle{[]byte()}
}

const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"

type withFootnoteBacklinkTitle struct {
	value []byte
}

func ( *withFootnoteBacklinkTitle) ( *renderer.Config) {
	.Options[optFootnoteBacklinkTitle] = .value
}

func ( *withFootnoteBacklinkTitle) ( *FootnoteConfig) {
	.BacklinkTitle = .value
}

// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
func [ []byte | string]( ) FootnoteOption {
	return &withFootnoteBacklinkTitle{[]byte()}
}

const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"

type withFootnoteLinkClass struct {
	value []byte
}

func ( *withFootnoteLinkClass) ( *renderer.Config) {
	.Options[optFootnoteLinkClass] = .value
}

func ( *withFootnoteLinkClass) ( *FootnoteConfig) {
	.LinkClass = .value
}

// WithFootnoteLinkClass is a functional option that is a class for footnote links.
func [ []byte | string]( ) FootnoteOption {
	return &withFootnoteLinkClass{[]byte()}
}

const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"

type withFootnoteBacklinkClass struct {
	value []byte
}

func ( *withFootnoteBacklinkClass) ( *renderer.Config) {
	.Options[optFootnoteBacklinkClass] = .value
}

func ( *withFootnoteBacklinkClass) ( *FootnoteConfig) {
	.BacklinkClass = .value
}

// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
func [ []byte | string]( ) FootnoteOption {
	return &withFootnoteBacklinkClass{[]byte()}
}

const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"

type withFootnoteBacklinkHTML struct {
	value []byte
}

func ( *withFootnoteBacklinkHTML) ( *renderer.Config) {
	.Options[optFootnoteBacklinkHTML] = .value
}

func ( *withFootnoteBacklinkHTML) ( *FootnoteConfig) {
	.BacklinkHTML = .value
}

// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
func [ []byte | string]( ) FootnoteOption {
	return &withFootnoteBacklinkHTML{[]byte()}
}

// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
// renders FootnoteLink nodes.
type FootnoteHTMLRenderer struct {
	FootnoteConfig
}

// NewFootnoteHTMLRenderer returns a new FootnoteHTMLRenderer.
func ( ...FootnoteOption) renderer.NodeRenderer {
	 := &FootnoteHTMLRenderer{
		FootnoteConfig: NewFootnoteConfig(),
	}
	for ,  := range  {
		.SetFootnoteOption(&.FootnoteConfig)
	}
	return 
}

// RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
func ( *FootnoteHTMLRenderer) ( renderer.NodeRendererFuncRegisterer) {
	.Register(ast.KindFootnoteLink, .renderFootnoteLink)
	.Register(ast.KindFootnoteBacklink, .renderFootnoteBacklink)
	.Register(ast.KindFootnote, .renderFootnote)
	.Register(ast.KindFootnoteList, .renderFootnoteList)
}

func ( *FootnoteHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	if  {
		 := .(*ast.FootnoteLink)
		 := strconv.Itoa(.Index)
		_, _ = .WriteString(`<sup id="`)
		_, _ = .Write(.idPrefix())
		_, _ = .WriteString(`fnref`)
		if .RefIndex > 0 {
			_, _ = .WriteString(fmt.Sprintf("%v", .RefIndex))
		}
		_ = .WriteByte(':')
		_, _ = .WriteString()
		_, _ = .WriteString(`"><a href="#`)
		_, _ = .Write(.idPrefix())
		_, _ = .WriteString(`fn:`)
		_, _ = .WriteString()
		_, _ = .WriteString(`" class="`)
		_, _ = .Write(applyFootnoteTemplate(.FootnoteConfig.LinkClass,
			.Index, .RefCount))
		if len(.FootnoteConfig.LinkTitle) > 0 {
			_, _ = .WriteString(`" title="`)
			_, _ = .Write(util.EscapeHTML(applyFootnoteTemplate(.FootnoteConfig.LinkTitle, .Index, .RefCount)))
		}
		_, _ = .WriteString(`" role="doc-noteref">`)

		_, _ = .WriteString()
		_, _ = .WriteString(`</a></sup>`)
	}
	return gast.WalkContinue, nil
}

func ( *FootnoteHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	if  {
		 := .(*ast.FootnoteBacklink)
		 := strconv.Itoa(.Index)
		_, _ = .WriteString(`&#160;<a href="#`)
		_, _ = .Write(.idPrefix())
		_, _ = .WriteString(`fnref`)
		if .RefIndex > 0 {
			_, _ = .WriteString(fmt.Sprintf("%v", .RefIndex))
		}
		_ = .WriteByte(':')
		_, _ = .WriteString()
		_, _ = .WriteString(`" class="`)
		_, _ = .Write(applyFootnoteTemplate(.FootnoteConfig.BacklinkClass, .Index, .RefCount))
		if len(.FootnoteConfig.BacklinkTitle) > 0 {
			_, _ = .WriteString(`" title="`)
			_, _ = .Write(util.EscapeHTML(applyFootnoteTemplate(.FootnoteConfig.BacklinkTitle, .Index, .RefCount)))
		}
		_, _ = .WriteString(`" role="doc-backlink">`)
		_, _ = .Write(applyFootnoteTemplate(.FootnoteConfig.BacklinkHTML, .Index, .RefCount))
		_, _ = .WriteString(`</a>`)
	}
	return gast.WalkContinue, nil
}

func ( *FootnoteHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	 := .(*ast.Footnote)
	 := strconv.Itoa(.Index)
	if  {
		_, _ = .WriteString(`<li id="`)
		_, _ = .Write(.idPrefix())
		_, _ = .WriteString(`fn:`)
		_, _ = .WriteString()
		_, _ = .WriteString(`"`)
		if .Attributes() != nil {
			html.RenderAttributes(, , html.ListItemAttributeFilter)
		}
		_, _ = .WriteString(">\n")
	} else {
		_, _ = .WriteString("</li>\n")
	}
	return gast.WalkContinue, nil
}

func ( *FootnoteHTMLRenderer) (
	 util.BufWriter,  []byte,  gast.Node,  bool) (gast.WalkStatus, error) {
	if  {
		_, _ = .WriteString(`<div class="footnotes" role="doc-endnotes"`)
		if .Attributes() != nil {
			html.RenderAttributes(, , html.GlobalAttributeFilter)
		}
		_ = .WriteByte('>')
		if .Config.XHTML {
			_, _ = .WriteString("\n<hr />\n")
		} else {
			_, _ = .WriteString("\n<hr>\n")
		}
		_, _ = .WriteString("<ol>\n")
	} else {
		_, _ = .WriteString("</ol>\n")
		_, _ = .WriteString("</div>\n")
	}
	return gast.WalkContinue, nil
}

func ( *FootnoteHTMLRenderer) ( gast.Node) []byte {
	if .FootnoteConfig.IDPrefix != nil {
		return .FootnoteConfig.IDPrefix
	}
	if .FootnoteConfig.IDPrefixFunction != nil {
		return .FootnoteConfig.IDPrefixFunction()
	}
	return []byte("")
}

func applyFootnoteTemplate( []byte, ,  int) []byte {
	 := true
	for ,  := range  {
		if  != 0 {
			if [-1] == '^' &&  == '^' {
				 = false
				break
			}
			if [-1] == '%' &&  == '%' {
				 = false
				break
			}
		}
	}
	if  {
		return 
	}
	 := []byte(strconv.Itoa())
	 := []byte(strconv.Itoa())
	 := bytes.Replace(, []byte("^^"), , -1)
	return bytes.Replace(, []byte("%%"), , -1)
}

type footnote struct {
	options []FootnoteOption
}

// Footnote is an extension that allow you to use PHP Markdown Extra Footnotes.
var Footnote = &footnote{
	options: []FootnoteOption{},
}

// NewFootnote returns a new extension with given options.
func ( ...FootnoteOption) goldmark.Extender {
	return &footnote{
		options: ,
	}
}

func ( *footnote) ( goldmark.Markdown) {
	.Parser().AddOptions(
		parser.WithBlockParsers(
			util.Prioritized(NewFootnoteBlockParser(), 999),
		),
		parser.WithInlineParsers(
			util.Prioritized(NewFootnoteParser(), 101),
		),
		parser.WithASTTransformers(
			util.Prioritized(NewFootnoteASTTransformer(), 999),
		),
	)
	.Renderer().AddOptions(renderer.WithNodeRenderers(
		util.Prioritized(NewFootnoteHTMLRenderer(.options...), 500),
	))
}