package audit

import "context"

// entrypointCtxKey is the unexported key type for storing the current
// AuditEntrypoint on a context.Context. Unexported so nothing outside this
// package can set or read it by bypassing the helpers below — this keeps the
// propagation surface auditable and prevents an "implicit ctx binding" where
// callers forget to read via the helper.
type entrypointCtxKey struct{}

// WithEntrypoint returns a derived context carrying ep as the current audit
// Entrypoint. Executor entry points (FlowExecutor.ExecuteFlow,
// FlowTriggerListener.executeRecordTriggerFlows, etc.) call this once at
// their boundary so downstream record-mutation code can recover the correct
// Entrypoint via EntrypointFromContext.
//
// Stack-push semantics (design §3.5): each call overwrites the top of stack.
// Recursive flows / nested triggers accept the "last writer wins" information
// loss documented in §11 OQ5; a future EntrypointStack field may preserve
// the full chain. Storing only the current top is intentional — it matches
// how the AuditEvent.Entrypoint column is a single value per row.
//
// This helper is a TRANSPORT convenience, not a substitute for the §3.1 CH-4
// invariant. Callers constructing an AuditEvent MUST still pass Entrypoint
// explicitly on the struct literal; the emitter will not read it from ctx.
// The intended pattern is:
//
//	ep := audit.EntrypointFromContext(ctx)
//	ev := audit.AuditEvent{..., Entrypoint: ep, ...}
func WithEntrypoint(ctx context.Context, ep AuditEntrypoint) context.Context {
	return context.WithValue(ctx, entrypointCtxKey{}, ep)
}

// EntrypointFromContext returns the Entrypoint previously stored on ctx via
// WithEntrypoint, or EntrypointUnknown when no value is present or the
// stored value is not a typed AuditEntrypoint.
//
// Returning EntrypointUnknown (rather than the empty string) preserves the
// §3.1 CH-4 invariant downstream: every AuditEvent built from ctx will have
// a non-zero, Validate()-accepted Entrypoint. "No ctx set" becomes an
// observable UNKNOWN row (+ WARN + Prometheus counter from the emitter),
// NOT a silent REST_API rewrite.
func EntrypointFromContext(ctx context.Context) AuditEntrypoint {
	if ctx == nil {
		return EntrypointUnknown
	}
	v, ok := ctx.Value(entrypointCtxKey{}).(AuditEntrypoint)
	if !ok || v == "" {
		return EntrypointUnknown
	}
	return v
}
