// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless requiredF by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hugolib

import (
	"fmt"
	"strings"
	"testing"

	qt "github.com/frankban/quicktest"
)

func TestRenderHooksRSS(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.org"
disableKinds = ["taxonomy", "term"]
-- layouts/home.html --
{{ $p := site.GetPage "p1.md" }}
{{ $p2 := site.GetPage "p2.md" }}
P1: {{ $p.Content }}
P2: {{ $p2.Content }}
-- layouts/index.xml --
{{ $p2 := site.GetPage "p2.md" }}
{{ $p3 := site.GetPage "p3.md" }}
P2: {{ $p2.Content }}
P3: {{ $p3.Content }}
-- layouts/_markup/render-link.html --
html-link: {{ .Destination | safeURL }}|
-- layouts/_markup/render-link.rss.xml --
xml-link: {{ .Destination | safeURL }}|
-- layouts/_markup/render-heading.html --
html-heading: {{ .Text }}|
-- layouts/_markup/render-heading.rss.xml --
xml-heading: {{ .Text }}|
-- content/p1.md --
---
title: "p1"
---
P1. [I'm an inline-style link](https://www.gohugo.io)

# Heading in p1

-- content/p2.md --
---
title: "p2"
---
P2. [I'm an inline-style link](https://www.bep.is)

# Heading in p2

-- content/p3.md --
---
title: "p3"
outputs: ["rss"]
---
P3. [I'm an inline-style link](https://www.example.org)
`
	b := Test(t, files)

	b.AssertFileContent("public/index.html", `
P1: <p>P1. html-link: https://www.gohugo.io|</p>
html-heading: Heading in p1|
html-heading: Heading in p2|
`)
	b.AssertFileContent("public/index.xml", `
P2: <p>P2. xml-link: https://www.bep.is|</p>
P3: <p>P3. xml-link: https://www.example.org|</p>
xml-heading: Heading in p2|
`)
}

// Issue 13242.
func TestRenderHooksRSSOnly(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.org"
disableKinds = ["taxonomy", "term"]
-- layouts/home.html --
{{ $p := site.GetPage "p1.md" }}
{{ $p2 := site.GetPage "p2.md" }}
P1: {{ $p.Content }}
P2: {{ $p2.Content }}
-- layouts/index.xml --
{{ $p2 := site.GetPage "p2.md" }}
{{ $p3 := site.GetPage "p3.md" }}
P2: {{ $p2.Content }}
P3: {{ $p3.Content }}
-- layouts/_markup/render-link.rss.xml --
xml-link: {{ .Destination | safeURL }}|
-- layouts/_markup/render-heading.rss.xml --
xml-heading: {{ .Text }}|
-- content/p1.md --
---
title: "p1"
---
P1. [I'm an inline-style link](https://www.gohugo.io)

# Heading in p1

-- content/p2.md --
---
title: "p2"
---
P2. [I'm an inline-style link](https://www.bep.is)

# Heading in p2

-- content/p3.md --
---
title: "p3"
outputs: ["rss"]
---
P3. [I'm an inline-style link](https://www.example.org)
`
	b := Test(t, files)

	b.AssertFileContent("public/index.html", `
P1: <p>P1. <a href="https://www.gohugo.io">I&rsquo;m an inline-style link</a></p>
<h1 id="heading-in-p1">Heading in p1</h1>
<h1 id="heading-in-p2">Heading in p2</h1>
`)

	b.AssertFileContent("public/index.xml", `
P2: <p>P2. xml-link: https://www.bep.is|</p>
P3: <p>P3. xml-link: https://www.example.org|</p>
xml-heading: Heading in p2|
`)
}

// https://github.com/gohugoio/hugo/issues/6629
func TestRenderLinkWithMarkupInText(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL="https://example.org"
[markup]
  [markup.goldmark]
    [markup.goldmark.renderer]
      unsafe = true
-- layouts/home.html --
{{ $p := site.GetPage "p1.md" }}
P1: {{ $p.Content }}
-- layouts/_markup/render-link.html --
html-link: {{ .Destination | safeURL }}|Text: {{ .Text }}|Plain: {{ .PlainText | safeHTML }}
-- layouts/_markup/render-image.html --
html-image: {{ .Destination | safeURL }}|Text: {{ .Text }}|Plain: {{ .PlainText | safeHTML }}
-- content/p1.md --
---
title: "p1"
---

START: [**should be bold**](https://gohugo.io)END

Some regular **markup**.

Image:

![Hello<br> Goodbye](image.jpg)END
`

	b := Test(t, files)

	b.AssertFileContent("public/index.html", `
  P1: <p>START: html-link: https://gohugo.io|Text: <strong>should be bold</strong>|Plain: should be boldEND</p>
<p>Some regular <strong>markup</strong>.</p>
<p>html-image: image.jpg|Text: Hello<br> Goodbye|Plain: Hello GoodbyeEND</p>
`)
}

func TestRenderHookContentFragmentsOnSelf(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.org"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"]
-- content/p1.md --
---
title: "p1"
---

## A {#z}
## B
## C

-- content/p2.md --
---
title: "p2"
---

## D
## E
## F

-- layouts/_markup/render-heading.html --
Heading: {{ .Text }}|
{{ with .Page }}
Self Fragments: {{ .Fragments.Identifiers }}|
{{ end }}
{{ with (site.GetPage "p1.md") }}
P1 Fragments: {{ .Fragments.Identifiers }}|
{{ end }}
-- layouts/single.html --
{{ .Content}}
`

	b := Test(t, files)

	b.AssertFileContent("public/p1/index.html", `
Self Fragments: [b c z]
P1 Fragments: [b c z]
	`)
	b.AssertFileContent("public/p2/index.html", `
Self Fragments: [d e f]
P1 Fragments: [b c z]
	`)
}

func TestDefaultRenderHooksMultilingual(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.org"
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT"]
defaultContentLanguage = "nn"
defaultContentLanguageInSubdir = true
[markup]
[markup.goldmark]
duplicateResourceFiles = false
[markup.goldmark.renderhooks]
[markup.goldmark.renderhooks.link]
#useEmbedded = 'never'
[markup.goldmark.renderhooks.image]
#useEmbedded = 'never'
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
-- content/p1/index.md --
---
title: "p1"
---
[P2](p2)
![Pixel](pixel.png)
-- content/p2/index.md --
---
title: "p2"
---
[P1](p1)
![Pixel](pixel.jpg)
-- content/p1/index.en.md --
---
title: "p1 en"
---
[P2](p2)
![Pixel](pixel.png)
-- content/p2/index.en.md --
---
title: "p2 en"
---
[P1](p1)
![Pixel](pixel.png)

-- content/p1/pixel.nn.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- content/p2/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- layouts/single.html --
{{ .Title }}|{{ .Content }}|$

`

	t.Run("Default multilingual", func(t *testing.T) {
		b := Test(t, files)

		b.AssertFileContent("public/nn/p1/index.html",
			"p1|<p><a href=\"/nn/p2/\">P2</a\n></p>", "<img src=\"/nn/p1/pixel.nn.png\" alt=\"Pixel\">")
		b.AssertFileContent("public/en/p1/index.html",
			"p1 en|<p><a href=\"/en/p2/\">P2</a\n></p>", "<img src=\"/nn/p1/pixel.nn.png\" alt=\"Pixel\">")
	})

	t.Run("Disabled", func(t *testing.T) {
		b := Test(t, strings.ReplaceAll(files, "#useEmbedded = 'never'", "useEmbedded = 'never'"))

		b.AssertFileContent("public/nn/p1/index.html",
			"p1|<p><a href=\"p2\">P2</a>", "<img src=\"pixel.png\" alt=\"Pixel\">")
	})
}

func TestRenderHooksDefaultEscape(t *testing.T) {
	files := `
-- hugo.toml --
[markup.goldmark.extensions.typographer]
disable = true
[markup.goldmark.renderHooks.image]
useEmbedded = 'WHEN'
[markup.goldmark.renderHooks.link]
useEmbedded = 'WHEN'
[markup.goldmark.parser]
wrapStandAloneImageWithinParagraph = false
[markup.goldmark.parser.attribute]
block = true
title = true

-- content/_index.md --
---
title: "Home"
---
Link: [text-"<>&](/destination-"<> 'title-"<>&')

Image: ![alt-"<>&](/destination-"<> 'title-"<>&')
{class="><script>alert()</script>" id="baz"}

-- layouts/home.html --
{{ .Content }}
`

	for _, when := range []string{"auto", "never", "always", "fallback"} {
		t.Run(fmt.Sprint(when), func(t *testing.T) {
			t.Parallel()
			b := Test(t, strings.ReplaceAll(files, "WHEN", fmt.Sprint(when)))

			// The escaping is slightly different between the two.
			if when == "always" || when == "fallback" {
				b.AssertFileContent("public/index.html",
					"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-&#34;&lt;&gt;&amp;\">text-&quot;&lt;&gt;&amp;</a>",
					"img src=\"/destination-%22%3C%3E\" alt=\"alt-&#34;&lt;&gt;&amp;\" title=\"title-&#34;&lt;&gt;&amp;\">",
					"&gt;&lt;script&gt;",
				)
			} else {
				b.AssertFileContent("public/index.html",
					"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-&quot;&lt;&gt;&amp;\">text-&quot;&lt;&gt;&amp;</a>",
					"Image: <img src=\"/destination-%22%3C%3E\" alt=\"alt-&quot;&lt;&gt;&amp;\" title=\"title-&quot;&lt;&gt;&amp;\">",
				)
			}
		})
	}
}

// Issue 13410.
func TestRenderHooksMultilineTitlePlainText(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
-- content/p1.md --
---
title: "p1"
---

First line.
Second line.
----------------
-- layouts/_markup/render-heading.html --
Plain text: {{ .PlainText }}|Text: {{ .Text }}|
-- layouts/single.html --
Content: {{ .Content}}|
}
`
	b := Test(t, files)

	b.AssertFileContent("public/p1/index.html",
		"Content: Plain text: First line.\nSecond line.|",
		"|Text: First line.\nSecond line.||\n",
	)
}

func TestContentOutputReuseRenderHooksAndShortcodesHTMLOnly(t *testing.T) {
	files := `
-- hugo.toml --
-- layouts/home.html --
HTML: {{ .Title }}|{{ .Content }}|
-- layouts/index.xml --
XML: {{ .Title }}|{{ .Content }}|
-- layouts/_markup/render-heading.html --
Render heading.
-- layouts/_shortcodes/myshortcode.html --
My shortcode.
-- content/_index.md --
---
title: "Home"
---
## Heading

{{< myshortcode >}}
`
	b := Test(t, files)

	s := b.H.Sites[0]
	b.Assert(s.home.pageOutputTemplateVariationsState.Load(), qt.Equals, uint32(1))
	b.AssertFileContent("public/index.html", "HTML: Home|Render heading.\nMy shortcode.\n|")
	b.AssertFileContent("public/index.xml", "XML: Home|Render heading.\nMy shortcode.\n|")
}

func TestContentOutputNoReuseRenderHooksInBothHTMLAnXML(t *testing.T) {
	files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
-- layouts/home.html --
HTML: {{ .Title }}|{{ .Content }}|
-- layouts/index.xml --
XML: {{ .Title }}|{{ .Content }}|
-- layouts/_markup/render-heading.html --
Render heading.
-- layouts/_markup/render-heading.xml --
Render heading XML.
-- content/_index.md --
---
title: "Home"
---
## Heading


`
	b := Test(t, files)

	s := b.H.Sites[0]
	b.Assert(s.home.pageOutputTemplateVariationsState.Load() > 1, qt.IsTrue)
	b.AssertFileContentExact("public/index.xml", "XML: Home|Render heading XML.|")
	b.AssertFileContentExact("public/index.html", "HTML: Home|Render heading.|")
}

func TestContentOutputNoReuseShortcodesInBothHTMLAnXML(t *testing.T) {
	files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
-- layouts/home.html --
HTML: {{ .Title }}|{{ .Content }}|
-- layouts/index.xml --
XML: {{ .Title }}|{{ .Content }}|
-- layouts/_markup/render-heading.html --
Render heading.

-- layouts/_shortcodes/myshortcode.html --
My shortcode HTML.
-- layouts/_shortcodes/myshortcode.xml --
My shortcode XML.
-- content/_index.md --
---
title: "Home"
---
## Heading

{{< myshortcode >}}


`
	b := Test(t, files)

	b.AssertFileContentExact("public/index.xml", "My shortcode XML.")
	b.AssertFileContentExact("public/index.html", "My shortcode HTML.")
	s := b.H.Sites[0]
	b.Assert(s.home.pageOutputTemplateVariationsState.Load() > 1, qt.IsTrue)
}
