// 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 sitesmatrix_test

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing"

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

func TestPageRotate(t *testing.T) {
	t.Parallel()
	files := `
-- hugo.toml --
baseURL = "https://example.org/"
defaultContentVersion = "v4.0.0"
defaultContentVersionInSubdir = true
defaultContentRoleInSubdir = true
defaultContentRole = "guest"
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
disableKinds = ["taxonomy", "term", "rss", "sitemap"]

[cascade]
versions = ["v2**"]

[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[roles]
[roles.guest]
weight = 300
[roles.member]
weight = 200
[versions]
[versions."v2.0.0"]
[versions."v1.2.3"]
[versions."v2.1.0"]
[versions."v3.0.0"]
[versions."v4.0.0"]
-- content/_index.en.md --
---
title: "Home"
sites:
  matrix:
    roles: ["**"]
    versions: ["**"]
---
-- content/_index.nn.md --
---
title: "Heim"
sites:
  matrix:
    roles: ["**"]
    versions: ["**"]
---
-- content/memberonlypost.md --
---
title: "Member Only"
sites:
  matrix:
    roles: ["member"]
    languages: ["**"]
---
Member content.
-- content/publicpost.md --
---
title: "Public"
sites:
  matrix:
    versions: ["! v2.1.*", "v1.2.3", "v2.**"]
  complements:
    versions: ["v3**"]
---
Users with guest role will see this.
-- content/v3publicpost.md --
---
title: "Public v3"
sites:
  matrix:
    versions: ["v3**"]
    languages: ["**"]
---
Users with guest role will see this.
-- layouts/all.html --
Rotate(language): {{ with .Rotate "language" }}{{ range . }}{{ template "printp" . }}|{{ end }}{{ end }}$
Rotate(version): {{ with .Rotate "version" }}{{ range . }}{{ template "printp" . }}|{{ end }}{{ end }}$
Rotate(role): {{ with .Rotate "role" }}{{ range . }}{{ template "printp" . }}|{{ end }}{{ end }}$
.Site.Dimension.language {{ (.Site.Dimension "language").Name }}|
.Site.Dimension.version: {{ (.Site.Dimension "version").Name }}|
.Site.Dimension.role: {{ (.Site.Dimension "role").Name }}|
{{ define "printp" }}{{ .RelPermalink }}:{{ with .Site }}{{ template "prints" . }}{{ end }}{{ end }}
{{ define "prints" }}/l:{{ .Language.Name }}/v:{{ .Version.Name }}/r:{{ .Role.Name }}{{ end }}


`

	for range 3 {
		b := hugolib.Test(t, files)

		b.AssertFileContent("public/guest/v2.0.0/en/publicpost/index.html",
			"Rotate(language): /guest/v2.0.0/en/publicpost/:/l:en/v:v2.0.0/r:guest|$",
			"Rotate(version): /guest/v4.0.0/en/publicpost/:/l:en/v:v4.0.0/r:guest|/guest/v3.0.0/en/publicpost/:/l:en/v:v3.0.0/r:guest|/guest/v2.0.0/en/publicpost/:/l:en/v:v2.0.0/r:guest|/guest/v1.2.3/en/publicpost/:/l:en/v:v1.2.3/r:guest|$",
			"Rotate(role): /guest/v2.0.0/en/publicpost/:/l:en/v:v2.0.0/r:guest|$",
		)

		b.AssertFileContent("public/guest/v3.0.0/en/index.html",
			"Rotate(language): /guest/v3.0.0/en/:/l:en/v:v3.0.0/r:guest|/guest/v3.0.0/nn/:/l:nn/v:v3.0.0/r:guest|$",
			"Rotate(version): /guest/v4.0.0/en/:/l:en/v:v4.0.0/r:guest|/guest/v3.0.0/en/:/l:en/v:v3.0.0/r:guest|/guest/v2.1.0/en/:/l:en/v:v2.1.0/r:guest|/guest/v2.0.0/en/:/l:en/v:v2.0.0/r:guest|/guest/v1.2.3/en/:/l:en/v:v1.2.3/r:guest",
			"Rotate(role): /member/v3.0.0/en/:/l:en/v:v3.0.0/r:member|/guest/v3.0.0/en/:/l:en/v:v3.0.0/r:guest|$",
			".Site.Dimension.language en|",
			".Site.Dimension.version: v3.0.0|",
			".Site.Dimension.role: guest|",
		)

	}
}

func TestFileMountSitesMatrix(t *testing.T) {
	filesTemplate := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "section"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultCOntentVersionInSubDir = true
[versions]
[versions."v1.2.3"]
[versions."v2.0.0"]
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[module]
[[module.mounts]]
source = 'content/en'
target = 'content'
DIMSEN
[[module.mounts]]
source = 'content/nn'
target = 'content'
DIMSNN
[[module.mounts]]
source = 'content/all'
target = 'content'
[module.mounts.sites.matrix]
languages = ["**"]
versions = ["**"]
-- content/en/p1/index.md --
---
title: "Title English"
---
-- content/en/p1/mytext.txt --
Text English
-- content/nn/p1/index.md --
---
title: "Tittel Nynorsk"
---
-- content/all/p2/index.md --
---
title: "p2 all"
---
-- content/nn/p1/mytext.txt --
Tekst Nynorsk
-- layouts/all.html --
{{ $mytext := .Resources.Get "mytext.txt" }}
{{ .Title }}|{{ with  $mytext }}{{ .Content | safeHTML }}{{ end }}|
site.GetPage p2: {{ with .Site.GetPage "p2" }}{{ .Title }}|{{ end }}$
site.GetPage p1: {{ with .Site.GetPage "p1" }}{{ .Title }}|{{ end }}$

`

	testOne := func(t *testing.T, name, files string) {
		t.Run(name, func(t *testing.T) {
			t.Parallel()

			b := hugolib.Test(t, files)

			nn20 := b.SiteHelper("nn", "v2.0.0", "")
			b.Assert(nn20.PageHelper("/p2").MatrixFromFile()["matrix"],
				qt.DeepEquals,
				map[string][]string{"languages": {"en", "nn"}, "roles": {"guest"}, "versions": {"v2.0.0", "v1.2.3"}})

			b.AssertFileContent("public/v2.0.0/nn/p1/index.html", "Tittel Nynorsk", "Tekst Nynorsk", "site.GetPage p1: Tittel Nynorsk|")
			b.AssertFileContent("public/v2.0.0/nn/p2/index.html", "p2 all||", "site.GetPage p2: p2 all", "site.GetPage p1: Tittel Nynorsk|")
			b.AssertFileContent("public/v2.0.0/nn/p2/index.html", "p2 all||", "site.GetPage p1: Tittel Nynorsk|$")
			b.AssertFileContent("public/v1.2.3/en/p2/index.html", "p2 all||", "site.GetPage p2: p2 all")
		})
	}

	// Format from v0.148.0:
	dims := `[module.mounts.sites.matrix]
languages = ["en"]
versions = ["v1**"]
`
	files := strings.Replace(filesTemplate, "DIMSEN", dims, 1)
	dims = strings.Replace(dims, `["en"]`, `["nn"]`, 1)
	dims = strings.Replace(dims, `["v1**"]`, `["v2**"]`, 1)
	files = strings.Replace(files, "DIMSNN", dims, 1)
	testOne(t, "new", files)

	// Old format:
	files = strings.Replace(filesTemplate, "DIMSEN", `lang = "en"`, 1)
	files = strings.Replace(files, "DIMSNN", `lang = "nn"`, 1)
	testOne(t, "old", files)
}

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

	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultCOntentVersionInSubDir = true
defaultContentVersion = "v2.0.0"
[taxonomies]
tag = "tags"
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[versions]
[versions."v1.2.3"]
[versions."v2.0.0"]
[module]
[[module.mounts]]
source = 'content/nn'
target = 'content'
[module.mounts.sites.matrix]
languages = ["nn"]
versions  = ["v1.**"]
[[module.mounts]]
source = 'content/en'
target = 'content'
[module.mounts.sites.matrix]
languages = ["**"]
versions  = ["**"]
-- content/en/_index.md --
---
title: "English Home"
tags: ["tag1"]
---
-- content/en/p1.md --
---
title: "English p1"
---
-- content/nn/_index.md --
---
title: "Nynorsk Heim"
tags: ["tag2"]
---
-- layouts/all.html --
title: {{ .Title }}|
tags: {{ range $term, $taxonomy := .Site.Taxonomies.tags }}{{ $term }}: {{ range $taxonomy.Pages }}{{ .Title }}: {{ .RelPermalink}}|{{ end }}{{ end }}$
`

	for range 2 {
		b := hugolib.Test(t, files)

		// b.AssertPublishDir("asdf")
		b.AssertFileContent("public/v1.2.3/nn/index.html", "title: Nynorsk Heim|", "tags: tag2: Nynorsk Heim: /v1.2.3/nn/|$")
		b.AssertFileContent("public/v2.0.0/en/index.html", "title: English Home|", "tags: tag1: English Home: /v2.0.0/en/|$")
		b.AssertFileContent("public/v2.0.0/nn/index.html", "title: English Home|", "tags: tag1: English Home: /v2.0.0/nn/|$") // v2.0.0 is only in English.
		b.AssertFileContent("public/v1.2.3/en/index.html", "title: English Home|")
	}
}

