// 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 tpl contains template functions and related types.
package tpl

import (
	"context"
	"slices"
	"strings"
	"sync"
	"unicode"

	"github.com/bep/helpers/contexthelpers"
	bp "github.com/gohugoio/hugo/bufferpool"
	"github.com/gohugoio/hugo/common/collections"

	"github.com/gohugoio/hugo/identity"
	"github.com/gohugoio/hugo/langs"

	htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
	texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
)

// Template is the common interface between text/template and html/template.
type Template interface {
	Name() string
	Prepare() (*texttemplate.Template, error)
}

// RenderingContext represents the currently rendered site/language.
type RenderingContext struct {
	Site       site
	SiteOutIdx int
}

type (
	contextKey uint8
)

const (
	contextKeyDependencyManagerScopedProvider contextKey = iota
	contextKeyDependencyScope
	contextKeyPage
	contextKeyIsInGoldmark
	cntextKeyCurrentTemplateInfo
	contextKeyPartialDecoratorIDStack
	contextKeyIsInPartialCached
)

// Context manages values passed in the context to templates.
var Context = struct {
	DependencyManagerScopedProvider    contexthelpers.ContextDispatcher[identity.DependencyManagerScopedProvider]
	GetDependencyManagerInCurrentScope func(context.Context) identity.Manager
	DependencyScope                    contexthelpers.ContextDispatcher[int]
	Page                               contexthelpers.ContextDispatcher[page]
	IsInGoldmark                       contexthelpers.ContextDispatcher[bool]
	CurrentTemplate                    contexthelpers.ContextDispatcher[*CurrentTemplateInfo]
	PartialDecoratorIDStack            contexthelpers.ContextDispatcher[*collections.Stack[*StringBool]]
	IsInPartialCached                  contexthelpers.ContextDispatcher[bool]
}{
	DependencyManagerScopedProvider: contexthelpers.NewContextDispatcher[identity.DependencyManagerScopedProvider](contextKeyDependencyManagerScopedProvider),
	DependencyScope:                 contexthelpers.NewContextDispatcher[int](contextKeyDependencyScope),
	Page:                            contexthelpers.NewContextDispatcher[page](contextKeyPage),
	IsInGoldmark:                    contexthelpers.NewContextDispatcher[bool](contextKeyIsInGoldmark),
	CurrentTemplate:                 contexthelpers.NewContextDispatcher[*CurrentTemplateInfo](cntextKeyCurrentTemplateInfo),
	PartialDecoratorIDStack:         contexthelpers.NewContextDispatcher[*collections.Stack[*StringBool]](contextKeyPartialDecoratorIDStack),
	IsInPartialCached:               contexthelpers.NewContextDispatcher[bool](contextKeyIsInPartialCached),
}

func init() {
	Context.GetDependencyManagerInCurrentScope = func(ctx context.Context) identity.Manager {
		idmsp := Context.DependencyManagerScopedProvider.Get(ctx)
		if idmsp != nil {
			return idmsp.GetDependencyManagerForScope(Context.DependencyScope.Get(ctx))
		}
		return nil
	}
}

// StringBool is a helper struct to hold a string and a bool value.
type StringBool struct {
	Str  string
	Bool bool
}

type page interface {
	IsBranch() bool
}

type site interface {
	Language() *langs.Language
}

const (
	// HugoDeferredTemplatePrefix is the prefix for placeholders for deferred templates.
	HugoDeferredTemplatePrefix = "__hdeferred/"
	// HugoDeferredTemplateSuffix is the suffix for placeholders for deferred templates.
	HugoDeferredTemplateSuffix = "__d="
)

const hugoNewLinePlaceholder = "___hugonl_"

var stripHTMLReplacerPre = strings.NewReplacer("\n", " ", "</p>", hugoNewLinePlaceholder, "<br>", hugoNewLinePlaceholder, "<br />", hugoNewLinePlaceholder)

// StripHTML strips out all HTML tags in s.
func StripHTML(s string) string {
	// Shortcut strings with no tags in them
	if !strings.ContainsAny(s, "<>") {
		return s
	}

	pre := stripHTMLReplacerPre.Replace(s)
	preReplaced := pre != s

	s = htmltemplate.StripTags(pre)

	if preReplaced {
		s = strings.ReplaceAll(s, hugoNewLinePlaceholder, "\n")
	}

	var wasSpace bool
	b := bp.GetBuffer()
	defer bp.PutBuffer(b)
	for _, r := range s {
		isSpace := unicode.IsSpace(r)
		if !(isSpace && wasSpace) {
			b.WriteRune(r)
		}
		wasSpace = isSpace
	}

	if b.Len() > 0 {
		s = b.String()
	}

	return s
}

// DeferredExecution holds the template and data for a deferred execution.
type DeferredExecution struct {
	Mu           sync.Mutex
	Ctx          context.Context
	TemplatePath string
	Data         any

	Executed bool
	Result   string
}

type CurrentTemplateInfoOps interface {
	CurrentTemplateInfoCommonOps
	Base() CurrentTemplateInfoCommonOps
}

type CurrentTemplateInfoCommonOps interface {
	// Template name.
	Name() string
	// Template source filename.
	// Will be empty for internal templates.
	Filename() string
}

// CurrentTemplateInfo as returned in templates.Current.
type CurrentTemplateInfo struct {
	Parent *CurrentTemplateInfo
	Level  int
	Key    string
	CurrentTemplateInfoOps
}

// CurrentTemplateInfos is a slice of CurrentTemplateInfo.
type CurrentTemplateInfos []*CurrentTemplateInfo

// Reverse creates a copy of the slice and reverses it.
func (c CurrentTemplateInfos) Reverse() CurrentTemplateInfos {
	if len(c) == 0 {
		return c
	}
	r := make(CurrentTemplateInfos, len(c))
	copy(r, c)
	slices.Reverse(r)
	return r
}

// Ancestors returns the ancestors of the current template.
func (ti *CurrentTemplateInfo) Ancestors() CurrentTemplateInfos {
	var ancestors []*CurrentTemplateInfo
	for ti.Parent != nil {
		ti = ti.Parent
		ancestors = append(ancestors, ti)
	}
	return ancestors
}
