package routing

import (
	
	
	
	

	
	
)

const (
	// Production routing declares at most three alternative flavors. Keeping a
	// separate budget for each flavor makes limit behavior independent of worker
	// scheduling while this cap bounds the aggregate operation to three budgets.
	maxRouteSearchFlavors = 3

	// maxRouteSearchWorkUnits is calibrated against representative routing
	// corpora. The larger measured per-flavor peak is 51,125,738 units. 120
	// million leaves over 2x headroom while the
	// independent OVG and graph limits bound topology and allocation.
	maxRouteSearchWorkUnits uint64 = 120_000_000

	routeSearchContextCheckStride uint64 = 1024
)

var errRouteSearchWorkLimit = errors.New("TALA route-search work limit exceeded")

// workBudget is implemented by both the standalone route-stage guard and the
// route-search guard. Shared geometric helpers can therefore account the work
// to the operation that invoked them without changing their routing behavior.
type workBudget interface {
	step() error
	add(uint64) error
	check() error
}

type routeSearchWorkGuard struct {
	ctx       context.Context
	done      <-chan struct{}
	flavor    RouteGenerationFlavor
	used      uint64
	limit     uint64
	metrics   *routeSearchTelemetry
	metricID  int // telemetry only; initialized to -1 before concurrent workers
	aggregate workBudget
	nextCheck uint64
	// The production stage envelope is the sum of independently enforced OVG,
	// flavor, and post-route envelopes. Charge a flavor's actual total when it
	// joins instead of contending on the shared counter for every inner-loop
	// operation. Injected limits retain exact per-operation accounting.
	deferAggregate   bool
	aggregateCharged uint64
}

func newRouteSearchWorkGuard( context.Context,  RouteGenerationFlavor,  uint64) (*routeSearchWorkGuard, error) {
	if  == nil {
		return nil, fmt.Errorf("TALA EdgeRouting requires a context")
	}
	 := &routeSearchWorkGuard{
		ctx:       ,
		done:      .Done(),
		flavor:    ,
		limit:     ,
		metrics:   routeSearchTelemetryFromContext(),
		metricID:  -1,
		aggregate: routeAggregateWorkFromContext(),
	}
	if ,  := .aggregate.(*routeWorkGuard);  {
		.enableParallel()
		.deferAggregate = .limit == maxEdgeRoutingStageWorkUnits &&  == maxRouteSearchWorkUnits
	}
	if  := .check();  != nil {
		return nil, 
	}
	.nextCheck = routeSearchContextCheckStride
	return , nil
}

// bind switches from the caller context used during router construction to the
// worker context. The latter is canceled when a sibling flavor panics.
func ( *routeSearchWorkGuard) ( context.Context) error {
	if  == nil {
		return fmt.Errorf("TALA EdgeRouting requires a context")
	}
	.ctx = 
	.done = .Done()
	if  := .check();  != nil {
		return 
	}
	.nextCheck = .used + routeSearchContextCheckStride
	return nil
}

func ( *routeSearchWorkGuard) () error {
	// Single-step calls sit in the innermost scans. Poll every iteration so a
	// synthetic context without a Done signal remains observable immediately.
	// Standard contexts use the same bounded polling stride as the bulk path.
	if .done == nil || .used >= .nextCheck {
		if  := .check();  != nil {
			return 
		}
		.nextCheck = .used + routeSearchContextCheckStride
	}
	if .used >= .limit {
		if  := .check();  != nil {
			return 
		}
		return fmt.Errorf(
			"%w: TALA EdgeRouting flavor %s work exceeds limit %d",
			errRouteSearchWorkLimit,
			.flavor,
			.limit,
		)
	}
	.used++
	if .deferAggregate || .aggregate == nil {
		return nil
	}
	return .aggregate.add(1)
}

func ( *routeSearchWorkGuard) ( uint64) error {
	if .used > .limit ||  > .limit-.used {
		if  := .check();  != nil {
			return 
		}
		return fmt.Errorf(
			"%w: TALA EdgeRouting flavor %s work exceeds limit %d",
			errRouteSearchWorkLimit,
			.flavor,
			.limit,
		)
	}
	.used += 
	if !.deferAggregate && .aggregate != nil {
		if  := .aggregate.add();  != nil {
			return 
		}
	}
	if .done == nil ||  == 0 || .used >= .nextCheck {
		if  := .check();  != nil {
			return 
		}
		.nextCheck = .used + routeSearchContextCheckStride
	}
	return nil
}

func ( *routeSearchWorkGuard) () error {
	if  := cachedContextErr(.ctx, .done);  != nil {
		return fmt.Errorf("EdgeRouting: %w", )
	}
	return nil
}

func ( *routeSearchWorkGuard) (,  uint64) error {
	,  := limits.CheckedMulUint64(, )
	if ! {
		if  := .check();  != nil {
			return 
		}
		return fmt.Errorf("%w: route-search work arithmetic overflow", errRouteSearchWorkLimit)
	}
	return .add()
}

