package d2ir

import (
	
	
	
	
	
	

	

	
	
	
	
	
	
)

type globContext struct {
	root   *globContext
	refctx *RefContext

	// Set of BoardIDA that this glob has already applied to.
	appliedFields map[string]struct{}
	// Set of Edge IDs that this glob has already applied to.
	appliedEdges map[string]struct{}
}

type compiler struct {
	err *d2parser.ParseError

	fs      fs.FS
	imports []string
	// importStack is used to detect cyclic imports.
	importStack []string
	seenImports map[string]struct{}
	utf16Pos    bool

	// Stack of globs that must be recomputed at each new object in and below the current scope.
	globContextStack [][]*globContext
	// Used to prevent field globs causing infinite loops.
	globRefContextStack []*RefContext
	// Used to check whether ampersands are allowed in the current map.
	mapRefContextStack   []*RefContext
	lazyGlobBeingApplied bool
}

type CompileOptions struct {
	UTF16Pos bool
	// Pass nil to disable imports.
	FS fs.FS
}

func ( *compiler) ( d2ast.Node,  string,  ...interface{}) {
	.err.Errors = append(.err.Errors, d2parser.Errorf(, , ...).(d2ast.Error))
}

func ( *d2ast.Map,  *CompileOptions) (*Map, []string, error) {
	if  == nil {
		 = &CompileOptions{}
	}
	 := &compiler{
		err: &d2parser.ParseError{},
		fs:  .FS,

		seenImports: make(map[string]struct{}),
		utf16Pos:    .UTF16Pos,
	}
	 := &Map{}
	.initRoot()
	.parent.(*Field).References[0].Context_.Scope = 
	.parent.(*Field).References[0].Context_.ScopeAST = 

	.pushImportStack(&d2ast.Import{
		Path: []*d2ast.StringBox{d2ast.RawStringBox(.GetRange().Path, true)},
	})
	defer .popImportStack()

	.compileMap(, , )
	.compileSubstitutions(, nil)
	.overlayClasses()
	if !.err.Empty() {
		return nil, nil, .err
	}
	return , .imports, nil
}

func ( *compiler) ( *Map) {
	 := .GetField(d2ast.FlatUnquotedString("classes"))
	if  == nil || .Map() == nil {
		return
	}

	 := .GetField(d2ast.FlatUnquotedString("layers"))
	if  == nil {
		return
	}
	 := .Map()
	if  == nil {
		return
	}

	for ,  := range .Fields {
		if .Map() == nil || .Primary() != nil {
			continue
		}
		 := .Map()
		 := .GetField(d2ast.FlatUnquotedString("classes"))

		if  == nil {
			 = .Copy().(*Field)
			.Fields = append(.Fields, )
		} else {
			 := .Copy().(*Field)
			OverlayMap(.Map(), .Map())
			.DeleteField("classes")
			.Fields = append(.Fields, )
		}

		.()
	}
}

func ( *compiler) ( *Map,  []*Map) {
	for ,  := range .Fields {
		if .Name == nil {
			continue
		}
		if .Name.ScalarString() == "vars" && .Name.IsUnquoted() && .Map() != nil {
			 = append([]*Map{.Map()}, ...)
		}
	}
	for  := 0;  < len(.Fields); ++ {
		 := .Fields[]
		if .Primary() != nil {
			 := .resolveSubstitutions(, )
			if  {
				--
			}
		}
		if ,  := .Composite.(*Array);  {
			for ,  := range .Values {
				if ,  := .(*Scalar);  {
					 := .resolveSubstitutions(, )
					if  {
						--
					}
				}
			}
		} else if .Map() != nil {
			if .Name != nil && .Name.ScalarString() == "vars" && .Name.IsUnquoted() {
				.(.Map(), )
				.validateConfigs(.Map().GetField(d2ast.FlatUnquotedString("d2-config")))
			} else {
				.(.Map(), )
			}
		}
	}
	for ,  := range .Edges {
		if .Primary() != nil {
			.resolveSubstitutions(, )
		}
		if .Map() != nil {
			.(.Map(), )
		}
	}
}

