package d2graph

import (
	"oss.terrastruct.com/d2/d2target"
)

func (obj *Object) IsSequenceDiagram() bool {
	return obj != nil && obj.Shape.Value == d2target.ShapeSequenceDiagram
}

func (obj *Object) OuterSequenceDiagram() *Object {
	for obj != nil {
		obj = obj.Parent
		if obj.IsSequenceDiagram() {
			return obj
		}
	}
	return nil
}

// groups are objects in sequence diagrams that have no messages connected
// and does not have a note as a child (a note can appear within a group, but it's a child of an actor)
func (obj *Object) IsSequenceDiagramGroup() bool {
	if obj.OuterSequenceDiagram() == nil {
		return false
	}
	for _, e := range obj.Graph.Edges {
		if e.Src == obj || e.Dst == obj {
			return false
		}
	}
	for _, ch := range obj.ChildrenArray {
		// if the child contains a message, it's a span, not a note
		if !ch.ContainsAnyEdge(obj.Graph.Edges) {
			return false
		}
	}
	return obj.ContainsAnyObject(obj.Graph.Objects) || obj.ContainsAnyEdge(obj.Graph.Edges)
}

// notes are descendant of actors with no edges and no children
func (obj *Object) IsSequenceDiagramNote() bool {
	if obj.OuterSequenceDiagram() == nil {
		return false
	}
	return !obj.hasEdgeRef() && !obj.ContainsAnyEdge(obj.Graph.Edges) && len(obj.ChildrenArray) == 0 && !obj.ContainsAnyObject(obj.Graph.Objects)
}

func (obj *Object) hasEdgeRef() bool {
	for _, ref := range obj.References {
		if ref.MapKey != nil && len(ref.MapKey.Edges) > 0 {
			return true
		}
	}

	return false
}

func (obj *Object) ContainsAnyObject(objects []*Object) bool {
	for _, o := range objects {
		if o.ContainedBy(obj) {
			return true
		}
	}
	return false
}

func (o *Object) ContainedBy(obj *Object) bool {
	for _, ref := range o.References {
		curr := ref.ScopeObj
		for curr != nil {
			if curr == obj {
				return true
			}
			curr = curr.Parent
		}
	}
	return false
}

func (obj *Object) ContainsAnyEdge(edges []*Edge) bool {
	for _, e := range edges {
		if e.ContainedBy(obj) {
			return true
		}
	}
	return false
}

func (e *Edge) ContainedBy(obj *Object) bool {
	for _, ref := range e.References {
		curr := ref.ScopeObj
		for curr != nil {
			if curr == obj {
				return true
			}
			curr = curr.Parent
		}
	}
	return false
}

func (e *Edge) GetGroup() *Object {
	for _, ref := range e.References {
		if ref.ScopeObj.IsSequenceDiagramGroup() {
			return ref.ScopeObj
		}
	}
	return nil
}
