// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package mdstripper

import (
	"bytes"

	"github.com/russross/blackfriday"
)

// MarkdownStripper extends blackfriday.Renderer
type MarkdownStripper struct {
	blackfriday.Renderer
	links     []string
	coallesce bool
}

const (
	blackfridayExtensions = 0 |
		blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
		blackfriday.EXTENSION_TABLES |
		blackfriday.EXTENSION_FENCED_CODE |
		blackfriday.EXTENSION_STRIKETHROUGH |
		blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK |
		blackfriday.EXTENSION_DEFINITION_LISTS |
		blackfriday.EXTENSION_FOOTNOTES |
		blackfriday.EXTENSION_HEADER_IDS |
		blackfriday.EXTENSION_AUTO_HEADER_IDS |
		// Not included in modules/markup/markdown/markdown.go;
		// required here to process inline links
		blackfriday.EXTENSION_AUTOLINK
)

//revive:disable:var-naming Implementing the Rendering interface requires breaking some linting rules

// StripMarkdown parses markdown content by removing all markup and code blocks
//	in order to extract links and other references
func StripMarkdown(rawBytes []byte) (string, []string) {
	stripper := &MarkdownStripper{
		links: make([]string, 0, 10),
	}
	body := blackfriday.Markdown(rawBytes, stripper, blackfridayExtensions)
	return string(body), stripper.GetLinks()
}

// StripMarkdownBytes parses markdown content by removing all markup and code blocks
//	in order to extract links and other references
func StripMarkdownBytes(rawBytes []byte) ([]byte, []string) {
	stripper := &MarkdownStripper{
		links: make([]string, 0, 10),
	}
	body := blackfriday.Markdown(rawBytes, stripper, blackfridayExtensions)
	return body, stripper.GetLinks()
}

// block-level callbacks

// BlockCode dummy function to proceed with rendering
func (r *MarkdownStripper) BlockCode(out *bytes.Buffer, text []byte, infoString string) {
	// Not rendered
	r.coallesce = false
}

// BlockQuote dummy function to proceed with rendering
func (r *MarkdownStripper) BlockQuote(out *bytes.Buffer, text []byte) {
	// FIXME: perhaps it's better to leave out block quote for this?
	r.processString(out, text, false)
}

// BlockHtml dummy function to proceed with rendering
func (r *MarkdownStripper) BlockHtml(out *bytes.Buffer, text []byte) { //nolint
	// Not rendered
	r.coallesce = false
}

// Header dummy function to proceed with rendering
func (r *MarkdownStripper) Header(out *bytes.Buffer, text func() bool, level int, id string) {
	text()
	r.coallesce = false
}

// HRule dummy function to proceed with rendering
func (r *MarkdownStripper) HRule(out *bytes.Buffer) {
	// Not rendered
	r.coallesce = false
}

// List dummy function to proceed with rendering
func (r *MarkdownStripper) List(out *bytes.Buffer, text func() bool, flags int) {
	text()
	r.coallesce = false
}

// ListItem dummy function to proceed with rendering
func (r *MarkdownStripper) ListItem(out *bytes.Buffer, text []byte, flags int) {
	r.processString(out, text, false)
}

// Paragraph dummy function to proceed with rendering
func (r *MarkdownStripper) Paragraph(out *bytes.Buffer, text func() bool) {
	text()
	r.coallesce = false
}

// Table dummy function to proceed with rendering
func (r *MarkdownStripper) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
	r.processString(out, header, false)
	r.processString(out, body, false)
}

// TableRow dummy function to proceed with rendering
func (r *MarkdownStripper) TableRow(out *bytes.Buffer, text []byte) {
	r.processString(out, text, false)
}

// TableHeaderCell dummy function to proceed with rendering
func (r *MarkdownStripper) TableHeaderCell(out *bytes.Buffer, text []byte, flags int) {
	r.processString(out, text, false)
}

// TableCell dummy function to proceed with rendering
func (r *MarkdownStripper) TableCell(out *bytes.Buffer, text []byte, flags int) {
	r.processString(out, text, false)
}