func ( *compiler) ( *Field) {
	if  == nil || .Map() == nil {
		return
	}

	if NodeBoardKind(ParentMap(ParentMap())) == "" {
		.errorf(.LastRef().AST(), `"%s" can only appear at root vars`, .Name.ScalarString())
		return
	}

	for ,  := range .Map().Fields {
		var  string
		if .Primary() == nil {
			if .Name.ScalarString() != "theme-overrides" && .Name.ScalarString() != "dark-theme-overrides" && .Name.ScalarString() != "data" {
				.errorf(.LastRef().AST(), `"%s" needs a value`, .Name.ScalarString())
				continue
			}
		} else {
			 = .Primary().Value.ScalarString()
		}

		switch .Name.ScalarString() {
		case "sketch", "center":
			,  := strconv.ParseBool()
			if  != nil {
				.errorf(.LastRef().AST(), `expected a boolean for "%s", got "%s"`, .Name.ScalarString(), )
				continue
			}
		case "theme-overrides", "dark-theme-overrides", "data":
			if .Map() == nil {
				.errorf(.LastRef().AST(), `"%s" needs a map`, .Name.ScalarString())
				continue
			}
		case "theme-id", "dark-theme-id":
			,  := strconv.Atoi()
			if  != nil {
				.errorf(.LastRef().AST(), `expected an integer for "%s", got "%s"`, .Name.ScalarString(), )
				continue
			}
			if d2themescatalog.Find(int64()) == (d2themes.Theme{}) {
				.errorf(.LastRef().AST(), `%d is not a valid theme ID`, )
				continue
			}
		case "pad":
			,  := strconv.Atoi()
			if  != nil {
				.errorf(.LastRef().AST(), `expected an integer for "%s", got "%s"`, .Name.ScalarString(), )
				continue
			}
		case "layout-engine":
		default:
			.errorf(.LastRef().AST(), `"%s" is not a valid config`, .Name.ScalarString())
		}
	}
}

func ( *compiler) ( []*Map,  Node) ( bool) {
	var  bool
	var  *Field

	switch s := .Primary().Value.(type) {
	case *d2ast.UnquotedString:
		for ,  := range .Value {
			if .Substitution != nil {
				for ,  := range  {
					 = .resolveSubstitution(, , .Substitution,  == 0)
					if  != nil {
						if .Primary() != nil {
							if ,  := .Primary().Value.(*d2ast.Null);  {
								 = nil
							}
						}
						break
					}
				}
				if  == nil {
					.errorf(.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(.Substitution.IDA(), "."))
					return
				}
				if .Substitution.Spread {
					if .Composite == nil {
						.errorf(.Substitution, "cannot spread non-composite")
						continue
					}
					switch n := .(type) {
					case *Scalar: // Array value
						,  := .Composite.(*Array)
						if ! {
							.errorf(.Substitution, "cannot spread non-array into array")
							continue
						}
						 := .parent.(*Array)
						for ,  := range .Values {
							if  ==  {
								.Values = append(append(.Values[:], .Values...), .Values[+1:]...)
								break
							}
						}
					case *Field:
						 := ParentMap()
						if .Map() != nil {
							ExpandSubstitution(, .Map(), )
						}
						// Remove the placeholder field
						for ,  := range .Fields {
							if  ==  {
								.Fields = append(.Fields[:], .Fields[+1:]...)
								 = true
								break
							}
						}
					}
				}
				if .Primary() == nil {
					if .Composite == nil {
						.errorf(.LastRef().AST(), `cannot substitute variable without value: "%s"`, strings.Join(.Substitution.IDA(), "."))
						return
					}
					if len(.Value) > 1 {
						.errorf(.LastRef().AST(), `cannot substitute composite variable "%s" as part of a string`, strings.Join(.Substitution.IDA(), "."))
						return
					}
					switch n := .(type) {
					case *Field:
						.Primary_ = nil
					case *Edge:
						.Primary_ = nil
					}
				} else {
					if  == 0 && len(.Value) == 1 {
						.Primary().Value = .Primary().Value
					} else {
						.Value[].String = go2.Pointer(.Primary().Value.ScalarString())
						 = true
					}
				}
				if .Composite != nil {
					switch n := .(type) {
					case *Field:
						.Composite = .Composite
					case *Edge:
						if .Composite.Map() == nil {
							.errorf(.LastRef().AST(), `cannot substitute array variable "%s" to an edge`, strings.Join(.Substitution.IDA(), "."))
							return
						}
						.Map_ = .Composite.Map()
					}
				}
			}
		}
		if  {
			.Coalesce()
		}
	case *d2ast.DoubleQuotedString:
		for ,  := range .Value {
			if .Substitution != nil {
				for ,  := range  {
					 = .resolveSubstitution(, , .Substitution,  == 0)
					if  != nil {
						break
					}
				}
				if  == nil {
					.errorf(.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(.Substitution.IDA(), "."))
					return
				}
				if .Primary() == nil && .Composite != nil {
					.errorf(.LastRef().AST(), `cannot substitute map variable "%s" in quotes`, strings.Join(.Substitution.IDA(), "."))
					return
				}
				.Value[].String = go2.Pointer(.Primary().Value.ScalarString())
				 = true
			}
		}
		if  {
			.Coalesce()
		}
	case *d2ast.BlockString:
		 := make(map[string]string)
		for ,  := range  {
			.collectVariables(, )
		}
		 := textmeasure.ReplaceSubstitutionsMarkdown(.Value, )

		// Update the block string value
		.Value = 
	}
	return 
}

