// Package obs is the observability seam: trace ids threaded through context,
// the slog handler that stamps them on every log line, gated LLM-exchange
// logging, and recovered goroutines. Порт vojo trace.go/logging.go +
// вынесенный из bot.go safego; ось запроса заменена с (sender, verbose) на
// (book, chapter, chunk, stage, role) — домен перевода.
package obs

import (
	"context"
	"crypto/rand"
	"encoding/hex"
)

// trace.go threads a per-run correlation id and the small request facts the
// logger needs through context — the userver / OpenTelemetry idiom: mint once
// at the top of a unit of work, and every log line below it (down to the HTTP
// call to the model) carries the same trace_id without passing a logger by
// hand.
//
// The id is 16 random bytes rendered as 32 hex chars — the W3C Trace-Context /
// OTel trace-id shape — so the field maps straight onto an OTel exporter later.

type ctxKey int

const reqInfoKey ctxKey = iota

// ReqInfo is the per-call data carried in context. Zero-value fields are
// simply omitted from logs.
type ReqInfo struct {
	TraceID string
	Book    string
	Chapter int
	Chunk   int
	Stage   string // pipeline stage: draft | edit | ...
	Role    string // agent role: translator | editor | ...
	// LogBodies gates raw LLM request/response body logging (privacy by
	// default; decided once at admission so the transport just reads the flag).
	LogBodies bool
}

// WithReqInfo stamps the call's info onto ctx. WithTimeout/WithCancel preserve
// values, so it flows down into the model transport.
func WithReqInfo(ctx context.Context, ri ReqInfo) context.Context {
	return context.WithValue(ctx, reqInfoKey, ri)
}

// ReqInfoFromContext returns the call info, if any.
func ReqInfoFromContext(ctx context.Context) (ReqInfo, bool) {
	ri, ok := ctx.Value(reqInfoKey).(ReqInfo)
	return ri, ok
}

// TraceFromContext returns the trace id, or "" when ctx carries none (startup
// paths) — the slog handler then simply omits trace_id.
func TraceFromContext(ctx context.Context) string {
	if ri, ok := ReqInfoFromContext(ctx); ok {
		return ri.TraceID
	}
	return ""
}

// NewTraceID mints a random 16-byte id as 32 hex chars (the OTel trace-id
// shape). crypto/rand.Read never returns an error on modern Go (an entropy
// failure crashes the process rather than returning a short read).
func NewTraceID() string {
	var b [16]byte
	_, _ = rand.Read(b[:])
	return hex.EncodeToString(b[:])
}
