package d2ir

import (
	
	
	

	
)

// DefaultMaxVariableExpansion is the maximum amount of work that variable
// substitutions and the copies they induce may add to one compilation. One
// work unit is one copied IR node or copied slice element, one inserted array
// member, one produced string byte, one Markdown match candidate, or one byte
// prepared for a Markdown variable lookup pattern.
const DefaultMaxVariableExpansion int64 = 65_536

type variableExpansionBudget struct {
	limit int64
	used  int64
}

type variableExpansionLimitError struct {
	limit int64
}

func ( *variableExpansionLimitError) () string {
	return fmt.Sprintf("variable substitution expansion exceeds limit of %d work units", .limit)
}

func newVariableExpansionBudget( int64) (*variableExpansionBudget, error) {
	if  < 0 {
		return nil, fmt.Errorf("MaxVariableExpansion must not be negative")
	}
	if  == 0 {
		 = DefaultMaxVariableExpansion
	}
	return &variableExpansionBudget{limit: }, nil
}

func ( *variableExpansionBudget) () int64 {
	if  == nil || .used >= .limit {
		return 0
	}
	return .limit - .used
}

func ( *variableExpansionBudget) ( int64) error {
	if  < 0 ||  > .limit-.used {
		return &variableExpansionLimitError{limit: .limit}
	}
	.used += 
	return nil
}

func ( *compiler) () bool {
	if .halted {
		return true
	}
	if .ctx == nil {
		.ctx = context.Background()
	}
	if .variableExpansion == nil {
		.variableExpansion = &variableExpansionBudget{limit: DefaultMaxVariableExpansion}
	}
	if .globExpansion == nil {
		.globExpansion = &globExpansionBudget{limit: DefaultMaxGlobExpansion}
	}
	if .edgeExpansion == nil {
		.edgeExpansion = &edgeExpansionBudget{limit: DefaultMaxEdgeExpansion}
	}
	if .edgeExpansionWork == nil {
		.edgeExpansionWork = &edgeExpansionWorkBudget{limit: DefaultMaxEdgeExpansionWork}
	}
	if  := .ctx.Err();  != nil {
		.contextErr = 
		.halted = true
		return true
	}
	return false
}

func ( *compiler) ( d2ast.Node,  int64) bool {
	if .stopped() {
		return false
	}
	if  := .variableExpansion.reserve();  != nil {
		.expansionErr = 
		if  != nil {
			.errorf(, "%v", )
		}
		.halted = true
		return false
	}
	return true
}

func ( *compiler) ( d2ast.Node,  Node) bool {
	 := walkVariableExpansion(.ctx, , nil, func( int64) error {
		if !.reserveVariableExpansion(, ) {
			if .contextErr != nil {
				return .contextErr
			}
			return &variableExpansionLimitError{limit: .variableExpansion.limit}
		}
		return nil
	})
	return .handleVariableExpansionWalkError()
}

func ( *compiler) ( d2ast.Node,  *d2ast.Map) bool {
	 := true
	d2ast.Walk(, func( d2ast.Node) bool {
		if ! || .stopped() {
			 = false
			return false
		}
		 = .reserveVariableExpansion(, variableExpansionASTNodeUnits())
		return 
	})
	return 
}

func ( *compiler) ( error) bool {
	if  == nil {
		return true
	}
	if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
		.contextErr = 
		.halted = true
	}
	return false
}

func variableExpansionBudgetFor( *Map) *variableExpansionBudget {
	if  == nil {
		return nil
	}
	 := variableExpansionRoot()
	return .variableExpansion
}

func ensureVariableExpansionBudget( *Map) *variableExpansionBudget {
	 := variableExpansionRoot()
	if .variableExpansion == nil {
		.variableExpansion = &variableExpansionBudget{limit: DefaultMaxVariableExpansion}
	}
	return .variableExpansion
}

func variableExpansionRoot( *Map) *Map {
	for  != nil {
		 := ParentMap()
		if  == nil {
			return 
		}
		 = 
	}
	return nil
}

// ReserveVariableExpansionAliases accounts for logical IR nodes reached more
// than once through composite aliases. It must run before graph materialization,
// which turns those shared references into distinct graph objects.
func ( context.Context,  *Map) error {
	if  == nil {
		return fmt.Errorf("cannot reserve variable expansion for a nil IR map")
	}
	if  == nil {
		 = context.Background()
	}
	 := ensureVariableExpansionBudget()
	 := make(map[Node]struct{})
	return walkVariableExpansion(, , , .reserve)
}

// ReserveVariableExpansionCopy accounts for the logical IR nodes an automatic
// copy will materialize. It shares the same compilation-wide budget used by
// substitutions and alias materialization.
func ( context.Context,  *Map) error {
	if  == nil {
		return fmt.Errorf("cannot reserve variable expansion copy for a nil IR map")
	}
	if  == nil {
		 = context.Background()
	}
	 := ensureVariableExpansionBudget()
	return walkVariableExpansion(, , nil, .reserve)
}

