package cli

import (
	"bufio"
	"context"
	"io"
	"strings"
	"time"

	"iop/apps/node/internal/runtime"
)

// lineEmitter parses one stdout line (already trimmed of trailing newline) and
// returns the resulting RuntimeEvents to push to the sink. Empty events are
// skipped. Returning a non-nil error aborts the run.
type lineEmitter interface {
	Name() string
	Emit(line string) ([]runtime.RuntimeEvent, error)
}

// registeredEmitter wraps a lineEmitter with its maximum scanner buffer size.
type registeredEmitter struct {
	emitter    lineEmitter
	scanBufMax int
}

// jsonEmitters maps OutputFormat names to their registered emitter + buffer config.
var jsonEmitters = map[string]registeredEmitter{
	"stream-json":   {emitter: streamJSONEmitter{}, scanBufMax: 4 * 1024 * 1024},
	"claude-json":   {emitter: claudeJSONEmitter{}, scanBufMax: 8 * 1024 * 1024},
	"codex-json":    {emitter: codexJSONEmitter{}, scanBufMax: 4 * 1024 * 1024},
	"opencode-json": {emitter: opencodeJSONEmitter{}, scanBufMax: 4 * 1024 * 1024},
	"cline-json":    {emitter: clineJSONEmitter{}, scanBufMax: 8 * 1024 * 1024},
}

// driveJSONLines runs a shared scanner loop over stdout, accumulates raw output
// into outBuf, dispatches each line through the emitter, and forwards events
// to sink. Returns total OutputTokens approximation.
func driveJSONLines(
	ctx context.Context,
	stdout io.Reader,
	sink runtime.EventSink,
	runID string,
	outBuf *strings.Builder,
	emitter lineEmitter,
	scanBufMax int,
) (int, error) {
	scanner := bufio.NewScanner(stdout)
	scanner.Buffer(make([]byte, 64*1024), scanBufMax)
	outputTokens := 0

	for scanner.Scan() {
		line := scanner.Bytes()
		outBuf.Write(line)
		outBuf.WriteByte('\n')

		trimmed := strings.TrimSpace(string(line))
		if trimmed == "" || trimmed[0] != '{' {
			continue
		}

		events, err := emitter.Emit(trimmed)
		if err != nil {
			return outputTokens, err
		}

		for _, ev := range events {
			ev.RunID = runID
			if ev.Timestamp.IsZero() {
				ev.Timestamp = time.Now()
			}
			if ev.Type == runtime.EventTypeDelta {
				outputTokens += len(strings.Fields(ev.Delta))
			}
			_ = sink.Emit(ctx, ev)
		}
	}

	if err := scanner.Err(); err != nil {
		return outputTokens, err
	}
	return outputTokens, nil
}
