package dispatcher

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent"
	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/drivercontract"
	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeevents"
	"github.com/tidbcloud/AgentTeam/desktop/internal/consts"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

func mapRuntimeEventToAgentEvents(ev runtimeevents.Event) []types.AgentEvent {
	agentID := runtimeEventAgentID(ev)
	var out []types.AgentEvent

	if diagnostic, ok := runtimeevents.DataAs[*runtimeevents.RuntimeDiagnosticData](ev); ok && strings.TrimSpace(diagnostic.EventClass) != "" {
		out = append(out, types.RuntimeEvent{
			AgentID:        agentID,
			MessageID:      strings.TrimSpace(ev.Trace.MessageID),
			WakeToken:      strings.TrimSpace(ev.Trace.WakeToken),
			EventClass:     strings.TrimSpace(diagnostic.EventClass),
			Target:         strings.TrimSpace(ev.Trace.Target),
			ReplyMessageID: strings.TrimSpace(ev.Trace.ReplyMessageID),
			Detail:         strings.TrimSpace(diagnostic.Detail),
		})
		return out
	}
	if class := payloadString(ev, "event_class"); class != "" {
		out = append(out, types.RuntimeEvent{
			AgentID:        agentID,
			MessageID:      strings.TrimSpace(ev.Trace.MessageID),
			WakeToken:      strings.TrimSpace(ev.Trace.WakeToken),
			EventClass:     class,
			Target:         strings.TrimSpace(ev.Trace.Target),
			ReplyMessageID: strings.TrimSpace(firstNonEmpty(ev.Trace.ReplyMessageID, payloadString(ev, "reply_message_id"))),
			Detail:         runtimeEventDetail(ev),
		})
		return out
	}

	switch ev.Kind {
	case runtimeevents.KindSessionStarting:
		out = append(out,
			types.StatusEvent{
				AgentID: agentID,
				Status:  "active",
			},
			types.ActivityEvent{
				AgentID:  agentID,
				Activity: "working",
				Detail:   firstNonEmpty(runtimeEventDetail(ev), "Starting…"),
			},
		)
		return out
	case runtimeevents.KindSessionStarted:
		activity := firstNonEmpty(runtimeEventLifecycleActivity(ev), "working")
		out = append(out, types.StatusEvent{
			AgentID: agentID,
			Status:  "active",
		})
		out = append(out, types.ActivityEvent{
			AgentID:  agentID,
			Activity: activity,
			Detail:   runtimeEventDetail(ev),
		})
		return out
	case runtimeevents.KindSessionSleeping:
		out = append(out,
			types.StatusEvent{
				AgentID: agentID,
				Status:  "sleeping",
			},
			types.ActivityEvent{
				AgentID:  agentID,
				Activity: "sleeping",
				Detail:   runtimeEventDetail(ev),
			},
		)
		return out
	case runtimeevents.KindSessionStopped:
		out = append(out,
			types.SessionEvent{
				AgentID:   agentID,
				SessionID: "",
			},
			types.StatusEvent{
				AgentID: agentID,
				Status:  "inactive",
			},
			types.ActivityEvent{
				AgentID:  agentID,
				Activity: "offline",
				Detail:   runtimeEventDetail(ev),
			},
		)
		return out
	case runtimeevents.KindTurnSessionInit:
		return out
	case runtimeevents.KindWakeReceived,
		runtimeevents.KindWakeReplySent,
		runtimeevents.KindWakeNoReply,
		runtimeevents.KindWakeFailed:
		out = append(out, types.WakeStatusEvent{
			AgentID:        agentID,
			MessageID:      strings.TrimSpace(ev.Trace.MessageID),
			WakeToken:      strings.TrimSpace(ev.Trace.WakeToken),
			Target:         strings.TrimSpace(ev.Trace.Target),
			Status:         wakeStatusForRuntimeEvent(ev.Kind),
			ReplyMessageID: strings.TrimSpace(firstNonEmpty(ev.Trace.ReplyMessageID, payloadString(ev, "reply_message_id"))),
			Detail:         runtimeEventDetail(ev),
		})
		return out
	case runtimeevents.KindRecoveryPointPrepared:
		if data, ok := runtimeevents.DataAs[*runtimeevents.RecoveryPointData](ev); ok {
			out = append(out, types.RecoveryPointPreparedEvent{
				AgentID:     agentID,
				MessageID:   strings.TrimSpace(ev.Trace.MessageID),
				ThreadID:    firstNonEmpty(strings.TrimSpace(data.ThreadID), ev.Context.SessionID),
				Strategy:    strings.TrimSpace(data.Strategy),
				RecoveryRef: strings.TrimSpace(data.RecoveryRef),
			})
			return out
		}
		out = append(out, types.RecoveryPointPreparedEvent{
			AgentID:     agentID,
			MessageID:   strings.TrimSpace(ev.Trace.MessageID),
			ThreadID:    firstNonEmpty(payloadString(ev, "thread_id"), ev.Context.SessionID),
			Strategy:    payloadString(ev, "strategy"),
			RecoveryRef: payloadString(ev, "recovery_ref"),
		})
		return out
	}

	if class := legacyRuntimeEventClass(ev.Kind); class != "" {
		out = append(out, types.RuntimeEvent{
			AgentID:        agentID,
			MessageID:      strings.TrimSpace(ev.Trace.MessageID),
			WakeToken:      strings.TrimSpace(ev.Trace.WakeToken),
			EventClass:     class,
			Target:         strings.TrimSpace(ev.Trace.Target),
			ReplyMessageID: strings.TrimSpace(firstNonEmpty(ev.Trace.ReplyMessageID, payloadString(ev, "reply_message_id"))),
			Detail:         runtimeEventDetail(ev),
		})
		return out
	}

	activity, detail := runtimeEventActivity(ev)
	if activity != "" {
		out = append(out, types.ActivityEvent{
			AgentID:  agentID,
			Activity: activity,
			Detail:   detail,
		})
	}
	if entries := runtimeEventTrajectoryEntries(ev); len(entries) > 0 {
		out = append(out, types.TrajectoryEvent{
			AgentID:   agentID,
			SessionID: strings.TrimSpace(ev.Context.SessionID),
			Timestamp: runtimeEventUnixMillis(ev),
			Entries:   entries,
		})
	}
	return out
}