const filesVariationsSitesMatrixBase = `
-- hugo.toml --
baseURL = "https://example.org/"
disableKinds = ["rss", "sitemap", "section"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultCOntentVersionInSubDir = true
defaultContentVersion = "v2.0.0"
defaultContentRole = "guest"
defaultContentRoleInSubDir = true
[taxonomies]
tag = "tags"
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[versions]
[versions."v1.2.3"]
[versions."v1.4.0"]
[versions."v2.0.0"]
[roles]
[roles.guest]
weight = 300
[roles.member]
weight = 200
[module]
[[module.mounts]]
source = 'content/nn'
target = 'content'
[module.mounts.sites.matrix]
languages = ["nn"]
versions  = ["v1.2.*"]
[[module.mounts]]
source = 'content/en'
target = 'content'
[module.mounts.sites.matrix]
languages = ["**"]
versions  = ["**"]
[[module.mounts]]
source = 'content/other'
target = 'content'
-- content/en/_index.md --
---
title: "English Home"
tags: ["tag1"]
---

Ref home: {{< ref "/" >}}|
-- content/en/p1.md --
---
title: "English p1"
---
-- content/nn/_index.md --
---
title: "Nynorsk Heim"
tags: ["tag2"]
---

Ref home: {{< ref "/" >}}|
`

const filesVariationsSitesMatrix = filesVariationsSitesMatrixBase + `
-- layouts/all.html --
title: {{ .Title }}|
tags: {{ range $term, $taxonomy := .Site.Taxonomies.tags }}{{ $term }}: {{ range $taxonomy.Pages }}{{ .Title }}: {{ .RelPermalink}}|{{ end }}{{ end }}$
.Language.IsDefault: {{ with .Rotate "language" }}{{ range . }}{{ .RelPermalink }}: {{ with .Site.Language }}{{ .Name}}: {{ .IsDefault }}|{{ end }}{{ end }}{{ end }}$
.Version.IsDefault: {{ with .Rotate "version" }}{{ range . }}{{ .RelPermalink }}: {{ with .Site.Version }}{{ .Name}}: {{ .IsDefault }}|{{ end }}{{ end }}{{ end }}$
.Role.IsDefault: {{ with .Rotate "role" }}{{ range . }}{{ .RelPermalink }}: {{ with .Site.Role }}{{ .Name}}: {{ .IsDefault }}|{{ end }}{{ end }}{{ end }}$
`

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

	files := filesVariationsSitesMatrix

	files += `
-- content/other/p2.md --
---
title: "NN p2"
sites:
  matrix:
    languages:
      - nn
    versions:
      - v1.2.3
---
`
	b := hugolib.Test(t, files)
	b.AssertFileContent("public/guest/v1.2.3/nn/p2/index.html", "title: NN p2|")
}

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

	files := filesVariationsSitesMatrix

	// nn mount config is nn, v1.2.*.
	files += `
-- content/nn/p2.md --
---
title: "EN p2"
sites:
  matrix:
    languages:
      - en
    versions:
      - v1.4.*
---
`
	b := hugolib.Test(t, files)
	b.AssertFileContent("public/guest/v1.4.0/en/p2/index.html", "title: EN p2|")
}

func TestContentFilesMountSitesMatrixResourcesVersionsAndLanguages(t *testing.T) {
	// The assertions below seems reasonable, but it's also a constructed and very rare corner case
	// that's hard to support without adding too much complexity.
	// Keep the test for now in case I can come up with something simple.
	t.Skip("TODO")
	t.Parallel()

	filesTemplate := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "section"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultContentVersion = "v1.2.3"
defaultContentVersionInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[versions]
[versions."v1.2.3"]
[versions."v2.0.0"]

[[module.mounts]]
source = 'content/v1'
target = 'content'
[module.mounts.sites.matrix]
versions = ["v1.2.*"]
languages = ["*"]
[[module.mounts]]
source = 'content/v2'
target = 'content'
[module.mounts.sites.matrix]
versions = ["v2.0.*"]
languages = ["*"]
-- content/v1/p1/index.en.md --
---
title: "Title English v1"
---
-- content/v2/p1/index.en.md --
---
title: "Title English v2"
---
-- content/v1/p1/index.nn.md --
---
title: "Tittel Nynorsk"
---
-- content/v2/p1/mytext.nn.txt --
Tekst Nynorsk
-- layouts/all.html --
Resources: {{ range .Resources }}{{ .RelPermalink }}|{{ end }}$
`

	b := hugolib.Test(t, filesTemplate)
	b.AssertFileContent("public/v1.2.3/nn/p1/index.html", "Resources: $")
	b.AssertFileContent("public/v1.2.3/en/p1/index.html", "Resources: $")
	b.AssertFileContent("public/v2.0.0/en/p1/index.html", "Resources: /v2.0.0/en/p1/mytext.nn.txt|$")
}

func TestFileMountSitesMatrixResourcesRoles(t *testing.T) {
	filesTemplate := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "section"]
defaultContentRole = "guest"
defaultContentRoleInSubDir = true
[roles]
[roles.guest]
weight = 300
[roles.member]
weight = 200
[mounts]
[[module.mounts]]
source = 'content/guest'
target = 'content'
[module.mounts.sites.matrix]
roles = "guest"
[[module.mounts]]
source = 'content/member'
target = 'content'
[module.mounts.sites.matrix]
roles = "member"
-- layouts/all.html --
{{ .Title }}|{{ .RelPermalink }}|Resources: {{ range .Resources }}{{ .RelPermalink }}|{{ end }}$
-- content/guest/p1/index.md --
---
title: "Guest Gallery"
---
-- content/member/p1/index.md --
---
title: "Member Gallery"
---
-- content/guest/p1/mytext.txt --
Text Guest
-- content/member/p1/mytext2.txt --
Text Member

`

	t.Run("Issue 1", func(t *testing.T) {
		t.Parallel()
		files := strings.Replace(filesTemplate, "content/member/p1/index.md", "content/member/p1_removed/index.md", 1)
		files = strings.Replace(files, `roles = "guest"`, `roles = "*"`, 1)
		b := hugolib.Test(t, files)

		// The current behavior is well intended: We avoid copying the same resources to multiple places.
		// But for the typical role use case, this typically leads to 404 errors for shared resources in the member section.
		b.AssertFileContent("public/guest/p1/index.html", "Guest Gallery|/guest/p1/|Resources: /guest/p1/mytext.txt|/guest/p1/mytext2.txt|$")
		b.AssertFileContent("public/member/p1/index.html", "Guest Gallery|/member/p1/|Resources: /member/p1/mytext.txt|/member/p1/mytext2.txt|$")
	})

	t.Run("Issue 2", func(t *testing.T) {
		t.Parallel()
		files := filesTemplate
		b := hugolib.Test(t, files)

		// This comes from how we handled languages before we added version and role:
		// You would typically add 1 image resources and then translate the markdown files to multiple languages.
		// To make sure that all languages got a complete set when doing Page.Resources, we pull in missing resources from, in this case, the member section.
		// This obviously doesn't work for the role dimension, but it works for the language dimension, and we need to make sure that we don't break that.
		b.AssertFileContent("public/guest/p1/index.html", "Guest Gallery|/guest/p1/|Resources: /guest/p1/mytext.txt|$")
		b.AssertFileContent("public/member/p1/index.html", "Member Gallery|/member/p1/|Resources: /member/p1/mytext2.txt|$")
	})
}