func ( *compiler) ( *Map,  map[string]string) {
	if  == nil {
		return
	}
	for ,  := range .Fields {
		if .Primary() != nil {
			[.Name.ScalarString()] = .Primary().Value.ScalarString()
		} else if .Map() != nil {
			.(.Map(), )
		}
	}
}

func ( *compiler) ( *Map,  Node,  *d2ast.Substitution,  bool) *Field {
	if  == nil {
		return nil
	}

	,  := .(*Field)
	 := ParentField()

	for ,  := range .Path {
		 := .GetField(.Unbox())
		if  == nil {
			return nil
		}
		// Consider this case:
		//
		// ```
		// vars: {
		//   x: a
		// }
		// hi: {
		//   vars: {
		//     x: ${x}-b
		//   }
		//   yo: ${x}
		// }
		// ```
		//
		// When resolving hi.vars.x, the vars stack includes itself.
		// So this next if clause says, "ignore if we're using the current scope's vars to try to resolve a substitution that requires a var from further in the stack"
		if  && .Name != nil && .Name.ScalarString() == .Unbox().ScalarString() &&  && .Name.ScalarString() == "vars" && .Name.IsUnquoted() {
			return nil
		}

		if  == len(.Path)-1 {
			return 
		}
		 = .Map()
	}
	return nil
}

func ( *compiler) ( *Map,  *Field) {
	if .Map() == nil || .Primary() != nil {
		.errorf(.References[0].Context_.Key, "invalid %s", NodeBoardKind())
		return
	}
	 = .CopyBase()
	// Certain fields should never carry forward.
	// If you give your scenario a label, you don't want all steps in a scenario to be labeled the same.
	.DeleteField("label")
	OverlayMap(, .Map())
	.Composite = 
}

func ( *globContext) () *globContext {
	 := *
	.refctx = .root.refctx.Copy()
	return &
}

func ( *globContext) ( *globContext) {
	.appliedFields = make(map[string]struct{})
	for ,  := range .appliedFields {
		.appliedFields[] = 
	}
	.appliedEdges = make(map[string]struct{})
	for ,  := range .appliedEdges {
		.appliedEdges[] = 
	}
}

func ( *compiler) ( *Map, ,  *d2ast.Map) bool {
	for ,  := range .Nodes {
		switch {
		case .MapKey != nil:
			 := .ampersandFilter(&RefContext{
				Key:      .MapKey,
				Scope:    ,
				ScopeMap: ,
				ScopeAST: ,
			})
			if .MapKey.NotAmpersand {
				 = !
			}
			if ! {
				if len(.mapRefContextStack) == 0 {
					return false
				}
				// Unapply glob if appropriate.
				 := .getGlobContext(.mapRefContextStack[len(.mapRefContextStack)-1])
				if  == nil {
					return false
				}
				var  string
				if .refctx.Key.HasMultiGlob() {
					 = d2format.Format(d2ast.MakeKeyPathString(IDA()))
				} else {
					 = d2format.Format(d2ast.MakeKeyPathString(BoardIDA()))
				}
				delete(.appliedFields, )
				delete(.appliedEdges, )
				return false
			}
		}
	}
	return true
}

