package d2sequence

import (
	
	
	
	
	
	
	
	

	

	
	
	
	
	
)

type sequenceDiagram struct {
	root      *d2graph.Object
	messages  []*d2graph.Edge
	lifelines []*d2graph.Edge
	actors    []*d2graph.Object
	groups    []*d2graph.Object
	spans     []*d2graph.Object
	notes     []*d2graph.Object

	// can be either actors or spans
	// rank: left to right position of actors/spans (spans have the same rank as their parents)
	objectRank map[*d2graph.Object]int

	// keep track of the first and last message of a given actor/span
	firstMessage map[*d2graph.Object]*d2graph.Edge
	lastMessage  map[*d2graph.Object]*d2graph.Edge

	// the distance from actor[i] center to actor[i+1] center
	// every neighbor actors need different distances depending on the message labels between them
	actorXStep []float64

	yStep          float64
	maxActorHeight float64

	verticalIndices map[string]int
}

func getObjEarliestLineNum( *d2graph.Object) int {
	 := int(math.MaxInt32)
	for ,  := range .References {
		if .MapKey == nil {
			continue
		}
		if .Key.HasGlob() {
			continue
		}
		 = go2.IntMin(, .MapKey.Range.Start.Line)
	}
	return 
}

func getEdgeEarliestLineNum( *d2graph.Edge) int {
	 := int(math.MaxInt32)
	for ,  := range .References {
		if .MapKey == nil {
			continue
		}
		if .Edge.Src.HasGlob() || .Edge.Dst.HasGlob() {
			continue
		}
		 = go2.IntMin(, .MapKey.Range.Start.Line)
	}
	return 
}

func newSequenceDiagram( []*d2graph.Object,  []*d2graph.Edge) (*sequenceDiagram, error) {
	var  []*d2graph.Object
	var  []*d2graph.Object

	slices.SortFunc(, func(,  *d2graph.Object) int {
		return cmp.Compare(getObjEarliestLineNum(), getObjEarliestLineNum())
	})
	slices.SortFunc(, func(,  *d2graph.Edge) int {
		return cmp.Compare(getEdgeEarliestLineNum(), getEdgeEarliestLineNum())
	})

	for ,  := range  {
		if .IsSequenceDiagramGroup() {
			 := []*d2graph.Object{}
			// Groups may have more nested groups
			for len() > 0 {
				 := [0]
				.LabelPosition = go2.Pointer(label.InsideTopLeft.String())
				 = append(, )
				 = [1:]
				 = append(, .ChildrenArray...)
			}
		} else {
			 = append(, )
		}
	}

	if len() == 0 {
		return nil, errors.New("no actors declared in sequence diagram")
	}

	 := &sequenceDiagram{
		messages:        ,
		actors:          ,
		groups:          ,
		spans:           nil,
		notes:           nil,
		lifelines:       nil,
		objectRank:      make(map[*d2graph.Object]int),
		firstMessage:    make(map[*d2graph.Object]*d2graph.Edge),
		lastMessage:     make(map[*d2graph.Object]*d2graph.Edge),
		actorXStep:      make([]float64, len()-1),
		yStep:           MIN_MESSAGE_DISTANCE,
		maxActorHeight:  0.,
		verticalIndices: make(map[string]int),
	}

	for ,  := range  {
		.root = .Parent
		.objectRank[] = 

		if .Width < MIN_ACTOR_WIDTH {
			 := strings.ToLower(.Shape.Value)
			switch  {
			case d2target.ShapePerson, d2target.ShapeOval, d2target.ShapeSquare, d2target.ShapeCircle:
				// scale shape up to min width uniformly
				.Height *= MIN_ACTOR_WIDTH / .Width
			}
			.Width = MIN_ACTOR_WIDTH
		}
		.maxActorHeight = math.Max(.maxActorHeight, .Height)

		 := make([]*d2graph.Object, len(.ChildrenArray))
		copy(, .ChildrenArray)
		 := 0.
		for len() > 0 {
			 := [0]
			 = [1:]

			// spans are children of actors that have edges
			// edge groups are children of actors with no edges and children edges
			if .IsSequenceDiagramNote() {
				.verticalIndices[.AbsID()] = getObjEarliestLineNum()
				.Shape = d2graph.Scalar{Value: shape.PAGE_TYPE}
				.notes = append(.notes, )
				.objectRank[] = 
				.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
				 = math.Max(, .Width)
			} else {
				// spans have no labels
				// TODO why not? Spans should be able to
				.Label = d2graph.Scalar{Value: ""}
				.Shape = d2graph.Scalar{Value: shape.SQUARE_TYPE}
				.spans = append(.spans, )
				.objectRank[] = 
			}

			 = append(, .ChildrenArray...)
		}

		if  != len()-1 {
			 := .Width / 2.
			 := [+1].Width / 2.
			.actorXStep[] = math.Max(++HORIZONTAL_PAD, MIN_ACTOR_DISTANCE)
			.actorXStep[] = math.Max(/2.+HORIZONTAL_PAD, .actorXStep[])
			if  > 0 {
				.actorXStep[-1] = math.Max(/2.+HORIZONTAL_PAD, .actorXStep[-1])
			}
		}
	}

	for ,  := range .messages {
		.verticalIndices[.AbsID()] = getEdgeEarliestLineNum()

		// ensures that long labels, spanning over multiple actors, don't make for large gaps between actors
		// by distributing the label length across the actors rank difference
		 := math.Abs(float64(.objectRank[.Src]) - float64(.objectRank[.Dst]))
		if  != 0 {
			 := float64(.LabelDimensions.Width) / 
			for  := go2.IntMin(.objectRank[.Src], .objectRank[.Dst]);  <= go2.IntMax(.objectRank[.Src], .objectRank[.Dst])-1; ++ {
				.actorXStep[] = math.Max(.actorXStep[], +LABEL_HORIZONTAL_PAD)
			}
		} else {
			// self edge
			 := .objectRank[.Src]
			if  < len(.actorXStep) {
				 := float64(.LabelDimensions.Width) + label.PADDING*4
				.actorXStep[] = math.Max(.actorXStep[], )
			}
		}
		.lastMessage[.Src] = 
		if ,  := .firstMessage[.Src]; ! {
			.firstMessage[.Src] = 
		}
		.lastMessage[.Dst] = 
		if ,  := .firstMessage[.Dst]; ! {
			.firstMessage[.Dst] = 
		}
	}

	.yStep += VERTICAL_PAD
	.maxActorHeight += VERTICAL_PAD
	if .root.HasLabel() {
		.maxActorHeight += float64(.root.LabelDimensions.Height)
	}

	return , nil
}