func TestFileMountSitesMatrixResourcesRolesContentAdapter(t *testing.T) {
	files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term", "rss", "sitemap", "section"]
defaultContentRole = "guest"
defaultContentRoleInSubDir = true
[roles]
[roles.guest]
weight = 300
[roles.member]
weight = 200
-- layouts/all.html --
{{ .Title }}|{{ .RelPermalink }}|Resources: {{ range .Resources }}{{ .RelPermalink }}|{{ end }}$
-- content/_content.gotmpl --
{{ $guest := dict "roles" "guest" }}
{{ $member := dict "roles" "member" }}
{{ $contentMarkdownGuest := dict "value" "**Guest**"  "mediaType" "text/markdown" }}
{{ $contentMarkdownMember := dict "value" "**Member**"  "mediaType" "text/markdown" }}
{{ $contentTextGuest:= dict "value" "Guest"  "mediaType" "text/plain" }}
{{ $contentTextMember:= dict "value" "Member"  "mediaType" "text/plain" }}
{{ .AddPage (dict "path" "p1" "title" "P1 guest" "content" $contentMarkdownGuest "sites" (dict "matrix"  $guest )) }}
{{ .AddPage (dict "path" "p1" "title" "P1 member" "content" $contentMarkdownMember "sites" (dict "matrix"  $member )) }}

{{ .AddResource (dict "path" "p1/hello1.txt" "title" "Hello guest" "content" $contentTextGuest "sites" (dict "matrix"  $guest )) }}
{{ .AddResource (dict "path" "p1/hello2.txt" "title" "Hello member" "content" $contentTextMember "sites" (dict "matrix"  $member )) }}
{{ .AddResource (dict "path" "p1/hello3.txt" "title" "Hello member 2" "content" $contentTextMember "sites" (dict "matrix"  $member )) }}

`
	b := hugolib.Test(t, files)
	b.AssertFileContent("public/member/p1/index.html", "P1 member|/member/p1/|Resources: /member/p1/hello2.txt|/member/p1/hello3.txt|$")
	b.AssertFileContent("public/guest/p1/index.html", "P1 guest|/guest/p1/|Resources: /guest/p1/hello1.txt|$")
}

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

	files := filesVariationsSitesMatrixBase + `
-- layouts/all.html --
Title: {{ .Title }}|
Home: {{ with .Site.GetPage "/" }}{{ with .Site }}Language: {{ .Language.Name }}|Version: {{ .Version.Name }}|Role: {{ .Role.Name }}{{ end }}|{{ end }}$
Content: {{ .Content }}$
`

	b := hugolib.Test(t, files)
	b.AssertFileContent(
		"public/guest/v2.0.0/en/index.html", "Home: Language: en|Version: v2.0.0|Role: guest|$",
		"Ref home: https://example.org/guest/v2.0.0/en/|",
	)
	b.AssertFileContent(
		"public/member/v1.4.0/nn/index.html", "Home: Language: nn|Version: v1.4.0|Role: member|$",
	)

	b.AssertFileContent(
		"public/guest/v1.2.3/nn/index.html", "Home: Language: nn|Version: v1.2.3|Role: guest|$",
		"Ref home: https://example.org/guest/v1.2.3/nn/|",
	)
}

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

	files := filesVariationsSitesMatrix

	// nn mount config is nn, v1.2.*.
	// This changes only the language, not the version.
	files += `
-- content/nn/p2.md --
---
title: "EN p2"
sites:
  matrix:
    languages:
      - en
---
`
	b := hugolib.Test(t, files)
	b.AssertFileContent("public/guest/v1.2.3/en/p2/index.html", "title: EN p2|")
}

func TestCascadeMatrixInFrontMatter(t *testing.T) {
	t.Parallel()
	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[languages.sv]
weight = 3
-- content/_index.md --
+++
title = "English home"
[cascade]
[cascade.params]
p1 = "p1cascade"
[cascade.target.sites.matrix]
languages = ["en"]
+++
-- content/_index.nn.md --
+++
title = "Scandinavian home"
[sites.matrix]
languages = "{nn,sv}"
[cascade]
[cascade.params]
p1 = "p1cascadescandinavian"
[cascade.sites.matrix]
languages = "{nn,sv}"
[cascade.target]
path = "**scandinavian**"
+++
-- content/mysection/_index.md --
+++
title = "My section"
[cascade.target.sites.matrix]
languages = "**"
+++
-- content/mysection/p1.md --
+++
title = "English p1"
+++
-- content/mysection/scandinavianpages/p1.md --
+++
title = "Scandinavian p1"
+++
-- layouts/all.html --
{{ .Title }}|{{ .Site.Language.Name }}|{{ .Site.Version.Name }}|p1: {{ .Params.p1 }}|
`
	b := hugolib.Test(t, files)
	b.AssertFileContent("public/en/mysection/p1/index.html", "English p1|en|v1.0.0|p1: p1cascade|")
	b.AssertFileContent("public/sv/mysection/scandinavianpages/p1/index.html", "Scandinavian p1|sv|v1.0.0|p1: p1cascadescandinavian|")
}

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

	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[cascade.params]
p1 = "p1cascadeall"
p2 = "p2cascadeall"
[languages]
[languages.en]
weight = 1
[languages.en.cascade.params]
p1 = "p1cascadeen"
[languages.nn]
weight = 2
[languages.nn.cascade.params]
p1 = "p1cascadenn"
-- content/_index.md --
---
title: "English home"
---
-- content/mysection/p1.md --
---
title: "English p1"
---
-- content/mysection/p1.nn.md --
---
title: "Nynorsk p1"
---
-- layouts/all.html --
{{ .Title }}|{{ .Site.Language.Name }}|{{ .Site.Version.Name }}|p1: {{ .Params.p1 }}|p2: {{ .Params.p2 }}|

`
	b := hugolib.Test(t, files)
	b.AssertFileContent("public/en/mysection/p1/index.html", "English p1|en|v1.0.0|p1: p1cascadeen|p2: p2cascadeall|")
	b.AssertFileContent("public/nn/mysection/p1/index.html", "Nynorsk p1|nn|v1.0.0|p1: p1cascadenn|p2: p2cascadeall|")
}

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

	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[cascade.params]
p1 = "p1cascadeall"
p2 = "p2cascadeall"
-- content/mysection/p1.md --
-- layouts/all.html --
{{ .Title }}|{{ .Kind }}|{{ .Site.Language.Name }}|{{ .Site.Version.Name }}|p1: {{ .Params.p1 }}|p2: {{ .Params.p2 }}|
`

	b := hugolib.Test(t, files)
	b.AssertFileContent("public/en/index.html", "|home|en|v1.0.0|p1: p1cascadeall|p2: p2cascadeall|")
	b.AssertFileContent("public/en/mysection/index.html", "Mysections|section|en|v1.0.0|p1: p1cascadeall|p2: p2cascadeall|")
	b.AssertFileContent("public/en/mysection/p1/index.html", "|page|en|v1.0.0|p1: p1cascadeall|p2: p2cascadeall|")
}

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

	// Pick language from mount, role from cascade and version from front matter.
	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultCOntentVersionInSubDir = true
defaultContentVersion = "v1.2.3"
defaultContentRole = "guest"
defaultContentRoleInSubDir = true

[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2

[versions]
[versions."v1.2.3"]
[versions."v1.4.0"]
[versions."v2.0.0"]

[roles]
[roles.guest]
weight = 300
[roles.member]
weight = 200

[[module.mounts]]
source = 'content/other'
target = 'content'
[module.mounts.sites.matrix]
languages = ["nn"] # used.
versions = ["v1.2.*"] # not used.
roles = ["guest"] # not used.
[module.mounts.sites.complements]
languages = ["en"] # used.
versions = ["v1.4.*"] # not used.
roles = ["member"] # not used.

[cascade.sites.matrix]
roles = ["member"] # used
versions = ["v1.2.*"] # not used.
[cascade.sites.complements]
roles = ["guest"] # used
versions = ["v2**"] # not used.

-- content/other/p2.md --
+++
title = "NN p2"
[sites.matrix]
versions = ["v1.2.*","v1.4.*"]
[sites.complements]
versions = ["v2.*.*"]
+++
-- content/other/p3.md --
+++
title = "NN p3"
[sites.matrix]
versions = "v1.4.*"
+++
-- layouts/all.html --
All. {{ site.Language.Name }}|{{ site.Version.Name }}|{{ site.Role.Name }}

`

	b := hugolib.Test(t, files)
	b.AssertFileContent("public/member/v1.4.0/nn/p2/index.html", "All.")
	s := b.SiteHelper("nn", "v1.4.0", "member")
	p2 := s.PageHelper("/p2")
	s.Assert(p2.MatrixFromPageConfig(), qt.DeepEquals,
		map[string]map[string][]string{
			"complements": {
				"languages": {"en"},
				"roles":     {"guest"},
				"versions":  {"v2.0.0"},
			},
			"matrix": {
				"languages": {"nn"},
				"roles":     {"member"},
				"versions":  {"v1.4.0", "v1.2.3"},
			},
		},
	)

	p3 := s.PageHelper("/p3")
	s.Assert(p3.MatrixFromPageConfig(), qt.DeepEquals, map[string]map[string][]string{
		"complements": {
			"languages": {"en"},
			"roles":     {"guest"},
			"versions":  {"v2.0.0"},
		},
		"matrix": {
			"languages": {"nn"},
			"roles":     {"member"},
			"versions":  {"v1.4.0"},
		},
	})
}

func TestLanguageVersionRoleIsDefault(t *testing.T) {
	files := filesVariationsSitesMatrix

	b := hugolib.Test(t, files)
	b.AssertFileContent("public/guest/v2.0.0/en/index.html",
		".Language.IsDefault: /guest/v2.0.0/en/: en: true|/guest/v2.0.0/nn/: nn: false|$",
		".Version.IsDefault: /guest/v2.0.0/en/: v2.0.0: true|/guest/v1.4.0/en/: v1.4.0: false|/guest/v1.2.3/en/: v1.2.3: false|$",
		".Role.IsDefault: /member/v2.0.0/en/: member: false|/guest/v2.0.0/en/: guest: true|$",
	)
}

func TestModuleMountsLanguageOverlap(t *testing.T) {
	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
theme = "mytheme"
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[[module.mounts]]
source = 'content'
target = 'content'
[module.mounts.sites.matrix]
languages = "en"
-- content/p1.md --
---
title: "Project p1"
---
-- themes/mytheme/hugo.toml --

[[module.mounts]]
source = 'content'
target = 'content'
[module.mounts.sites.matrix]
languages = "**"
-- themes/mytheme/content/p1.md --
---
title: "Theme p1"
---
-- themes/mytheme/content/p2.md --
---
title: "Theme p1"
---
-- layouts/all.html --
{{ .Title }}|
`

	b := hugolib.Test(t, files)

	sEn := b.SiteHelper("en", "", "")
	sNN := b.SiteHelper("nn", "", "guest")

	b.Assert(sEn.PageHelper("/p1").MatrixFromPageConfig()["matrix"], qt.DeepEquals, map[string][]string{"languages": {"en"}, "roles": {"guest"}, "versions": {"v1.0.0"}})
	b.Assert(sEn.PageHelper("/p2").MatrixFromPageConfig()["matrix"], qt.DeepEquals, map[string][]string{
		"languages": {"en", "nn"},
		"roles":     {"guest"},
		"versions":  {"v1.0.0"},
	})

	p1NN := sNN.PageHelper("/p1")
	p2NN := sNN.PageHelper("/p2")

	b.Assert(p2NN.MatrixFromPageConfig()["matrix"], qt.DeepEquals, map[string][]string{
		"languages": {"en", "nn"},
		"roles":     {"guest"},
		"versions":  {"v1.0.0"},
	})

	b.Assert(p1NN.MatrixFromFile()["matrix"], qt.DeepEquals, map[string][]string{
		"languages": {"nn"}, // It's defined as ** in the mount.
		"roles":     {"guest"},
		"versions":  {"v1.0.0"},
	})
}

func TestMountLanguageComplements(t *testing.T) {
	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.sv]
weight = 2
[languages.da]
weight = 3
[[module.mounts]]
source = 'content/en'
target = 'content'
[module.mounts.sites.matrix]
languages = "en"
[module.mounts.sites.complements]
languages = "{sv,da}"
[[module.mounts]]
source = 'content/sv'
target = 'content'
[module.mounts.sites.matrix]
languages = "sv"
-- content/en/p1.md --
---
title: "English p1"
---
-- content/en/p2.md --
---
title: "English p2"
---
-- content/sv/p1.md --
---
title: "Swedish p1"
---
-- layouts/home.html --
RegularPagesRecursive: {{ range .RegularPagesRecursive }}{{ .Title }}|{{ .RelPermalink }}{{ end }}$
-- layouts/all.html --
{{ .Title }}|{{ .Site.Language.Name }}|
`

	b := hugolib.Test(t, files)
	b.AssertFileContent("public/en/index.html", "RegularPagesRecursive: English p1|/en/p1/English p2|/en/p2/$")
	b.AssertFileContent("public/sv/index.html", "RegularPagesRecursive: English p2|/en/p2/Swedish p1|/sv/p1/$")
	b.AssertFileContent("public/da/index.html", "RegularPagesRecursive: English p1|/en/p1/English p2|/en/p2/$")
}

func TestContentAdapterSitesMatrixResources(t *testing.T) {
	t.Parallel()
	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[languages.sv]
weight = 3
[languages.da]
weight = 4
-- content/_content.gotmpl --
{{ $scandinavia := dict "languages" "{nn,sv}" }}
{{ $en := dict "languages" "en" }}
{{ $da := dict "languages" "da" }}
{{ $contentMarkdown := dict "value" "**Hello World**"  "mediaType" "text/markdown" }}
{{ $contentTextEnglish := dict "value" "Hello World"  "mediaType" "text/plain" }}
{{ $contentTextNorsk := dict "value" "Hallo verd"  "mediaType" "text/plain" }}
{{ .AddPage (dict "path" "p1" "title" "P1 en" "content" $contentMarkdown "sites" (dict "matrix"  $en )) }}
{{ .AddPage (dict "path" "p1" "title" "P1 scandinavia" "content" $contentMarkdown "sites" (dict "matrix"  $scandinavia )) }}
{{ .AddPage (dict "path" "p1" "title" "P1 da" "content" $contentMarkdown "sites" (dict "matrix"  $da )) }}
{{ .AddResource (dict "path" "p1/hello.txt" "title" "Hello en" "content" $contentTextEnglish "sites" (dict "matrix"  $en )) }}
{{ .AddResource (dict "path" "p1/hello.txt" "title" "Hello scandinavia" "content" $contentTextNorsk "sites" (dict "matrix"  $scandinavia "complements" $da )) }}
-- layouts/all.html --
len .Resources: {{ len .Resources}}|
{{ $hello := .Resources.Get "hello.txt" }}
All. {{ .Title }}|Hello: {{ with $hello }}{{ .RelPermalink }}|{{ .Content | safeHTML }}{{ end }}|
`

	b := hugolib.Test(t, files)
	b.AssertFileContent("public/en/p1/index.html", "Hello: /en/p1/hello.txt|Hello World|")
	b.AssertFileContent("public/nn/p1/index.html", "P1 scandinavia|", "/nn/p1/hello.txt|Hallo verd|")
	b.AssertFileContent("public/sv/p1/index.html", "P1 scandinavia|", "/sv/p1/hello.txt|Hallo verd|")
	b.AssertFileContent("public/da/p1/index.html", "P1 da|", "/sv/p1/hello.txt|Hallo verd|") // Because it's closest of the Scandinavian complements.
}

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

	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
