package checker

import (
	"github.com/microsoft/typescript-go/internal/ast"
	"github.com/microsoft/typescript-go/internal/core"
	"github.com/microsoft/typescript-go/internal/nodebuilder"
	"github.com/microsoft/typescript-go/internal/printer"
)

type NodeBuilder struct {
	ctxStack  []*NodeBuilderContext
	host      Host
	impl      *NodeBuilderImpl
	verbosity *VerbosityContext // nil for non-hover callers
}

// VerbosityContext controls hover-expansion behavior in the node builder.
// A nil VerbosityContext means no expansion (non-hover callers).
// Level 0 = default hover (maxExpansionDepth = 0; detects expandability without expanding).
// Level 1+ = expansion enabled (maxExpansionDepth = Level).
type VerbosityContext struct {
	Level                int  // 0 = default (no expansion), 1+ = expansion depth
	MaxTruncationLength  int  // 0 = use default
	CanIncreaseVerbosity bool // output: whether increasing Level would reveal more
	Truncated            bool // output: whether output was truncated
}

// EmitContext implements NodeBuilderInterface.
func (b *NodeBuilder) EmitContext() *printer.EmitContext {
	return b.impl.e
}

func (b *NodeBuilder) enterContext(enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) {
	verbosityLevel := -1
	maxTruncationLength := 0
	if b.verbosity != nil {
		verbosityLevel = b.verbosity.Level
		maxTruncationLength = b.verbosity.MaxTruncationLength
	}
	b.ctxStack = append(b.ctxStack, b.impl.ctx)
	b.impl.ctx = &NodeBuilderContext{
		host:                     b.host,
		tracker:                  tracker,
		flags:                    flags,
		internalFlags:            internalFlags,
		maxExpansionDepth:        verbosityLevel,
		maxTruncationLength:      maxTruncationLength,
		enclosingDeclaration:     enclosingDeclaration,
		enclosingFile:            ast.GetSourceFileOfNode(enclosingDeclaration),
		inferTypeParameters:      make([]*Type, 0),
		symbolDepth:              make(map[CompositeSymbolIdentity]int),
		trackedSymbols:           make([]*TrackedSymbolArgs, 0),
		reverseMappedStack:       make([]*ast.Symbol, 0),
		enclosingSymbolTypes:     make(map[ast.SymbolId]*Type),
		remappedSymbolReferences: make(map[ast.SymbolId]*ast.Symbol),
	}
	tracker = NewSymbolTrackerImpl(b.impl.ctx, tracker)
	b.impl.ctx.tracker = tracker
}

// propagateVerbosityOut copies expansion signals from the context to the VerbosityContext output.
func (b *NodeBuilder) propagateVerbosityOut() {
	if b.verbosity != nil {
		// Only set to true, never clear — multiple calls share the same VerbosityContext
		if b.impl.ctx.canIncreaseExpansionDepth {
			b.verbosity.CanIncreaseVerbosity = true
		}
		if b.impl.ctx.expansionTruncated {
			b.verbosity.Truncated = true
		}
	}
}

func (b *NodeBuilder) popContext() {
	stackSize := len(b.ctxStack)
	if stackSize == 0 {
		b.impl.ctx = nil
	} else {
		b.impl.ctx = b.ctxStack[stackSize-1]
		b.ctxStack = b.ctxStack[:stackSize-1]
	}
}

func (b *NodeBuilder) exitContext(result *ast.Node) *ast.Node {
	b.propagateVerbosityOut()
	b.exitContextCheck()
	defer b.popContext()
	if b.impl.ctx.encounteredError {
		return nil
	}
	return result
}

func (b *NodeBuilder) exitContextSlice(result []*ast.Node) []*ast.Node {
	b.propagateVerbosityOut()
	b.exitContextCheck()
	defer b.popContext()
	if b.impl.ctx.encounteredError {
		return nil
	}
	return result
}

func (b *NodeBuilder) exitContextCheck() {
	if b.impl.ctx.truncating && b.impl.ctx.flags&nodebuilder.FlagsNoTruncation != 0 {
		b.impl.ctx.tracker.ReportTruncationError()
	}
}

// IndexInfoToIndexSignatureDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) IndexInfoToIndexSignatureDeclaration(info *IndexInfo, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.indexInfoToIndexSignatureDeclarationHelper(info, nil))
}

// SerializeReturnTypeForSignature implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeReturnTypeForSignature(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	signature := b.impl.ch.getSignatureFromDeclaration(signatureDeclaration)
	_, cleanup := b.impl.enterSignatureScope(signature)
	result := b.impl.serializeReturnTypeForSignature(signature, true)
	cleanup()
	return b.exitContext(result)
}

func (b *NodeBuilder) SerializeTypeParametersForSignature(signatureDeclaration *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	symbol := b.impl.ch.getSymbolOfDeclaration(signatureDeclaration)
	typeParams := b.SymbolToTypeParameterDeclarations(symbol, enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContextSlice(typeParams)
}

// SerializeTypeForDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeTypeForDeclaration(declaration *ast.Node, symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.serializeTypeForDeclaration(declaration, nil, symbol, true))
}

// SerializeTypeForExpression implements NodeBuilderInterface.
func (b *NodeBuilder) SerializeTypeForExpression(expr *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.serializeTypeForExpression(expr))
}

// SignatureToSignatureDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) SignatureToSignatureDeclaration(signature *Signature, kind ast.Kind, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.signatureToSignatureDeclarationHelper(signature, kind, nil))
}

// ExpandSymbolForHover produces declaration nodes for a symbol with verbosity level support.
func (b *NodeBuilder) ExpandSymbolForHover(symbol *ast.Symbol, meaning ast.SymbolFlags) []*ast.Node {
	b.enterContext(nil, nodebuilder.FlagsIgnoreErrors|nodebuilder.FlagsMultilineObjectLiterals|nodebuilder.FlagsUseAliasDefinedOutsideCurrentScope, nodebuilder.InternalFlagsNone, nil)

	// Push the declared type onto the type stack to prevent re-expansion.
	// We push a nil sentinel after the real type so that isTypeOnStack
	// (which skips the last element) still checks declaredType.
	declaredType := b.impl.ch.getDeclaredTypeOfSymbol(symbol)
	b.impl.ctx.typeStack = append(b.impl.ctx.typeStack, declaredType)
	b.impl.ctx.typeStack = append(b.impl.ctx.typeStack, nil)

	nodes := b.impl.expandSymbolForHover(symbol)

	b.impl.ctx.typeStack = b.impl.ctx.typeStack[:len(b.impl.ctx.typeStack)-2]

	b.propagateVerbosityOut()

	// Simplify declarations by applying original modifiers
	result := make([]*ast.Node, 0, len(nodes))
	for _, node := range nodes {
		switch node.Kind {
		case ast.KindClassDeclaration:
			result = append(result, simplifyClassDeclaration(b.impl.f, node, symbol))
		case ast.KindEnumDeclaration:
			result = append(result, simplifyModifiers(b.impl.f, node, ast.IsEnumDeclaration, symbol))
		case ast.KindInterfaceDeclaration:
			if meaning&ast.SymbolFlagsInterface != 0 {
				result = append(result, simplifyModifiers(b.impl.f, node, ast.IsInterfaceDeclaration, symbol))
			}
		case ast.KindModuleDeclaration:
			result = append(result, simplifyModifiers(b.impl.f, node, ast.IsModuleDeclaration, symbol))
		}
	}

	return b.exitContextSlice(result)
}

func simplifyClassDeclaration(f *ast.NodeFactory, classDecl *ast.Node, symbol *ast.Symbol) *ast.Node {
	classDeclarations := core.Filter(symbol.Declarations, ast.IsClassLike)
	var originalClassDecl *ast.Node
	if len(classDeclarations) > 0 {
		originalClassDecl = classDeclarations[0]
	} else {
		originalClassDecl = classDecl
	}
	modifiers := originalClassDecl.ModifierFlags() & ^(ast.ModifierFlagsExport | ast.ModifierFlagsAmbient)
	isAnonymous := ast.IsClassExpression(originalClassDecl)
	if isAnonymous {
		cd := classDecl.AsClassDeclaration()
		classDecl = f.UpdateClassDeclaration(
			cd,
			classDecl.Modifiers(),
			nil,
			cd.TypeParameters,
			cd.HeritageClauses,
			cd.Members,
		)
	}
	return ast.ReplaceModifiers(f, classDecl, f.NewModifierList(ast.CreateModifiersFromModifierFlags(modifiers, f.NewModifier)))
}