func ( *compiler) ( *Map, ,  *d2ast.Map) {
	var  []*globContext
	if len(.globContextStack) > 0 {
		 := .globContexts()
		// A root layer with existing glob context stack implies it's an import
		// In which case, the previous globs should be inherited (the else block)
		if NodeBoardKind() == BoardLayer && !.Root() {
			for ,  := range  {
				if .refctx.Key.HasTripleGlob() {
					 := .copy()
					.refctx.ScopeMap = 
					 = append(, )
				}
			}
		} else if NodeBoardKind() == BoardScenario {
			for ,  := range  {
				 := .copy()
				.refctx.ScopeMap = 
				if !.refctx.Key.HasMultiGlob() {
					// Triple globs already apply independently to each board
					.copyApplied()
				}
				 = append(, )
			}
			for ,  := range  {
				 := .copy()
				.refctx.ScopeMap = 
				// We don't want globs applied in a given scenario to affect future boards
				// Copying the applied fields and edges keeps the applications scoped to this board
				// Note that this is different from steps, where applications carry over
				if !.refctx.Key.HasMultiGlob() {
					// Triple globs already apply independently to each board
					.copyApplied()
				}
				 = append(, )
			}
		} else if NodeBoardKind() == BoardStep {
			for ,  := range  {
				 := .copy()
				.refctx.ScopeMap = 
				 = append(, )
			}
		} else {
			 = append(, ...)
		}
	}
	.globContextStack = append(.globContextStack, )
	defer func() {
		.globs = .globContexts()
		.globContextStack = .globContextStack[:len(.globContextStack)-1]
	}()

	 := .ampersandFilterMap(, , )
	if ! {
		return
	}

	for ,  := range .Nodes {
		switch {
		case .MapKey != nil:
			.compileKey(&RefContext{
				Key:      .MapKey,
				Scope:    ,
				ScopeMap: ,
				ScopeAST: ,
			})
		case .Substitution != nil:
			// placeholder field to be resolved at the end
			 := &Field{
				parent: ,
				Primary_: &Scalar{
					Value: &d2ast.UnquotedString{
						Value: []d2ast.InterpolationBox{{Substitution: .Substitution}},
					},
				},
				References: []*FieldReference{{
					Context_: &RefContext{
						Scope:    ,
						ScopeMap: ,
						ScopeAST: ,
					},
				}},
			}
			.Fields = append(.Fields, )
		case .Import != nil:
			// Spread import
			,  := ._import(.Import)
			if ! {
				continue
			}
			if .Map() == nil {
				.errorf(.Import, "cannot spread import non map into map")
				continue
			}
			.(Importable).SetImportAST(.Import)

			for ,  := range .Map().globs {
				if !.refctx.Key.HasTripleGlob() {
					continue
				}
				 := .copy()
				.refctx.ScopeMap = 
				.compileKey(.refctx)
				.ensureGlobContext(.refctx)
			}

			OverlayMap(, .Map())
			 := .Import.Dir()
			.extendLinks(, ParentField(), )

			if ,  := .(*Field);  {
				if .Primary_ != nil {
					 := ParentField()
					if  != nil {
						.Primary_ = .Primary_
					}
				}
			}
		}
	}
}

func ( *compiler) () []*globContext {
	return .globContextStack[len(.globContextStack)-1]
}

func ( *compiler) ( *RefContext) *globContext {
	for ,  := range .globContexts() {
		if .refctx.Equal() {
			return 
		}
	}
	return nil
}

func ( *compiler) ( *RefContext) *globContext {
	 := .getGlobContext()
	if  != nil {
		return 
	}
	 = &globContext{
		refctx:        ,
		appliedFields: make(map[string]struct{}),
		appliedEdges:  make(map[string]struct{}),
	}
	.root = 
	.globContextStack[len(.globContextStack)-1] = append(.globContexts(), )
	return 
}

func ( *compiler) ( *RefContext) {
	if .Key.HasGlob() {
		// These printlns are for debugging infinite loops.
		// println("og", refctx.Edge, refctx.Key, refctx.Scope, refctx.ScopeMap, refctx.ScopeAST)
		for ,  := range .globRefContextStack {
			// println("st", refctx2.Edge, refctx2.Key, refctx2.Scope, refctx2.ScopeMap, refctx2.ScopeAST)
			if .Equal() {
				// Break the infinite loop.
				return
			}
			// println("keys", d2format.Format(refctx2.Key), d2format.Format(refctx.Key))
		}
		.globRefContextStack = append(.globRefContextStack, )
		defer func() {
			.globRefContextStack = .globRefContextStack[:len(.globRefContextStack)-1]
		}()
		.ensureGlobContext()
	}
	 := .ScopeMap.FieldCountRecursive()
	 := .ScopeMap.EdgeCountRecursive()
	if len(.Key.Edges) == 0 {
		.compileField(.ScopeMap, .Key.Key, )
	} else {
		.compileEdges()
	}
	if  != .ScopeMap.FieldCountRecursive() ||  != .ScopeMap.EdgeCountRecursive() {
		for ,  := range .globContexts() {
			// println(d2format.Format(gctx2.refctx.Key), d2format.Format(refctx.Key))
			 := .lazyGlobBeingApplied
			.lazyGlobBeingApplied = true
			.(.refctx)
			.lazyGlobBeingApplied = 
		}
	}
}

