import { Err } from "../result-_socO0Ud.js";
import { AnyTaggedError } from "../types-ojNaDB4n.js";
import { tapErr } from "../tap-err-C0xfVXtz.js";

//#region src/logger/types.d.ts

/**
 * Five levels, no `fatal`. Matches Rust's `tracing` exactly.
 *
 * Process termination is the application's call, not the library's — so
 * there is no `fatal`. Apps that want to exit on a specific error do so
 * explicitly (`process.exit`, `Bun.exit`) at the call site.
 */
type LogLevel = "trace" | "debug" | "info" | "warn" | "error";
/**
 * The normalized event every sink receives.
 *
 * - `ts` is epoch millis (not a `Date`) — cheap to create, easy to serialize,
 *   trivially monotonic for ordering. Sinks that want ISO-8601 on the wire
 *   convert at serialization time.
 * - `source` is the logger's namespace, stamped once at `createLogger` and
 *   carried on every event. Analogous to `tracing`'s `target`.
 * - `message` is human text. For `warn`/`error` it's copied from the
 *   typed error's `message` field (so `event.message === event.data.message`
 *   on those levels — intentional duplication so sinks have one uniform
 *   rendering path regardless of event origin; they never need to know
 *   whether `data` is a tagged error or free-form payload). The variant
 *   template owns the phrasing; sinks just render.
 * - `data` carries the structured payload. For `warn`/`error` it's the
 *   typed error object itself (including `name` + captured fields). For
 *   `info`/`debug`/`trace` it's free-form, caller-supplied.
 */
type LogEvent = {
  ts: number;
  level: LogLevel;
  source: string;
  message: string;
  data?: unknown;
};
/**
 * A sink is a callable that accepts events, with optional resource cleanup.
 *
 * The intersection with `Partial<AsyncDisposable>` lets the same type cover
 * both pure functions (no-op dispose) and stateful sinks (file writers,
 * network sockets). Callers who need guaranteed cleanup narrow to a
 * `LogSink & AsyncDisposable` return type or bind with `await using`.
 */
type LogSink = ((event: LogEvent) => void) & Partial<AsyncDisposable>;
/**
 * Accepted by `log.warn` / `log.error`. Union of two shapes:
 *
 * - `AnyTaggedError` — the raw tagged error `{ name, message, ...fields }`.
 *   Arrives via `result.error` after narrowing a Result.
 * - `Err<AnyTaggedError>` — the `{ error: tagged, data: null }` wrapper
 *   that `defineErrors` factories return directly.
 *
 * The logger unwraps via `"name" in err` inside `unwrapLoggable` — a
 * purely structural discriminator:
 *
 * - `AnyTaggedError` always has `name` at the top level (stamped by
 *   `defineErrors` from the factory key — a hard invariant of every
 *   tagged error).
 * - `Err<AnyTaggedError>` has exactly `{ error, data }` at the top level.
 *   The tagged error's `name` lives on `err.error.name`, not `err.name`.
 *
 * Intentionally **not** checking `err.data === null`: that's also true for
 * `Ok(T)` when `T = null` (see wellcrafted's `Ok(null)`/`Err(null)`
 * structural collision edge — discussed in
 * `docs/articles/ok-null-is-fine-err-null-is-a-lie.md`). Always discriminate
 * by an invariant non-null property (`name` here), never by null-presence.
 *
 * Native `Error` instances also satisfy `AnyTaggedError` structurally
 * (`name` and `message` are both present), so `log.warn(new Error("x"))`
 * works out of the box — useful when migrating from `console.warn(err)`
 * call sites that caught a plain `Error`.
 *
 * @example Err-wrapped (direct mint)
 * log.warn(MyError.Thing({ cause }));
 *
 * @example Raw tagged (from result.error, after narrowing)
 * if (isErr(result)) log.warn(result.error);
 *
 * @example Plain Error (migration from console.warn)
 * try { risky(); } catch (e) { log.warn(e as Error); }
 */
