// 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 required 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"
	"sync"
	"testing"

	qt "github.com/frankban/quicktest"
	"github.com/gohugoio/hugo/hugolib/sitesmatrix"
)

func TestContentMapSite(t *testing.T) {
	t.Parallel()

	pageTempl := `
---
title: "Page %d"
date: "2019-06-%02d"
lastMod: "2019-06-%02d"
categories: [%q]
---

Page content.
`
	createPage := func(i int) string {
		return fmt.Sprintf(pageTempl, i, i, i+1, "funny")
	}

	createPageInCategory := func(i int, category string) string {
		return fmt.Sprintf(pageTempl, i, i, i+1, category)
	}

	draftTemplate := `---
title: "Draft"
draft: true
---

`

	files := `
-- hugo.toml --
baseURL = "http://example.com"
-- layouts/home.html --
Num Regular: {{ len .Site.RegularPages }}|{{ range .Site.RegularPages }}{{ .RelPermalink }}|{{ end }}$
Main Sections: {{ .Site.Params.mainSections }}
Pag Num Pages: {{ len .Paginator.Pages }}
{{ $home := .Site.Home }}
{{ $blog := .Site.GetPage "blog" }}
{{ $categories := .Site.GetPage "categories" }}
{{ $funny := .Site.GetPage "categories/funny" }}
{{ $blogSub := .Site.GetPage "blog/subsection" }}
{{ $page := .Site.GetPage "blog/page1" }}
{{ $page2 := .Site.GetPage "blog/page2" }}
{{ $page4 := .Site.GetPage "blog/subsection/page4" }}
{{ $bundle := .Site.GetPage "blog/bundle" }}
{{ $overlap1 := .Site.GetPage "overlap" }}
{{ $overlap2 := .Site.GetPage "overlap2" }}

Home: {{ template "print-page" $home }}
Blog Section: {{ template "print-page" $blog }}
Blog Sub Section: {{ template "print-page" $blogSub }}
Page: {{ template "print-page" $page }}
Bundle: {{ template "print-page" $bundle }}
IsDescendant: true: {{ $page.IsDescendant $blog }} true: {{ $blogSub.IsDescendant $blog }} true: {{ $bundle.IsDescendant $blog }} true: {{ $page4.IsDescendant $blog }} true: {{ $blog.IsDescendant $home }} false: {{ $blog.IsDescendant $blog }} false: {{ $home.IsDescendant $blog }}
IsAncestor: true: {{ $blog.IsAncestor $page }} true: {{ $home.IsAncestor $blog }} true: {{ $blog.IsAncestor $blogSub }} true: {{ $blog.IsAncestor $bundle }} true: {{ $blog.IsAncestor $page4 }} true: {{ $home.IsAncestor $page }} false: {{ $blog.IsAncestor $blog }} false: {{ $page.IsAncestor $blog }} false: {{ $blog.IsAncestor $home }}  false: {{ $blogSub.IsAncestor $blog }}
IsDescendant overlap1: false: {{ $overlap1.IsDescendant $overlap2 }}
IsDescendant overlap2: false: {{ $overlap2.IsDescendant $overlap1 }}
IsAncestor overlap1: false: {{ $overlap1.IsAncestor $overlap2 }}
IsAncestor overlap2: false: {{ $overlap2.IsAncestor $overlap1 }}
FirstSection: {{ $blogSub.FirstSection.RelPermalink }} {{ $blog.FirstSection.RelPermalink }} {{ $home.FirstSection.RelPermalink }} {{ $page.FirstSection.RelPermalink }}
InSection: true: {{ $page.InSection $blog }} false: {{ $page.InSection $blogSub }}
Next: {{ $page2.Next.RelPermalink }}
NextInSection: {{ $page2.NextInSection.RelPermalink }}
Pages: {{ range $blog.Pages }}{{ .RelPermalink }}|{{ end }}
Sections: {{ range $home.Sections }}{{ .RelPermalink }}|{{ end }}:END
Categories: {{ range .Site.Taxonomies.categories }}{{ .Page.RelPermalink }}; {{ .Page.Title }}; {{ .Count }}|{{ end }}:END
Category Terms:  {{ $categories.Kind}}: {{ range $categories.Data.Terms.Alphabetical }}{{ .Page.RelPermalink }}; {{ .Page.Title }}; {{ .Count }}|{{ end }}:END
Category Funny:  {{ $funny.Kind}}; {{ $funny.Data.Term }}: {{ range $funny.Pages }}{{ .RelPermalink }}|{{ end }}:END
Pag Num Pages: {{ len .Paginator.Pages }}
Pag Blog Num Pages: {{ len $blog.Paginator.Pages }}
Blog Num RegularPages: {{ len $blog.RegularPages }}|{{ range $blog.RegularPages }}P: {{ .RelPermalink }}|{{ end }}
Blog Num Pages: {{ len $blog.Pages }}

Draft1: {{ if (.Site.GetPage "blog/subsection/draft") }}FOUND{{ end }}|
Draft2: {{ if (.Site.GetPage "blog/draftsection") }}FOUND{{ end }}|
Draft3: {{ if (.Site.GetPage "blog/draftsection/page") }}FOUND{{ end }}|
Draft4: {{ if (.Site.GetPage "blog/draftsection/sub") }}FOUND{{ end }}|
Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|

{{ define "print-page" }}{{ .Title }}|{{ .RelPermalink }}|{{ .Date.Format "2006-01-02" }}|Current Section: {{ with .CurrentSection }}{{ .Path }}{{ else }}NIL{{ end }}|Resources: {{ range .Resources }}{{ .ResourceType }}: {{ .RelPermalink }}|{{ end }}{{ end }}
-- content/_index.md --
---
title: "Hugo Home"
cascade:
    description: "Common Description"
---

Home Content.
-- content/blog/page1.md --
` + createPage(1) + `
-- content/blog/page2.md --
` + createPage(2) + `
-- content/blog/page3.md --
` + createPage(3) + `
-- content/blog/bundle/index.md --
` + createPage(4) + `
-- content/blog/bundle/data.json --
data
-- content/blog/bundle/page.md --
` + createPage(5) + `
-- content/blog/subsection/_index.md --
` + createPage(6) + `
-- content/blog/subsection/subdata.json --
data
-- content/blog/subsection/page4.md --
` + createPage(7) + `
-- content/blog/subsection/page5.md --
` + createPage(8) + `
-- content/blog/subsection/draft/index.md --
` + draftTemplate + `
-- content/blog/subsection/draft/data.json --
data
-- content/blog/draftsection/_index.md --
` + draftTemplate + `
-- content/blog/draftsection/page/index.md --
` + createPage(9) + `
-- content/blog/draftsection/page/folder/data.json --
data
-- content/blog/draftsection/sub/_index.md --
` + createPage(10) + `
-- content/blog/draftsection/sub/page.md --
` + createPage(11) + `
-- content/docs/page6.md --
` + createPage(12) + `
-- content/tags/_index.md --
` + createPageInCategory(13, "sad") + `
-- content/overlap/_index.md --
` + createPageInCategory(14, "sad") + `
-- content/overlap2/_index.md --
` + createPage(15) + `
`

	b := Test(t, files)

	b.AssertFileContent("public/index.html",

		`
	 Num Regular: 9
        Main Sections: [blog]
        Pag Num Pages: 9

      Home: Hugo Home|/|2019-06-15|Current Section: /|Resources:
        Blog Section: Blogs|/blog/|2019-06-11|Current Section: /blog|Resources:
        Blog Sub Section: Page 6|/blog/subsection/|2019-06-06|Current Section: /blog/subsection|Resources: application: /blog/subsection/subdata.json|
        Page: Page 1|/blog/page1/|2019-06-01|Current Section: /blog|Resources:
        Bundle: Page 4|/blog/bundle/|2019-06-04|Current Section: /blog|Resources: application: /blog/bundle/data.json|page: |
        IsDescendant: true: true true: true true: true true: true true: true false: false false: false
        IsAncestor: true: true true: true true: true true: true true: true true: true false: false false: false false: false  false: false
        IsDescendant overlap1: false: false
        IsDescendant overlap2: false: false
        IsAncestor overlap1: false: false
        IsAncestor overlap2: false: false
        FirstSection: /blog/ /blog/ / /blog/
        InSection: true: true false: false
        Next: /blog/page3/
        NextInSection: /blog/page3/
		Pages: /blog/subsection/|/blog/bundle/|/blog/page3/|/blog/page2/|/blog/page1/|
		Sections: /overlap2/|/overlap/|/docs/|/blog/|:END
		Categories: /categories/funny/; Funny; 12|/categories/sad/; Sad; 2|:END
        Category Terms:  taxonomy: /categories/funny/; Funny; 12|/categories/sad/; Sad; 2|:END
		Category Funny:  term; funny: /overlap2/|/docs/page6/|/blog/draftsection/sub/page/|/blog/draftsection/sub/|/blog/draftsection/page/|/blog/subsection/page5/|/blog/subsection/page4/|/blog/subsection/|/blog/bundle/|/blog/page3/|/blog/page2/|/blog/page1/|:END
 		Pag Num Pages: 9
        Pag Blog Num Pages: 4
        Blog Num RegularPages: 4
        Blog Num Pages: 5

        Draft1: |
        Draft2: FOUND|
        Draft3: FOUND|
        Draft4: FOUND|
        Draft5: FOUND|

`)
}