func simplifyModifiers(f *ast.NodeFactory, newDecl *ast.Node, isDeclKind func(*ast.Node) bool, symbol *ast.Symbol) *ast.Node {
	decls := core.Filter(symbol.Declarations, isDeclKind)
	var declWithModifiers *ast.Node
	if len(decls) > 0 {
		declWithModifiers = decls[0]
	} else {
		declWithModifiers = newDecl
	}
	modifiers := declWithModifiers.ModifierFlags() & ^(ast.ModifierFlagsExport | ast.ModifierFlagsAmbient)
	return ast.ReplaceModifiers(f, newDecl, f.NewModifierList(ast.CreateModifiersFromModifierFlags(modifiers, f.NewModifier)))
}

// SymbolToEntityName implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToEntityName(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.symbolToName(symbol, meaning, false))
}

// SymbolToExpression implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToExpression(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.symbolToExpression(symbol, meaning))
}

// SymbolToNode implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToNode(symbol *ast.Symbol, meaning ast.SymbolFlags, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.symbolToNode(symbol, meaning))
}

// SymbolToParameterDeclaration implements NodeBuilderInterface.
func (b NodeBuilder) SymbolToParameterDeclaration(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.symbolToParameterDeclaration(symbol, false))
}

// SymbolToTypeParameterDeclarations implements NodeBuilderInterface.
func (b *NodeBuilder) SymbolToTypeParameterDeclarations(symbol *ast.Symbol, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) []*ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContextSlice(b.impl.symbolToTypeParameterDeclarations(symbol))
}

// TypeParameterToDeclaration implements NodeBuilderInterface.
func (b *NodeBuilder) TypeParameterToDeclaration(parameter *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.typeParameterToDeclaration(parameter))
}

// TypePredicateToTypePredicateNode implements NodeBuilderInterface.
func (b *NodeBuilder) TypePredicateToTypePredicateNode(predicate *TypePredicate, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.typePredicateToTypePredicateNode(predicate))
}

// TypeToTypeNode implements NodeBuilderInterface.
func (b *NodeBuilder) TypeToTypeNode(typ *Type, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.typeToTypeNode(typ))
}

func (b *NodeBuilder) TryJSTypeNodeToTypeNode(node *ast.Node, enclosingDeclaration *ast.Node, flags nodebuilder.Flags, internalFlags nodebuilder.InternalFlags, tracker nodebuilder.SymbolTracker) *ast.Node {
	b.enterContext(enclosingDeclaration, flags, internalFlags, tracker)
	return b.exitContext(b.impl.tryJSTypeNodeToTypeNode(node))
}

// var _ NodeBuilderInterface = NewNodeBuilderAPI(nil, nil)

func NewNodeBuilder(ch *Checker, e *printer.EmitContext) *NodeBuilder {
	return NewNodeBuilderEx(ch, e, nil /*idToSymbol*/)
}

func NewNodeBuilderEx(ch *Checker, e *printer.EmitContext, idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *NodeBuilder {
	impl := newNodeBuilderImpl(ch, e, idToSymbol)
	return &NodeBuilder{impl: impl, ctxStack: make([]*NodeBuilderContext, 0, 1), host: ch.program}
}

func (c *Checker) getNodeBuilder() (*NodeBuilder, func()) {
	releaseNodes := func() {
		c.typeToStringNodebuilder.EmitContext().Factory.ReleaseArenas() // Allow any allocated nodes to be freed if they're no longer in a cache
	}
	if c.typeToStringNodebuilder != nil {
		return c.typeToStringNodebuilder, releaseNodes
	}
	c.typeToStringNodebuilder = c.getNodeBuilderEx(nil /*idToSymbol*/)
	return c.typeToStringNodebuilder, releaseNodes
}

func (c *Checker) getNodeBuilderEx(idToSymbol map[*ast.IdentifierNode]*ast.Symbol) *NodeBuilder {
	b := NewNodeBuilderEx(c, printer.NewEmitContext(), idToSymbol)
	return b
}
