/**
 * ConsoleProgressReporter — the default terminal adapter for the
 * ProgressReporter port (W0053).
 *
 * Renders phase-start / phase-progress / phase-complete as human-readable
 * lines on stdout. Progress lines are rate-limited so a high-frequency
 * producer (one event per artifact) never floods the terminal; the adapter
 * flushes at most once per `tickMs` (default 1s) and always flushes the
 * final line on `phaseComplete`.
 *
 * `verbose: true` emits one line per item — matches the CLI's existing
 * `--verbose` semantics without re-rendering the rate-limited line twice.
 */
export class ConsoleProgressReporter {
    tickMs;
    verbose;
    write;
    now;
    phases = new Map();
    constructor(options = {}) {
        this.tickMs = options.tickMs ?? 1000;
        this.verbose = options.verbose ?? false;
        this.write = options.write ?? ((line) => console.log(line));
        this.now = options.now ?? (() => Date.now());
    }
    phaseStart(event) {
        this.phases.set(event.phaseId, {
            phaseId: event.phaseId,
            label: event.label,
            totalItems: event.totalItems,
            totalBytes: event.totalBytes,
            startedAt: event.startedAt,
            itemsCompleted: 0,
            bytesCompleted: 0,
            lastFlushAt: event.startedAt,
        });
        const total = event.totalItems !== undefined ? ` (${event.totalItems} items)` : "";
        const detail = event.detail ? ` → ${event.detail}` : "";
        this.write("");
        this.write(`  ➜ ${event.label}${detail}${total}…`);
    }
    phaseProgress(event) {
        const state = this.phases.get(event.phaseId);
        if (!state)
            return;
        state.itemsCompleted += event.items ?? 1;
        state.bytesCompleted += event.bytes ?? 0;
        if (this.verbose) {
            const item = event.label ?? `item ${state.itemsCompleted}`;
            const bytes = event.bytes ?? 0;
            this.write(`    · ${progressPrefix(state)} ${item} (${formatBytes(bytes)})`);
            return;
        }
        const now = this.now();
        if (now - state.lastFlushAt < this.tickMs)
            return;
        state.lastFlushAt = now;
        this.write(`    ${renderProgressLine(state, now)}`);
    }
    phaseComplete(event) {
        const state = this.phases.get(event.phaseId);
        // Prefer the adapter's running totals — we've been counting each batch
        // event anyway, and most producers call `phaseComplete` without knowing
        // cumulative numbers themselves. Fall back to the event fields when the
        // reporter has no state (e.g. first phaseComplete without a phaseStart).
        const itemsCompleted = Math.max(state?.itemsCompleted ?? 0, event.itemsCompleted);
        const bytesCompleted = Math.max(state?.bytesCompleted ?? 0, event.bytesCompleted);
        const durationMs = event.durationMs;
        const label = state?.label ?? event.phaseId;
        const summary = event.summary ??
            `${itemsCompleted} item${itemsCompleted === 1 ? "" : "s"} · ${formatBytes(bytesCompleted)} · ${formatDuration(durationMs)} elapsed`;
        this.write(`  ✓ ${label} — ${summary}`);
        this.phases.delete(event.phaseId);
    }
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
/**
 * Shared prefix used on every rate-limited and verbose progress line so the
 * context is visible without having to scroll up to the phase header.
 */
function progressPrefix(state) {
    return `${state.label}:`;
}
function renderProgressLine(state, now) {
    const elapsed = formatDuration(now - state.startedAt);
    const itemsPart = state.totalItems !== undefined
        ? `${state.itemsCompleted}/${state.totalItems}`
        : String(state.itemsCompleted);
    return `${progressPrefix(state)} ${itemsPart} · ${formatBytes(state.bytesCompleted)} · ${elapsed} elapsed`;
}
function formatBytes(bytes) {
    if (bytes < 1024)
        return `${bytes} B`;
    if (bytes < 1024 * 1024)
        return `${(bytes / 1024).toFixed(1)} KB`;
    if (bytes < 1024 * 1024 * 1024)
        return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
    return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
}
function formatDuration(ms) {
    if (ms < 1000)
        return `${ms}ms`;
    if (ms < 60_000)
        return `${(ms / 1000).toFixed(1)}s`;
    const minutes = Math.floor(ms / 60_000);
    const seconds = Math.round((ms % 60_000) / 1000);
    return `${minutes}m${seconds}s`;
}