type LoggableError = AnyTaggedError | Err<AnyTaggedError>;
/**
 * The logger surface.
 *
 * Shape split is intentional:
 * - `warn`/`error` take a typed error **unary** — message and structured
 *   data both live on the variant, level is chosen at the call site.
 * - `trace`/`debug`/`info` are free-form — diagnostic events don't need
 *   enumeration and often don't have a useful "name" to dedupe on.
 *
 * Mirrors Rust's `tracing::warn!(?err)` vs `tracing::info!("msg", ...)`.
 */
type Logger = {
  error(err: LoggableError): void;
  warn(err: LoggableError): void;
  info(message: string, data?: unknown): void;
  debug(message: string, data?: unknown): void;
  trace(message: string, data?: unknown): void;
};
//# sourceMappingURL=types.d.ts.map
//#endregion
//#region src/logger/console-sink.d.ts
/**
 * Default sink. Writes to `console.*` with a `[source]` prefix.
 *
 * Kept as a singleton value (not a factory) because it takes no config —
 * adding `createConsoleSink({ format })` would be ceremony for a pattern
 * the user can trivially replace by writing their own sink.
 *
 * `console[event.level]` routes directly without a detached lookup table.
 * `LogLevel` is a subset of the Console method keys, so TS errors at this
 * access if a future level drifts (e.g. adding `fatal`). Calling the method
 * through `console[...]` preserves the `this` binding — avoids "Illegal
 * invocation" in runtimes that require it.
 *
 * `satisfies LogSink` (not `: LogSink`) keeps the inferred callable type
 * precise — `LogSink` is an intersection with optional dispose, and the
 * annotation form would widen an unnecessary Partial into the value type.
 *
 * No dispose handler — `console` is not a resource.
 *
 * ### CLI authors: stream routing
 *
 * `console[level]` routes by level, not uniformly to stdout:
 * - `console.log` (not used here) is the only method that writes stdout.
 * - `console.info`, `console.debug`, `console.warn`, `console.error`,
 *   `console.trace` all write **stderr** in Node/Bun.
 *
 * For a CLI that emits structured program output on stdout and diagnostics
 * on stderr, this default is correct — every logger event goes to stderr.
 * Authors who expect `log.info` to print to stdout will be surprised; write
 * a custom sink that routes to `process.stdout` if that's the requirement.
 */
declare const consoleSink: (event: LogEvent) => void;
//# sourceMappingURL=console-sink.d.ts.map

//#endregion
//#region src/logger/create-logger.d.ts
/**
 * Create a logger bound to a `source` namespace and a sink.
 *
 * Design choices:
 * - **Positional args, not a bag.** Two arguments, both with obvious meaning;
 *   a `{ source, sink }` object would be ceremony.
 * - **`sink` defaults to `consoleSink`.** Most callers during development
 *   want console output with zero setup. Production apps swap it out via DI
 *   at the attach/wire-up site.
 * - **No global default logger.** There is no `setDefaultLogger()` and no
 *   module-level registry. Every consumer takes a `log?: Logger` option
 *   and defaults to `createLogger('<source>')` if omitted. Globals make
 *   test isolation and sink composition painful.
 * - **Method shorthand in the return object** over higher-order factories.
 *   The five methods differ in two simple ways (error-unary vs free-form,
 *   plus the level string); spelling them out beats an `emitErr("warn")`
 *   riddle.
 *
 * ### Source convention
 *
 * `source` is a free-form string, but downstream filtering and tail-log
 * grep become much easier when call sites converge on a shape. Recommended:
 *
 *     '<package>/<module>'    e.g. 'workspace/sync-supervisor'
 *     '<app>/<feature>'       e.g. 'fuji/daemon-route'
 *     '<package>/<area>'      e.g. 'auth/oauth-app'
 *
 * Keep it lowercase-kebab. Avoid bare factory names like
 * `'attachSqliteMaterializer'`: they don't carry package context.
 *
 * ### Module-scope vs injected
 *
 * `const log = createLogger('source')` at module scope is fine for
 * process-singleton modules (CLI commands, app bootstrap, leaf utilities
 * the host does not customize per instance). For anything that can be
 * instantiated more than once per process with different routing needs
 * (per-document attach primitives, per-account auth state, per-workspace
 * services), accept `log?: Logger` at the factory boundary and default
 * to `createLogger('source')` for development ergonomics.
 *
 * @example Library code (caller wires the sink)
 * function attachThing(ydoc: Doc, opts: { log?: Logger }) {
 *   const log = opts.log ?? createLogger('workspace/thing');
 *   // ...
 * }
 *
 * @example App wiring (share one sink, multiple loggers)
 * const sink = composeSinks(consoleSink, myCustomSink);
 * attachThing(ydoc, { log: createLogger('workspace/thing', sink) });
 * attachOther(ydoc, { log: createLogger('workspace/other', sink) });
 */
