/*
 * Amsterdam Web Communities System
 * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 */
// Package ui holds the support for the Amsterdam user interface, wrapping Echo and Jet templates.
package ui

import (
	"context"
	_ "embed"
	"fmt"
	"slices"
	"strconv"
	"strings"
	"sync"

	"git.erbosoft.com/amy/amsterdam/database"
	"git.erbosoft.com/amy/amsterdam/util"
	lru "github.com/hashicorp/golang-lru"
	"gopkg.in/yaml.v3"
)

// MenuItem represents an item within a menu definition.
type MenuItem struct {
	Text       string `yaml:"text"`
	Link       string `yaml:"link"`
	Disabled   bool   `yaml:"disabled"`
	Hazard     bool   `yaml:"hazard"`
	Permission string `yaml:"permission"`
	Ifdef      string `yaml:"ifdef"`
	P          *MenuDefinition
}

// Show checks permissions to see if we can display the menu item.
func (mi *MenuItem) Show(ctxt AmContext) bool {
	if mi.Permission == "" {
		return true
	}
	u := ctxt.CurrentUser()
	var eperm uint16
	switch mi.P.PermSet {
	case "user":
		eperm = u.BaseLevel
	case "community":
		eperm = ctxt.EffectiveLevel()
	default:
		eperm = database.AmRole("NotInList").Level()
	}
	if util.IsNumeric(mi.Permission) {
		v, _ := strconv.Atoi(mi.Permission)
		return uint16(v) <= eperm
	}
	switch mi.P.PermSet {
	case "user":
		return database.AmTestPermission(mi.Permission, eperm)
	case "community":
		return ctxt.CurrentCommunity().TestPermission(mi.Permission, eperm)
	default:
		return false
	}
}

// MenuDefinition represents a full menu definition.
type MenuDefinition struct {
	ID       string     `yaml:"id"`
	Title    string     `yaml:"title"`
	Subtitle string     `yaml:"subtitle"`
	PermSet  string     `yaml:"permSet"`
	Warning  string     `yaml:"warning"`
	Items    []MenuItem `yaml:"items"`
	Tag      string
}

// FilterCommunity creates a copy of this menu filtered to the specified community.
func (mdef *MenuDefinition) FilterCommunity(comm *database.Community) *MenuDefinition {
	newmd := MenuDefinition{
		ID:       mdef.ID,
		Title:    mdef.Title,
		Subtitle: strings.ReplaceAll(mdef.Subtitle, "[CNAME]", comm.Name),
		PermSet:  mdef.PermSet,
		Warning:  mdef.Warning,
		Items:    make([]MenuItem, len(mdef.Items)),
		Tag:      mdef.Tag,
	}
	for i, it := range mdef.Items {
		newmd.Items[i].Text = it.Text
		newmd.Items[i].Link = strings.ReplaceAll(it.Link, "[CID]", comm.Alias)
		newmd.Items[i].Disabled = it.Disabled
		newmd.Items[i].Hazard = it.Hazard
		newmd.Items[i].Permission = it.Permission
		newmd.Items[i].Ifdef = it.Ifdef
		newmd.Items[i].P = &newmd
	}
	return &newmd
}

// FilterConference creates a copy of this menu filtered to the specified community and conference.
func (mdef *MenuDefinition) FilterConference(comm *database.Community, confAlias string) *MenuDefinition {
	newmd := MenuDefinition{
		ID:       mdef.ID,
		Title:    mdef.Title,
		Subtitle: strings.ReplaceAll(mdef.Subtitle, "[CNAME]", comm.Name),
		PermSet:  mdef.PermSet,
		Warning:  mdef.Warning,
		Items:    make([]MenuItem, len(mdef.Items)),
		Tag:      mdef.Tag,
	}
	for i, it := range mdef.Items {
		newmd.Items[i].Text = it.Text
		s1 := strings.ReplaceAll(it.Link, "[CID]", comm.Alias)
		newmd.Items[i].Link = strings.ReplaceAll(s1, "[CONFID]", confAlias)
		newmd.Items[i].Disabled = it.Disabled
		newmd.Items[i].Hazard = it.Hazard
		newmd.Items[i].Permission = it.Permission
		newmd.Items[i].Ifdef = it.Ifdef
		newmd.Items[i].P = &newmd
	}
	return &newmd
}

// MenuDefs represents the set of all menu definitions.
type MenuDefs struct {
	D     []MenuDefinition `yaml:"menudefs"`
	table map[string]*MenuDefinition
}

//go:embed menudefs.yaml
var initMenuData []byte

// menuDefinitions gives the menu definitions.
var menuDefinitions MenuDefs

// Cache of community menus.
var menuCache *lru.Cache

// Mutex controlling access to the cache.
var menuCacheMutex sync.Mutex

// init loads the menu definitions.
func init() {
	var err error
	if menuCache, err = lru.New(100); err != nil {
		panic(err)
	}
	if err = yaml.Unmarshal(initMenuData, &menuDefinitions); err != nil {
		panic(err) // can't happen
	}
	menuDefinitions.table = make(map[string]*MenuDefinition)
	for i, d := range menuDefinitions.D {
		menuDefinitions.table[d.ID] = &(menuDefinitions.D[i])
		for j := range menuDefinitions.D[i].Items {
			menuDefinitions.D[i].Items[j].P = &(menuDefinitions.D[i])
		}
		menuDefinitions.D[i].Tag = ""
	}
}

// AmMenu returns a menu definition.
func AmMenu(name string) *MenuDefinition {
	return menuDefinitions.table[name]
}

/* AmBuildCommunityMenu buids a community menu for the specified community.
 * Parameters:
 *     ctx - Standard Go context value.
 *     comm - The community to build the menu for.
 * Returns:
 *     The new menu definition.
 *     Standard Go error status.
 */
func AmBuildCommunityMenu(ctx context.Context, comm *database.Community) (*MenuDefinition, error) {
	menuCacheMutex.Lock()
	defer menuCacheMutex.Unlock()
	m, ok := menuCache.Get(comm.Id)
	if ok {
		return m.(*MenuDefinition), nil
	}
	sdef, err := database.AmGetCommunityServices(ctx, comm.Id)
	if err != nil {
		return nil, err
	}
	slices.SortFunc(sdef, func(a, b *database.ServiceDef) int {
		return a.LinkSequence - b.LinkSequence
	})
	mia := make([]MenuItem, len(sdef))
	md := MenuDefinition{
		ID:      "community",
		Title:   comm.Name,
		PermSet: "community",
		Tag:     "community",
		Items:   mia,
	}
	for i, sd := range sdef {
		mia[i].Text = sd.Title
		mia[i].Link = strings.ReplaceAll(sd.Link, "[CID]", comm.Alias)
		mia[i].Disabled = false
		mia[i].P = &md
		if sd.RequirePermission == "" {
			if sd.RequireRole == "" {
				mia[i].Permission = ""
			} else {
				mia[i].Permission = fmt.Sprintf("%d", database.AmRole(sd.RequireRole).Level())
			}
		} else if sd.RequireRole == "" {
			mia[i].Permission = sd.RequirePermission
		} else {
			v1 := comm.PermissionLevel(sd.RequirePermission)
			v2 := database.AmRole(sd.RequireRole).Level()
			if v2 > v1 {
				v1 = v2
			}
			mia[i].Permission = fmt.Sprintf("%d", v1)
		}
	}
	menuCache.Add(comm.Id, &md)
	return &md, nil
}