func TestIntegrationTestTemplate(t *testing.T) {
	t.Parallel()
	c := qt.New(t)

	files := `
-- hugo.toml --
title = "Integration Test"
disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT", "RSS"]
-- layouts/home.html --
Home: {{ .Title }}|

	`

	b := Test(c, files)

	b.AssertFileContent("public/index.html", "Home: Integration Test|")
}

// Issue #11840
// Resource language fallback should be the closest language going up.
func TestBundleResourceLanguageBestMatch(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
defaultContentLanguage = "fr"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.fr]
weight = 2
[languages.de]
weight = 3
-- layouts/home.html --
{{ $bundle := site.GetPage "bundle" }}
{{ $r := $bundle.Resources.GetMatch "*.txt" }}
{{ .Language.Lang }}: {{ with $r }}{{ .RelPermalink }}|{{ .Content }}{{ end}}
-- content/bundle/index.fr.md --
---
title: "Bundle Fr"
---
-- content/bundle/index.en.md --
---
title: "Bundle En"
---
-- content/bundle/index.de.md --
---
title: "Bundle De"
---
-- content/bundle/data.fr.txt --
Data fr
-- content/bundle/data.en.txt --
Data en

`
	for range 5 {
		b := Test(t, files)

		b.AssertFileContent("public/fr/index.html", "fr: /fr/bundle/data.fr.txt|Data fr")
		b.AssertFileContent("public/en/index.html", "en: /en/bundle/data.en.txt|Data en")

		b.AssertFileContent("public/de/index.html", "de: /fr/bundle/data.fr.txt|Data fr")
	}
}

// Issue #11944
func TestBundleResourcesGetWithSpacesInFilename(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term"]
-- content/bundle/index.md --
-- content/bundle/data with Spaces.txt --
Data.
-- layouts/home.html --
{{ $bundle := site.GetPage "bundle" }}
{{ $r := $bundle.Resources.Get "data with Spaces.txt" }}
R: {{ with $r }}{{ .Content }}{{ end }}|
`
	b := Test(t, files)

	b.AssertFileContent("public/index.html", "R: Data.")
}

