package d2talalayout

import (
	
	
	
	
	
	
	

	
	
)

const (
	maxSeeds                  = 16
	maxSeedEntries            = maxSeeds * 4
	defaultMaxSeedConcurrency = 4
)

// Options controls deterministic local layout attempts. The zero value uses
// the package defaults. Seeds stored on the D2 graph take precedence over
// Seeds. MaxConcurrency bounds simultaneous attempts; zero selects the package
// default.
type Options struct {
	// Seeds selects deterministic attempts. Nil uses the package defaults; an
	// explicitly empty slice is invalid.
	Seeds []int64 `json:"tala-seeds"`

	// MaxConcurrency bounds simultaneous attempts. Zero uses the package default.
	MaxConcurrency int `json:"-"`
}

// DefaultOptions returns an independent copy of the package defaults.
func () Options {
	return Options{
		Seeds:          []int64{1, 2, 3},
		MaxConcurrency: defaultSeedConcurrency(),
	}
}

// DefaultLayout lays out graph with fresh default options. Like all D2 layout
// functions, it mutates graph and must not run concurrently against the same
// graph.
func ( context.Context,  *d2graph.Graph) error {
	return Layout(, , nil)
}

type localSeedAttempt struct {
	index  int
	seed   int64
	result seedResult
	err    error
}

func defaultSeedConcurrency() int {
	return min(runtime.GOMAXPROCS(0), defaultMaxSeedConcurrency)
}

func normalizeSeeds( []int64) ([]int64, error) {
	if len() == 0 {
		return nil, fmt.Errorf("tala requires at least one seed")
	}
	if len() > maxSeedEntries {
		return nil, fmt.Errorf("tala accepts at most %d seed entries", maxSeedEntries)
	}

	 := min(len(), maxSeeds)
	 := make([]int64, 0, )
	 := make(map[int64]struct{}, )
	for ,  := range  {
		if ,  := [];  {
			continue
		}
		[] = struct{}{}
		 = append(, )
		if len() > maxSeeds {
			return nil, fmt.Errorf("tala supports at most %d unique seeds", maxSeeds)
		}
	}
	return , nil
}

func seedsFromData( any) ([]int64, error) {
	 := reflect.ValueOf()
	if !.IsValid() || (.Kind() != reflect.Array && .Kind() != reflect.Slice) {
		return nil, fmt.Errorf("tala-seeds must be a list of signed 64-bit integers")
	}
	if .Len() > maxSeedEntries {
		return nil, fmt.Errorf("tala accepts at most %d seed entries", maxSeedEntries)
	}

	 := make([]int64, 0, min(.Len(), maxSeeds))
	for  := 0;  < .Len(); ++ {
		 := .Index().Interface()
		,  := strconv.ParseInt(fmt.Sprint(), 10, 64)
		if  != nil {
			return nil, fmt.Errorf("invalid tala seed at index %d (%v): must be a signed 64-bit integer", , )
		}
		 = append(, )
	}
	return , nil
}

func layoutPlan( *d2graph.Graph,  *Options) ([]int64, int, error) {
	 := DefaultOptions()
	if  == nil {
		 = &
	}

	 := .Seeds
	if  == nil {
		 = .Seeds
	}
	if .Data != nil {
		if ,  := .Data["tala-seeds"];  {
			var  error
			,  = seedsFromData()
			if  != nil {
				return nil, 0, 
			}
		}
	}

	,  := normalizeSeeds()
	if  != nil {
		return nil, 0, 
	}
	 := .MaxConcurrency
	if  == 0 {
		 = .MaxConcurrency
	}
	if  < 0 ||  > maxSeeds {
		return nil, 0, fmt.Errorf("tala MaxConcurrency must be between 1 and %d, or zero for the default", maxSeeds)
	}
	if  > len() {
		 = len()
	}
	return , , nil
}

func runLocalSeed(
	 context.Context,
	 seedInput,
	 int,
	 int64,
) ( localSeedAttempt) {
	.index = 
	.seed = 
	defer func() {
		if recover() != nil {
			.result = seedResult{}
			.err = fmt.Errorf("TALA seed layout failed due to an internal invariant")
		}
	}()

	,  := runSeed(, , )
	if  != nil {
		.err = 
		return 
	}
	.result, .err = evaluateSeedResult(, , )
	return 
}

type localSeedFunc func(index int, seed int64) localSeedAttempt

