/*
Copyright © 2025 Benny Powers <web@bennypowers.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package mcp

import (
	"encoding/json"
	"fmt"
	"math"
	"strings"
	"sync"

	LSP "bennypowers.dev/cem/lsp"
	"bennypowers.dev/cem/lsp/document"
	"bennypowers.dev/cem/lsp/helpers"
	lspTypes "bennypowers.dev/cem/lsp/types"
	M "bennypowers.dev/cem/manifest"
	"bennypowers.dev/cem/mcp/constants"
	"bennypowers.dev/cem/mcp/security"
	MCPTypes "bennypowers.dev/cem/mcp/types"
	"bennypowers.dev/cem/types"
	V "bennypowers.dev/cem/validate"
)


// MCPContext manages custom elements manifests for MCP context
// This is a lightweight wrapper around the LSP registry for reuse
type MCPContext struct {
	workspace       types.WorkspaceContext
	lspRegistry     *LSP.Registry
	documentManager lspTypes.DocumentManager
	mcpCache        map[string]MCPTypes.ElementInfo // Cache for converted MCP elements

	// Lazy-computed cached values for performance
	commonPrefixes     []string // Common element tag name prefixes
	allCSSProperties   []string // All CSS custom properties across elements
	computedCacheValid bool     // Whether computed cache is valid

	mu sync.RWMutex
}

// MCPCustomElementDeclaration embeds manifest types with MCP-specific data
type MCPCustomElementDeclaration struct {
	*M.RenderableCustomElementDeclaration
	Guidelines  []string `json:"guidelines,omitempty"`
	CacheKey    string   `json:"cacheKey,omitempty"`
	ModulePath  string   `json:"modulePath,omitempty"`
	PackageName string   `json:"packageName,omitempty"`
}

// NewMCPCustomElementDeclaration creates a new MCP custom element declaration from a manifest element
func NewMCPCustomElementDeclaration(element *M.CustomElement, tagName string) *MCPCustomElementDeclaration {
	// Create a minimal CustomElementDeclaration from the CustomElement
	decl := &M.CustomElementDeclaration{
		CustomElement: *element,
	}
	decl.Name = tagName
	decl.TagName = tagName

	// Create a RenderableCustomElementDeclaration
	renderable := &M.RenderableCustomElementDeclaration{
		CustomElementDeclaration: decl,
	}

	// Extract guidelines from element description and attribute descriptions
	guidelines := extractGuidelinesFromElement(element)

	return &MCPCustomElementDeclaration{
		RenderableCustomElementDeclaration: renderable,
		Guidelines:                         guidelines,
		CacheKey:                          generateCacheKey(tagName, element),
		ModulePath:                        "",  // Set by registry when available
		PackageName:                       "",  // Set by registry when available
	}
}

// Use the interface from mcp/types instead of struct alias


// Helper functions

// getTypeString safely extracts type text from manifest Type

// NewMCPContext creates a new MCP context
func NewMCPContext(workspace types.WorkspaceContext) (*MCPContext, error) {
	helpers.SafeDebugLog("Creating MCP registry for workspace: %s", workspace.Root())

	// Create the underlying LSP registry for reuse
	lspRegistry, err := LSP.NewRegistryWithDefaults()
	if err != nil {
		return nil, fmt.Errorf("failed to create LSP registry: %w", err)
	}

	// Create a shared document manager for validation
	documentManager, err := document.NewDocumentManager()
	if err != nil {
		return nil, fmt.Errorf("failed to create document manager: %w", err)
	}

	context := &MCPContext{
		workspace:       workspace,
		lspRegistry:     lspRegistry,
		documentManager: documentManager,
		mcpCache:        make(map[string]MCPTypes.ElementInfo),
	}

	return context, nil
}

// LoadManifests loads all available manifests
func (ctx *MCPContext) LoadManifests() error {
	ctx.mu.Lock()
	defer ctx.mu.Unlock()

	helpers.SafeDebugLog("Loading manifests for MCP context")

	// Clear the cache when reloading manifests
	ctx.mcpCache = make(map[string]MCPTypes.ElementInfo)

	// Invalidate computed cache
	ctx.computedCacheValid = false
	ctx.commonPrefixes = nil
	ctx.allCSSProperties = nil

	if err := ctx.lspRegistry.LoadFromWorkspace(ctx.workspace); err != nil {
		return fmt.Errorf("failed to load manifests from workspace %q: %w", ctx.workspace.Root(), err)
	}
	return nil
}

// CommonPrefixes returns common element tag name prefixes (lazy-computed and cached)
func (ctx *MCPContext) CommonPrefixes() []string {
	ctx.mu.RLock()
	if ctx.computedCacheValid && ctx.commonPrefixes != nil {
		defer ctx.mu.RUnlock()
		return ctx.commonPrefixes
	}
	ctx.mu.RUnlock()

	ctx.mu.Lock()
	defer ctx.mu.Unlock()

	// Double-check after acquiring write lock
	if ctx.computedCacheValid && ctx.commonPrefixes != nil {
		return ctx.commonPrefixes
	}

	ctx.ensureComputedCacheValidLocked()
	return ctx.commonPrefixes
}

// AllCSSProperties returns all CSS custom properties across elements (lazy-computed and cached)
func (ctx *MCPContext) AllCSSProperties() []string {
	ctx.mu.RLock()
	if ctx.computedCacheValid && ctx.allCSSProperties != nil {
		defer ctx.mu.RUnlock()
		return ctx.allCSSProperties
	}
	ctx.mu.RUnlock()

	ctx.mu.Lock()
	defer ctx.mu.Unlock()

	// Double-check after acquiring write lock
	if ctx.computedCacheValid && ctx.allCSSProperties != nil {
		return ctx.allCSSProperties
	}

	ctx.ensureComputedCacheValidLocked()
	return ctx.allCSSProperties
}

// registrySnapshot represents an immutable snapshot of registry state for cache computation
type registrySnapshot struct {
	elements map[string]MCPTypes.ElementInfo
}

// takeRegistrySnapshot creates an atomic snapshot of the current registry state
// This method must be called while holding at least a read lock
func (ctx *MCPContext) takeRegistrySnapshot() (*registrySnapshot, error) {
	// Extract all tag names from registry
	tagNames := make([]string, 0, len(ctx.lspRegistry.Elements))
	for tagName := range ctx.lspRegistry.Elements {
		tagNames = append(tagNames, tagName)
	}

	// Convert elements to MCPTypes while holding the lock
	// This avoids the need for lock juggling later
	elements := make(map[string]MCPTypes.ElementInfo, len(tagNames))
	for _, tagName := range tagNames {
		element := ctx.lspRegistry.Elements[tagName]
		if element != nil {
			// Convert element using the existing conversion logic
			info := ctx.convertElement(element, tagName)
			elements[tagName] = info
		}
	}

	return &registrySnapshot{
		elements: elements,
	}, nil
}

// ensureComputedCacheValidLocked computes cached values if invalid (must hold write lock)
func (ctx *MCPContext) ensureComputedCacheValidLocked() {
	if ctx.computedCacheValid {
		return
	}

	// Take atomic snapshot of registry state while holding the lock
	snapshot, err := ctx.takeRegistrySnapshot()
	if err != nil {
		// Initialize with empty slices on error - cache computation is not critical
		ctx.commonPrefixes = []string{}
		ctx.allCSSProperties = []string{}
		ctx.computedCacheValid = true
		return
	}

	// Convert snapshot elements to slice for computation
	elements := make([]MCPTypes.ElementInfo, 0, len(snapshot.elements))
	for _, element := range snapshot.elements {
		elements = append(elements, element)
	}

	// Compute cached values from consistent snapshot
	// No lock juggling needed - all data is from atomic snapshot
	ctx.commonPrefixes = ctx.computeCommonPrefixes(elements)
	ctx.allCSSProperties = ctx.computeAllCSSProperties(elements)

	// Mark cache as valid
	ctx.computedCacheValid = true
}

// computeCommonPrefixes extracts common prefixes from element tag names
func (ctx *MCPContext) computeCommonPrefixes(elements []MCPTypes.ElementInfo) []string {
	prefixCount := make(map[string]int)

	for _, element := range elements {
		tagName := element.TagName()
		if parts := strings.Split(tagName, "-"); len(parts) > 1 {
			prefix := parts[0]
			prefixCount[prefix]++
		}
	}

	// Return prefixes used by multiple elements
	prefixes := make([]string, 0) // Initialize as empty slice, not nil
	for prefix, count := range prefixCount {
		if count > 1 {
			prefixes = append(prefixes, prefix)
		}
	}

	return prefixes
}

// computeAllCSSProperties extracts all CSS custom properties from all elements
func (ctx *MCPContext) computeAllCSSProperties(elements []MCPTypes.ElementInfo) []string {
	propertySet := make(map[string]bool)

	for _, element := range elements {
		for _, prop := range element.CssProperties() {
			propertySet[prop.Name] = true
		}
	}

	properties := make([]string, 0, len(propertySet))
	for prop := range propertySet {
		properties = append(properties, prop)
	}

	return properties
}

// DocumentManager returns the shared document manager for validation
func (ctx *MCPContext) DocumentManager() lspTypes.DocumentManager {
	return ctx.documentManager
}

// GetElementInfo returns enhanced element information for MCP context
func (ctx *MCPContext) GetElementInfo(tagName string) (MCPTypes.ElementInfo, error) {
	ctx.mu.RLock()

	// Check cache first
	if cached, exists := ctx.mcpCache[tagName]; exists {
		ctx.mu.RUnlock()
		return cached, nil
	}

	// Get basic element from LSP registry
	element := ctx.lspRegistry.Elements[tagName]
	if element == nil {
		ctx.mu.RUnlock()
		return nil, fmt.Errorf("failed to get element info for %q: element not found in registry", tagName)
	}
	ctx.mu.RUnlock()

	// Convert to enhanced MCP format (outside of read lock to avoid blocking)
	info := ctx.convertElement(element, tagName)

	// Cache the result
	ctx.mu.Lock()
	ctx.mcpCache[tagName] = info
	ctx.mu.Unlock()

	return info, nil
}

// GetAllElements returns all available elements
func (ctx *MCPContext) GetAllElements() map[string]MCPTypes.ElementInfo {
	ctx.mu.RLock()
	// Extract tag names while holding the lock
	tagNames := make([]string, 0, len(ctx.lspRegistry.Elements))
	for tagName := range ctx.lspRegistry.Elements {
		tagNames = append(tagNames, tagName)
	}
	ctx.mu.RUnlock()

	// Convert elements outside the lock to avoid deadlock
	elements := make(map[string]MCPTypes.ElementInfo)
	for _, tagName := range tagNames {
		if info, err := ctx.GetElementInfo(tagName); err == nil {
			elements[tagName] = info
		}
	}

	return elements
}

// GetManifestSchema returns the JSON schema for custom elements manifests
func (ctx *MCPContext) GetManifestSchema() (map[string]interface{}, error) {
	// Use the same schema detection and retrieval logic as the schema resource
	versions := ctx.GetManifestSchemaVersions()

	// If no manifests found, use latest stable version as fallback
	schemaVersion := constants.DefaultSchemaVersion
	if len(versions) == 1 {
		schemaVersion = versions[0]
	} else if len(versions) > 1 {
		// Multiple versions: prefer the highest version, with speculative versions favored
		schemaVersion = ctx.selectBestSchemaVersion(versions)
	}

	// Get the actual JSON schema using the same method as the validate command and schema resource
	schemaData, err := V.GetSchema(schemaVersion)
	if err != nil {
		return nil, fmt.Errorf("failed to load schema: %w", err)
	}

	// Parse the JSON schema data into a map for return
	var schema map[string]interface{}
	if err := json.Unmarshal(schemaData, &schema); err != nil {
		return nil, fmt.Errorf("failed to parse schema JSON: %w", err)
	}

	return schema, nil
}

// GetManifestSchemaVersions returns all unique schema versions found in loaded manifests
func (ctx *MCPContext) GetManifestSchemaVersions() []string {
	ctx.mu.RLock()
	defer ctx.mu.RUnlock()

	if ctx.lspRegistry == nil {
		return []string{}
	}

	versionSet := make(map[string]struct{})
	var versions []string

	// Extract schema versions from all loaded manifests
	for _, manifest := range ctx.lspRegistry.Manifests {
		if manifest != nil && manifest.SchemaVersion != "" {
			if _, exists := versionSet[manifest.SchemaVersion]; !exists {
				versionSet[manifest.SchemaVersion] = struct{}{}
				versions = append(versions, manifest.SchemaVersion)
			}
		}
	}

	return versions
}

// selectBestSchemaVersion chooses the best schema version from multiple options
func (ctx *MCPContext) selectBestSchemaVersion(versions []string) string {
	// Fallback if empty
	if len(versions) == 0 {
		return constants.DefaultSchemaVersion
	}

	// Simple heuristic: prefer speculative versions, then highest semantic version
	var best string
	var hasSpeculative bool

	for _, version := range versions {
		if version == "" {
			continue
		}

		// Always prefer speculative versions as they are most complete
		if strings.Contains(version, "speculative") {
			if !hasSpeculative || version > best {
				best = version
				hasSpeculative = true
			}
		} else if !hasSpeculative {
			// Among non-speculative versions, take the highest
			if best == "" || version > best {
				best = version
			}
		}
	}

	// If no valid version found, use fallback
	if best == "" {
		return constants.DefaultSchemaVersion
	}

	return best
}

// Helper methods for converting LSP types to MCP types

// convertElement converts a manifest element to enhanced MCP format using the new constructor
func (ctx *MCPContext) convertElement(element *M.CustomElement, tagName string) MCPTypes.ElementInfo {
	// Use the proper constructor to create the MCP element declaration
	mcpElement := NewMCPCustomElementDeclaration(element, tagName)

	return &MCPElementInfoAdapter{
		MCPCustomElementDeclaration: mcpElement,
	}
}

// Helper methods for extracting guidelines and examples

// extractGuidelinesFromElement extracts usage guidelines from element description and attributes
func extractGuidelinesFromElement(element *M.CustomElement) []string {
	var guidelines []string

	// Note: CustomElement doesn't have Description directly - it's in the embedded ClassDeclaration
	// For now, we'll extract from attributes only until we have full access to the declaration
	for _, attr := range element.Attributes {
		if attr.Description != "" {
			guidelines = append(guidelines, fmt.Sprintf("%s: %s", attr.Name, attr.Description))
		}
	}

	return guidelines
}


// generateCacheKey generates a unique cache key for the element
func generateCacheKey(tagName string, element *M.CustomElement) string {
	// Use tag name for basic cache key
	return tagName
}


// MCPContextAdapter implements MCPTypes.MCPContext interface for tools package
type MCPContextAdapter struct {
	*MCPContext
}

// NewMCPContextAdapter creates an adapter that implements the tools interface
func NewMCPContextAdapter(context *MCPContext) MCPTypes.MCPContext {
	return &MCPContextAdapter{MCPContext: context}
}

// ElementInfo implements MCPTypes.MCPContext interface
func (ctx *MCPContextAdapter) ElementInfo(tagName string) (MCPTypes.ElementInfo, error) {
	element, err := ctx.GetElementInfo(tagName)
	if err != nil {
		return nil, err
	}
	return element, nil
}

// AllElements implements MCPTypes.MCPContext interface
func (ctx *MCPContextAdapter) AllElements() map[string]MCPTypes.ElementInfo {
	elements := ctx.GetAllElements()
	return elements
}

// GetManifestSchemaVersions implements MCPTypes.MCPContext interface
func (ctx *MCPContextAdapter) GetManifestSchemaVersions() []string {
	return ctx.MCPContext.GetManifestSchemaVersions()
}

// DocumentManager implements MCPTypes.MCPContext interface
func (ctx *MCPContextAdapter) DocumentManager() lspTypes.DocumentManager {
	return ctx.MCPContext.DocumentManager()
}

// ElementInfoAdapter implements MCPTypes.ElementInfo interface
// MCPElementInfoAdapter implements MCPTypes.ElementInfo interface for MCP-specific behavior
type MCPElementInfoAdapter struct {
	*MCPCustomElementDeclaration
}

// Declaration returns the underlying manifest declaration
func (e *MCPElementInfoAdapter) Declaration() *M.CustomElementDeclaration {
	if e.CustomElementDeclaration != nil {
		return e.CustomElementDeclaration
	}
	return nil
}

// Convenience accessors that delegate to embedded manifest types
func (e *MCPElementInfoAdapter) TagName() string {
	if e.RenderableCustomElementDeclaration != nil {
		return e.CustomElementDeclaration.TagName
	}
	return ""
}

// Template-friendly access
func (e *MCPElementInfoAdapter) Element() *M.CustomElementDeclaration {
	if e.RenderableCustomElementDeclaration != nil {
		return e.CustomElementDeclaration
	}
	return nil
}

func (e *MCPElementInfoAdapter) Name() string {
	if e.RenderableCustomElementDeclaration != nil {
		return e.RenderableCustomElementDeclaration.Name()
	}
	return ""
}

func (e *MCPElementInfoAdapter) Summary() string {
	if e.RenderableCustomElementDeclaration != nil {
		// Use the Summary method from manifest types
		return security.SanitizeDescription(e.RenderableCustomElementDeclaration.Summary())
	}
	return ""
}

func (e *MCPElementInfoAdapter) Description() string {
	if e.RenderableCustomElementDeclaration != nil {
		return security.SanitizeDescription(e.RenderableCustomElementDeclaration.Description())
	}
	return ""
}

func (e *MCPElementInfoAdapter) Module() string {
	return e.ModulePath
}

func (e *MCPElementInfoAdapter) Package() string {
	return e.PackageName
}

// Member accessors returning manifest types directly
func (e *MCPElementInfoAdapter) Attributes() []M.Attribute {
	if decl := e.Declaration(); decl != nil {
		return decl.Attributes
	}
	return nil
}

func (e *MCPElementInfoAdapter) Slots() []M.Slot {
	if decl := e.Declaration(); decl != nil {
		return decl.Slots
	}
	return nil
}

func (e *MCPElementInfoAdapter) Events() []M.Event {
	if decl := e.Declaration(); decl != nil {
		return decl.Events
	}
	return nil
}

func (e *MCPElementInfoAdapter) CssProperties() []M.CssCustomProperty {
	if decl := e.Declaration(); decl != nil {
		return decl.CssProperties
	}
	return nil
}

func (e *MCPElementInfoAdapter) CssParts() []M.CssPart {
	if decl := e.Declaration(); decl != nil {
		return decl.CssParts
	}
	return nil
}

func (e *MCPElementInfoAdapter) CssStates() []M.CssCustomState {
	if decl := e.Declaration(); decl != nil {
		return decl.CssStates
	}
	return nil
}

// MCP-specific behavior
func (e *MCPElementInfoAdapter) Guidelines() []string {
	return e.MCPCustomElementDeclaration.Guidelines
}

func (e *MCPElementInfoAdapter) Examples() []MCPTypes.Example {
	// Extract examples from element and attributes
	var examples []MCPTypes.Example

	// Basic usage example
	if e.TagName() != "" {
		examples = append(examples, MCPTypes.ExampleInfo{
			TitleValue:       "Basic Usage",
			DescriptionValue: fmt.Sprintf("Standard implementation of %s", e.TagName()),
			CodeValue:        fmt.Sprintf("<%s></%s>", e.TagName(), e.TagName()),
			LanguageValue:    "html",
		})

		// Example with attributes if available
		if attrs := e.Attributes(); len(attrs) > 0 {
			var attrParts []string
			attrLimit := int(math.Min(3, float64(len(attrs)))) // Limit to first 3 attributes
			for _, attr := range attrs[:attrLimit] {
				if attr.Default != "" {
					attrParts = append(attrParts, fmt.Sprintf(`%s="%s"`, attr.Name, attr.Default))
				} else {
					// Provide example values based on type
					switch attr.Type.Text {
					case "boolean":
						attrParts = append(attrParts, attr.Name)
					case "string":
						attrParts = append(attrParts, fmt.Sprintf(`%s="example"`, attr.Name))
					case "number":
						attrParts = append(attrParts, fmt.Sprintf(`%s="0"`, attr.Name))
					default:
						attrParts = append(attrParts, fmt.Sprintf(`%s="value"`, attr.Name))
					}
				}
			}

			if len(attrParts) > 0 {
				examples = append(examples, MCPTypes.ExampleInfo{
					TitleValue:       "With Attributes",
					DescriptionValue: fmt.Sprintf("Using %s with common attributes", e.TagName()),
					CodeValue:        fmt.Sprintf("<%s %s></%s>", e.TagName(), strings.Join(attrParts, " "), e.TagName()),
					LanguageValue:    "html",
				})
			}
		}

		// Example with slots if available
		if slots := e.Slots(); len(slots) > 0 {
			var slotContent []string
			slotLimit := int(math.Min(2, float64(len(slots)))) // Limit to first 2 slots
			for _, slot := range slots[:slotLimit] {
				if slot.Name == "" {
					slotContent = append(slotContent, "Default content")
				} else {
					slotContent = append(slotContent, fmt.Sprintf(`<span slot="%s">%s content</span>`, slot.Name, slot.Name))
				}
			}

			if len(slotContent) > 0 {
				examples = append(examples, MCPTypes.ExampleInfo{
					TitleValue:       "With Content Slots",
					DescriptionValue: fmt.Sprintf("Using %s with slotted content", e.TagName()),
					CodeValue:        fmt.Sprintf("<%s>\n  %s\n</%s>", e.TagName(), strings.Join(slotContent, "\n  "), e.TagName()),
					LanguageValue:    "html",
				})
			}
		}
	}

	return examples
}
