// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package markup

import (
	"strings"
	"unicode"

	"gitea.dev/modules/emoji"
	"gitea.dev/modules/setting"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

func createEmoji(ctx *RenderContext, content, name string) *html.Node {
	span := &html.Node{
		Type: html.ElementNode,
		Data: atom.Span.String(),
		Attr: []html.Attribute{},
	}
	span.Attr = append(span.Attr, ctx.RenderInternal.NodeSafeAttr("class", "emoji"))
	if name != "" {
		span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: name})
	}

	text := &html.Node{
		Type: html.TextNode,
		Data: content,
	}

	span.AppendChild(text)
	return span
}

func createCustomEmoji(ctx *RenderContext, alias string) *html.Node {
	span := &html.Node{
		Type: html.ElementNode,
		Data: atom.Span.String(),
		Attr: []html.Attribute{},
	}
	span.Attr = append(span.Attr, ctx.RenderInternal.NodeSafeAttr("class", "emoji"))
	span.Attr = append(span.Attr, html.Attribute{Key: "aria-label", Val: alias})

	img := &html.Node{
		Type:     html.ElementNode,
		DataAtom: atom.Img,
		Data:     "img",
		Attr:     []html.Attribute{},
	}
	img.Attr = append(img.Attr, html.Attribute{Key: "alt", Val: ":" + alias + ":"})
	img.Attr = append(img.Attr, html.Attribute{Key: "src", Val: setting.StaticURLPrefix + "/assets/img/emoji/" + alias + ".png"})

	span.AppendChild(img)
	return span
}

// emojiShortCodeProcessor for rendering text like :smile: into emoji
func emojiShortCodeProcessor(ctx *RenderContext, node *html.Node) {
	start := 0
	next := node.NextSibling
	for node != nil && node != next && start < len(node.Data) {
		m := globalVars().emojiShortCodeRegex.FindStringSubmatchIndex(node.Data[start:])
		if m == nil {
			return
		}
		m[0] += start
		m[1] += start
		start = m[1]

		// don't render a shortcode whose ":" directly follows a backtick (an unclosed code span)
		if m[0] > 0 && node.Data[m[0]-1] == '`' {
			continue
		}

		alias := node.Data[m[0]:m[1]]

		var nextChar byte
		if m[1] < len(node.Data) {
			nextChar = node.Data[m[1]]
		}
		if nextChar == ':' || unicode.IsLetter(rune(nextChar)) || unicode.IsDigit(rune(nextChar)) {
			continue
		}

		alias = strings.Trim(alias, ":")
		converted := emoji.FromAlias(alias)
		if converted != nil {
			// standard emoji
			replaceContent(node, m[0], m[1], createEmoji(ctx, converted.Emoji, converted.Description))
			node = node.NextSibling.NextSibling
			start = 0 // restart searching start since node has changed
		} else if _, exist := setting.UI.CustomEmojisMap[alias]; exist {
			// custom reaction
			replaceContent(node, m[0], m[1], createCustomEmoji(ctx, alias))
			node = node.NextSibling.NextSibling
			start = 0 // restart searching start since node has changed
		}
	}
}

// emoji processor to match emoji and add emoji class
func emojiProcessor(ctx *RenderContext, node *html.Node) {
	start := 0
	next := node.NextSibling
	for node != nil && node != next && start < len(node.Data) {
		m := emoji.FindEmojiSubmatchIndex(node.Data[start:])
		if m == nil {
			return
		}
		m[0] += start
		m[1] += start

		codepoint := node.Data[m[0]:m[1]]
		start = m[1]
		val := emoji.FromCode(codepoint)
		if val != nil {
			replaceContent(node, m[0], m[1], createEmoji(ctx, codepoint, val.Description))
			node = node.NextSibling.NextSibling
			start = 0
		}
	}
}
