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

package markdown

import (
	"strings"
	"testing"

	"code.gitea.io/gitea/modules/markup"
	"code.gitea.io/gitea/modules/setting"
	"code.gitea.io/gitea/modules/test"

	"github.com/stretchr/testify/assert"
)

const nl = "\n"

func TestMathRender(t *testing.T) {
	setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseInlineDollar: true, ParseInlineParentheses: true}
	testcases := []struct {
		testcase string
		expected string
	}{
		{
			"$a$",
			`<p><code class="language-math">a</code></p>` + nl,
		},
		{
			"$ a $",
			`<p><code class="language-math">a</code></p>` + nl,
		},
		{
			"$a$$b$",
			`<p><code class="language-math">a</code><code class="language-math">b</code></p>` + nl,
		},
		{
			"$a$ $b$",
			`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
		},
		{
			`\(a\) \(b\)`,
			`<p><code class="language-math">a</code> <code class="language-math">b</code></p>` + nl,
		},
		{
			`$a$.`,
			`<p><code class="language-math">a</code>.</p>` + nl,
		},
		{
			`.$a$`,
			`<p>.$a$</p>` + nl,
		},
		{
			`$a a$b b$`,
			`<p>$a a$b b$</p>` + nl,
		},
		{
			`a a$b b`,
			`<p>a a$b b</p>` + nl,
		},
		{
			`a$b $a a$b b$`,
			`<p>a$b $a a$b b$</p>` + nl,
		},
		{
			"a$x$", // Pattern: "word$other$" The real world example is: "Price is between US$1 and US$2.", so don't parse this.
			`<p>a$x$</p>` + nl,
		},
		{
			"$x$a",
			`<p>$x$a</p>` + nl,
		},
		{
			"$a$ ($b$) [$c$] {$d$}",
			`<p><code class="language-math">a</code> (<code class="language-math">b</code>) [$c$] {$d$}</p>` + nl,
		},
		{
			"[$a$](link)",
			`<p><a href="/link" rel="nofollow"><code class="language-math">a</code></a></p>` + nl,
		},
		{
			"$$a$$",
			`<p><code class="language-math">a</code></p>` + nl,
		},
		{
			"$$a$$ test",
			`<p><code class="language-math">a</code> test</p>` + nl,
		},
		{
			"test $$a$$",
			`<p>test <code class="language-math">a</code></p>` + nl,
		},
		{
			`foo $x=\$$ bar`,
			`<p>foo <code class="language-math">x=\$</code> bar</p>` + nl,
		},
		{
			`$\text{$b$}$`,
			`<p><code class="language-math">\text{$b$}</code></p>` + nl,
		},
		{
			"a$`b`$c",
			`<p>a<code class="language-math">b</code>c</p>` + nl,
		},
		{
			"a $`b`$ c",
			`<p>a <code class="language-math">b</code> c</p>` + nl,
		},
		{
			"a$``b``$c x$```y```$z",
			`<p>a<code class="language-math">b</code>c x<code class="language-math">y</code>z</p>` + nl,
		},
	}

	for _, test := range testcases {
		t.Run(test.testcase, func(t *testing.T) {
			res, err := RenderString(markup.NewTestRenderContext(), test.testcase)
			assert.NoError(t, err)
			assert.Equal(t, test.expected, string(res))
		})
	}
}

func TestMathRenderBlockIndent(t *testing.T) {
	setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{ParseBlockDollar: true, ParseBlockSquareBrackets: true}
	testcases := []struct {
		name     string
		testcase string
		expected string
	}{
		{
			"indent-0",
			`
\[
\alpha
\]
`,
			`<pre class="code-block is-loading"><code class="language-math display">
\alpha
</code></pre>
`,
		},
		{
			"indent-1",
			`
 \[
 \alpha
 \]
`,
			`<pre class="code-block is-loading"><code class="language-math display">
\alpha
</code></pre>
`,
		},
		{
			"indent-2-mismatch",
			`
  \[
a
 b
  c
   d
  \]
`,
			`<pre class="code-block is-loading"><code class="language-math display">
a
b
c
 d
</code></pre>
`,
		},
		{
			"indent-2",
			`
  \[
  a
   b
  c
  \]
`,
			`<pre class="code-block is-loading"><code class="language-math display">
a
 b
c
</code></pre>
`,
		},
		{
			"indent-0-oneline",
			`$$ x $$
foo`,
			`<code class="language-math display"> x </code>
<p>foo</p>
`,
		},
		{
			"indent-3-oneline",
			`   $$ x $$<SPACE>
foo`,
			`<code class="language-math display"> x </code>
<p>foo</p>
`,
		},
		{
			"quote-block",
			`
> \[
> a
> \]
> \[
> b
> \]
`,
			`<blockquote>
<pre class="code-block is-loading"><code class="language-math display">
a
</code></pre>
<pre class="code-block is-loading"><code class="language-math display">
b
</code></pre>
</blockquote>
`,
		},
		{
			"list-block",
			`
1. a
   \[
   x
   \]
2. b`,
			`<ol>
<li>a
<pre class="code-block is-loading"><code class="language-math display">
x
</code></pre>
</li>
<li>b</li>
</ol>
`,
		},
		{
			"inline-non-math",
			`\[x]`,
			`<p>[x]</p>` + nl,
		},
	}

	for _, test := range testcases {
		t.Run(test.name, func(t *testing.T) {
			res, err := RenderString(markup.NewTestRenderContext(), strings.ReplaceAll(test.testcase, "<SPACE>", " "))
			assert.NoError(t, err)
			assert.Equal(t, test.expected, string(res), "unexpected result for test case:\n%s", test.testcase)
		})
	}
}

func TestMathRenderOptions(t *testing.T) {
	setting.Markdown.MathCodeBlockOptions = setting.MarkdownMathCodeBlockOptions{}
	defer test.MockVariableValue(&setting.Markdown.MathCodeBlockOptions)
	test := func(t *testing.T, expected, input string) {
		res, err := RenderString(markup.NewTestRenderContext(), input)
		assert.NoError(t, err)
		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(res)), "input: %s", input)
	}

	// default (non-conflict) inline syntax
	test(t, `<p><code class="language-math">a</code></p>`, "$`a`$")

	// ParseInlineDollar
	test(t, `<p>$a$</p>`, `$a$`)
	setting.Markdown.MathCodeBlockOptions.ParseInlineDollar = true
	test(t, `<p><code class="language-math">a</code></p>`, `$a$`)

	// ParseInlineParentheses
	test(t, `<p>(a)</p>`, `\(a\)`)
	setting.Markdown.MathCodeBlockOptions.ParseInlineParentheses = true
	test(t, `<p><code class="language-math">a</code></p>`, `\(a\)`)

	// ParseBlockDollar
	test(t, `<p>$$
a
$$</p>
`, `
$$
a
$$
`)
	setting.Markdown.MathCodeBlockOptions.ParseBlockDollar = true
	test(t, `<pre class="code-block is-loading"><code class="language-math display">
a
</code></pre>
`, `
$$
a
$$
`)

	// ParseBlockSquareBrackets
	test(t, `<p>[
a
]</p>
`, `
\[
a
\]
`)
	setting.Markdown.MathCodeBlockOptions.ParseBlockSquareBrackets = true
	test(t, `<pre class="code-block is-loading"><code class="language-math display">
a
</code></pre>
`, `
\[
a
\]
`)
}
