package engine

import (
	
	
	
	
	

	

	
	
	
	
	
	
	
	
	
	
	
)

const maxGraphSize = limits.MaxGraphSize

// LayoutOptions configures one deterministic layout attempt.
type LayoutOptions struct {
	Seed int64
}

type pipelineStage struct {
	name string
	run  func(*pipeline, context.Context) error
}

type routingSnapshot struct {
	ovg   *routing.OVG
	graph *layoutgraph.Graph
}

func newRoutingSnapshot( context.Context,  *routing.OVG,  *layoutgraph.Graph) (*routingSnapshot, error) {
	// Clone the OVG and graph at the same point in the routing pipeline so tests
	// and developer tooling can render a consistent snapshot.
	 := routing.NewOVG(nil)
	,  := json.Marshal()
	if  != nil {
		return nil, 
	}
	if  := json.Unmarshal(, );  != nil {
		return nil, 
	}
	,  := layoutgraph.Clone(, )
	if  != nil {
		return nil, 
	}
	return &routingSnapshot{
		ovg:   ,
		graph: ,
	}, nil
}

type pipeline struct {
	graph                    *layoutgraph.Graph
	seed                     int64
	random                   *rand.Rand
	hierarchyPlacementRandom *rand.Rand
	stages                   []pipelineStage
	storeSnapshots           bool
	snapshots                []*routingSnapshot
	stageDurations           []time.Duration
	forceReroute             bool
	edgeRoutingComplete      bool
	alignAxesNeeded          bool
}

// defaultPipelineStages is immutable after initialization. Tests that wrap or
// replace stages must first install a private stages override on their pipeline.
var defaultPipelineStages = [...]pipelineStage{
	{name: "Prescale", run: (*pipeline).prescaleStage},
	{name: "PreprocessSequences", run: (*pipeline).preprocessSequenceStage},
	{name: "Preprocess", run: (*pipeline).preprocessStage},
	{name: "PreprocessTrees", run: (*pipeline).preprocessTreesStage},
	{name: "PreprocessHierarchies", run: (*pipeline).preprocessHierarchies},
	{name: "PreprocessClusters", run: (*pipeline).preprocessClusters},
	{name: "PreprocessHubs", run: (*pipeline).preprocessHubs},
	{name: "NodePlacement", run: (*pipeline).nodePlacementStage},
	{name: "SwapStuff", run: (*pipeline).swapNodesStage},
	{name: "Transpose", run: (*pipeline).transposeStage},
	{name: "AlignAxes", run: (*pipeline).alignAxes},
	{name: "GapNormalization", run: (*pipeline).gapNormalizationStage},
	{name: "AlignAxes", run: (*pipeline).alignAxes},
	{name: "OptimizeClusters", run: (*pipeline).optimizeClustersStage},
	{name: "AlignAxes", run: (*pipeline).alignAxes},
	{name: "BalanceSymmetry", run: (*pipeline).balanceSymmetryStage},
	{name: "Equidistance", run: (*pipeline).equidistanceStage},
	{name: "AlignAxes", run: (*pipeline).alignAxes},
	{name: "BinPack", run: (*pipeline).binPack},
	{name: "CleanupStuff", run: (*pipeline).cleanupGroupsStage},
	{name: "Rescale", run: (*pipeline).rescaleStage},
	{name: "EdgeRouting", run: (*pipeline).edgeRoutingStage},
	{name: "Crosshatch", run: (*pipeline).crosshatchStage},
	{name: "Dejitter", run: (*pipeline).dejitterStage},
	{name: "EdgeRouting", run: (*pipeline).edgeRoutingStage},
	{name: "SimplifyEdgeRoutes", run: (*pipeline).simplifyEdgeRoutes},
	{name: "SwapEdgePorts", run: (*pipeline).swapEdgePorts},
	{name: "StraightEdgesFallback", run: (*pipeline).straightEdgesFallback},
	{name: "BalanceEdgeSegments", run: (*pipeline).balanceEdgeSegments},
	{name: "FixClusterEdgeBranching", run: (*pipeline).fixClusterEdgeBranching},
	{name: "TraceEdgesToShapeBorder", run: (*pipeline).traceEdgesToShapeBorder},
	{name: "ReorderDuplicates", run: (*pipeline).reorderDuplicates},
	// Second binpack doesn't try to avoid edges.
	{name: "BinPack", run: (*pipeline).binPack},
	{name: "PlaceLabels", run: (*pipeline).placeLabels},
	{name: "NudgeEdgeChannels", run: (*pipeline).nudgeEdgeChannels},
	{name: "ShortcutEdgeRoutes", run: (*pipeline).shortcutEdgeRoutes},
	{name: "Normalize", run: (*pipeline).normalizeStage},
}

func ( *pipeline) () []pipelineStage {
	if .stages != nil {
		return .stages
	}
	return defaultPipelineStages[:]
}

func ( *pipeline) () {
	.stageDurations = make([]time.Duration, len(.stagePlan()))
}