declare function createLogger(source: string, sink?: LogSink): Logger;
//# sourceMappingURL=create-logger.d.ts.map
//#endregion
//#region src/logger/memory-sink.d.ts
/**
 * In-memory sink for tests. Returns `{ sink, events }` so callers can
 * both wire the sink and inspect captured events without a module-level
 * spy or `console.*` interception.
 *
 * A factory (not a singleton) so each test gets an isolated array; sharing
 * state across tests would leak events.
 *
 * Returning `{ sink, events }` (rather than an array with a method) keeps
 * the two roles separate — `sink` goes to `createLogger`, `events` goes to
 * assertions.
 *
 * Uses `satisfies LogSink` on the sink expression rather than `: LogSink =`
 * to preserve the precise inferred callable type.
 *
 * @example
 * const { sink, events } = memorySink();
 * const log = createLogger("test", sink);
 * log.warn(MyError.Thing({ cause: new Error("boom") }));
 * expect(events).toHaveLength(1);
 * expect(events[0]).toMatchObject({ level: "warn", source: "test" });
 */
declare function memorySink(): {
  sink: LogSink;
  events: LogEvent[];
};
//# sourceMappingURL=memory-sink.d.ts.map

//#endregion
//#region src/logger/compose-sinks.d.ts
/**
 * Fan one event out to every sink in order.
 *
 * Disposal: the returned sink has a `[Symbol.asyncDispose]` that forwards
 * to each member via optional chaining. Members without dispose (e.g.
 * `consoleSink`) are silent no-ops; members that own resources (file,
 * network) flush and close. Mix pure and stateful sinks freely — no
 * wrapping required.
 *
 * Fan-out is sequential and unguarded. If a member sink throws on emit,
 * later members do not receive the event — by design, since swallowing
 * sink errors hides real bugs. Wrap individual sinks yourself for
 * best-effort delivery.
 *
 * Dispose is sequential and awaits each member. If one throws, later
 * members don't get their chance; callers who want best-effort cleanup
 * should wrap the composed dispose themselves.
 *
 * Built with `Object.assign` + `satisfies LogSink` rather than mutating a
 * pre-typed `const` — avoids the widening that comes with `: LogSink =` and
 * keeps the inferred type precise (callable + definite dispose, not
 * callable + Partial dispose).
 *
 * @example
 * await using file = jsonlFileSink("/tmp/app.jsonl");
 * const sink = composeSinks(consoleSink, file);
 * const log = createLogger("source", sink);
 */
declare function composeSinks(...sinks: LogSink[]): LogSink;
//# sourceMappingURL=compose-sinks.d.ts.map

//#endregion
export { LogEvent, LogLevel, LogSink, LoggableError, Logger, composeSinks, consoleSink, createLogger, memorySink, tapErr };
//# sourceMappingURL=index.d.ts.map