package d2format

import (
	
	
	

	
)

// TODO: edges with shared path should be fmted as <rel>.(x -> y)
func ( d2ast.Node) string {
	var  printer
	.node()
	return .sb.String()
}

type printer struct {
	sb        strings.Builder
	indentStr string
	inKey     bool
}

func ( *printer) () {
	.indentStr += " " + " "
}

func ( *printer) () {
	.indentStr = .indentStr[:len(.indentStr)-2]
}

func ( *printer) () {
	.sb.WriteByte('\n')
	.sb.WriteString(.indentStr)
}

func ( *printer) ( d2ast.Node) {
	switch n := .(type) {
	case *d2ast.Comment:
		.comment()
	case *d2ast.BlockComment:
		.blockComment()
	case *d2ast.Null:
		.sb.WriteString("null")
	case *d2ast.Boolean:
		.sb.WriteString(strconv.FormatBool(.Value))
	case *d2ast.Number:
		.sb.WriteString(.Raw)
	case *d2ast.UnquotedString:
		.interpolationBoxes(.Value, false)
	case *d2ast.DoubleQuotedString:
		.sb.WriteByte('"')
		.interpolationBoxes(.Value, true)
		.sb.WriteByte('"')
	case *d2ast.SingleQuotedString:
		.sb.WriteByte('\'')
		if .Raw == "" {
			.Raw = escapeSingleQuotedValue(.Value)
		}
		.sb.WriteString(escapeSingleQuotedValue(.Value))
		.sb.WriteByte('\'')
	case *d2ast.BlockString:
		.blockString()
	case *d2ast.Substitution:
		.substitution()
	case *d2ast.Import:
		._import()
	case *d2ast.Array:
		.array()
	case *d2ast.Map:
		._map()
	case *d2ast.Key:
		.mapKey()
	case *d2ast.KeyPath:
		.key()
	case *d2ast.Edge:
		.edge()
	case *d2ast.EdgeIndex:
		.edgeIndex()
	}
}

func ( *printer) ( *d2ast.Comment) {
	 := strings.Split(.Value, "\n")
	for ,  := range  {
		.sb.WriteString("#")
		if  != "" {
			.sb.WriteByte(' ')
		}
		.sb.WriteString()
		if  < len()-1 {
			.newline()
		}
	}
}

func ( *printer) ( *d2ast.BlockComment) {
	.sb.WriteString(`"""`)
	if .Range.OneLine() {
		.sb.WriteByte(' ')
	}

	 := strings.Split(.Value, "\n")
	for ,  := range  {
		if !.Range.OneLine() {
			if  == "" {
				.sb.WriteByte('\n')
			} else {
				.newline()
			}
		}
		.sb.WriteString()
	}

	if !.Range.OneLine() {
		.newline()
	} else {
		.sb.WriteByte(' ')
	}
	.sb.WriteString(`"""`)
}

func ( *printer) ( []d2ast.InterpolationBox,  bool) {
	for ,  := range  {
		if .Substitution != nil {
			.substitution(.Substitution)
			continue
		}
		if .StringRaw == nil {
			var  string
			if  {
				 = escapeDoubledQuotedValue(*.String, .inKey)
			} else {
				 = escapeUnquotedValue(*.String, .inKey)
			}
			.StringRaw = &
		}
		if ! {
			if ,  := d2ast.ReservedKeywords[strings.ToLower(*.StringRaw)];  {
				 := strings.ToLower(*.StringRaw)
				.StringRaw = &
			}
		}
		.sb.WriteString(*.StringRaw)
	}
}

func ( *printer) ( *d2ast.BlockString) {
	 := .Quote
	for strings.Contains(.Value, "|"+) {
		if  == "" {
			 += "|"
		} else {
			 += string([len()-1])
		}
	}
	for strings.Contains(.Value, +"|") {
		 += string([len()-1])
	}

	if .Range == (d2ast.Range{}) {
		if strings.IndexByte(.Value, '\n') > -1 {
			.Range = d2ast.MakeRange(",1:0:0-2:0:0")
		}
		.Value = strings.TrimSpace(.Value)
	}

	.sb.WriteString("|" + )
	.sb.WriteString(.Tag)
	if !.Range.OneLine() {
		.indent()
	} else {
		.sb.WriteByte(' ')
	}

	 := strings.Split(.Value, "\n")
	for ,  := range  {
		if !.Range.OneLine() {
			if  == "" {
				.sb.WriteByte('\n')
			} else {
				.newline()
			}
		}
		.sb.WriteString()
	}

	if !.Range.OneLine() {
		.deindent()
		.newline()
	} else if .Value != "" {
		.sb.WriteByte(' ')
	}
	.sb.WriteString( + "|")
}