func coordinateLocalSeeds( context.Context,  []int64,  int,  localSeedFunc) (seedResult, error) {
	if len() == 0 {
		return seedResult{}, fmt.Errorf("tala requires at least one seed")
	}
	if  < 1 {
		return seedResult{}, fmt.Errorf("tala seed concurrency must be positive")
	}
	if  > len() {
		 = len()
	}
	 := make(chan int, len())
	for  := range  {
		 <- 
	}
	close()

	 := make(chan localSeedAttempt)
	var  sync.WaitGroup
	for range  {
		.Go(func() {
			for  := range  {
				 <- (, [])
			}
		})
	}
	go func() {
		.Wait()
		close()
	}()

	 := -1
	var  seedResult
	 := make([]error, len())
	for  := range  {
		if .err != nil {
			[.index] = fmt.Errorf("seed %d: %w", .seed, .err)
			continue
		}
		 := 0
		if  >= 0 {
			 = .result.score.compare(.score)
		}
		if  < 0 ||  < 0 || ( == 0 && .index > ) {
			 = .index
			 = .result
		}
	}

	if  := .Err();  != nil {
		return seedResult{}, fmt.Errorf("tala layout stopped before all seeds completed: %w", )
	}

	if  < 0 {
		 := errors.Join(...)
		if  == nil {
			return seedResult{}, fmt.Errorf("no TALA seed produced a layout")
		}
		return seedResult{}, fmt.Errorf("all TALA seed attempts failed: %w", )
	}
	return , nil
}

func runLocalSeeds( context.Context,  seedInput,  []int64,  int) (seedResult, error) {
	return coordinateLocalSeeds(, , , func( int,  int64) localSeedAttempt {
		return runLocalSeed(, , , )
	})
}

// Layout runs every configured seed locally, selects the best complete result
// deterministically, and then atomically applies it to graph. It never selects
// a result from only a timing-dependent subset of the configured seeds. The
// caller must not mutate graph concurrently.
func ( context.Context,  *d2graph.Graph,  *Options) ( error) {
	defer recoverAsError("layout", &)

	if  == nil {
		return fmt.Errorf("TALA layout requires a context")
	}
	if  := .Err();  != nil {
		return 
	}
	if  == nil || .Root == nil {
		return fmt.Errorf("tala requires a D2 graph with a root object")
	}
	, ,  := layoutPlan(, )
	if  != nil {
		return 
	}
	,  := newSeedInput(, )
	if  != nil {
		return 
	}
	// Logging and request metadata commonly wrap Background with values, leaving
	// Done nil. Give the CPU-heavy engine phase a real cancellation channel so
	// its hot work guards can poll that channel without repeatedly traversing
	// every value wrapper. Values and parent cancellation semantics are retained.
	 := 
	 := func() {}
	if .Done() == nil {
		,  = context.WithCancel()
	}
	defer ()
	,  := runLocalSeeds(, , , )
	if  != nil {
		return 
	}
	 := 
	,  = considerSeedCandidate(, , func() (seedResult, error) {
		,  := engine.CompoundCandidate(, .graph)
		if  != nil {
			return seedResult{}, 
		}
		if  == .graph {
			return , nil
		}
		return evaluateSeedResult(, , )
	})
	if  != nil {
		return 
	}
	if .graph != .graph {
		 := 
		,  = refineSeedResult(, , func() (seedResult, error) {
			,  := engine.PreserveCompoundRoutes(, .graph, .graph)
			if  != nil {
				return seedResult{}, 
			}
			return evaluateSeedResult(, , )
		})
		if  != nil {
			return 
		}
	}
	return applySeedResult(, , )
}

// Optional strategies may reject a geometry or exhaust their own search. They
// must not invalidate a complete ordinary result. Caller cancellation remains
// an error; a timing-dependent subset of candidates is never selected.
func considerSeedCandidate( context.Context,  seedResult,  func() (seedResult, error)) (seedResult, error) {
	,  := refineSeedResult(, , )
	if  != nil {
		return , 
	}
	if .score.compare(.score) < 0 {
		return , nil
	}
	return , nil
}

// A routing refinement preserves the selected placement. Its enclosure checks
// retain already-valid interior paths; the completed-layout score does
// not measure their shared fan structure. Validation still runs before apply.
func refineSeedResult( context.Context,  seedResult,  func() (seedResult, error)) ( seedResult,  error) {
	 = 
	defer func() {
		if recover() != nil {
			,  = , nil
		}
		if  := .Err();  != nil {
			 = 
		}
	}()
	,  := ()
	if  != nil {
		if errors.Is(, context.Canceled) || errors.Is(, context.DeadlineExceeded) {
			return , 
		}
		return , nil
	}
	return , nil
}