func runtimeEventUnixMillis(ev runtimeevents.Event) int64 {
	if ev.Time.IsZero() {
		return time.Now().UnixMilli()
	}
	return ev.Time.UnixMilli()
}

func runtimeEventActivity(ev runtimeevents.Event) (activity string, detail string) {
	switch ev.Kind {
	case runtimeevents.KindTurnThinking,
		runtimeevents.KindTurnText:
		return "thinking", payloadString(ev, "title")
	case runtimeevents.KindTurnToolCall:
		toolName := runtimeEventToolName(ev)
		classification := classifyEventTool(toolName)
		switch classification {
		case "send":
			return "working", "Sending message…"
		default:
			return "working", toolDisplayName(toolName)
		}
	case runtimeevents.KindTurnEnded,
		runtimeevents.KindTurnIdle:
		return "online", "Idle"
	case runtimeevents.KindTurnCompacting:
		return "working", "Compacting context..."
	case runtimeevents.KindTurnCompacted:
		return firstNonEmpty(runtimeEventLifecycleActivity(ev), "working"),
			firstNonEmpty(runtimeEventDetail(ev), "Context compacted.")
	case runtimeevents.KindDeliveryAccepted:
		return runtimeEventLifecycleActivity(ev), runtimeEventDetail(ev)
	}
	return "", ""
}

func runtimeEventAgentID(ev runtimeevents.Event) string {
	return firstNonEmpty(ev.Context.ServerAgentID, ev.Context.AgentID)
}

func runtimeEventLifecycleActivity(ev runtimeevents.Event) string {
	if data, ok := runtimeevents.DataAs[*runtimeevents.LifecycleData](ev); ok {
		return strings.TrimSpace(data.Activity)
	}
	return payloadString(ev, "activity")
}

func runtimeEventDetail(ev runtimeevents.Event) string {
	if data, ok := runtimeevents.DataAs[*runtimeevents.LifecycleData](ev); ok {
		return strings.TrimSpace(data.Detail)
	}
	if data, ok := runtimeevents.DataAs[*runtimeevents.WakeData](ev); ok {
		return strings.TrimSpace(data.Detail)
	}
	if data, ok := runtimeevents.DataAs[*runtimeevents.RuntimeDiagnosticData](ev); ok {
		return strings.TrimSpace(data.Detail)
	}
	return payloadString(ev, "detail")
}