func ( *sequenceDiagram) () error {
	.placeActors()
	.placeNotes()
	if  := .routeMessages();  != nil {
		return 
	}
	.placeSpans()
	.adjustRouteEndpoints()
	.placeGroups()
	.addLifelineEdges()
	return nil
}

func ( *sequenceDiagram) () {
	sort.SliceStable(.groups, func(,  int) bool {
		return .groups[].Level() > .groups[].Level()
	})
	for ,  := range .groups {
		.ZIndex = GROUP_Z_INDEX
		.placeGroup()
	}
	for ,  := range .groups {
		.adjustGroupLabel()
	}
}

func ( *sequenceDiagram) ( *d2graph.Object) {
	 := math.Inf(1)
	 := math.Inf(1)
	 := math.Inf(-1)
	 := math.Inf(-1)

	for ,  := range .messages {
		if .ContainedBy() {
			for ,  := range .Route {
				 := float64(.LabelDimensions.Height) / 2.
				 := math.Max(+GROUP_CONTAINER_PADDING, MIN_MESSAGE_DISTANCE/2.)
				 = math.Min(, .X-HORIZONTAL_PAD)
				 = math.Min(, .Y-)
				 = math.Max(, .X+HORIZONTAL_PAD)
				 = math.Max(, .Y+)
			}
		}
	}
	// Groups should horizontally encompass all notes of the actor
	for ,  := range .notes {
		 := false
		for ,  := range .References {
			if .Key.HasGlob() {
				continue
			}
			 := .ScopeObj
			for  != nil {
				if  ==  {
					 = true
					break
				}
				 = .Parent
			}
			if  {
				break
			}
		}
		if  {
			 = math.Min(, .TopLeft.X-HORIZONTAL_PAD)
			 = math.Min(, .TopLeft.Y-MIN_MESSAGE_DISTANCE/2.)
			 = math.Max(, .TopLeft.X+.Width+HORIZONTAL_PAD)
			 = math.Max(, .TopLeft.Y+.Height+MIN_MESSAGE_DISTANCE/2.)
		}
	}

	for ,  := range .ChildrenArray {
		for ,  := range .groups {
			if  ==  {
				 = math.Min(, .TopLeft.X-GROUP_CONTAINER_PADDING)
				 = math.Min(, .TopLeft.Y-GROUP_CONTAINER_PADDING)
				 = math.Max(, .TopLeft.X+.Width+GROUP_CONTAINER_PADDING)
				 = math.Max(, .TopLeft.Y+.Height+GROUP_CONTAINER_PADDING)
				break
			}
		}
	}

	.Box = geo.NewBox(
		geo.NewPoint(
			,
			,
		),
		-,
		-,
	)
}