func ( *routeSearchWorkGuard) (,  uint64) error {
	,  := limits.CheckedAddUint64(, )
	if ! {
		if  := .check();  != nil {
			return 
		}
		return fmt.Errorf("%w: route-search work arithmetic overflow", errRouteSearchWorkLimit)
	}
	return .add()
}

func ( *routeSearchWorkGuard) ( int) error {
	if  < 2 {
		return .step()
	}
	 := uint64(0)
	for  := uint64( - 1);  > 0;  >>= 1 {
		++
	}
	return .reserveProduct(uint64(), )
}

func ( *routeSearchWorkGuard) () error {
	if .metrics != nil {
		.metrics.record(, .used)
	}
	if .deferAggregate && .used != .aggregateCharged {
		 := .used - .aggregateCharged
		if  := .aggregate.add();  != nil {
			return 
		}
		.aggregateCharged = .used
	}
	return .check()
}

// routeSearchTelemetry is installed by calibration tests through context. It
// is concurrency-safe because flavor workers run in parallel.
type routeSearchTelemetry struct {
	mu      sync.Mutex
	samples []uint64
}

// SearchTelemetry collects per-flavor work samples for routing diagnostics
// and calibration. It is safe to share across concurrent route workers.
type SearchTelemetry struct {
	state routeSearchTelemetry
}

// MaxSearchWorkUnits is the calibrated per-flavor route-search budget.
const MaxSearchWorkUnits = maxRouteSearchWorkUnits

// WithSearchTelemetry attaches route-search diagnostics to a routing context.
func ( context.Context,  *SearchTelemetry) context.Context {
	if  == nil {
		return 
	}
	return withRouteSearchTelemetry(, &.state)
}

// WorkSamples returns a stable copy of the recorded per-flavor work totals.
func ( *SearchTelemetry) () []uint64 {
	if  == nil {
		return nil
	}
	return .state.snapshot()
}

type routeSearchTelemetryKey struct{}

func withRouteSearchTelemetry( context.Context,  *routeSearchTelemetry) context.Context {
	return context.WithValue(, routeSearchTelemetryKey{}, )
}

func routeSearchTelemetryFromContext( context.Context) *routeSearchTelemetry {
	,  := .Value(routeSearchTelemetryKey{}).(*routeSearchTelemetry)
	return 
}

func ( *routeSearchTelemetry) ( *routeSearchWorkGuard,  uint64) {
	.mu.Lock()
	if .metricID < 0 {
		.metricID = len(.samples)
		.samples = append(.samples, )
	} else {
		.samples[.metricID] = 
	}
	.mu.Unlock()
}

func ( *routeSearchTelemetry) () []uint64 {
	.mu.Lock()
	defer .mu.Unlock()
	return append([]uint64{}, .samples...)
}

func routeSearchIsDescendantOf( workBudget, ,  *layoutgraph.Node) (bool, error) {
	for {
		if  := .step();  != nil {
			return false, 
		}
		if  ==  {
			return true, nil
		}
		if  == nil {
			return false, nil
		}
		switch {
		case .Container != nil:
			 = .Container
		case .Cluster != nil:
			 = .Cluster.Vessel
		case .Sequence != nil:
			 = .Sequence.Vessel
		default:
			return  == nil, nil
		}
	}
}

func routeSearchHasFixedAncestor( workBudget,  *layoutgraph.Node) (bool, error) {
	for  := ;  != nil;  = .OwningContainer() {
		if  := .step();  != nil {
			return false, 
		}
		if .FixedTopLeft != nil {
			return true, nil
		}
	}
	return false, nil
}

// fixedOverlapsForRoute mirrors fixedOverlapsFor while accounting hierarchy
// and pair scans. Each flavor derives its own immutable result so budget use is
// independent of worker scheduling and shared-cache ownership.
func fixedOverlapsForRoute( layoutgraph.Nodes,  *routeSearchWorkGuard) (map[*layoutgraph.Node]struct{}, error) {
	 := make([]*layoutgraph.Node, 0, len()/4)
	for ,  := range  {
		if  := .step();  != nil {
			return nil, 
		}
		,  := routeSearchHasFixedAncestor(, )
		if  != nil {
			return nil, 
		}
		if  {
			 = append(, )
		}
	}

	 := make(map[*layoutgraph.Node]struct{})
	for ,  := range  {
		if  := .step();  != nil {
			return nil, 
		}
		if ,  := [];  {
			continue
		}
		for ,  := range  {
			if  := .step();  != nil {
				return nil, 
			}
			,  := routeSearchIsDescendantOf(, , )
			if  != nil {
				return nil, 
			}
			,  := routeSearchIsDescendantOf(, , )
			if  != nil {
				return nil, 
			}
			if  ==  ||  ||  {
				continue
			}
			if .Overlaps(.Box) {
				[] = struct{}{}
				,  := routeSearchHasFixedAncestor(, )
				if  != nil {
					return nil, 
				}
				if  {
					[] = struct{}{}
				}
				break
			}
		}
	}

	return , .check()
}