func runtimeEventToolName(ev runtimeevents.Event) string {
	if data, ok := runtimeevents.DataAs[*runtimeevents.ToolCallData](ev); ok {
		return strings.TrimSpace(data.ToolName)
	}
	return payloadString(ev, "tool_name")
}

func runtimeEventToolCallID(ev runtimeevents.Event) string {
	if data, ok := runtimeevents.DataAs[*runtimeevents.ToolCallData](ev); ok {
		return strings.TrimSpace(data.ToolCallID)
	}
	return payloadString(ev, "tool_call_id", "toolCallId", "call_id", "callId")
}

func runtimeEventToolMetadata(ev runtimeevents.Event) map[string]any {
	if data, ok := runtimeevents.DataAs[*runtimeevents.ToolCallData](ev); ok {
		return cloneTrajectoryMetadata(data.Metadata)
	}
	if value, ok := payloadValue(ev, "metadata"); ok {
		if metadata, ok := value.(map[string]any); ok {
			return cloneTrajectoryMetadata(metadata)
		}
	}
	return nil
}

func runtimeEventDeliveryMetadata(ev runtimeevents.Event, state string, acked bool) map[string]any {
	metadata := cloneTrajectoryMetadata(payloadMetadata(ev))
	if metadata == nil {
		metadata = make(map[string]any, 4)
	}
	metadata["backgroundNotice"] = true
	metadata["deliveryState"] = state
	metadata["runtimeSurface"] = acked
	if inputID := strings.TrimSpace(ev.Trace.CorrelationID); inputID != "" {
		metadata["runtimeInputId"] = inputID
	} else if inputID := strings.TrimSpace(ev.Trace.MessageID); inputID != "" {
		metadata["runtimeInputId"] = inputID
	}
	return metadata
}

func payloadMetadata(ev runtimeevents.Event) map[string]any {
	if data, ok := runtimeevents.DataAs[*runtimeevents.LifecycleData](ev); ok {
		return data.Metadata
	}
	if value, ok := payloadValue(ev, "metadata"); ok {
		if metadata, ok := value.(map[string]any); ok {
			return metadata
		}
	}
	return nil
}

func cloneTrajectoryMetadata(metadata map[string]any) map[string]any {
	if len(metadata) == 0 {
		return nil
	}
	out := make(map[string]any, len(metadata))
	for key, value := range metadata {
		out[key] = value
	}
	return out
}

func wakeStatusForRuntimeEvent(kind runtimeevents.Kind) string {
	switch kind {
	case runtimeevents.KindWakeReceived:
		return consts.WakeStatusReceived
	case runtimeevents.KindWakeReplySent:
		return consts.WakeStatusReplySent
	case runtimeevents.KindWakeNoReply:
		return consts.WakeStatusCompletedWithoutVisibleReply
	case runtimeevents.KindWakeFailed:
		return consts.WakeStatusFailed
	}
	return ""
}

func legacyRuntimeEventClass(kind runtimeevents.Kind) string {
	switch kind {
	case runtimeevents.KindRuntimeStartFailed:
		return consts.RuntimeEventClassRuntimeStartFailed
	case runtimeevents.KindRuntimeExitedUnexpectedly:
		return consts.RuntimeEventClassRuntimeExitedUnexpectedly
	case runtimeevents.KindRuntimeRetrying:
		return consts.RuntimeEventClassRuntimeRetrying
	case runtimeevents.KindWakeFailed:
		return consts.RuntimeEventClassWakeFailed
	case runtimeevents.KindDeliveryDeferred:
		return consts.RuntimeEventClassDeliveryDeferred
	case runtimeevents.KindDeliveryReplayed:
		return consts.RuntimeEventClassDeliveryReplayed
	case runtimeevents.KindRuntimeHeartbeatStale:
		return consts.RuntimeEventClassHeartbeatStale
	case runtimeevents.KindContextWindowExceeded:
		return consts.RuntimeEventClassContextWindowExceeded
	case runtimeevents.KindBusSubscriberDropped:
		return consts.RuntimeEventClassEventBusSubscriberDropped
	case runtimeevents.KindBusSinkFailed:
		return consts.RuntimeEventClassEventBusSinkFailed
	}
	return ""
}

func payloadText(ev runtimeevents.Event) string {
	if data, ok := runtimeevents.DataAs[*runtimeevents.TurnTextData](ev); ok {
		return data.Text
	}
	if data, ok := runtimeevents.DataAs[*runtimeevents.PromptInputData](ev); ok {
		return data.Text
	}
	if data, ok := runtimeevents.DataAs[*runtimeevents.LifecycleData](ev); ok {
		return data.Text
	}
	return firstNonEmpty(payloadString(ev, "text"), runtimeEventDetail(ev))
}