// Footnotes dummy function to proceed with rendering
func (r *MarkdownStripper) Footnotes(out *bytes.Buffer, text func() bool) {
	text()
}

// FootnoteItem dummy function to proceed with rendering
func (r *MarkdownStripper) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
	r.processString(out, text, false)
}

// TitleBlock dummy function to proceed with rendering
func (r *MarkdownStripper) TitleBlock(out *bytes.Buffer, text []byte) {
	r.processString(out, text, false)
}

// Span-level callbacks

// AutoLink dummy function to proceed with rendering
func (r *MarkdownStripper) AutoLink(out *bytes.Buffer, link []byte, kind int) {
	r.processLink(out, link, []byte{})
}

// CodeSpan dummy function to proceed with rendering
func (r *MarkdownStripper) CodeSpan(out *bytes.Buffer, text []byte) {
	// Not rendered
	r.coallesce = false
}

// DoubleEmphasis dummy function to proceed with rendering
func (r *MarkdownStripper) DoubleEmphasis(out *bytes.Buffer, text []byte) {
	r.processString(out, text, false)
}

// Emphasis dummy function to proceed with rendering
func (r *MarkdownStripper) Emphasis(out *bytes.Buffer, text []byte) {
	r.processString(out, text, false)
}

// Image dummy function to proceed with rendering
func (r *MarkdownStripper) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
	// Not rendered
	r.coallesce = false
}

// LineBreak dummy function to proceed with rendering
func (r *MarkdownStripper) LineBreak(out *bytes.Buffer) {
	// Not rendered
	r.coallesce = false
}

// Link dummy function to proceed with rendering
func (r *MarkdownStripper) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
	r.processLink(out, link, content)
}

// RawHtmlTag dummy function to proceed with rendering
func (r *MarkdownStripper) RawHtmlTag(out *bytes.Buffer, tag []byte) { //nolint
	// Not rendered
	r.coallesce = false
}

// TripleEmphasis dummy function to proceed with rendering
func (r *MarkdownStripper) TripleEmphasis(out *bytes.Buffer, text []byte) {
	r.processString(out, text, false)
}

// StrikeThrough dummy function to proceed with rendering
func (r *MarkdownStripper) StrikeThrough(out *bytes.Buffer, text []byte) {
	r.processString(out, text, false)
}

// FootnoteRef dummy function to proceed with rendering
func (r *MarkdownStripper) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
	// Not rendered
	r.coallesce = false
}

// Low-level callbacks

// Entity dummy function to proceed with rendering
func (r *MarkdownStripper) Entity(out *bytes.Buffer, entity []byte) {
	// FIXME: literal entities are not parsed; perhaps they should
	r.coallesce = false
}

// NormalText dummy function to proceed with rendering
func (r *MarkdownStripper) NormalText(out *bytes.Buffer, text []byte) {
	r.processString(out, text, true)
}

// Header and footer

// DocumentHeader dummy function to proceed with rendering
func (r *MarkdownStripper) DocumentHeader(out *bytes.Buffer) {
	r.coallesce = false
}

// DocumentFooter dummy function to proceed with rendering
func (r *MarkdownStripper) DocumentFooter(out *bytes.Buffer) {
	r.coallesce = false
}

// GetFlags returns rendering flags
func (r *MarkdownStripper) GetFlags() int {
	return 0
}

//revive:enable:var-naming

func doubleSpace(out *bytes.Buffer) {
	if out.Len() > 0 {
		out.WriteByte('\n')
	}
}

func (r *MarkdownStripper) processString(out *bytes.Buffer, text []byte, coallesce bool) {
	// Always break-up words
	if !coallesce || !r.coallesce {
		doubleSpace(out)
	}
	out.Write(text)
	r.coallesce = coallesce
}
func (r *MarkdownStripper) processLink(out *bytes.Buffer, link []byte, content []byte) {
	// Links are processed out of band
	r.links = append(r.links, string(link))
	r.coallesce = false
}

// GetLinks returns the list of link data collected while parsing
func (r *MarkdownStripper) GetLinks() []string {
	return r.links
}