// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markup
import (
	"context"
	"fmt"
	"io"
	"net/url"
	"strings"
	"sync"
	"code.gitea.io/gitea/modules/git"
	"code.gitea.io/gitea/modules/gitrepo"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/util"
	"github.com/yuin/goldmark/ast"
)
type RenderMetaMode string
const (
	RenderMetaAsDetails RenderMetaMode = "details" // default
	RenderMetaAsNone    RenderMetaMode = "none"
	RenderMetaAsTable   RenderMetaMode = "table"
)
type RenderContentMode string
const (
	RenderContentAsDefault RenderContentMode = "" // empty means "default", no special handling, maybe just a simple "document"
	RenderContentAsComment RenderContentMode = "comment"
	RenderContentAsTitle   RenderContentMode = "title"
	RenderContentAsWiki    RenderContentMode = "wiki"
)
var RenderBehaviorForTesting struct {
	// Markdown line break rendering has 2 default behaviors:
	// * Use hard: replace "\n" with "
" for comments, setting.Markdown.EnableHardLineBreakInComments=true
	// * Keep soft: "\n" for non-comments (a.k.a. documents), setting.Markdown.EnableHardLineBreakInDocuments=false
	// In history, there was a mess:
	// * The behavior was controlled by `Metas["mode"] != "document",
	// * However, many places render the content without setting "mode" in Metas, all these places used comment line break setting incorrectly
	ForceHardLineBreak bool
	// Gitea will emit some internal attributes for various purposes, these attributes don't affect rendering.
	// But there are too many hard-coded test cases, to avoid changing all of them again and again, we can disable emitting these internal attributes.
	DisableInternalAttributes bool
}
// RenderContext represents a render context
type RenderContext struct {
	Ctx          context.Context
	RelativePath string // relative path from tree root of the branch
	// eg: "orgmode", "asciicast", "console"
	// for file mode, it could be left as empty, and will be detected by file extension in RelativePath
	MarkupType string
	// what the content will be used for: eg: for comment or for wiki? or just render a file?
	ContentMode RenderContentMode
	Links            Links             // special link references for rendering, especially when there is a branch/tree path
	Metas            map[string]string // user&repo, format&style®exp (for external issue pattern), teams&org (for mention), BranchNameSubURL(for iframe&asciicast)
	DefaultLink      string            // TODO: need to figure out
	GitRepo          *git.Repository
	Repo             gitrepo.Repository
	ShaExistCache    map[string]bool
	cancelFn         func()
	SidebarTocNode   ast.Node
	RenderMetaAs     RenderMetaMode
	InStandalonePage bool // used by external render. the router "/org/repo/render/..." will output the rendered content in a standalone page
}
// Cancel runs any cleanup functions that have been registered for this Ctx
func (ctx *RenderContext) Cancel() {
	if ctx == nil {
		return
	}
	ctx.ShaExistCache = map[string]bool{}
	if ctx.cancelFn == nil {
		return
	}
	ctx.cancelFn()
}
// AddCancel adds the provided fn as a Cleanup for this Ctx
func (ctx *RenderContext) AddCancel(fn func()) {
	if ctx == nil {
		return
	}
	oldCancelFn := ctx.cancelFn
	if oldCancelFn == nil {
		ctx.cancelFn = fn
		return
	}
	ctx.cancelFn = func() {
		defer oldCancelFn()
		fn()
	}
}
// Render renders markup file to HTML with all specific handling stuff.
func Render(ctx *RenderContext, input io.Reader, output io.Writer) error {
	if ctx.MarkupType == "" && ctx.RelativePath != "" {
		ctx.MarkupType = DetectMarkupTypeByFileName(ctx.RelativePath)
		if ctx.MarkupType == "" {
			return util.NewInvalidArgumentErrorf("unsupported file to render: %q", ctx.RelativePath)
		}
	}
	renderer := renderers[ctx.MarkupType]
	if renderer == nil {
		return util.NewInvalidArgumentErrorf("unsupported markup type: %q", ctx.MarkupType)
	}
	if ctx.RelativePath != "" {
		if externalRender, ok := renderer.(ExternalRenderer); ok && externalRender.DisplayInIFrame() {
			if !ctx.InStandalonePage {
				// for an external "DisplayInIFrame" render, it could only output its content in a standalone page
				// otherwise, a