func ( *compiler) ( *Map,  *d2ast.KeyPath,  *RefContext) {
	if .Key.Ampersand || .Key.NotAmpersand {
		return
	}

	,  := .EnsureField(, , true, )
	if  != nil {
		.err.Errors = append(.err.Errors, .(d2ast.Error))
		return
	}

	for ,  := range  {
		._compileField(, )
	}
}

func ( *compiler) ( *RefContext) bool {
	if !.Key.Ampersand && !.Key.NotAmpersand {
		return true
	}
	if len(.mapRefContextStack) == 0 || !.mapRefContextStack[len(.mapRefContextStack)-1].Key.SupportsGlobFilters() {
		.errorf(.Key, "glob filters cannot be used outside globs")
		return false
	}
	if len(.Key.Edges) > 0 {
		return true
	}

	,  := .ScopeMap.EnsureField(.Key.Key, , false, )
	if  != nil {
		.err.Errors = append(.err.Errors, .(d2ast.Error))
		return false
	}
	if len() == 0 {
		if .Key.Value.ScalarBox().Unbox().ScalarString() == "*" {
			return false
		}
		// The field/edge has no value for this filter
		// But the filter might still match default, e.g. opacity 1
		// So we make a fake field for the default
		// NOTE: this does not apply to things that themes control, like stroke and fill
		// Nor does it apply to layout things like width and height
		switch .Key.Key.Last().ScalarString() {
		case "shape":
			 := &Field{
				Primary_: &Scalar{
					Value: d2ast.FlatUnquotedString("rectangle"),
				},
			}
			return ._ampersandFilter(, )
		case "border-radius", "stroke-dash":
			 := &Field{
				Primary_: &Scalar{
					Value: d2ast.FlatUnquotedString("0"),
				},
			}
			return ._ampersandFilter(, )
		case "opacity":
			 := &Field{
				Primary_: &Scalar{
					Value: d2ast.FlatUnquotedString("1"),
				},
			}
			return ._ampersandFilter(, )
		case "stroke-width":
			 := &Field{
				Primary_: &Scalar{
					Value: d2ast.FlatUnquotedString("2"),
				},
			}
			return ._ampersandFilter(, )
		case "icon", "tooltip", "link":
			 := &Field{
				Primary_: &Scalar{
					Value: d2ast.FlatUnquotedString(""),
				},
			}
			return ._ampersandFilter(, )
		case "shadow", "multiple", "3d", "animated", "filled":
			 := &Field{
				Primary_: &Scalar{
					Value: d2ast.FlatUnquotedString("false"),
				},
			}
			return ._ampersandFilter(, )
		case "leaf":
			 := .Key.Value.ScalarBox().Unbox().ScalarString()
			,  := strconv.ParseBool()
			if  != nil {
				.errorf(.Key, `&leaf must be "true" or "false", got %q`, )
				return false
			}

			 := .ScopeMap.Parent().(*Field)
			 := .Map() == nil || !.Map().IsContainer()
			return  == 
		case "connected":
			 := .Key.Value.ScalarBox().Unbox().ScalarString()
			,  := strconv.ParseBool()
			if  != nil {
				.errorf(.Key, `&connected must be "true" or "false", got %q`, )
				return false
			}
			 := .ScopeMap.Parent().(*Field)
			 := false
			for ,  := range .References {
				if .InEdge() {
					 = true
					break
				}
			}
			return  == 
		case "label":
			 := &Field{}
			 := .ScopeMap.Parent()
			if .Primary() == nil {
				switch n := .(type) {
				case *Field:
					// The label value for fields is their key value
					.Primary_ = &Scalar{
						Value: .Name,
					}
				case *Edge:
					// But for edges, it's nothing
					return false
				}
			} else {
				.Primary_ = .Primary()
			}
			return ._ampersandFilter(, )
		default:
			return false
		}
	}
	for ,  := range  {
		 := ._ampersandFilter(, )
		if ! {
			return false
		}
	}
	return true
}