disableLiveReload = true
[languages]
[languages.en]
weight = 1
[languages.sv]
weight = 2
[languages.da]
weight = 3
[languages.no]
weight = 4
-- content/_content.gotmpl --
{{ $contentMarkdown := dict "value" "**Hello World**"  "mediaType" "text/markdown" }}
{{ $en := dict "languages" "en" }}
{{ $sv := dict "languages" "sv" }}
{{ $da := dict "languages" "da" }}
{{ $no := dict "languages" "no" }}
{{ .AddPage (dict "path" "p1" "title" "P1-1" "content" $contentMarkdown "sites" (dict "matrix"  $en )) }}
{{ .AddPage (dict "path" "p1" "title" "P1-2" "content" $contentMarkdown "sites" (dict "matrix"  $sv )) }}
{{ .AddPage (dict "path" "p1" "title" "P1-3" "content" $contentMarkdown "sites" (dict "matrix"  $da )) }}
-- layouts/all.html --
All. {{ .Title }}|{{ .Site.Language.Name }}|
`

	b := hugolib.TestRunning(t, files)

	b.AssertFileExists("public/no/p1/index.html", false)
	b.AssertFileExists("public/sv/p1/index.html", true)

	b.RemovePublishDir()
	b.AssertFileExists("public/sv/p1/index.html", false)

	b.EditFileReplaceAll("content/_content.gotmpl", `"sv"`, `"no"`).Build()

	b.AssertFileExists("public/no/p1/index.html", true)
	b.AssertFileExists("public/sv/p1/index.html", false)
}

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

	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
disableLiveReload = true
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.sv]
weight = 2
[languages.da]
weight = 3
[languages.no]
weight = 4
-- content/_content.gotmpl --
{{ $contentTextEnglish := dict "value" "Hello World"  "mediaType" "text/plain" }}
{{ $contentTextSwedish := dict "value" "Hei världen"  "mediaType" "text/plain" }}
{{ $contentTextDanish := dict "value" "Hej verden"  "mediaType" "text/plain" }}

{{ $en := dict "languages" "en" }}
{{ $sv := dict "languages" "sv" }}
{{ $da := dict "languages" "da" }}
{{ .AddResource (dict "path" "hello.txt" "title" "Hello" "content" $contentTextEnglish "sites" (dict "matrix"  $en )) }}
{{ .AddResource (dict "path" "hello.txt" "title" "Hello" "content" $contentTextSwedish "sites" (dict "matrix"  $sv )) }}
{{ .AddResource (dict "path" "hello.txt" "title" "Hello" "content" $contentTextDanish "sites" (dict "matrix"  $da )) }}

-- layouts/all.html --
{{ $hello := .Resources.Get "hello.txt" }}
All. {{ .Title }}|{{ .Site.Language.Name }}|hello: {{ with $hello }}{{ .RelPermalink }}: {{ .Content }}|{{ end }}|
`

	b := hugolib.TestRunning(t, files)

	b.AssertFileContent("public/en/index.html", "en|hello: /en/hello.txt: Hello World|")
	b.AssertFileContent("public/sv/index.html", "sv|hello: /sv/hello.txt: Hei världen|")
	b.AssertFileContent("public/no/index.html", "no|hello: |")

	b.EditFileReplaceAll("content/_content.gotmpl", `"sv"`, `"no"`).Build()

	b.AssertFileContent("public/no/index.html", "no|hello: /no/hello.txt: Hei världen|")
	b.AssertFileContent("public/en/index.html", "en|hello: /en/hello.txt: Hello World|")
	b.AssertFileContent("public/sv/index.html", "|sv|hello: |")
}

const filesContentAdapterSitesMatrixFromConfig = `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultContentRole = "enrole"
defaultContentRoleInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.en.cascade.sites.matrix]
roles = ["enrole"]
[languages.nn]
weight = 2
[languages.nn.cascade.sites.matrix]
roles = ["nnrole"]
[roles]
[roles.enrole]
weight = 200
[roles.nnrole]
weight = 100
-- layouts/all.html --
All. {{ .Title }}|{{ .Site.Language.Name }}|{{ .Site.Role.Name }}|
Resources: {{ range .Resources }}{{ .Title }}|{{ end }}$

`

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

	files := filesContentAdapterSitesMatrixFromConfig + `
-- content/_content.gotmpl --
{{ $title := printf "P1 %s:%s" .Site.Language.Name .Site.Role.Name }}
{{ .AddPage (dict "path" "p1" "title" $title ) }}
`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/enrole/en/p1/index.html", "P1 en:enrole|en|enrole|")
	b.AssertPublishDir("! nn/p1")
}

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

	files := filesContentAdapterSitesMatrixFromConfig + `
-- content/_content.gotmpl --
{{ .EnableAllLanguages }}
{{ $title := printf "P1 %s:%s" .Site.Language.Name .Site.Role.Name }}
{{ .AddPage (dict "path" "p1" "title" $title ) }}
{{ $.AddResource (dict "path" "p1/mytext.txt" "title" $title "content" (dict "value" $title) )}}
`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/enrole/en/p1/index.html", "P1 en:enrole|en|enrole|", "Resources: P1 en:enrole|$")
	b.AssertFileContent("public/nnrole/nn/p1/index.html", "P1 nn:nnrole|nn|nnrole|", "Resources: P1 nn:nnrole|$")
}

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

	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
[languages]
[languages.en]
weight = 1
[languages.nn]
weight = 2
[languages.sv]
weight = 3
[languages.da]
weight = 4
[languages.de]
weight = 5
-- content/p1.en.md --
---
title: "P1 en"
---
-- content/p1._scandinavian_.md --
---
title: "P1 scandinavian"
sites:
  matrix:
    languages: "{nn,sv,da}"
---
-- content/p1.de.md --
---
title: "P1 de"
---
-- layouts/all.html --
All.{{ .Title }}|
`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/en/p1/index.html", "All.P1 en|")
	b.AssertFileContent("public/nn/p1/index.html", "All.P1 scandinavian|")
	b.AssertFileContent("public/sv/p1/index.html", "All.P1 scandinavian|")
	b.AssertFileContent("public/da/p1/index.html", "All.P1 scandinavian|")
	b.AssertFileContent("public/de/p1/index.html", "All.P1 de|")
}

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

	files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
-- layouts/all.html --
All. {{ .Title }}|Lang: {{ .Site.Language.Name }}|Ver: {{ .Site.Version.Name }}|Role: {{ .Site.Role.Name }}|
`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/index.html", "All. |Lang: en|Ver: v1.0.0|Role: guest|")
}

