package reposync

import (
	"fmt"

	"github.com/pingcap/agent-git-service/internal/sync/bulk"
)

const (
	DefaultBatchMaxItems = bulk.DefaultMaxItems
	DefaultBatchMaxBytes = bulk.DefaultMaxBytes
)

// BatchLimits mirrors the importer's proven adaptive shard defaults.
type BatchLimits struct {
	MaxItems int
	MaxBytes int64
}

// PhysicalBatch is an execution optimization, not a second durable state
// machine. Per-object success is always written back to the logical RunItem.
type PhysicalBatch struct {
	Key             string
	ObjectType      string
	Action          string
	DependencyPhase int
	PayloadBytes    int64
	Items           []PlannedItem
	// Bootstrap is derived from the durable Run planner mode. It allows native
	// Git transport to prepare the complete empty target once while logical
	// RunItem verification remains per ref.
	Bootstrap bool
}

// BuildPhysicalBatches groups compatible write operations by dependency phase,
// object type, action, item count, and encoded byte size. Non-write RunItems
// stay in the run report and are intentionally not submitted to writers.
func BuildPhysicalBatches(items []PlannedItem, limits BatchLimits) ([]PhysicalBatch, error) {
	normalized, err := (bulk.Limits{MaxItems: limits.MaxItems, MaxBytes: limits.MaxBytes}).Normalize()
	if err != nil {
		return nil, err
	}
	limits.MaxItems, limits.MaxBytes = normalized.MaxItems, normalized.MaxBytes

	writeItems := make([]PlannedItem, 0, len(items))
	for _, item := range items {
		switch item.Action {
		case ActionCreate, ActionUpdate, ActionDelete:
			writeItems = append(writeItems, item)
		}
	}
	sortPlannedItems(writeItems)

	batches := make([]PhysicalBatch, 0)
	for start := 0; start < len(writeItems); {
		key := fmt.Sprintf("%06d:%s:%s", writeItems[start].DependencyPhase, writeItems[start].ObjectType, writeItems[start].Action)
		end := start + 1
		for end < len(writeItems) && fmt.Sprintf("%06d:%s:%s", writeItems[end].DependencyPhase, writeItems[end].ObjectType, writeItems[end].Action) == key {
			end++
		}
		shards, err := bulk.Shard(writeItems[start:end], normalized, func(item PlannedItem) int64 { return item.PayloadBytes })
		if err != nil {
			return nil, err
		}
		for _, shard := range shards {
			batch := PhysicalBatch{Key: key, ObjectType: shard[0].ObjectType, Action: shard[0].Action, DependencyPhase: shard[0].DependencyPhase, Items: shard}
			for _, item := range shard {
				batch.PayloadBytes += item.PayloadBytes
			}
			batches = append(batches, batch)
		}
		start = end
	}
	return batches, nil
}