func payloadToolInput(ev runtimeevents.Event) string {
	if data, ok := runtimeevents.DataAs[*runtimeevents.ToolCallData](ev); ok {
		return strings.TrimSpace(data.ToolInput)
	}
	if value, ok := payloadValue(ev, "tool_input", "input"); ok {
		return fmt.Sprintf("%v", value)
	}
	return ""
}

func payloadString(ev runtimeevents.Event, keys ...string) string {
	value, ok := payloadValue(ev, keys...)
	if !ok || value == nil {
		return ""
	}
	switch v := value.(type) {
	case string:
		return strings.TrimSpace(v)
	case fmt.Stringer:
		return strings.TrimSpace(v.String())
	default:
		return strings.TrimSpace(fmt.Sprintf("%v", v))
	}
}

func payloadUint64(ev runtimeevents.Event, keys ...string) uint64 {
	value, ok := payloadValue(ev, keys...)
	if !ok || value == nil {
		return 0
	}
	switch v := value.(type) {
	case uint64:
		return v
	case uint:
		return uint64(v)
	case uint32:
		return uint64(v)
	case int:
		if v < 0 {
			return 0
		}
		return uint64(v)
	case int64:
		if v < 0 {
			return 0
		}
		return uint64(v)
	case int32:
		if v < 0 {
			return 0
		}
		return uint64(v)
	case float64:
		if v < 0 {
			return 0
		}
		return uint64(v)
	case string:
		parsed, err := strconv.ParseUint(strings.TrimSpace(v), 10, 64)
		if err != nil {
			return 0
		}
		return parsed
	default:
		return 0
	}
}

func payloadDuration(ev runtimeevents.Event, keys ...string) time.Duration {
	value, ok := payloadValue(ev, keys...)
	if !ok || value == nil {
		return 0
	}
	switch v := value.(type) {
	case time.Duration:
		return v
	case int64:
		return time.Duration(v) * time.Millisecond
	case int:
		return time.Duration(v) * time.Millisecond
	case uint64:
		return time.Duration(v) * time.Millisecond
	case float64:
		return time.Duration(v) * time.Millisecond
	case string:
		if d, err := time.ParseDuration(strings.TrimSpace(v)); err == nil {
			return d
		}
		parsed, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
		if err != nil {
			return 0
		}
		return time.Duration(parsed) * time.Millisecond
	default:
		return 0
	}
}

func payloadValue(ev runtimeevents.Event, keys ...string) (any, bool) {
	if len(keys) == 0 || ev.Data == nil {
		return nil, false
	}
	values := runtimeEventDataValues(ev)
	if len(values) == 0 {
		return nil, false
	}
	for _, key := range keys {
		if value, ok := values[key]; ok {
			return value, true
		}
	}
	return nil, false
}

func runtimeEventDataValues(ev runtimeevents.Event) map[string]any {
	switch data := ev.Data.(type) {
	case *runtimeevents.MapData:
		return data.Map
	case *runtimeevents.LifecycleData:
		return map[string]any{
			"activity":    data.Activity,
			"detail":      data.Detail,
			"text":        data.Text,
			"prompt_kind": data.PromptKind,
			"event_type":  data.EventType,
			"metadata":    data.Metadata,
		}
	case *runtimeevents.WakeData:
		return map[string]any{
			"detail": data.Detail,
		}
	case *runtimeevents.RuntimeDiagnosticData:
		return map[string]any{
			"event_class": data.EventClass,
			"detail":      data.Detail,
		}
	case *runtimeevents.TurnTextData:
		return map[string]any{
			"text":       data.Text,
			"message":    data.Text,
			"error":      data.Text,
			"title":      data.Title,
			"section_id": data.SectionID,
			"sectionId":  data.SectionID,
			"metadata":   data.Metadata,
		}
	case *runtimeevents.ToolCallData:
		return map[string]any{
			"tool_name":    data.ToolName,
			"tool_call_id": data.ToolCallID,
			"tool_input":   data.ToolInput,
			"input":        data.ToolInput,
			"metadata":     data.Metadata,
		}
	case *runtimeevents.PromptInputData:
		return map[string]any{
			"text":        data.Text,
			"prompt_kind": data.PromptKind,
			"event_type":  data.EventType,
		}
	case *runtimeevents.RecoveryPointData:
		return map[string]any{
			"thread_id":    data.ThreadID,
			"strategy":     data.Strategy,
			"recovery_ref": data.RecoveryRef,
		}
	case *runtimeevents.UsageTokensData:
		return map[string]any{
			"api_key":               data.APIKey,
			"input_tokens":          data.InputTokens,
			"input":                 data.InputTokens,
			"cached_input_tokens":   data.CachedInputTokens,
			"cached":                data.CachedInputTokens,
			"output_tokens":         data.OutputTokens,
			"output":                data.OutputTokens,
			"active_context_tokens": data.ActiveContextTokens,
			"context_window_tokens": data.ContextWindowTokens,
			"duration":              data.Duration,
		}
	default:
		return nil
	}
}