func newSitesMatrixContentBenchmarkBuilder(t testing.TB, numPages int, skipRender, multipleDimensions bool) *hugolib.IntegrationTestBuilder {
	var files strings.Builder
	files.WriteString(`
-- hugo.toml --
disableKinds = ["rss", "sitemap", "section", "taxonomy", "term"]
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultCOntentVersionInSubDir = true
defaultContentVersion = "v1.0.0"
defaultContentRole = "guest"
defaultContentRoleInSubDir = true
title = "Benchmark"

[languages]
[languages.en]
weight = 1


`)

	if multipleDimensions {
		files.WriteString(`
[languages.nn]
weight = 2
[languages.sv]
weight = 3

[versions]
[versions."v1.0.0"]
[versions."v2.0.0"]

[roles]
[roles.guest]
weight = 300
[roles.member]
weight = 200
`)
	}

	files.WriteString(`

[[module.mounts]]
source = 'content'
target = 'content'
[module.mounts.sites.matrix]
languages = ["**"]
versions  = ["**"]
roles = ["**"]
-- layouts/all.html --
All. {{ .Title }}|
`)
	if multipleDimensions {
		files.WriteString(`
Rotate(language): {{ with .Rotate "language" }}{{ range . }}{{ template "printp" . }}|{{ end }}{{ end }}$
Rotate(version): {{ with .Rotate "version" }}{{ range . }}{{ template "printp" . }}|{{ end }}{{ end }}$
Rotate(role): {{ with .Rotate "role" }}{{ range . }}{{ template "printp" . }}|{{ end }}{{ end }}$
{{ define "printp" }}{{ .RelPermalink }}:{{ with .Site }}{{ template "prints" . }}{{ end }}{{ end }}
{{ define "prints" }}/l:{{ .Language.Name }}/v:{{ .Version.Name }}/r:{{ .Role.Name }}{{ end }}

`)
	}

	for i := range numPages {
		files.WriteString(fmt.Sprintf(`
-- content/p%d/index.md --
---
title: "P%d"
---
`, i+1, i+1))
	}

	b := hugolib.NewIntegrationTestBuilder(
		hugolib.IntegrationTestConfig{
			T:           t,
			TxtarString: files.String(),
			BuildCfg: hugolib.BuildCfg{
				SkipRender: skipRender,
			},
		})
	return b
}