func ( *sequenceDiagram) ( *d2graph.Object) {
	if !.HasLabel() {
		return
	}

	 := (.LabelDimensions.Height + EDGE_GROUP_LABEL_PADDING/2.)
	if  < GROUP_CONTAINER_PADDING {
		return
	}

	.Height += float64()

	// Extend stuff within this group
	for ,  := range .groups {
		if .TopLeft.Y < .TopLeft.Y && .TopLeft.Y+.Height > .TopLeft.Y {
			.Height += float64()
		}
	}
	for ,  := range .spans {
		if .TopLeft.Y < .TopLeft.Y && .TopLeft.Y+.Height > .TopLeft.Y {
			.Height += float64()
		}
	}

	// Move stuff down
	for ,  := range .messages {
		if go2.Min(.Route[0].Y, .Route[len(.Route)-1].Y) > .TopLeft.Y {
			for ,  := range .Route {
				.Y += float64()
			}
		}
	}
	for ,  := range .spans {
		if .TopLeft.Y > .TopLeft.Y {
			.TopLeft.Y += float64()
		}
	}
	for ,  := range .groups {
		if .TopLeft.Y > .TopLeft.Y {
			.TopLeft.Y += float64()
		}
	}
	for ,  := range .notes {
		if .TopLeft.Y > .TopLeft.Y {
			.TopLeft.Y += float64()
		}
	}
}

// placeActors places actors bottom aligned, side by side with centers spaced by sd.actorXStep
func ( *sequenceDiagram) () {
	 := .actors[0].Width / 2.
	for ,  := range .actors {
		var  float64
		if .HasOutsideBottomLabel() {
			.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String())
			 = .maxActorHeight - .Height
			if .HasLabel() {
				 -= float64(.LabelDimensions.Height)
			}
		} else {
			.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
			 = .maxActorHeight - .Height
		}
		 := .Width / 2.
		.TopLeft = geo.NewPoint(math.Round(-), )
		if  != len(.actors)-1 {
			 += .actorXStep[]
		}
	}
}

// addLifelineEdges adds a new edge for each actor in the graph that represents the its lifeline
// . ┌──────────────┐
// . │     actor    │
// . └──────┬───────┘
// .        │
// .        │ lifeline
// .        │
// .        │
func ( *sequenceDiagram) () {
	 := 0.
	if len(.messages) > 0 {
		 := .messages[len(.messages)-1].Route
		for ,  := range  {
			 = math.Max(, .Y)
		}
	}
	for ,  := range .notes {
		 = math.Max(, .TopLeft.Y+.Height)
	}
	for ,  := range .actors {
		 = math.Max(, .TopLeft.Y+.Height)
	}
	 += .yStep

	for ,  := range .actors {
		 := .Center()
		.Y = .TopLeft.Y + .Height
		if *.LabelPosition == label.OutsideBottomCenter.String() && .HasLabel() {
			.Y += float64(.LabelDimensions.Height) + LIFELINE_LABEL_PAD
		}
		 := .Center()
		.Y = 
		 := d2graph.Style{
			StrokeDash:  &d2graph.Scalar{Value: fmt.Sprintf("%d", LIFELINE_STROKE_DASH)},
			StrokeWidth: &d2graph.Scalar{Value: fmt.Sprintf("%d", LIFELINE_STROKE_WIDTH)},
		}
		if .Style.StrokeDash != nil {
			.StrokeDash = &d2graph.Scalar{Value: .Style.StrokeDash.Value}
		}
		if .Style.Stroke != nil {
			.Stroke = &d2graph.Scalar{Value: .Style.Stroke.Value}
		}

		.lifelines = append(.lifelines, &d2graph.Edge{
			Attributes: d2graph.Attributes{Style: },
			Src:        ,
			SrcArrow:   false,
			Dst: &d2graph.Object{
				ID: .ID + fmt.Sprintf("-lifeline-end-%d", go2.StringToIntHash(.ID+"-lifeline-end")),
			},
			DstArrow: false,
			Route:    []*geo.Point{, },
			ZIndex:   LIFELINE_Z_INDEX,
		})
	}
}