func ( *printer) ( []*d2ast.StringBox) {
	for ,  := range  {
		.node(.Unbox())
		if  < len()-1 {
			.sb.WriteByte('.')
		}
	}
}

func ( *printer) ( *d2ast.Substitution) {
	if .Spread {
		.sb.WriteString("...")
	}
	.sb.WriteString("${")
	.path(.Path)
	.sb.WriteByte('}')
}

func ( *printer) ( *d2ast.Import) {
	if .Spread {
		.sb.WriteString("...")
	}
	.sb.WriteString("@")
	 := path.Clean(.Pre)
	if  != "." {
		.sb.WriteString()
		.sb.WriteRune('/')
	}
	if len(.Path) > 0 {
		 := *
		.Path = append([]*d2ast.StringBox{}, .Path...)
		.Path[0] = d2ast.RawStringBox(path.Clean(.Path[0].Unbox().ScalarString()), true)
		 = &
	}
	.path(.Path)
}

func ( *printer) ( *d2ast.Array) {
	.sb.WriteByte('[')
	if !.Range.OneLine() {
		.indent()
	}

	 := d2ast.Node()
	for  := 0;  < len(.Nodes); ++ {
		 := .Nodes[]
		 := .Unbox()

		// Handle inline comments.
		if  > 0 && (.Comment != nil || .BlockComment != nil) {
			if .GetRange().Start.Line == .GetRange().End.Line && .GetRange().OneLine() {
				.sb.WriteByte(' ')
				.node()
				continue
			}
		}

		if !.Range.OneLine() {
			if  !=  {
				if .GetRange().Start.Line-.GetRange().End.Line > 1 {
					.sb.WriteByte('\n')
				}
			}
			.newline()
		} else if  > 0 {
			.sb.WriteString("; ")
		}

		.node()
		 = 
	}

	if !.Range.OneLine() {
		.deindent()
		.newline()
	}
	.sb.WriteByte(']')
}

func ( *printer) ( *d2ast.Map) {
	if !.IsFileMap() {
		.sb.WriteByte('{')
		if !.Range.OneLine() {
			.indent()
		}
	}

	 := []d2ast.MapNodeBox{}
	 := []d2ast.MapNodeBox{}
	 := []d2ast.MapNodeBox{}

	 := d2ast.Node()
	for  := 0;  < len(.Nodes); ++ {
		 := .Nodes[]
		 := .Unbox()
		// extract out layer, scenario, and step nodes and skip
		if .IsBoardNode() {
			switch .MapKey.Key.Path[0].Unbox().ScalarString() {
			case "layers":
				// remove useless
				if .MapKey.Value.Map != nil && len(.MapKey.Value.Map.Nodes) > 0 {
					 = append(, )
				}
			case "scenarios":
				if .MapKey.Value.Map != nil && len(.MapKey.Value.Map.Nodes) > 0 {
					 = append(, )
				}
			case "steps":
				if .MapKey.Value.Map != nil && len(.MapKey.Value.Map.Nodes) > 0 {
					 = append(, )
				}
			}
			 = 
			continue
		}

		// Handle inline comments.
		if  > 0 && (.Comment != nil || .BlockComment != nil) {
			if .GetRange().Start.Line == .GetRange().End.Line && .GetRange().OneLine() {
				.sb.WriteByte(' ')
				.node()
				continue
			}
		}

		if !.Range.OneLine() {
			if  !=  {
				if .GetRange().Start.Line-.GetRange().End.Line > 1 {
					.sb.WriteByte('\n')
				}
			}
			if !.IsFileMap() ||  > 0 {
				.newline()
			}
		} else if  > 0 {
			.sb.WriteString("; ")
		}

		.node()
		 = 
	}

	 := []d2ast.MapNodeBox{}
	 = append(, ...)
	 = append(, ...)
	 = append(, ...)

	// draw board nodes
	for  := 0;  < len(); ++ {
		 := [].Unbox()
		// if this board is the very first line of the file, don't add an extra newline
		if .GetRange().Start.Line != 0 {
			.sb.WriteByte('\n')
		}
		// if scope only has boards, don't newline the first board
		if  != 0 || len(.Nodes) > len() {
			.sb.WriteByte('\n')
		}

		.sb.WriteString(.indentStr)
		.node()
		 = 
	}

	if !.IsFileMap() {
		if !.Range.OneLine() {
			.deindent()
			.newline()
		}
		.sb.WriteByte('}')
	} else if len(.Nodes) > 0 {
		// Always write a trailing newline for nonempty file maps.
		.sb.WriteByte('\n')
	}
}