// See #14132. We recently reworked the config structs for languages, versions, and roles,
// which made them incomplete when generating the docshelper YAML file.
// Add a test here to ensure we don't regress.
func TestUnmarshalSitesMatrixConfig(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
defaultContentLanguage = "en"
defaultContentLanguageInSubDir = true
defaultCOntentVersionInSubDir = true
defaultContentVersion = "v1.0.0"
defaultContentRole = "guest"
defaultContentRoleInSubDir = true

[moule.mounts]
source = 'content'
target = 'content'


[languages]
[languages.en]

[versions]
[versions."v1.0.0"]

[roles]
[roles.guest]

`

	b := hugolib.Test(t, files)

	toJSONAndMap := func(v any) map[string]any {
		bb, err := json.Marshal(v)
		b.Assert(err, qt.IsNil)
		var m map[string]any
		err = json.Unmarshal(bb, &m)
		b.Assert(err, qt.IsNil)
		return m
	}

	conf := b.H.Configs.Base

	b.Assert(toJSONAndMap(conf.Languages), qt.DeepEquals,
		map[string]any{
			"en": map[string]any{
				"Direction": "",
				"Disabled":  bool(false),
				"Label":     "",
				"Locale":    "",
				"Title":     "",
				"Weight":    float64(0),
			},
		})

	b.Assert(toJSONAndMap(conf.Versions), qt.DeepEquals, map[string]any{
		"v1.0.0": map[string]any{
			"Weight": float64(0),
		},
	})

	b.Assert(toJSONAndMap(conf.Roles), qt.DeepEquals, map[string]any{
		"guest": map[string]any{
			"Weight": float64(0),
		},
	})

	firstMount := conf.Module.Mounts[0]
	b.Assert(toJSONAndMap(firstMount.Sites.Matrix), qt.DeepEquals, map[string]any{
		"languages": nil,
		"versions":  nil,
		"roles":     nil,
	})
	b.Assert(toJSONAndMap(firstMount.Sites.Complements), qt.DeepEquals, map[string]any{
		"languages": nil,
		"versions":  nil,
		"roles":     nil,
	})
}

func TestSitesMatrixContentBenchmark(t *testing.T) {
	const numPages = 3
	b := newSitesMatrixContentBenchmarkBuilder(t, numPages, false, true)

	b.Build()

	for _, lang := range []string{"en", "nn", "sv"} {
		for _, ver := range []string{"v1.0.0", "v2.0.0"} {
			for _, role := range []string{"guest", "member"} {
				base := "public/" + role + "/" + ver + "/" + lang
				b.AssertFileContent(base+"/index.html", "All. Benchmark|")
				for i := range numPages {
					b.AssertFileContent(fmt.Sprintf("%s/p%d/index.html", base, i+1), fmt.Sprintf("All. P%d|", i+1))
				}

			}
		}
	}
}

func BenchmarkSitesMatrixContent(b *testing.B) {
	for _, numPages := range []int{10, 100} {
		for _, multipleDimensions := range []bool{false, true} {
			b.Run(fmt.Sprintf("n%d/md%t", numPages, multipleDimensions), func(b *testing.B) {
				for b.Loop() {
					b.StopTimer()
					bb := newSitesMatrixContentBenchmarkBuilder(b, numPages, true, multipleDimensions)
					b.StartTimer()
					bb.Build()
				}
			})
		}
	}
}

// Just a test to test the development of concurrency in page assembly.
func TestCreateAllPagesPartitionSections(t *testing.T) {
	files := `
-- hugo.toml --
baseURL = "https://example.org/"
disableKinds = ["rss", "sitemap", "taxonomy", "term"]
-- content/_index.md --
-- content/s1/_index.md --
-- content/s1/p1.md --
-- content/s1/p2.md --
-- content/s2/_index.md --
-- content/s2/p1.md --
-- content/s2_1/_index.md --
-- content/s2_1/p1.md --
-- layouts/all.html --
{{ .Kind }}|{{ .RelPermalink }}|
`

	for range 3 {
		b := hugolib.Test(t, files)
		b.AssertFileContent("public/s1/index.html", "section|/s1/|")
	}
}

const defaultContentDimensionRedirectsFiletemplate = `
-- hugo.toml --
baseURL = "BASE_URL_PLACEHOLDER"
disableKinds = ["sitemap", "taxonomy", "term"]
defaultContentLanguageInSubDir = true
defaultContentVersionInSubDir = true
defaultContentRoleInSubDir = true
disableDefaultLanguageRedirect = false
disableDefaultSiteRedirect = false
defaultContentLanguage = "en"
defaultContentVersion = "v1.0.0"
defaultContentRole = "guest"
[languages]
[languages.en]
weight = 2
[languages.nn]
weight = 1
[versions]
[versions."v1.0.0"]
weight = 2
[versions."v2.0.0"]
weight = 1
[roles]
[roles.guest]
[roles.member]
[outputs]
home = [ 'rss', 'amp', 'html']
-- layouts/home.html --
Home.
`

// Issue 14354
func TestSitesMatrixRedirects(t *testing.T) {
	t.Parallel()

	files := strings.ReplaceAll(defaultContentDimensionRedirectsFiletemplate, "BASE_URL_PLACEHOLDER", "https://example.org/")

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/index.html", "url=https://example.org/guest/v1.0.0/en/\"", `<link rel="canonical" href="https://example.org/guest/v1.0.0/en/">`)
	b.AssertFileContent("public/amp/index.html", "url=https://example.org/guest/v1.0.0/en/amp/\"", `<link rel="canonical" href="https://example.org/guest/v1.0.0/en/">`)
	b.AssertFileContent("public/guest/index.html", "url=https://example.org/guest/v1.0.0/en/\"")
	b.AssertFileContent("public/guest/v1.0.0/index.html", "url=https://example.org/guest/v1.0.0/en/\"")
	b.AssertFileContent("public/guest/v1.0.0/en/index.html", "Home.")
	b.AssertFileContent("public/guest/v1.0.0/en/amp/index.html", "Home.")
}

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

	files := strings.ReplaceAll(defaultContentDimensionRedirectsFiletemplate, "BASE_URL_PLACEHOLDER", "https://example.org/docs/")

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/index.html", "url=https://example.org/docs/guest/v1.0.0/en/\"")
	b.AssertFileContent("public/amp/index.html", "url=https://example.org/docs/guest/v1.0.0/en/amp/\"", `<link rel="canonical" href="https://example.org/docs/guest/v1.0.0/en/">`)
	b.AssertFileContent("public/guest/index.html", "url=https://example.org/docs/guest/v1.0.0/en/\"")
	b.AssertFileContent("public/guest/v1.0.0/index.html", "url=https://example.org/docs/guest/v1.0.0/en/\"")
	b.AssertFileContent("public/guest/v1.0.0/en/index.html", "Home.")
}

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

	files := strings.ReplaceAll(defaultContentDimensionRedirectsFiletemplate, "BASE_URL_PLACEHOLDER", "https://example.org/")

	runTest := func(files string) {
		b := hugolib.Test(t, files)

		b.AssertFileExists("public/index.html", false)
		b.AssertFileExists("public/guest/index.html", false)
		b.AssertFileExists("public/guest/v1.0.0/index.html", false)
		b.AssertFileContent("public/guest/v1.0.0/en/index.html", "Home.")
	}

	runTest(strings.ReplaceAll(files, "disableDefaultLanguageRedirect = false", "disableDefaultLanguageRedirect = true"))
	runTest(strings.ReplaceAll(files, "disableDefaultSiteRedirect = false", "disableDefaultSiteRedirect = true"))
}

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

	files := strings.ReplaceAll(defaultContentDimensionRedirectsFiletemplate, "BASE_URL_PLACEHOLDER", "https://example.org/")
	files = strings.ReplaceAll(files, "defaultContentVersionInSubDir = true", "defaultContentVersionInSubDir = false")

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/index.html", "url=https://example.org/guest/en/\"")
	b.AssertFileContent("public/guest/index.html", "url=https://example.org/guest/en/\"")
	b.AssertFileExists("public/guest/v1.0.0/index.html", false)
	b.AssertFileContent("public/guest/en/index.html", "Home.")
}

func TestSitesMatrixRedirectsMultiHost(t *testing.T) {
	t.Parallel()
	//
	files := `
-- hugo.toml --
disableKinds = ["sitemap", "taxonomy", "term"]
defaultContentLanguageInSubDir = true
defaultContentVersionInSubDir = true
defaultContentRoleInSubDir = true
disableDefaultLanguageRedirect = false
disableDefaultSiteRedirect = false
defaultContentLanguage = "en"
[languages]
[languages.en]
weight = 1
baseURL = "https://en.example.org/"
[languages.nn]
weight = 2
baseURL = "https://nn.example.org/"
[outputs]
home = [ 'rss', 'amp', 'html']
-- layouts/home.html --
Home.
`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/en/index.html", "url=https://en.example.org/guest/v1.0.0/\"", `<link rel="canonical" href="https://en.example.org/guest/v1.0.0/">`)
	b.AssertFileContent("public/en/amp/index.html", "url=https://en.example.org/guest/v1.0.0/amp/\"", `<link rel="canonical" href="https://en.example.org/guest/v1.0.0/">`)
	b.AssertFileContent("public/en/guest/index.html", "url=https://en.example.org/guest/v1.0.0/\"")
	b.AssertFileContent("public/en/guest/v1.0.0/index.html", "Home.")
	b.AssertFileContent("public/en/guest/v1.0.0/amp/index.html", "Home.")
}

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

	files := `
-- hugo.yml --
baseURL: https://example.org
disableKinds: [taxonomy]
defaultContentVersionInSubDir: true
defaultContentRoleInSubDir: true
defaultContentLanguage: en
languages:
  en:
    label: English
  bn:
    label: Bengali
  fr:
    label: French
-- layouts/all.html --
All.

`
	b := hugolib.Test(t, files)

	b.AssertFileContent("public/guest/v1.0.0/en/index.html", "url=https://example.org/guest/v1.0.0/\"", `lang="en"`)

	files = strings.ReplaceAll(files, "defaultContentVersionInSubDir: true", "defaultContentVersionInSubDir: false")

	b = hugolib.Test(t, files)
	b.AssertFileContent("public/guest/en/index.html", "url=https://example.org/guest/\"")
}

func TestSitesMatrixRangeMatchers(t *testing.T) {
	t.Parallel()
	files := `TestSitesMatrixRangeMatchers
-- hugo.toml --
baseURL = "https://example.org/"
defaultContentVersion = "v4.0.0"
defaultContentVersionInSubDir = true
disableKinds = ["taxonomy", "term", "rss", "sitemap"]

[languages]
[languages.en]
weight = 1

[versions]
[versions."v1.0.0"]
[versions."v2.0.0"]
[versions."v2.1.0"]
[versions."v3.0.0"]
[versions."v4.0.0"]

-- content/_index.md --
---
title: "Home"
sites:
  matrix:
    versions: ["**"]
---
-- content/p1.md --
---
title: "P1 from v2.0.0"
sites:
  matrix:
    versions: [">= v2.0.0"]
---
Content for v2.0.0 and later.
-- content/p2.md --
---
title: "P2 between v2 and v3"
sites:
  matrix:
    versions: [">= v2.0.0", "<= v3.0.0"]
---
Content between v2.0.0 and v3.0.0 (inclusive).
-- content/p3.md --
---
title: "P3 before v3"
sites:
  matrix:
    versions: ["< v3.0.0"]
---
Content before v3.0.0.
-- content/p4.md --
---
title: "P4 after v2"
sites:
  matrix:
    versions: ["> v2.0.0"]
---
Content after v2.0.0 (exclusive).
-- content/p5.md --
---
title: "P5 range with negation"
sites:
  matrix:
    versions: [">= v2.0.0", "! v2.1.0", "<= v4.0.0"]
---
Content from v2.0.0 to v4.0.0 but not v2.1.0.
-- layouts/all.html --
Title: {{ .Title }}|
`

	b := hugolib.Test(t, files)

	// P1: >= v2.0.0 should be in v2.0.0, v2.1.0, v3.0.0, v4.0.0
	b.AssertFileContent("public/v2.0.0/p1/index.html", "Title: P1 from v2.0.0|")
	b.AssertFileContent("public/v2.1.0/p1/index.html", "Title: P1 from v2.0.0|")
	b.AssertFileContent("public/v3.0.0/p1/index.html", "Title: P1 from v2.0.0|")
	b.AssertFileContent("public/v4.0.0/p1/index.html", "Title: P1 from v2.0.0|")
	b.AssertFileExists("public/v1.0.0/p1/index.html", false)

	// P2: >= v2.0.0 AND <= v3.0.0 should be in v2.0.0, v2.1.0, v3.0.0
	b.AssertFileContent("public/v2.0.0/p2/index.html", "Title: P2 between v2 and v3|")
	b.AssertFileContent("public/v2.1.0/p2/index.html", "Title: P2 between v2 and v3|")
	b.AssertFileContent("public/v3.0.0/p2/index.html", "Title: P2 between v2 and v3|")
	b.AssertFileExists("public/v1.0.0/p2/index.html", false)
	b.AssertFileExists("public/v4.0.0/p2/index.html", false)

	// P3: < v3.0.0 should be in v1.0.0, v2.0.0, v2.1.0
	b.AssertFileContent("public/v1.0.0/p3/index.html", "Title: P3 before v3|")
	b.AssertFileContent("public/v2.0.0/p3/index.html", "Title: P3 before v3|")
	b.AssertFileContent("public/v2.1.0/p3/index.html", "Title: P3 before v3|")
	b.AssertFileExists("public/v3.0.0/p3/index.html", false)
	b.AssertFileExists("public/v4.0.0/p3/index.html", false)

	// P4: > v2.0.0 should be in v2.1.0, v3.0.0, v4.0.0
	b.AssertFileContent("public/v2.1.0/p4/index.html", "Title: P4 after v2|")
	b.AssertFileContent("public/v3.0.0/p4/index.html", "Title: P4 after v2|")
	b.AssertFileContent("public/v4.0.0/p4/index.html", "Title: P4 after v2|")
	b.AssertFileExists("public/v1.0.0/p4/index.html", false)
	b.AssertFileExists("public/v2.0.0/p4/index.html", false)

	// P5: >= v2.0.0 AND ! v2.1.0 AND <= v4.0.0 should be in v2.0.0, v3.0.0, v4.0.0
	b.AssertFileContent("public/v2.0.0/p5/index.html", "Title: P5 range with negation|")
	b.AssertFileContent("public/v3.0.0/p5/index.html", "Title: P5 range with negation|")
	b.AssertFileContent("public/v4.0.0/p5/index.html", "Title: P5 range with negation|")
	b.AssertFileExists("public/v1.0.0/p5/index.html", false)
	b.AssertFileExists("public/v2.1.0/p5/index.html", false) // Excluded by negation
}

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

	files := `
-- hugo.toml --
baseURL = "https://example.org/"
defaultContentVersion = "v1.0.0"
defaultContentVersionInSubDir = true
disableKinds = ["taxonomy", "term", "rss", "sitemap"]

[versions]
[versions."v1.0.0"]
[versions."v2.0.0"]

[[module.mounts]]
source = 'content/v1'
target = 'content'
[module.mounts.sites.matrix]
versions  = ["**"]
[[module.mounts]]
source = 'content/v21'
target = 'content'
[module.mounts.sites.matrix]
versions  = ["v2.0.0"]
[[module.mounts]]
source = 'content/v22'
target = 'content'
[module.mounts.sites.matrix]
versions  = ["v2.0.0"]
-- content/v1/p1.md --
---
title: "P1 v1"
---
-- content/v21/p1.md --
---
title: "P1 v21"
---
-- content/v22/p1.md --
---
title: "P1 v22"
---
-- layouts/all.html --
Title: {{ .Title }}|
`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/v1.0.0/p1/index.html", "Title: P1 v1|")
	b.AssertFileContent("public/v2.0.0/p1/index.html", "Title: P1 v1|")
}

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

	files := `
-- hugo.toml --

disableKinds = ["taxonomy", "term", "rss", "sitemap"]
defaultContentVersion = "v1"
defaultContentVersionInSubDir = true

[versions]
[versions."v1"]
[versions."v2"]
[[module.mounts]]
source = 'content'
target = 'content'
[module.mounts.sites.matrix]
versions = '> v2.0.0'
[[module.imports]]
ignoreConfig = true
path         = 'mytheme'
[[module.imports.mounts]]
source = 'content'
target = 'content'
[module.imports.mounts.sites.matrix]
versions = '<= v2.0.0'

-- themes/mytheme/content/p1.md --
---
title: "P1 from theme"
---
-- content/p1.md --
---
title: "P1 from site"
---
-- layouts/all.html --
Title: {{ .Title }}|Version: {{ .Site.Version.Name }}|

`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/v1/p1/index.html", "Title: P1 from theme|Version: v1|")
}

func TestSitesMatrixVersionsEditTemplate(t *testing.T) {
	files := `
-- hugo.toml --
defaultcontentVersion = "v1"
defaultcontentVersionInSubDir = true
[versions]
[versions."v1"]
[versions."v2"]
-- layouts/all.html --
Version: {{ .Site.Version.Name }}|
`

	b := hugolib.TestRunning(t, files)

	b.AssertFileContent("public/v1/index.html", "Version: v1|")

	b.EditFileReplaceAll("layouts/all.html", "Version:", "Edited version:").Build()

	b.AssertFileContent("public/v1/index.html", "Edited version: v1|")
}

// Issue 14540
func TestSiteComplementsOverrideAutomaticSectionPages(t *testing.T) {
	t.Parallel()

	files := `
-- hugo.toml --
disableKinds = ['rss', 'sitemap', 'taxonomy', 'term']
defaultContentLanguageInSubdir = true

[languages.en]
  weight = 1

[languages.de]
  weight = 2

[[module.mounts]]
  source = 'content/en'
  target = 'content'
  [module.mounts.sites.matrix]
    languages = ['en']
  [module.mounts.sites.complements]
    languages = ['de']

[[module.mounts]]
  source = 'content/de'
  target = 'content'
  [module.mounts.sites.matrix]
    languages = ['de']
-- layouts/home.html --
<ul>
{{ range $k, $_ := site.Pages -}}
  <li>[{{ $k }}] <a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end -}}
