// SiYuan - Refactor your thinking
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.

package api

import (
	"bytes"
	"io"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/88250/gulu"
	"github.com/88250/lute"
	"github.com/88250/lute/ast"
	"github.com/88250/lute/parse"
	"github.com/gabriel-vasile/mimetype"
	"github.com/gin-gonic/gin"
	"github.com/siyuan-note/filelock"
	"github.com/siyuan-note/httpclient"
	"github.com/siyuan-note/logging"
	"github.com/siyuan-note/siyuan/kernel/model"
	"github.com/siyuan-note/siyuan/kernel/util"
)

func extensionCopy(c *gin.Context) {
	ret := gulu.Ret.NewResult()
	defer c.JSON(200, ret)

	form, _ := c.MultipartForm()
	dom := form.Value["dom"][0]
	assets := filepath.Join(util.DataDir, "assets")
	if notebookVal := form.Value["notebook"]; 0 < len(notebookVal) {
		assets = filepath.Join(util.DataDir, notebookVal[0], "assets")
		if !gulu.File.IsDir(assets) {
			assets = filepath.Join(util.DataDir, "assets")
		}
	}

	if err := os.MkdirAll(assets, 0755); err != nil {
		logging.LogErrorf("create assets folder [%s] failed: %s", assets, err)
		ret.Msg = err.Error()
		return
	}

	uploaded := map[string]string{}
	for originalName, file := range form.File {
		oName, err := url.PathUnescape(originalName)
		if err != nil {
			if strings.Contains(originalName, "%u") {
				originalName = strings.ReplaceAll(originalName, "%u", "\\u")
				originalName, err = strconv.Unquote("\"" + originalName + "\"")
				if err != nil {
					continue
				}
				oName, err = url.PathUnescape(originalName)
				if err != nil {
					continue
				}
			} else {
				continue
			}
		}
		u, _ := url.Parse(oName)
		if "" == u.Path {
			continue
		}
		fName := path.Base(u.Path)

		f, err := file[0].Open()
		if err != nil {
			ret.Code = -1
			ret.Msg = err.Error()
			break
		}

		data, err := io.ReadAll(f)
		if err != nil {
			ret.Code = -1
			ret.Msg = err.Error()
			break
		}

		fName = util.FilterUploadFileName(fName)
		ext := util.Ext(fName)
		if "" == ext || strings.Contains(ext, "!") {
			// 改进浏览器剪藏扩展转换本地图片后缀 https://github.com/siyuan-note/siyuan/issues/7467
			if mtype := mimetype.Detect(data); nil != mtype {
				ext = mtype.Extension()
				fName += ext
			}
		}
		if "" == ext && bytes.HasPrefix(data, []byte("<svg ")) && bytes.HasSuffix(data, []byte("</svg>")) {
			ext = ".svg"
			fName += ext
		}

		fName = util.AssetName(fName)
		writePath := filepath.Join(assets, fName)
		if err = filelock.WriteFile(writePath, data); err != nil {
			ret.Code = -1
			ret.Msg = err.Error()
			break
		}

		uploaded[oName] = "assets/" + fName
	}

	luteEngine := util.NewLute()
	luteEngine.SetSup(true)
	luteEngine.SetSub(true)
	luteEngine.SetMark(true)
	luteEngine.SetGFMStrikethrough(true)
	luteEngine.SetInlineAsterisk(true)
	luteEngine.SetInlineUnderscore(true)
	var md string
	var withMath bool
	if nil != form.Value["href"] {
		if href := form.Value["href"][0]; strings.HasPrefix(href, "https://ld246.com/article/") || strings.HasPrefix(href, "https://liuyun.io/article/") {
			// 剪藏链滴帖子时直接使用 Markdown 接口的返回
			// https://ld246.com/article/raw/1724850322251
			href = strings.ReplaceAll(href, "https://ld246.com/article/", "https://ld246.com/article/raw/")
			href = strings.ReplaceAll(href, "https://liuyun.io/article/", "https://liuyun.io/article/raw/")
			resp, err := httpclient.NewCloudRequest30s().Get(href)
			if err != nil {
				logging.LogWarnf("get [%s] failed: %s", href, err)
			} else {
				bodyData, readErr := io.ReadAll(resp.Body)
				if nil != readErr {
					ret.Code = -1
					ret.Msg = "read response body failed: " + readErr.Error()
					return
				}

				md = string(bodyData)
				luteEngine.SetIndentCodeBlock(true) // 链滴支持缩进代码块，因此需要开启
				tree := parse.Parse("", []byte(md), luteEngine.ParseOptions)
				ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
					if ast.NodeInlineMath == n.Type {
						withMath = true
						return ast.WalkStop
					} else if ast.NodeCodeBlock == n.Type {
						if !n.IsFencedCodeBlock {
							// 将缩进代码块转换为围栏代码块
							n.IsFencedCodeBlock = true
							n.CodeBlockFenceChar = '`'
							n.PrependChild(&ast.Node{Type: ast.NodeCodeBlockFenceInfoMarker})
							n.PrependChild(&ast.Node{Type: ast.NodeCodeBlockFenceOpenMarker, Tokens: []byte("```"), CodeBlockFenceLen: 3})
							n.LastChild.InsertAfter(&ast.Node{Type: ast.NodeCodeBlockFenceCloseMarker, Tokens: []byte("```"), CodeBlockFenceLen: 3})
							code := n.ChildByType(ast.NodeCodeBlockCode)
							if nil != code {
								code.Tokens = bytes.TrimPrefix(code.Tokens, []byte("    "))
								code.Tokens = bytes.ReplaceAll(code.Tokens, []byte("\n    "), []byte("\n"))
								code.Tokens = bytes.TrimPrefix(code.Tokens, []byte("\t"))
								code.Tokens = bytes.ReplaceAll(code.Tokens, []byte("\n\t"), []byte("\n"))
							}
						}
					}
					return ast.WalkContinue
				})

				md, _ = lute.FormatNodeSync(tree.Root, luteEngine.ParseOptions, luteEngine.RenderOptions)
			}
		}
	}

	var tree *parse.Tree
	if "" == md {
		tree, withMath = model.HTML2Tree(dom, luteEngine)
		if nil == tree {
			md, withMath, _ = model.HTML2Markdown(dom, luteEngine)
			if withMath {
				luteEngine.SetInlineMath(true)
			}
			tree = parse.Parse("", []byte(md), luteEngine.ParseOptions)
		}
	} else {
		tree = parse.Parse("", []byte(md), luteEngine.ParseOptions)
	}

	var unlinks []*ast.Node
	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
		if !entering {
			return ast.WalkContinue
		}

		if ast.NodeText == n.Type {
			// 剔除行首空白
			if ast.NodeParagraph == n.Parent.Type && n.Parent.FirstChild == n {
				n.Tokens = bytes.TrimLeft(n.Tokens, " \t\n")
			}
		} else if ast.NodeImage == n.Type {
			if dest := n.ChildByType(ast.NodeLinkDest); nil != dest {
				assetPath := uploaded[string(dest.Tokens)]
				if "" == assetPath {
					assetPath = uploaded[string(dest.Tokens)+"?imageView2/2/interlace/1/format/webp"]
				}
				if "" != assetPath {
					dest.Tokens = []byte(assetPath)
				}
			}
		}
		return ast.WalkContinue
	})
	for _, unlink := range unlinks {
		unlink.Unlink()
	}

	parse.TextMarks2Inlines(tree) // 先将 TextMark 转换为 Inlines https://github.com/siyuan-note/siyuan/issues/13056
	parse.NestedInlines2FlattedSpansHybrid(tree, false)

	md, _ = lute.FormatNodeSync(tree.Root, luteEngine.ParseOptions, luteEngine.RenderOptions)
	ret.Data = map[string]interface{}{
		"md":       md,
		"withMath": withMath,
	}
	ret.Msg = model.Conf.Language(72)
}