func ( *compiler) ( *Field,  *RefContext) bool {
	if .Key.Value.ScalarBox().Unbox() == nil {
		.errorf(.Key, "glob filters cannot be composites")
		return false
	}

	if ,  := .Composite.(*Array);  {
		for ,  := range .Values {
			if ,  := .(*Scalar);  {
				if .Key.Value.ScalarBox().Unbox().ScalarString() == .Value.ScalarString() {
					return true
				}
			}
		}
	}

	if .Primary_ == nil {
		return false
	}

	,  := .Key.Value.ScalarBox().Unbox().(*d2ast.UnquotedString)

	if  && .Pattern != nil {
		return matchPattern(.Primary_.Value.ScalarString(), .Pattern)
	} else {
		if .Key.Value.ScalarBox().Unbox().ScalarString() != .Primary_.Value.ScalarString() {
			return false
		}
	}

	return true
}

func ( *compiler) ( *Field,  *RefContext) {
	// In case of filters, we need to pass filters before continuing
	if .Key.Value.Map != nil && .Key.Value.Map.HasFilter() {
		if .Map() == nil {
			.Composite = &Map{
				parent: ,
			}
		}
		.mapRefContextStack = append(.mapRefContextStack, )
		 := .ampersandFilterMap(.Map(), .Key.Value.Map, .ScopeAST)
		.mapRefContextStack = .mapRefContextStack[:len(.mapRefContextStack)-1]
		if ! {
			return
		}
	}

	if len(.Key.Edges) == 0 && (.Key.Primary.Null != nil || .Key.Value.Null != nil) {
		// For vars, if we delete the field, it may just resolve to an outer scope var of the same name
		// Instead we keep it around, so that resolveSubstitutions can find it
		if !IsVar(ParentMap()) {
			ParentMap().DeleteField(.Name.ScalarString())
			return
		}
	}

	if .Key.Primary.Unbox() != nil {
		if .ignoreLazyGlob() {
			return
		}
		.Primary_ = &Scalar{
			parent: ,
			Value:  .Key.Primary.Unbox(),
		}
	}

	if .Key.Value.Array != nil {
		 := &Array{
			parent: ,
		}
		.compileArray(, .Key.Value.Array, .ScopeAST)
		.Composite = 
	} else if .Key.Value.Map != nil {
		 := .Key.Value.Map
		if .Map() == nil {
			.Composite = &Map{
				parent: ,
			}
			switch NodeBoardKind() {
			case BoardScenario:
				.overlay(ParentBoard().Map(), )
			case BoardStep:
				 := ParentMap()
				for  := range .Fields {
					if .Fields[] ==  {
						if  == 0 {
							.overlay(ParentBoard().Map(), )
						} else {
							.overlay(.Fields[-1].Map(), )
						}
						break
					}
				}
			case BoardLayer:
			default:
				// If new board type, use that as the new scope AST, otherwise, carry on
				 = .ScopeAST
			}
		} else {
			 = .ScopeAST
		}
		.mapRefContextStack = append(.mapRefContextStack, )
		.compileMap(.Map(), .Key.Value.Map, )
		.mapRefContextStack = .mapRefContextStack[:len(.mapRefContextStack)-1]
		switch NodeBoardKind() {
		case BoardScenario, BoardStep:
			.overlayClasses(.Map())
		}
	} else if .Key.Value.Import != nil {
		// Non-spread import
		,  := ._import(.Key.Value.Import)
		if ! {
			return
		}
		.(Importable).SetImportAST(.Key.Value.Import)
		switch n := .(type) {
		case *Field:
			if .Primary_ != nil {
				.Primary_ = .Primary_.Copy().(*Scalar)
			}
			if .Composite != nil {
				.Composite = .Composite.Copy().(Composite)
			}
		case *Map:
			.Composite = &Map{
				parent: ,
			}
			switch NodeBoardKind() {
			case BoardScenario:
				.overlay(ParentBoard().Map(), )
			case BoardStep:
				 := ParentMap()
				for  := range .Fields {
					if .Fields[] ==  {
						if  == 0 {
							.overlay(ParentBoard().Map(), )
						} else {
							.overlay(.Fields[-1].Map(), )
						}
						break
					}
				}
			}
			OverlayMap(.Map(), )
			 := .Key.Value.Import.Dir()
			.extendLinks(.Map(), , )
			switch NodeBoardKind() {
			case BoardScenario, BoardStep:
				.overlayClasses(.Map())
			}
		}
	} else if .Key.Value.ScalarBox().Unbox() != nil {
		if .ignoreLazyGlob() {
			return
		}
		.Primary_ = &Scalar{
			parent: ,
			Value:  .Key.Value.ScalarBox().Unbox(),
		}
		// If the link is a board, we need to transform it into an absolute path.
		if .Name.ScalarString() == "link" && .Name.IsUnquoted() {
			.compileLink(, )
		}
	}
}