// Issue #11946.
func TestBundleResourcesGetDuplicateSortOrder(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.com"
-- content/bundle/index.md --
-- content/bundle/data-1.txt --
data-1.txt
-- content/bundle/data 1.txt --
data 1.txt
-- content/bundle/Data 1.txt --
Data 1.txt
-- content/bundle/Data-1.txt --
Data-1.txt
-- layouts/home.html --
{{ $bundle := site.GetPage "bundle" }}
{{ $r := $bundle.Resources.Get "data-1.txt" }}
R: {{ with $r }}{{ .Content }}{{ end }}|Len: {{ len $bundle.Resources }}|$

`

	for range 3 {
		b := Test(t, files)
		b.AssertFileContent("public/index.html", "R: Data 1.txt|", "Len: 1|")
	}
}

func TestBundleResourcesNoPublishedIssue12198(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
disableKinds = ['home','rss','sitemap','taxonomy','term']
-- content/s1/p1.md --
---
title: p1
---
-- content/s1/foo.txt --
foo.txt
-- content/s1/p1.txt --
p1.txt
-- content/s1/p1-foo.txt --
p1-foo.txt
-- layouts/list.html --
{{.Title }}|
-- layouts/single.html --
{{.Title }}|
	`

	b := Test(t, files)

	b.AssertFileExists("public/s1/index.html", true)
	b.AssertFileExists("public/s1/foo.txt", true)
	b.AssertFileExists("public/s1/p1.txt", true)     // failing test
	b.AssertFileExists("public/s1/p1-foo.txt", true) // failing test
	b.AssertFileExists("public/s1/p1/index.html", true)
}