func ( *d2graph.Object) bool {
	// lifeline ends only have ID and no graph parent or box set
	if .Graph != nil || .Parent != nil || .Box != nil {
		return false
	}
	if !strings.Contains(.ID, "-lifeline-end-") {
		return false
	}
	 := strings.Split(.ID, "-lifeline-end-")
	if len() > 1 {
		 := [len()-1]
		 := strings.Join([:len()-1], "-lifeline-end-")
		if strconv.Itoa(go2.StringToIntHash(+"-lifeline-end")) ==  {
			return true
		}
	}
	return false
}

func ( *sequenceDiagram) () {
	 := make(map[int]float64)
	for ,  := range .actors {
		[.objectRank[]] = .Center().X
	}

	for ,  := range .notes {
		 := .verticalIndices[.AbsID()]
		 := .maxActorHeight + .yStep

		for ,  := range .messages {
			if .verticalIndices[.AbsID()] <  {
				if .Src == .Dst {
					// For self-messages, account for the full vertical space they occupy
					 += .yStep + math.Max(float64(.LabelDimensions.Height), MIN_MESSAGE_DISTANCE)*1.5
				} else {
					 += .yStep + float64(.LabelDimensions.Height)
				}
			}
		}
		for ,  := range .notes {
			if .verticalIndices[.AbsID()] <  {
				 += .Height + .yStep
			}
		}

		 := [.objectRank[]] - (.Width / 2.)
		.Box.TopLeft = geo.NewPoint(, )
		.ZIndex = NOTE_Z_INDEX
	}
}

// placeSpans places spans over the object lifeline
// . ┌──────────┐
// . │  actor   │
// . └────┬─────┘
// .    ┌─┴──┐
// .    │    │
// .    |span|
// .    │    │
// .    └─┬──┘
// .      │
// .   lifeline
// .      │
func ( *sequenceDiagram) () {
	// quickly find the span center X
	 := make(map[int]float64)
	for ,  := range .actors {
		[.objectRank[]] = .Center().X
	}

	// places spans from most to least nested
	// the order is important because the only way a child span exists is if there's a message to it
	// however, the parent span might not have a message to it and then its position is based on the child position
	// or, there can be a message to it, but it comes after the child one meaning the top left position is still based on the child
	// and not on its own message
	 := make([]*d2graph.Object, len(.spans))
	copy(, .spans)
	sort.SliceStable(, func(,  int) bool {
		return [].Level() > [].Level()
	})
	for ,  := range  {
		// finds the position based on children
		 := math.Inf(1)
		 := math.Inf(-1)
		for ,  := range .ChildrenArray {
			 = math.Min(, .TopLeft.Y)
			 = math.Max(, .TopLeft.Y+.Height)
		}

		// finds the position if there are messages to this span
		 := math.Inf(1)
		if ,  := .firstMessage[];  {
			if .Src == .Dst ||  == .Src {
				 = .Route[0].Y
			} else {
				 = .Route[len(.Route)-1].Y
			}
		}
		 := math.Inf(-1)
		if ,  := .lastMessage[];  {
			if .Src == .Dst ||  == .Dst {
				 = .Route[len(.Route)-1].Y
			} else {
				 = .Route[0].Y
			}
		}

		// if it is the same as the child top left, add some padding
		 := math.Min(, )
		if  ==  ||  ==  {
			 -= SPAN_MESSAGE_PAD
		}
		 := math.Max(, )
		if  ==  ||  ==  {
			 += SPAN_MESSAGE_PAD
		}

		 := math.Max(-, MIN_SPAN_HEIGHT)
		// -1 because the actors count as 1 level
		 := SPAN_BASE_WIDTH + (float64(.Level()-.root.Level()-2) * SPAN_DEPTH_GROWTH_FACTOR)
		 := [.objectRank[]] - ( / 2.)
		.Box = geo.NewBox(geo.NewPoint(, ), , )
		.ZIndex = SPAN_Z_INDEX
	}
}