func ( *pipeline) ( context.Context) error {
	return routing.SimplifyEdgeRoutes(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return routing.ShortcutEdgeRoutes(, .graph)
}

// newPipeline initializes one deterministic layout pipeline.
func newPipeline( *layoutgraph.Graph,  int64,  bool) *pipeline {
	return &pipeline{
		graph:                    ,
		seed:                     ,
		hierarchyPlacementRandom: rand.New(rand.NewSource()),
		random:                   rand.New(rand.NewSource()),
		storeSnapshots:           ,
		alignAxesNeeded:          true,
	}
}

// prescaleStage takes trivial time, so it does not poll the context itself.
func ( *pipeline) ( context.Context) error {
	placement.Prescale(.graph)
	return nil
}

func ( *pipeline) ( context.Context) error {
	return grouping.AddSequences(, .graph, .random)
}

func ( *pipeline) ( context.Context) error {
	return proximity.AddHubs(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return grouping.AddClusters(, .graph, .seed, .random)
}

func ( *pipeline) ( context.Context) error {
	if  := hierarchy.Assign(, .graph, nil, hierarchy.Candidates(.graph));  != nil {
		return 
	}

	if  := hierarchy.Place(, .graph, nil, .hierarchyPlacementRandom);  != nil {
		return 
	}

	hierarchy.RemoveIsolatedMemberships(.graph)

	return nil
}

func ( *pipeline) ( context.Context) error {
	placement.Prepare(.graph)
	return nil
}

func ( *pipeline) ( context.Context) error {
	return trees.Preprocess(, .graph)
}

func ( *pipeline) ( context.Context) error {
	,  := placement.NormalizeGaps(, .graph)
	.alignAxesNeeded = 
	return 
}

func ( *pipeline) ( context.Context) error {
	return placement.Place(, .graph, .seed)
}

func ( *pipeline) ( context.Context) error {
	.graph.ComputeCellSize()
	var  error
	.alignAxesNeeded,  = placement.OptimizeClusters(, .graph)
	return 
}

func ( *pipeline) ( context.Context) error {
	return placement.BalanceSymmetry(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return packing.Pack(, .graph, nil)
}

func ( *pipeline) ( context.Context) error {
	return placement.Swap(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return placement.TransposeAll(, .graph)
}

func ( *pipeline) ( context.Context) error {
	grouping.Cleanup(.graph)
	return nil
}

func ( *pipeline) ( context.Context) error {
	.graph.ComputeCellSize()
	placement.Pad(.graph)
	return nil
}

func ( *pipeline) ( context.Context) error {
	return .runGraphRouting(, nil)
}

type routingSnapshotObserver struct {
	pipeline *pipeline
	ctx      context.Context
	started  bool
}

func ( *routingSnapshotObserver) ( *routing.OVG) error {
	if !.started {
		.pipeline.snapshots = nil
		.started = true
	}
	,  := newRoutingSnapshot(.ctx, , .pipeline.graph)
	if  != nil {
		return fmt.Errorf("copy edge-routing snapshot: %w", )
	}
	.pipeline.snapshots = append(.pipeline.snapshots, )
	return nil
}

func ( *pipeline) () {
	.edgeRoutingComplete = true
}

func ( *pipeline) ( *routing.OVG) error {
	.snapshots = nil
	return nil
}

func ( *pipeline) ( context.Context) routing.SubgraphRouteObserver {
	if !.storeSnapshots {
		return 
	}
	return &routingSnapshotObserver{pipeline: , ctx: }
}

func ( *pipeline) ( context.Context,  *uint64) ( error) {
	if  == nil || .graph == nil {
		return fmt.Errorf("TALA EdgeRouting requires a graph")
	}
	 := .snapshots
	 := .edgeRoutingComplete
	 := false
	defer func() {
		if  := recover();  != nil {
			.snapshots = 
			.edgeRoutingComplete = 
			panic()
		}
		if ! {
			.snapshots = 
			.edgeRoutingComplete = 
		}
	}()

	 := routing.GraphRouteOptions{
		ForceReroute:              .forceReroute,
		RoutesPreviouslyCompleted: .edgeRoutingComplete,
		Observer:                  .routeObserver(),
		CompletionObserver:        ,
	}
	var  bool
	if  == nil {
		,  = routing.RouteGraph(, .graph, )
	} else {
		,  = routing.RouteGraphWithWorkLimit(, .graph, , *)
	}
	if  != nil {
		return 
	}
	.edgeRoutingComplete = 
	 = true
	return nil
}

// straightEdgesFallback replaces expensive orthogonal routes with suitable
// straight routes after the normal routing attempts have completed.
func ( *pipeline) ( context.Context) error {
	return routing.StraightEdgesFallback(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return routing.TraceEdgesToShapeBorder(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return routing.BalanceEdgeSegments(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return routing.NudgeEdgeChannels(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return routing.FixClusterEdgeBranching(, .graph)
}

func ( *pipeline) ( context.Context) error {
	var  error
	.forceReroute,  = placement.Dejitter(, .graph)
	if  != nil {
		return 
	}

	// TODO account for nearby nodes and cascading nested-container expansion
	// before expanding containers at this stage.

	return nil
}

func ( *pipeline) ( context.Context) error {
	.graph.ComputeCellSize()
	var  error
	.alignAxesNeeded,  = placement.Equidistance(, .graph)
	return 
}

func ( *pipeline) ( context.Context) error {
	if !.alignAxesNeeded {
		return nil
	}
	.alignAxesNeeded = false
	return placement.Align(, .graph)
}

func ( *pipeline) ( context.Context) error {
	placement.Normalize(.graph)
	return nil
}

func ( *pipeline) ( context.Context) error {
	return routing.ReorderDuplicates(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return labeling.Place(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return routing.SwapAllEdgePorts(, .graph)
}

func ( *pipeline) ( context.Context) error {
	return routing.Crosshatch(, .graph)
}

func ( *pipeline) () {
	for ,  := range .graph.Edges {
		if  == nil {
			continue
		}
		.Points = nil
		if .Label == nil || !.Label.PositionFixed() {
			.LabelPercentage = 0
			if .Label != nil && .Label.Position.IsUnlocked() {
				.Label.Position = label.Unset
			}
		}
		.IsCurve = false
	}
	.graph.ResetPlacementCosts()
}

// runAllStages executes the layout stages in order against the pipeline graph.
func ( *pipeline) ( context.Context) error {
	, ,  := layoutgraph.EnsureTransactionWorkGuard(, "AutolayoutTransactions")
	if  != nil {
		return 
	}
	 := .stagePlan()
	 := .stageDurations != nil
	if  && len(.stageDurations) != len() {
		.stageDurations = make([]time.Duration, len())
	}
	for  := range  {
		 := &[]
		if  := .Err();  != nil {
			return fmt.Errorf("%s: %w", .name, )
		}
		if  == 0 {
			grouping.ResetClusters(.graph)
			// A full layout owns geometry, routing, and route-derived presentation.
			// An incoming route cannot remain authoritative once node placement, obstacle
			// sizing, and padding may change its coordinate space. Invalidate its
			// auto-placed label and cost state too, while retaining explicit locked
			// label positions. None of that stale state should influence the first,
			// pre-routing BinPack or the new route search.
			.resetFullLayoutRouteState()
		}
		var  time.Time
		if  {
			 = time.Now()
		}
		if  := .run(, );  != nil {
			return 
		}
		if  := .Err();  != nil {
			return fmt.Errorf("%s: %w", .name, )
		}
		if  {
			.stageDurations[] = time.Since()
		}

		// Only check overflow if nodes have been placed
		,  := .graph.BoundingBox()
		if  != nil &&  != nil {
			 := .X - .X
			 := .Y - .Y
			if ( > maxGraphSize) || ( > maxGraphSize) {
				return invariant.Errorf("Dimensions w:%v, h:%v reached after stage %v", , , .name)
			}
		}
	}
	return nil
}

// Layout returns one complete deterministic layout in a new graph. Input is
// validated and remains unchanged, so callers may safely reuse it for other
// layout attempts.
func ( context.Context,  *layoutgraph.Graph,  LayoutOptions) (*layoutgraph.Graph, error) {
	if  == nil {
		return nil, fmt.Errorf("TALA Autolayout requires a context")
	}
	if  := .Err();  != nil {
		return nil, fmt.Errorf("Autolayout: %w", )
	}
	if  == nil {
		return nil, fmt.Errorf("TALA Autolayout requires a graph")
	}
	,  := layoutgraph.Clone(, )
	if  != nil {
		if  := .Err();  != nil {
			return nil, fmt.Errorf("Autolayout: %w", )
		}
		return nil, 
	}
	if ,  := runLayout(, , , pipelineInstrumentation{}, false);  != nil {
		return nil, 
	}
	if  := .Err();  != nil {
		return nil, fmt.Errorf("Autolayout: %w", )
	}
	return , nil
}

type pipelineInstrumentation struct {
	storeSnapshots        bool
	measureStageDurations bool
}

func runLayout( context.Context,  *layoutgraph.Graph,  LayoutOptions,  pipelineInstrumentation,  bool) (*pipeline, error) {
	if  == nil {
		return nil, fmt.Errorf("TALA Autolayout requires a context")
	}
	if  := .Err();  != nil {
		return nil, fmt.Errorf("Autolayout: %w", )
	}
	if  == nil {
		return nil, fmt.Errorf("TALA Autolayout requires a graph")
	}
	if  {
		if  := layoutgraph.Validate(, "Autolayout", );  != nil {
			return nil, 
		}
	}
	if len(.Nodes) == 0 {
		return nil, nil
	}
	 := 
	 := func() {}
	if .Done() == nil {
		,  = context.WithCancel()
	}
	defer ()
	 := newPipeline(, .Seed, .storeSnapshots)
	if .measureStageDurations {
		.enableStageTiming()
	}
	return , .runAllStages()
}