package executor

import (
	"context"
	"fmt"
	"sync"

	"tangled.org/core/spindle/engine"
	"tangled.org/core/spindle/models"
)

// reservedEngine wraps a real engine so that the slot acquired up-front during
// ReserveSeat is the slot StartWorkflows gets, instead of acquiring a second
// one. Every other method delegates straight to the real engine, so the
// execution path runs byte-for-byte as it would standalone.
//
// This is the only change to the execution path on an executor.
type reservedEngine struct {
	models.Engine
	slot engine.WorkflowSlot
	once sync.Once
}

// newReservedEngine returns a wrapper around inner that hands back slot exactly
// once from AcquireWorkflowSlot.
func newReservedEngine(inner models.Engine, slot engine.WorkflowSlot) models.Engine {
	return &reservedEngine{Engine: inner, slot: slot}
}

// AcquireWorkflowSlot hands back the pre-acquired slot exactly once. The slot
// was already obtained (and resource-accounted) during ReserveSeat, so a second
// acquire would double-count.
func (e *reservedEngine) AcquireWorkflowSlot(ctx context.Context, wid models.WorkflowId, wf *models.Workflow, _ engine.AcquireMode) (engine.WorkflowSlot, error) {
	var slot engine.WorkflowSlot
	e.once.Do(func() {
		slot = e.slot
		e.slot = nil
	})
	if slot == nil {
		return nil, fmt.Errorf("reserved slot already consumed")
	}
	return slot, nil
}