// routeMessages routes horizontal edges (messages) from Src to Dst lifeline (actor/span center)
// in another step, routes are adjusted to spans borders when necessary
func ( *sequenceDiagram) () error {
	 := .maxActorHeight + .yStep
	for ,  := range .messages {
		.ZIndex = MESSAGE_Z_INDEX
		 := 0.
		for ,  := range .notes {
			if .verticalIndices[.AbsID()] < .verticalIndices[.AbsID()] {
				 += .Height + .yStep
			}
		}

		var ,  float64
		if  := getCenter(.Src);  != nil {
			 = .X
		} else {
			return fmt.Errorf("could not find center of %s. Is it declared as an actor?", .Src.ID)
		}
		if  := getCenter(.Dst);  != nil {
			 = .X
		} else {
			return fmt.Errorf("could not find center of %s. Is it declared as an actor?", .Dst.ID)
		}
		 := strings.HasPrefix(.Dst.AbsID(), .Src.AbsID()+".")
		 := strings.HasPrefix(.Src.AbsID(), .Dst.AbsID()+".")
		 := .Src == .Dst

		 := .Src
		for !.Parent.IsSequenceDiagram() {
			 = .Parent
		}
		 := .Dst
		for !.Parent.IsSequenceDiagram() {
			 = .Parent
		}
		 :=  == 

		if  ||  ||  ||  {
			 :=  + math.Max(SELF_MESSAGE_HORIZONTAL_TRAVEL, float64(.LabelDimensions.Width)/2.+label.PADDING*2)
			 :=  + 
			 :=  + math.Max(float64(.LabelDimensions.Height), MIN_MESSAGE_DISTANCE)*1.5
			.Route = []*geo.Point{
				geo.NewPoint(, ),
				geo.NewPoint(, ),
				geo.NewPoint(, ),
				geo.NewPoint(, ),
			}
			 =  + .yStep - 
		} else {
			 :=  +  + float64(.LabelDimensions.Height/2.)
			.Route = []*geo.Point{
				geo.NewPoint(, ),
				geo.NewPoint(, ),
			}
			 =  + float64(.LabelDimensions.Height/2.) + .yStep - 
		}

		if .Label.Value != "" {
			.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String())
		}
	}
	return nil
}

func getCenter( *d2graph.Object) *geo.Point {
	if  == nil {
		return nil
	} else if .Box != nil && .Box.TopLeft != nil {
		return .Center()
	}
	return (.Parent)
}

// adjustRouteEndpoints adjust the first and last points of message routes when they are spans
// routeMessages() will route to the actor lifelife as a reference point and this function
// adjust to span width when necessary
func ( *sequenceDiagram) () {
	for ,  := range .messages {
		 := .Route
		if !.isActor(.Src) {
			if .objectRank[.Src] <= .objectRank[.Dst] {
				[0].X += .Src.Width / 2.
			} else {
				[0].X -= .Src.Width / 2.
			}
		}
		if !.isActor(.Dst) {
			if .objectRank[.Src] < .objectRank[.Dst] {
				[len()-1].X -= .Dst.Width / 2.
			} else {
				[len()-1].X += .Dst.Width / 2.
			}
		}
	}
}

func ( *sequenceDiagram) ( *d2graph.Object) bool {
	return .Parent == .root
}

func ( *sequenceDiagram) () float64 {
	// the layout is always placed starting at 0, so the width is just the last actor
	 := .actors[len(.actors)-1]
	 := .TopLeft.X + .Width

	for ,  := range .messages {
		for ,  := range .Route {
			 = math.Max(, .X)
		}
		// Self referential messages may have labels that extend further
		if .Src == .Dst {
			 = math.Max(, .Route[1].X+float64(.LabelDimensions.Width)/2.)
		}
	}

	return 
}

func ( *sequenceDiagram) () float64 {
	return .lifelines[0].Route[1].Y
}

func ( *sequenceDiagram) ( *geo.Point) {
	 := append([]*d2graph.Object{}, .actors...)
	 = append(, .spans...)
	 = append(, .groups...)
	 = append(, .notes...)
	for ,  := range  {
		.TopLeft.X += .X
		.TopLeft.Y += .Y
	}

	 := append([]*d2graph.Edge{}, .messages...)
	 = append(, .lifelines...)
	for ,  := range  {
		for ,  := range .Route {
			.X += .X
			.Y += .Y
		}
	}
}