func runtimeEventWithDefaults(ev runtimeevents.Event) runtimeevents.Event {
	if ev.ID != "" && !ev.Time.IsZero() {
		return ev
	}
	base := runtimeevents.NewEvent(ev.Kind)
	if ev.ID != "" {
		base.ID = ev.ID
	}
	if !ev.Time.IsZero() {
		base.Time = ev.Time
	}
	base.Severity = ev.Severity
	base.Context = ev.Context
	base.Trace = ev.Trace
	base.Data = ev.Data
	return base
}

// runtimeEventFromParsedEvent is the single adapter from driver-normalized
// ParsedEvent values into the normalized runtimeevents stream. Keep this switch
// exhaustive; TestRuntimeEventFromParsedEventMappingManifest parses the
// consts.ParsedEventKind declarations to catch new driver events before their
// semantics spread into sinks or compatibility mappers.
func runtimeEventFromParsedEvent(serverID, agentID string, ev drivercontract.ParsedEvent) (runtimeevents.Event, bool) {
	ctx := runtimeevents.EventContext{
		ServerID:      serverID,
		AgentID:       agentID,
		ServerAgentID: agentID,
		SessionID:     strings.TrimSpace(ev.SessionID),
		Runtime:       strings.TrimSpace(ev.Runtime),
		Model:         strings.TrimSpace(ev.Model),
	}
	switch ev.Kind {
	case consts.ParsedEventKindSessionInit:
		return runtimeevents.NewEvent(
			runtimeevents.KindTurnSessionInit,
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindThinking:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewTurnTextDataWithMeta(
				runtimeevents.KindTurnThinking,
				ev.Text,
				strings.TrimSpace(ev.Title),
				strings.TrimSpace(ev.SectionID),
			),
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindText:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewTurnTextData(runtimeevents.KindTurnText, ev.Text),
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindToolCall:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewToolEventDataWithCallIDAndMetadata(
				runtimeevents.KindTurnToolCall,
				strings.TrimSpace(ev.ToolName),
				fmt.Sprintf("%v", ev.ToolInput),
				strings.TrimSpace(ev.ToolCallID),
				ev.Metadata,
			),
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindToolOutput:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewToolEventDataWithCallIDAndMetadata(
				runtimeevents.KindTurnToolOutput,
				strings.TrimSpace(ev.ToolName),
				ev.Text,
				strings.TrimSpace(ev.ToolCallID),
				ev.Metadata,
			),
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindSubagentActivity:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewLifecycleInputData(
				runtimeevents.KindTurnSubagentActivity,
				"working",
				firstNonEmpty(strings.TrimSpace(ev.Message), strings.TrimSpace(ev.Text)),
				"",
				"",
				strings.TrimSpace(ev.EventType),
				ev.Metadata,
			),
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindTurnEnd:
		return runtimeevents.NewEvent(
			runtimeevents.KindTurnEnded,
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindRuntimeIdle:
		return runtimeevents.NewEvent(
			runtimeevents.KindTurnIdle,
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindRuntimeCompacting:
		return runtimeevents.NewEvent(
			runtimeevents.KindTurnCompacting,
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindRuntimeCompacted:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewLifecycleInputData(
				runtimeevents.KindTurnCompacted,
				"working",
				firstNonEmpty(strings.TrimSpace(ev.Message), "Context compacted."),
				"",
				"",
				"",
				ev.Metadata,
			),
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindError:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewTurnTextDataWithMetadata(runtimeevents.KindTurnError, ev.Message, ev.Metadata),
			runtimeevents.WithContext(ctx),
			runtimeevents.WithSeverity(runtimeevents.SeverityError),
		), true
	case consts.ParsedEventKindUsage:
		usage := runtimeevents.NewUsageTokensData(
			"",
			ev.InputTokens,
			ev.CachedInputTokens,
			ev.OutputTokens,
			0,
		)
		usage.ActiveContextTokens = ev.ActiveContextTokens
		usage.ContextWindowTokens = ev.ContextWindowTokens
		return runtimeevents.NewDataEvent(
			usage,
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindPromptInput:
		return runtimeevents.NewDataEvent(
			runtimeevents.NewPromptInputData(
				ev.Text,
				strings.TrimSpace(ev.PromptKind),
				strings.TrimSpace(ev.EventType),
			),
			runtimeevents.WithContext(ctx),
		), true
	case consts.ParsedEventKindRuntimeInputPending:
		inputID := strings.TrimSpace(ev.RuntimeInputID)
		return runtimeevents.NewDataEvent(
			runtimeevents.NewLifecycleInputData(
				runtimeevents.KindDeliveryPending,
				"message delivered",
				firstNonEmpty(strings.TrimSpace(ev.Message), "New message delivered to agent."),
				strings.TrimSpace(ev.Text),
				firstNonEmpty(strings.TrimSpace(ev.PromptKind), consts.PromptKindDeliveryText),
				strings.TrimSpace(ev.EventType),
				ev.Metadata,
			),
			runtimeevents.WithContext(ctx),
			runtimeevents.WithTrace(runtimeevents.EventTrace{
				MessageID:     inputID,
				CorrelationID: inputID,
			}),
		), true
	case consts.ParsedEventKindRuntimeInputAcked:
		inputID := strings.TrimSpace(ev.RuntimeInputID)
		return runtimeevents.NewDataEvent(
			runtimeevents.NewLifecycleInputData(
				runtimeevents.KindDeliveryAcked,
				"agent ack",
				firstNonEmpty(strings.TrimSpace(ev.Message), strings.TrimSpace(ev.Text), "Agent acknowledged message input."),
				"",
				"",
				"",
				ev.Metadata,
			),
			runtimeevents.WithContext(ctx),
			runtimeevents.WithTrace(runtimeevents.EventTrace{
				MessageID:     inputID,
				CorrelationID: inputID,
			}),
		), true
	case consts.ParsedEventKindRuntimeInputUncertain:
		inputID := strings.TrimSpace(ev.RuntimeInputID)
		return runtimeevents.NewDataEvent(
			runtimeevents.NewLifecycleInputData(
				runtimeevents.KindDeliveryPending,
				"delivery uncertain",
				firstNonEmpty(strings.TrimSpace(ev.Message), strings.TrimSpace(ev.Text), "Message delivery is awaiting reconciliation."),
				"",
				"",
				"",
				ev.Metadata,
			),
			runtimeevents.WithContext(ctx),
			runtimeevents.WithTrace(runtimeevents.EventTrace{
				MessageID:     inputID,
				CorrelationID: inputID,
			}),
		), true
	default:
		if text := parsedEventFallbackText(ev); text != "" {
			return runtimeevents.NewDataEvent(
				runtimeevents.NewTurnTextData(runtimeevents.KindTurnText, text),
				runtimeevents.WithContext(ctx),
			), true
		}
		return runtimeevents.Event{}, false
	}
}

func parsedEventFallbackText(ev drivercontract.ParsedEvent) string {
	for _, value := range []string{
		ev.Text,
		ev.Message,
		strings.TrimSpace(fmt.Sprintf("%v", ev.ToolInput)),
		strings.TrimSpace(ev.ToolName),
		strings.TrimSpace(ev.Kind),
	} {
		if strings.TrimSpace(value) != "" && value != "<nil>" {
			return value
		}
	}
	return ""
}

func runtimeEventFromWakeStatus(serverID, serverAgentID string, ev agent.WakeStatusEvent) runtimeevents.Event {
	kind := runtimeEventKindForWakeStatus(ev.Status)
	return runtimeevents.NewDataEvent(
		runtimeevents.NewWakeData(kind, strings.TrimSpace(ev.Detail)),
		runtimeevents.WithContext(runtimeevents.EventContext{
			ServerID:      serverID,
			ServerAgentID: strings.TrimSpace(serverAgentID),
		}),
		runtimeevents.WithTrace(runtimeevents.EventTrace{
			MessageID:      strings.TrimSpace(ev.MessageID),
			WakeToken:      strings.TrimSpace(ev.WakeToken),
			Target:         strings.TrimSpace(ev.Target),
			ReplyMessageID: strings.TrimSpace(ev.ReplyMessageID),
		}),
	)
}

func runtimeEventKindForWakeStatus(status string) runtimeevents.Kind {
	switch strings.TrimSpace(status) {
	case consts.WakeStatusReceived:
		return runtimeevents.KindWakeReceived
	case consts.WakeStatusReplySent:
		return runtimeevents.KindWakeReplySent
	case consts.WakeStatusCompletedWithoutVisibleReply:
		return runtimeevents.KindWakeNoReply
	case consts.WakeStatusFailed:
		return runtimeevents.KindWakeFailed
	default:
		return runtimeevents.KindWakeFailed
	}
}

func runtimeEventFromAgentRuntimeEvent(serverID, serverAgentID string, ev agent.RuntimeEvent) runtimeevents.Event {
	return runtimeEventFromTypesRuntimeEvent(serverID, serverAgentID, types.RuntimeEvent{
		AgentID:        serverAgentID,
		MessageID:      ev.MessageID,
		WakeToken:      ev.WakeToken,
		EventClass:     ev.EventClass,
		Target:         ev.Target,
		ReplyMessageID: ev.ReplyMessageID,
		Detail:         ev.Detail,
	})
}

func runtimeEventFromTypesRuntimeEvent(serverID, serverAgentID string, ev types.RuntimeEvent) runtimeevents.Event {
	class := strings.TrimSpace(ev.EventClass)
	kind := runtimeEventKindForLegacyClass(class)
	if kind == "" {
		kind = runtimeevents.KindRuntimeEventParseFailed
	}
	out := runtimeevents.NewDataEvent(
		runtimeevents.NewRuntimeDiagnosticData(kind, class, strings.TrimSpace(ev.Detail)),
		runtimeevents.WithContext(runtimeevents.EventContext{
			ServerID:      serverID,
			AgentID:       strings.TrimSpace(ev.AgentID),
			ServerAgentID: firstNonEmpty(serverAgentID, ev.AgentID),
		}),
		runtimeevents.WithTrace(runtimeevents.EventTrace{
			MessageID:      strings.TrimSpace(ev.MessageID),
			WakeToken:      strings.TrimSpace(ev.WakeToken),
			Target:         strings.TrimSpace(ev.Target),
			ReplyMessageID: strings.TrimSpace(ev.ReplyMessageID),
		}),
	)
	if class == consts.RuntimeEventClassRuntimeStartFailed || class == consts.RuntimeEventClassWakeFailed || class == consts.RuntimeEventClassRuntimeExitedUnexpectedly || class == consts.RuntimeEventClassContextWindowExceeded {
		out.Severity = runtimeevents.SeverityWarn
	}
	return out
}

func runtimeEventKindForLegacyClass(class string) runtimeevents.Kind {
	switch strings.TrimSpace(class) {
	case consts.RuntimeEventClassRuntimeStartFailed:
		return runtimeevents.KindRuntimeStartFailed
	case consts.RuntimeEventClassRuntimeExitedUnexpectedly:
		return runtimeevents.KindRuntimeExitedUnexpectedly
	case consts.RuntimeEventClassWakeFailed:
		return runtimeevents.KindWakeFailed
	case consts.RuntimeEventClassRuntimeRetrying:
		return runtimeevents.KindRuntimeRetrying
	case consts.RuntimeEventClassDeliveryDeferred,
		consts.RuntimeEventClassRoutedMessageDeferred:
		return runtimeevents.KindDeliveryDeferred
	case consts.RuntimeEventClassDeliveryReplayed:
		return runtimeevents.KindDeliveryReplayed
	case consts.RuntimeEventClassHeartbeatStale:
		return runtimeevents.KindRuntimeHeartbeatStale
	case consts.RuntimeEventClassContextWindowExceeded:
		return runtimeevents.KindContextWindowExceeded
	case consts.RuntimeEventClassEventBusSubscriberDropped:
		return runtimeevents.KindBusSubscriberDropped
	case consts.RuntimeEventClassEventBusSinkFailed:
		return runtimeevents.KindBusSinkFailed
	default:
		return runtimeevents.KindRuntimeEventParseFailed
	}
}