// walkVariableExpansion walks logical occurrences rather than unique pointers.
// When seen is non-nil, only repeated occurrences consume budget.
func walkVariableExpansion( context.Context,  Node,  map[Node]struct{},  func(int64) error) error {
	 := []Node{}
	var  map[d2ast.Scalar]struct{}
	if  != nil {
		 = make(map[d2ast.Scalar]struct{})
	}
	for len() > 0 {
		if  := .Err();  != nil {
			return 
		}
		 := [len()-1]
		 = [:len()-1]
		if  == nil {
			continue
		}

		 :=  == nil
		if  != nil {
			if ,  := [];  {
				 = true
			} else {
				[] = struct{}{}
			}
		}
		if  {
			if  := (variableExpansionNodeUnits());  != nil {
				return 
			}
		}
		if ,  := .(*Scalar);  && .Value != nil &&  != nil {
			if ,  := [.Value];  {
				// Whole-scalar substitutions can create distinct IR Scalar nodes
				// that share one mutable AST value. If it grows later, pointer-only
				// Node accounting misses the duplicated logical value.
				if ! {
					if  := (variableExpansionScalarValueUnits(.Value));  != nil {
						return 
					}
				}
			} else {
				[.Value] = struct{}{}
			}
		}

		switch n := .(type) {
		case *Map:
			for  := len(.Edges) - 1;  >= 0; -- {
				 = append(, .Edges[])
			}
			for  := len(.Fields) - 1;  >= 0; -- {
				 = append(, .Fields[])
			}
		case *Field:
			if .Composite != nil {
				 = append(, .Composite)
			}
			if .Primary_ != nil {
				 = append(, .Primary_)
			}
		case *Edge:
			if .Map_ != nil {
				 = append(, .Map_)
			}
			if .Primary_ != nil {
				 = append(, .Primary_)
			}
		case *Array:
			for  := len(.Values) - 1;  >= 0; -- {
				 = append(, .Values[])
			}
		}
	}
	return nil
}

func variableExpansionNodeUnits( Node) int64 {
	 := int64(1)

	switch n := .(type) {
	case *Map:
		addVariableExpansionUnits(&, len(.Fields))
		addVariableExpansionUnits(&, len(.Edges))
	case *Field:
		addVariableExpansionUnits(&, len(.References))
		if .Name != nil {
			addVariableExpansionUnits64(&, variableExpansionScalarValueUnits(.Name))
		}
	case *Edge:
		addVariableExpansionUnits(&, len(.References))
		if .ID != nil {
			addVariableExpansionUnits(&, len(.ID.SrcPath))
			addVariableExpansionUnits(&, len(.ID.DstPath))
			for ,  := range .ID.SrcPath {
				addVariableExpansionUnits64(&, variableExpansionScalarValueUnits())
			}
			for ,  := range .ID.DstPath {
				addVariableExpansionUnits64(&, variableExpansionScalarValueUnits())
			}
		}
	case *Scalar:
		addVariableExpansionUnits64(&, variableExpansionScalarValueUnits(.Value))
	case *Array:
		addVariableExpansionUnits(&, len(.Values))
	}
	return 
}

func variableExpansionScalarValueUnits( d2ast.Scalar) int64 {
	 := int64(0)
	switch scalar := .(type) {
	case *d2ast.UnquotedString:
		addVariableExpansionUnits(&, len(.Pattern))
		addVariableExpansionUnits64(&, interpolationCopyUnits(.Value))
	case *d2ast.DoubleQuotedString:
		addVariableExpansionUnits64(&, interpolationCopyUnits(.Value))
	case *d2ast.SingleQuotedString:
		addVariableExpansionUnits(&, len(.Value))
	case *d2ast.BlockString:
		addVariableExpansionUnits(&, len(.Value))
	case *d2ast.Number:
		addVariableExpansionNumberUnits(&, )
	}
	return 
}

func variableExpansionASTNodeUnits( d2ast.Node) int64 {
	 := int64(1)
	switch n := .(type) {
	case *d2ast.Map:
		addVariableExpansionUnits(&, len(.Nodes))
	case *d2ast.Array:
		addVariableExpansionUnits(&, len(.Nodes))
	case *d2ast.Key:
		addVariableExpansionUnits(&, len(.Edges))
	case *d2ast.KeyPath:
		addVariableExpansionUnits(&, len(.Path))
	case *d2ast.Substitution:
		addVariableExpansionUnits(&, len(.Path))
	case *d2ast.Import:
		addVariableExpansionUnits(&, len(.Path))
	case *d2ast.UnquotedString:
		addVariableExpansionUnits(&, len(.Pattern))
		addVariableExpansionUnits64(&, interpolationCopyUnits(.Value))
	case *d2ast.DoubleQuotedString:
		addVariableExpansionUnits64(&, interpolationCopyUnits(.Value))
	case *d2ast.SingleQuotedString:
		addVariableExpansionUnits(&, len(.Value))
	case *d2ast.BlockString:
		addVariableExpansionUnits(&, len(.Value))
	case *d2ast.Number:
		addVariableExpansionNumberUnits(&, )
	}
	return 
}

func addVariableExpansionNumberUnits( *int64,  *d2ast.Number) {
	addVariableExpansionUnits(, len(.Raw))
	if .Value == nil {
		return
	}
	for ,  := range []int{.Value.Num().BitLen(), .Value.Denom().BitLen()} {
		 := int64( / 8)
		if %8 != 0 {
			++
		}
		addVariableExpansionUnits64(, )
	}
}

func interpolationCopyUnits( []d2ast.InterpolationBox) int64 {
	 := int64(0)
	addVariableExpansionUnits(&, len())
	for ,  := range  {
		if .String != nil {
			addVariableExpansionUnits(&, len(*.String))
		}
		if .StringRaw != nil {
			addVariableExpansionUnits(&, len(*.StringRaw))
		}
		if .Substitution != nil {
			addVariableExpansionUnits(&, len(.Substitution.Path))
		}
	}
	return 
}

func addVariableExpansionUnits( *int64,  int) {
	if  <= 0 {
		return
	}
	addVariableExpansionUnits64(, int64())
}

func addVariableExpansionUnits64( *int64,  int64) {
	if  <= 0 {
		return
	}
	const  = int64(^uint64(0) >> 1)
	if  > -* {
		* = 
		return
	}
	* += 
}