package progress

import (
	"fmt"
	"io"
	"sync"
	"time"
)

// TimingReporter records the first-seen timestamp of each stage and,
// when printed, emits a per-stage duration breakdown. Used by the
// `gortex index --profile` command to show where wall-clock time is
// being spent during a full index pass.
//
// Stage transitions are detected by the reporter seeing a *new* stage
// label arrive — subsequent ticks for the same stage (progress updates
// like "parsing 3000/5000") don't create a new entry. This matches the
// indexer's usage where it calls Report once at stage entry and then
// again with counter updates.
type TimingReporter struct {
	mu     sync.Mutex
	start  time.Time
	stages []stageEntry
	seen   map[string]int // stage → index in stages (also suppresses duplicates)
}

type stageEntry struct {
	name  string
	seen  time.Time
	ticks int
}

// NewTimingReporter returns a reporter with its clock anchored at now.
func NewTimingReporter() *TimingReporter {
	return &TimingReporter{
		start: time.Now(),
		seen:  make(map[string]int),
	}
}

// Report records a stage tick. The first tick for a given stage name
// is treated as the stage's start timestamp.
func (r *TimingReporter) Report(stage string, _, _ int) {
	if stage == "" {
		return
	}
	r.mu.Lock()
	defer r.mu.Unlock()
	if idx, ok := r.seen[stage]; ok {
		r.stages[idx].ticks++
		return
	}
	r.seen[stage] = len(r.stages)
	r.stages = append(r.stages, stageEntry{name: stage, seen: time.Now(), ticks: 1})
}

// WriteReport prints a two-column breakdown: per-stage duration and
// cumulative time since the reporter was created. end defaults to
// time.Now() when zero; pass an explicit value when the caller
// finishes before a final "indexing complete" stage tick is emitted.
func (r *TimingReporter) WriteReport(w io.Writer, end time.Time) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if end.IsZero() {
		end = time.Now()
	}
	if len(r.stages) == 0 {
		_, _ = fmt.Fprintln(w, "no stages recorded")
		return
	}

	_, _ = fmt.Fprintf(w, "%-32s %12s %12s\n", "stage", "duration", "cumulative")
	_, _ = fmt.Fprintf(w, "%-32s %12s %12s\n",
		"--------------------------------", "------------", "------------")
	for i, s := range r.stages {
		var nextStart time.Time
		if i+1 < len(r.stages) {
			nextStart = r.stages[i+1].seen
		} else {
			nextStart = end
		}
		duration := nextStart.Sub(s.seen)
		cumulative := nextStart.Sub(r.start)
		_, _ = fmt.Fprintf(w, "%-32s %12s %12s\n",
			s.name, formatMs(duration), formatMs(cumulative))
	}
}

// formatMs formats a duration as "123ms" / "4.56s" / "1m23s" depending
// on magnitude. Small-enough to be readable in a CLI dump.
func formatMs(d time.Duration) string {
	switch {
	case d < time.Second:
		return fmt.Sprintf("%dms", d.Milliseconds())
	case d < time.Minute:
		return fmt.Sprintf("%.2fs", d.Seconds())
	default:
		m := int(d / time.Minute)
		s := int((d % time.Minute) / time.Second)
		return fmt.Sprintf("%dm%02ds", m, s)
	}
}