// Issue 13228.
func TestBranchResourceOverlap(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- content/_index.md --
---
title: home
---
-- content/s1/_index.md --
---
title: s1
---
-- content/s1x/a.txt --
a.txt
-- layouts/home.html --
Home.
{{ range .Resources.Match "**" }}
  {{ .Name }}|
{{ end }}
`

	b := Test(t, files)

	b.AssertFileContent("public/index.html", "s1x/a.txt|")
}

func TestSitemapOverrideFilename(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
baseURL = 'https://example.org/'
disableKinds = ['page','rss','section','taxonomy','term']
defaultContentLanguage = 'de'
defaultContentLanguageInSubdir = true
[languages.de]
[languages.en]
[sitemap]
filename = 'foo.xml'
-- layouts/home.html --
irrelevant
`

	b := Test(t, files)

	b.AssertFileExists("public/de/foo.xml", true)
	b.AssertFileExists("public/en/foo.xml", true)
	b.AssertFileContent("public/foo.xml",
		"<loc>https://example.org/de/foo.xml</loc>",
		"<loc>https://example.org/en/foo.xml</loc>",
	)

	files = strings.ReplaceAll(files, "filename = 'foo.xml'", "")

	b = Test(t, files)

	b.AssertFileExists("public/de/sitemap.xml", true)
	b.AssertFileExists("public/en/sitemap.xml", true)
	b.AssertFileContent("public/sitemap.xml",
		"<loc>https://example.org/de/sitemap.xml</loc>",
		"<loc>https://example.org/en/sitemap.xml</loc>",
	)
}

func TestContentTreeReverseIndex(t *testing.T) {
	t.Parallel()

	c := qt.New(t)

	pageReverseIndex := newContentTreeTreverseIndex(
		func(get func(key any) (contentNode, bool), set func(key any, val contentNode)) {
			for i := range 10 {
				key := fmt.Sprint(i)
				set(key, &testContentNode{key: key})
			}
		},
	)

	for i := range 10 {
		key := fmt.Sprint(i)
		v := pageReverseIndex.Get(key)
		c.Assert(v, qt.Not(qt.IsNil))
		c.Assert(v.Path(), qt.Equals, key)
	}
}

// Issue 13019.
func TestContentTreeReverseIndexPara(t *testing.T) {
	t.Parallel()

	var wg sync.WaitGroup

	for range 10 {
		pageReverseIndex := newContentTreeTreverseIndex(
			func(get func(key any) (contentNode, bool), set func(key any, val contentNode)) {
				for i := range 10 {
					key := fmt.Sprint(i)
					set(key, &testContentNode{key: key})
				}
			},
		)

		for j := range 10 {
			wg.Add(1)
			go func(i int) {
				defer wg.Done()
				pageReverseIndex.Get(fmt.Sprint(i))
			}(j)
		}
	}
}

type testContentNode struct {
	key string
}

func (n *testContentNode) Path() string {
	return n.key
}

func (n *testContentNode) forEeachContentNode(f func(v sitesmatrix.Vector, n contentNode) bool) bool {
	panic("not supported")
}

// Issue 12274.
func TestHTMLNotContent(t *testing.T) {
	filesTemplate := `
-- hugo.toml.temp --
[contentTypes]
[contentTypes."text/markdown"]
# Empty for now.
-- hugo.yaml.temp --
contentTypes:
  text/markdown: {}
-- hugo.json.temp --
{
  "contentTypes": {
    "text/markdown": {}
  }
}
-- content/p1/index.md --
---
title: p1
---
-- content/p1/a.html --
<p>a</p>
-- content/p1/b.html --
<p>b</p>
-- content/p1/c.html --
<p>c</p>
-- layouts/single.html --
Path: {{ .Path }}|{{.Kind }}
|{{ (.Resources.Get "a.html").RelPermalink -}}
|{{ (.Resources.Get "b.html").RelPermalink -}}
|{{ (.Resources.Get "c.html").Publish }}
`

	for _, format := range []string{"toml", "yaml", "json"} {
		t.Run(format, func(t *testing.T) {
			t.Parallel()

			files := strings.Replace(filesTemplate, format+".temp", format, 1)
			b := Test(t, files)

			b.AssertFileContent("public/p1/index.html", "|/p1/a.html|/p1/b.html|")
			b.AssertFileContent("public/p1/a.html", "<p>a</p>")
			b.AssertFileContent("public/p1/b.html", "<p>b</p>")
			b.AssertFileContent("public/p1/c.html", "<p>c</p>")
		})
	}
}
