// Copyright 2019 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"
	"sync/atomic"

	"github.com/gohugoio/hugo/hugofs/files"
	"github.com/gohugoio/hugo/resources"

	"github.com/gohugoio/hugo/common/maps"
	"github.com/gohugoio/hugo/common/paths"

	"github.com/gohugoio/hugo/lazy"

	"github.com/gohugoio/hugo/resources/kinds"
	"github.com/gohugoio/hugo/resources/page"
	"github.com/gohugoio/hugo/resources/page/pagemeta"
)

var pageIDCounter atomic.Uint64

func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
	p, pth, err := h.doNewPage(m)
	if err != nil {
		// Make sure that any partially created page part is marked as stale.
		m.MarkStale()
	}
	return p, pth, err
}

func (h *HugoSites) doNewPage(m *pageMeta) (*pageState, *paths.Path, error) {
	m.Staler = &resources.AtomicStaler{}
	if m.pageMetaParams == nil {
		m.pageMetaParams = &pageMetaParams{
			pageConfig: &pagemeta.PageConfig{},
		}
	}
	if m.pageConfig.Params == nil {
		m.pageConfig.Params = maps.Params{}
	}

	pid := pageIDCounter.Add(1)
	pi, err := m.parseFrontMatter(h, pid)
	if err != nil {
		return nil, nil, err
	}

	if err := m.setMetaPre(pi, h.Log, h.Conf); err != nil {
		return nil, nil, m.wrapError(err, h.BaseFs.SourceFs)
	}
	pcfg := m.pageConfig
	if pcfg.Lang != "" {
		if h.Conf.IsLangDisabled(pcfg.Lang) {
			return nil, nil, nil
		}
	}

	if pcfg.Path != "" {
		s := m.pageConfig.Path
		// Paths from content adapters should never have any extension.
		if pcfg.IsFromContentAdapter || !paths.HasExt(s) {
			var (
				isBranch    bool
				isBranchSet bool
				ext         string = m.pageConfig.ContentMediaType.FirstSuffix.Suffix
			)
			if pcfg.Kind != "" {
				isBranch = kinds.IsBranch(pcfg.Kind)
				isBranchSet = true
			}

			if !pcfg.IsFromContentAdapter {
				if m.pathInfo != nil {
					if !isBranchSet {
						isBranch = m.pathInfo.IsBranchBundle()
					}
					if m.pathInfo.Ext() != "" {
						ext = m.pathInfo.Ext()
					}
				} else if m.f != nil {
					pi := m.f.FileInfo().Meta().PathInfo
					if !isBranchSet {
						isBranch = pi.IsBranchBundle()
					}
					if pi.Ext() != "" {
						ext = pi.Ext()
					}
				}
			}

			if isBranch {
				s += "/_index." + ext
			} else {
				s += "/index." + ext
			}

		}
		m.pathInfo = h.Conf.PathParser().Parse(files.ComponentFolderContent, s)
	} else if m.pathInfo == nil {
		if m.f != nil {
			m.pathInfo = m.f.FileInfo().Meta().PathInfo
		}

		if m.pathInfo == nil {
			panic(fmt.Sprintf("missing pathInfo in %v", m))
		}
	}

	ps, err := func() (*pageState, error) {
		if m.s == nil {
			// Identify the Site/language to associate this Page with.
			var lang string
			if pcfg.Lang != "" {
				lang = pcfg.Lang
			} else if m.f != nil {
				meta := m.f.FileInfo().Meta()
				lang = meta.Lang
			} else {
				lang = m.pathInfo.Lang()
			}

			m.s = h.resolveSite(lang)

			if m.s == nil {
				return nil, fmt.Errorf("no site found for language %q", lang)
			}
		}

		var tc viewName
		// Identify Page Kind.
		if m.pageConfig.Kind == "" {
			m.pageConfig.Kind = kinds.KindSection
			if m.pathInfo.Base() == "/" {
				m.pageConfig.Kind = kinds.KindHome
			} else if m.pathInfo.IsBranchBundle() {
				// A section, taxonomy or term.
				tc = m.s.pageMap.cfg.getTaxonomyConfig(m.Path())
				if !tc.IsZero() {
					// Either a taxonomy or a term.
					if tc.pluralTreeKey == m.Path() {
						m.pageConfig.Kind = kinds.KindTaxonomy
					} else {
						m.pageConfig.Kind = kinds.KindTerm
					}
				}
			} else if m.f != nil {
				m.pageConfig.Kind = kinds.KindPage
			}
		}

		if m.pageConfig.Kind == kinds.KindTerm || m.pageConfig.Kind == kinds.KindTaxonomy {
			if tc.IsZero() {
				tc = m.s.pageMap.cfg.getTaxonomyConfig(m.Path())
			}
			if tc.IsZero() {
				return nil, fmt.Errorf("no taxonomy configuration found for %q", m.Path())
			}
			m.singular = tc.singular
			if m.pageConfig.Kind == kinds.KindTerm {
				m.term = paths.TrimLeading(strings.TrimPrefix(m.pathInfo.Unnormalized().Base(), tc.pluralTreeKey))
			}
		}

		if m.pageConfig.Kind == kinds.KindPage && !m.s.conf.IsKindEnabled(m.pageConfig.Kind) {
			return nil, nil
		}

		// Parse the rest of the page content.
		m.content, err = m.newCachedContent(h, pi)
		if err != nil {
			return nil, m.wrapError(err, h.SourceFs)
		}

		ps := &pageState{
			pid:                               pid,
			pageOutput:                        nopPageOutput,
			pageOutputTemplateVariationsState: &atomic.Uint32{},
			resourcesPublishInit:              &sync.Once{},
			Staler:                            m,
			dependencyManager:                 m.s.Conf.NewIdentityManager(m.Path()),
			pageCommon: &pageCommon{
				FileProvider:              m,
				store:                     maps.NewScratch(),
				Positioner:                page.NopPage,
				InSectionPositioner:       page.NopPage,
				ResourceNameTitleProvider: m,
				ResourceParamsProvider:    m,
				PageMetaProvider:          m,
				PageMetaInternalProvider:  m,
				RelatedKeywordsProvider:   m,
				OutputFormatsProvider:     page.NopPage,
				ResourceTypeProvider:      pageTypesProvider,
				MediaTypeProvider:         pageTypesProvider,
				RefProvider:               page.NopPage,
				ShortcodeInfoProvider:     page.NopPage,
				LanguageProvider:          m.s,

				InternalDependencies: m.s,
				init:                 lazy.New(),
				m:                    m,
				s:                    m.s,
				sWrapped:             page.WrapSite(m.s),
			},
		}

		if m.f != nil {
			gi, err := m.s.h.gitInfoForPage(ps)
			if err != nil {
				return nil, fmt.Errorf("failed to load Git data: %w", err)
			}
			ps.gitInfo = gi
			owners, err := m.s.h.codeownersForPage(ps)
			if err != nil {
				return nil, fmt.Errorf("failed to load CODEOWNERS: %w", err)
			}
			ps.codeowners = owners
		}

		ps.pageMenus = &pageMenus{p: ps}
		ps.PageMenusProvider = ps.pageMenus
		ps.GetPageProvider = pageSiteAdapter{s: m.s, p: ps}
		ps.GitInfoProvider = ps
		ps.TranslationsProvider = ps
		ps.ResourceDataProvider = &pageData{pageState: ps}
		ps.RawContentProvider = ps
		ps.ChildCareProvider = ps
		ps.TreeProvider = pageTree{p: ps}
		ps.Eqer = ps
		ps.TranslationKeyProvider = ps
		ps.ShortcodeInfoProvider = ps
		ps.AlternativeOutputFormatsProvider = ps

		if err := ps.initLazyProviders(); err != nil {
			return nil, ps.wrapError(err)
		}
		return ps, nil
	}()

	if ps == nil {
		return nil, nil, err
	}

	return ps, ps.PathInfo(), err
}