</ul>
-- layouts/page.html --
{{ .Title }}|
-- layouts/section.html --
{{ .Title }}|
-- content/en/_index.md --
---
title: home en
---
-- content/en/s1/_index.md --
---
title: s1 en
---
-- content/en/s1/p1.md --
---
title: p1 en
---
-- content/en/s2/_index.md --
---
title: s2 en
---
-- content/en/s2/p2.md --
---
title: p2 en
---
-- content/de/s1/p1.md --
---
title: p1 de
---
`

	b := hugolib.Test(t, files)

	b.AssertFileContent("public/en/index.html", `
<ul>
<li>[0] <a href="/en/">home en</a></li>
<li>[1] <a href="/en/s1/p1/">p1 en</a></li>
<li>[2] <a href="/en/s2/p2/">p2 en</a></li>
<li>[3] <a href="/en/s1/">s1 en</a></li>
<li>[4] <a href="/en/s2/">s2 en</a></li>
</ul>
	`)

	b.AssertFileContent("public/de/index.html", `
<ul>
<li>[0] <a href="/en/">home en</a></li>
<li>[1] <a href="/de/s1/p1/">p1 de</a></li>
<li>[2] <a href="/en/s2/p2/">p2 en</a></li>
<li>[3] <a href="/en/s1/">s1 en</a></li>
<li>[4] <a href="/en/s2/">s2 en</a></li>
</ul>
	`)
}

// Issue 14756.
func TestFilenameIdentifierShouldReplaceMountDimension(t *testing.T) {
	t.Parallel()
	files := `
-- hugo.toml --
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
defaultContentLanguage = "en"
defaultContentRole = "guest"
defaultContentRoleInSubdir = true
[roles]
[roles.guest]
weight = 100
[roles.member]
weight = 200
[module]
[[module.mounts]]
source = 'content/member'
target = 'content'
[module.mounts.sites.matrix]
roles = "member"
[[module.mounts]]
source = 'content/guest'
target = 'content'
[module.mounts.sites.matrix]
roles = "*"
-- content/member/bundle/index.md --
---
title: "Bundle"
---
-- content/guest/bundle/index.md --
---
title: "Bundle"
---
-- content/guest/bundle/img._role_member_.txt --
memberimg
-- content/guest/bundle/guestimg.txt --
guestimg
-- layouts/page.html --
Role: {{ .Site.Role.Name }}|{{ range .Resources }}{{ .Name }}|{{ end }}$
-- layouts/list.html --
List.
`

	b := hugolib.Test(t, files)

	// The guest role should only see guestimg.txt (not img._role_member_.txt).
	b.AssertFileContent("public/guest/bundle/index.html", "Role: guest|guestimg.txt|$")
	// The member role should see both.
	b.AssertFileContent("public/member/bundle/index.html", "Role: member|guestimg.txt|img._role_member_.txt|$")
}