// Whether the current lazy glob being applied should not override the field
// if already set by a non glob key.
func ( *compiler) ( Node) bool {
	if .lazyGlobBeingApplied && .Primary() != nil {
		 := .LastPrimaryRef()
		if  != nil && !.DueToLazyGlob() {
			return true
		}
	}
	return false
}

// When importing a file, all of its board and icon links need to be extended to reflect their new path
func ( *compiler) ( *Map,  *Field,  string) {
	 := NodeBoardKind()
	 := IDA()
	for ,  := range .Fields {
		if .Name.ScalarString() == "link" && .Name.IsUnquoted() {
			if  != "" {
				.errorf(.LastRef().AST(), "a board itself cannot be linked; only objects within a board can be linked")
				continue
			}
			 := .Primary().Value.ScalarString()

			,  := url.Parse(html.UnescapeString())
			 :=  == nil && .Scheme != ""
			if  {
				continue
			}

			,  := d2parser.ParseKey()
			if  != nil {
				continue
			}
			 := .IDA()
			if len() == 0 {
				continue
			}

			for ,  := range [1:] {
				if .ScalarString() == "_" && .IsUnquoted() {
					if len() < 2 || len() < 2 {
						break
					}
					 = append([]d2ast.String{[0]}, [2:]...)
					 = [:len()-2]
				} else {
					break
				}
			}

			 := append(, [1:]...)
			 := d2ast.MakeKeyPathString()
			 := d2format.Format()
			.Primary_.Value = d2ast.MakeValueBox(d2ast.FlatUnquotedString()).ScalarBox().Unbox()
		}
		if .Name.ScalarString() == "icon" && .Name.IsUnquoted() && .Primary() != nil {
			 := .Primary().Value.ScalarString()
			// It's likely a substitution
			if  == "" {
				continue
			}
			,  := url.Parse(html.UnescapeString())
			 :=  == nil && .Scheme != ""
			if  {
				continue
			}
			 = path.Join(, )
			.Primary_.Value = d2ast.MakeValueBox(d2ast.FlatUnquotedString()).ScalarBox().Unbox()
		}
		if .Map() != nil {
			.(.Map(), , )
		}
	}
}

func ( *compiler) ( *Field,  *RefContext) {
	 := .Key.Value.ScalarBox().Unbox().ScalarString()
	,  := d2parser.ParseKey()
	if  != nil {
		return
	}

	 := IDA(.ScopeMap)

	if len() == 0 {
		return
	}

	 := .IDA()
	if len() == 0 {
		return
	}

	if [0].ScalarString() == "root" && [0].IsUnquoted() {
		.errorf(.Key.Key, "cannot refer to root in link")
		return
	}

	if ![0].IsUnquoted() {
		return
	}

	// If it doesn't start with one of these reserved words, the link is definitely not a board link.
	if !strings.EqualFold([0].ScalarString(), "layers") && !strings.EqualFold([0].ScalarString(), "scenarios") && !strings.EqualFold([0].ScalarString(), "steps") && [0].ScalarString() != "_" {
		return
	}

	// Chop off the non-board portion of the scope, like if this is being defined on a nested object (e.g. `x.y.z`)
	for  := len() - 1;  > 0; -- {
		if [-1].IsUnquoted() && (strings.EqualFold([-1].ScalarString(), "layers") || strings.EqualFold([-1].ScalarString(), "scenarios") || strings.EqualFold([-1].ScalarString(), "steps")) {
			 = [:+1]
			break
		}
		if [-1].ScalarString() == "root" && [-1].IsUnquoted() {
			 = [:]
			break
		}
	}

	// Resolve underscores
	for len() > 0 && [0].ScalarString() == "_" && [0].IsUnquoted() {
		if len() < 2 {
			// Leave the underscore. It will fail in compiler as a standalone board,
			// but if imported, will get further resolved in extendLinks
			break
		}
		// pop 2 off path per one underscore
		 = [:len()-2]
		 = [1:]
	}
	if len() == 0 {
		 = []d2ast.String{d2ast.FlatUnquotedString("root")}
	}

	// Create the absolute path by appending scope path with value specified
	 = append(, ...)
	 := d2ast.MakeKeyPathString()
	.Primary_.Value = d2ast.FlatUnquotedString(d2format.Format())
}