func ( *printer) ( *d2ast.Key) {
	if .Ampersand {
		.sb.WriteByte('&')
	} else if .NotAmpersand {
		.sb.WriteByte('!')
		.sb.WriteByte('&')
	}
	if .Key != nil {
		.key(.Key)
	}

	if len(.Edges) > 0 {
		if .Key != nil {
			.sb.WriteByte('.')
		}

		if .Key != nil || .EdgeIndex != nil || .EdgeKey != nil {
			.sb.WriteByte('(')
		}
		if .Edges[0].Src != nil {
			.key(.Edges[0].Src)
			.sb.WriteByte(' ')
		}
		for ,  := range .Edges {
			.edgeArrowAndDst()
			if  < len(.Edges)-1 {
				.sb.WriteByte(' ')
			}
		}
		if .Key != nil || .EdgeIndex != nil || .EdgeKey != nil {
			.sb.WriteByte(')')
		}

		if .EdgeIndex != nil {
			.edgeIndex(.EdgeIndex)
		}
		if .EdgeKey != nil {
			.sb.WriteByte('.')
			.key(.EdgeKey)
		}
	}

	if .Primary.Unbox() != nil {
		.sb.WriteString(": ")
		.node(.Primary.Unbox())
	}
	if .Value.Map != nil && len(.Value.Map.Nodes) == 0 {
		return
	}
	if .Value.Unbox() != nil {
		if .Primary.Unbox() == nil {
			.sb.WriteString(": ")
		} else {
			.sb.WriteByte(' ')
		}
		.node(.Value.Unbox())
	}
}

func ( *printer) ( *d2ast.KeyPath) {
	.inKey = true
	if  != nil {
		.path(.Path)
	}
	.inKey = false
}

func ( *printer) ( *d2ast.Edge) {
	if .Src != nil {
		.key(.Src)
		.sb.WriteByte(' ')
	}
	.edgeArrowAndDst()
}

func ( *printer) ( *d2ast.Edge) {
	if .SrcArrow == "" {
		.sb.WriteByte('-')
	} else {
		.sb.WriteString(.SrcArrow)
	}
	if .DstArrow == "" {
		.sb.WriteByte('-')
	} else {
		if .SrcArrow != "" {
			.sb.WriteByte('-')
		}
		.sb.WriteString(.DstArrow)
	}
	if .Dst != nil {
		.sb.WriteByte(' ')
		.key(.Dst)
	}
}

func ( *printer) ( *d2ast.EdgeIndex) {
	.sb.WriteByte('[')
	if .Glob {
		.sb.WriteByte('*')
	} else {
		.sb.WriteString(strconv.Itoa(*.Int))
	}
	.sb.WriteByte(']')
}

func ( *d2ast.KeyPath) ( []string) {
	for ,  := range .Path {
		// We format each string of the key to ensure the resulting strings can be parsed
		// correctly.
		 := &d2ast.KeyPath{
			Path: []*d2ast.StringBox{d2ast.MakeValueBox(d2ast.RawString(.Unbox().ScalarString(), true)).StringBox()},
		}
		 = append(, Format())
	}
	return 
}