package d2ir

import (
	
	
	
	

	
	
)

// ErrImportsDisabled is returned when D2 source requests an import but the
// caller did not provide CompileOptions.FS.
var ErrImportsDisabled = errors.New("d2ir: imports are disabled; provide CompileOptions.FS to enable them")

// MaxImportDepth is the maximum number of imported files permitted in one
// active import chain. The root source file is not counted.
const MaxImportDepth = 128

func ( *compiler) ( *d2ast.Import) (string, bool) {
	 := .PathWithPre()
	if  == "" && .Range != (d2ast.Range{}) {
		.errorf(, "imports must specify a path to import")
		return "", false
	}
	if len(.importStack) > 0 {
		if path.Ext() != ".d2" {
			 += ".d2"
		}

		if !filepath.IsAbs() {
			 = path.Join(path.Dir(.importStack[len(.importStack)-1]), )
		}
	}

	if len(.importStack) > 0 && len(.importStack)-1 >= MaxImportDepth {
		.errorf(, "maximum import depth of %d exceeded", MaxImportDepth)
		return "", false
	}

	for ,  := range .importStack {
		if  ==  {
			.errorf(, "detected cyclic import chain: %s", formatCyclicChain(.importStack[:]))
			return "", false
		}
	}

	.importStack = append(.importStack, )
	return , true
}

func ( *compiler) () {
	.importStack = .importStack[:len(.importStack)-1]
}

func formatCyclicChain( []string) string {
	var  strings.Builder
	for ,  := range  {
		.WriteString()
		.WriteString(" -> ")
	}
	.WriteString([0])
	return .String()
}

// Returns either *Map or *Field.
func ( *compiler) ( *d2ast.Import,  Node) (Node, bool) {
	,  := .(*Field)
	return .__import(, importTemplateSafeAt(), )
}

func ( *compiler) ( *Map,  *d2ast.Import) (Node, bool) {
	nilScopeMap()
	if  := .IDA(); len() > 0 {
		 := .getFieldIndexed(...)
		if  == nil {
			.errorf(, "import key %q doesn't exist inside import", )
			return nil, false
		}
		return , true
	}
	return , true
}

func ( *compiler) ( *d2ast.Import, ,  bool) (Node, bool) {
	if .stopped() {
		return nil, false
	}
	,  := .pushImportStack()
	if ! {
		return nil, false
	}
	defer .popImportStack()

	// Only get immediate imports.
	if len(.importStack) == 2 {
		if ,  := .seenImports[]; ! {
			.imports = append(.imports, .PathWithPre())
		}
	}

	 :=  && len(.globContexts()) == 0 && len(.mapRefContextStack) == 0
	if  {
		if  := .importTemplates[];  != nil {
			.seenImports[] = struct{}{}
			if  := .IDA();  && !.Spread && len() > 0 && .canSelect() {
				 := .ir.getFieldIndexed(...)
				if  == nil {
					.errorf(, "import key %q doesn't exist inside import", )
					return nil, false
				}
				if !.reserveVariableCopy(, ) {
					return nil, false
				}
				 := cloneImportField()
				nilScopeMap()
				return , true
			}
			if !.reserveVariableCopy(, .ir) {
				return nil, false
			}
			return .selectImport(cloneImportMap(.ir), )
		}
	}

	 := len(.err.Errors)
	, ,  := .loadImportAST(, , .err)
	if ! {
		if  != nil {
			.errorf(, "failed to import %q: %v", , )
		}
		return nil, false
	}

	 := &Map{}
	.initRoot()
	.parent.(*Field).References[0].Context_.Scope = 

	.compileMap(, , )
	if .stopped() {
		return nil, false
	}

	// We attempt to resolve variables in the imported file scope first
	// But ignore errors, in case the variable is meant to be resolved at the
	// importer
	 := make([]d2ast.Error, len(.err.Errors))
	copy(, .err.Errors)
	.compileSubstitutions(, nil)
	.err.Errors = 
	if .stopped() {
		return nil, false
	}

	.seenImports[] = struct{}{}
	if  && len(.err.Errors) ==  {
		if !.reserveVariableCopy(, ) {
			return nil, false
		}
		.importTemplates[] = &importTemplate{ir: cloneImportMap()}
	}

	return .selectImport(, )
}

// Imported IR templates are context independent for ordinary maps. Board
// imports are different: scenario and step inheritance is established while
// the import is attached to its destination board. Keep the parsed-AST cache
// there, but compile a fresh IR instance so board order and inheritance retain
// their source semantics.
func importTemplateSafeAt( Node) bool {
	for  != nil {
		if  := NodeBoardKind();  != "" {
			 := false
			switch n := .(type) {
			case *Field:
				 = .Root()
			case *Map:
				 = .Root()
			}
			if ! {
				return false
			}
		}
		 = .Parent()
	}
	return true
}

func ( *compiler) ( *d2ast.Import) (*Map, bool) {
	 := .PathWithPre()
	if  == "" && .Range != (d2ast.Range{}) {
		return nil, false
	}

	if len(.importStack) > 0 {
		if path.Ext() != ".d2" {
			 += ".d2"
		}

		if !filepath.IsAbs() {
			 = path.Join(path.Dir(.importStack[len(.importStack)-1]), )
		}
	}

	// Use a separate parse error to avoid polluting the main one
	 := &d2parser.ParseError{}
	, ,  := .loadImportAST(, , )
	if ! {
		return nil, false
	}

	 := &Map{}
	.initRoot()
	.parent.(*Field).References[0].Context_.Scope = 

	.compileMap(, , )
	if .stopped() {
		return nil, false
	}

	return , true
}

func ( *compiler) ( string,  d2ast.Node,  *d2parser.ParseError) (*d2ast.Map, error, bool) {
	if  := .parsedImports[];  != nil {
		if !.reserveVariableASTCopy(, ) {
			return nil, nil, false
		}
		,  := cloneASTMapContext(.ctx, )
		if  != nil {
			.handleVariableExpansionWalkError()
			return nil, nil, false
		}
		return , nil, true
	}

	if .fs == nil {
		return nil, ErrImportsDisabled, false
	}
	,  := .fs.Open()
	if  != nil {
		return nil, , false
	}
	defer .Close()

	,  := d2parser.ParseContext(.ctx, , , &d2parser.ParseOptions{
		UTF16Pos:   .utf16Pos,
		ParseError: ,
	})
	if  != nil {
		return nil, nil, false
	}
	.parsedImports[] = 
	if !.reserveVariableASTCopy(, ) {
		return nil, nil, false
	}
	,  := cloneASTMapContext(.ctx, )
	if  != nil {
		.handleVariableExpansionWalkError()
		return nil, nil, false
	}
	return , nil, true
}

func nilScopeMap( Node) {
	switch n := .(type) {
	case *Map:
		for ,  := range .Fields {
			()
		}
		for ,  := range .Edges {
			()
		}
	case *Edge:
		for ,  := range .References {
			.Context_.ScopeMap = nil
		}
		if .Map() != nil {
			(.Map())
		}
	case *Field:
		for ,  := range .References {
			.Context_.ScopeMap = nil
		}
		if .Map() != nil {
			(.Map())
		}
	}
}