func ( *compiler) ( *RefContext) {
	if .Key.Key == nil {
		._compileEdges()
		return
	}

	,  := .ScopeMap.EnsureField(.Key.Key, , true, )
	if  != nil {
		.err.Errors = append(.err.Errors, .(d2ast.Error))
		return
	}
	for ,  := range  {
		if ,  := .Composite.(*Array);  {
			.errorf(.Key.Key, "cannot index into array")
			return
		}
		if .Map() == nil {
			.Composite = &Map{
				parent: ,
			}
		}
		 := *
		.ScopeMap = .Map()
		._compileEdges(&)
	}
}

func ( *compiler) ( *RefContext) {
	 := NewEdgeIDs(.Key)
	for ,  := range  {
		if !.Glob && (.Key.Primary.Null != nil || .Key.Value.Null != nil) {
			.ScopeMap.DeleteEdge()
			continue
		}

		 = .Copy()
		.Edge = .Key.Edges[]

		var  []*Edge
		if .Index != nil || .Glob {
			 = .ScopeMap.GetEdges(, , )
			if len() == 0 {
				if !.Glob {
					.errorf(.Edge, "indexed edge does not exist")
				}
				continue
			}
			for ,  := range  {
				if .Key.Primary.Null != nil || .Key.Value.Null != nil {
					.ScopeMap.DeleteEdge(.ID)
					continue
				}
				.References = append(.References, &EdgeReference{
					Context_:       ,
					DueToGlob_:     len(.globRefContextStack) > 0,
					DueToLazyGlob_: .lazyGlobBeingApplied,
				})
				.ScopeMap.appendFieldReferences(0, .Edge.Src, , )
				.ScopeMap.appendFieldReferences(0, .Edge.Dst, , )
			}
		} else {
			var  error
			,  = .ScopeMap.CreateEdge(, , )
			if  != nil {
				.err.Errors = append(.err.Errors, .(d2ast.Error))
				continue
			}
		}

		for ,  := range  {
			if .Key.EdgeKey != nil {
				if .Map_ == nil {
					.Map_ = &Map{
						parent: ,
					}
				}
				.compileField(.Map_, .Key.EdgeKey, )
			} else {
				if .Key.Primary.Unbox() != nil {
					if .ignoreLazyGlob() {
						return
					}
					.Primary_ = &Scalar{
						parent: ,
						Value:  .Key.Primary.Unbox(),
					}
				}
				if .Key.Value.Array != nil {
					.errorf(.Key.Value.Unbox(), "edges cannot be assigned arrays")
					continue
				} else if .Key.Value.Map != nil {
					if .Map_ == nil {
						.Map_ = &Map{
							parent: ,
						}
					}
					.mapRefContextStack = append(.mapRefContextStack, )
					.compileMap(.Map_, .Key.Value.Map, .ScopeAST)
					.mapRefContextStack = .mapRefContextStack[:len(.mapRefContextStack)-1]
				} else if .Key.Value.ScalarBox().Unbox() != nil {
					if .ignoreLazyGlob() {
						return
					}
					.Primary_ = &Scalar{
						parent: ,
						Value:  .Key.Value.ScalarBox().Unbox(),
					}
				}
			}
		}
	}
}

func ( *compiler) ( *Array,  *d2ast.Array,  *d2ast.Map) {
	for ,  := range .Nodes {
		var  Value
		switch v := .Unbox().(type) {
		case *d2ast.Array:
			 := &Array{
				parent: ,
			}
			.(, , )
			 = 
		case *d2ast.Map:
			 := &Map{
				parent: ,
			}
			.compileMap(, , )
			 = 
		case d2ast.Scalar:
			 = &Scalar{
				parent: ,
				Value:  ,
			}
		case *d2ast.Import:
			,  := ._import()
			if ! {
				continue
			}
			.(Importable).SetImportAST()
			switch n := .(type) {
			case *Field:
				if .Spread {
					,  := .Composite.(*Array)
					if ! {
						.errorf(, "can only spread import array into array")
						continue
					}
					.Values = append(.Values, .Values...)
					continue
				}
				if .Composite != nil {
					 = .Composite
				} else {
					 = .Primary_
				}
			case *Map:
				if .Spread {
					.errorf(, "can only spread import array into array")
					continue
				}
				 = 
			}
		case *d2ast.Substitution:
			 = &Scalar{
				parent: ,
				Value: &d2ast.UnquotedString{
					Value: []d2ast.InterpolationBox{{Substitution: .Substitution}},
				},
			}
		}

		.Values = append(.Values, )
	}
}