import type { ModuleRef } from '@nestjs/core';
import type { ResolvedCoreConfig } from '../config/options.js';
import type { Entry, RecordInput } from '../entry/entry.js';
import type { Watcher } from '../nest/watcher.js';
/**
 * The published, versioned extension contract for `@dudousxd/nestjs-telescope`.
 *
 * Extensions are objects (usually returned by a factory so they can take options)
 * registered via `TelescopeModule.forRoot({ extensions: [...] })`. The host runs
 * their hooks at module init. Hooks are **multi** (every extension runs; results
 * accumulate). Single-slot hooks are intentionally not part of 0.x — the registry
 * is shaped to add them when a consumer needs one.
 *
 * @remarks Semver 0.x — the shape may change until 1.0. Out-of-repo extensions
 * should pin a compatible `@dudousxd/nestjs-telescope` peer range.
 */
export interface TelescopeExtension {
    /** Unique id — used in conflict/collision errors and for deterministic ordering. */
    name: string;
    /** Contribute watchers. Merged into the existing `forRoot.watchers` list. */
    watchers?(ctx: ExtensionContext): Watcher[];
    /** Contribute navigable entry types — makes the hard-coded UI ENTRY_TYPES dynamic. */
    entryTypes?(ctx: ExtensionContext): ExtensionEntryType[];
    /** Contribute declarative dashboard pages (the panel IR). */
    dashboards?(ctx: ExtensionContext): DashboardSpec[];
    /** Named server-side queries that panels bind to via `{ provider, query }`. */
    dataProviders?(ctx: ExtensionContext): DataProvider[];
    /**
     * Observe EVERY recorded input (pre-sampling, complete counts) — drives metrics
     * export. Fired synchronously on the hot path; keep it cheap. Isolated by the
     * host: a throw is swallowed and never affects capture.
     */
    observeRecord?(input: RecordInput): void;
    /**
     * Observe each just-persisted (post-sampling) batch — drives span/trace export.
     * Awaited off the host path inside the flush chain; a throw/rejection is
     * swallowed and never breaks the flush.
     */
    observeFlush?(entries: Entry[]): void | Promise<void>;
}
/** Read-only context handed to every extension hook, resolved at module init. */
export interface ExtensionContext {
    /** Resolve host services (e.g. a durable engine/store, or TELESCOPE_STORAGE). */
    readonly moduleRef: ModuleRef;
    readonly config: ResolvedCoreConfig;
}
/** A navigable entry type contributed by an extension (subset of the UI's EntryTypeDef). */
export interface ExtensionEntryType {
    /** Backend `type` filter value, e.g. 'durable'. */
    id: string;
    /** Nav label, e.g. 'Workflows'. */
    label: string;
    /** Tailwind `bg-*` dot color for the nav, e.g. 'bg-amber-400'. */
    dot: string;
}
/** Threshold coloring for a numeric panel. `direction` says which way is worse. */
export interface PanelThresholds {
    warn: number;
    bad: number;
    direction: 'up-bad' | 'down-bad';
}
/**
 * A group of panels rendered together with its own column count.
 *
 * `cols: 1` is the full-width row: one panel spanning the section. It exists because a section
 * renders as a fixed `grid-cols-N` with no `colSpan`, so without it the widest panel a dashboard
 * has — a table with ten or more columns — could only be declared in a 2-column grid, where it got
 * half the viewport and left a hole beside it while scrolling sideways inside its own card. That is
 * exactly how `@dudousxd/nestjs-durable-telescope`'s worker table shipped.
 */
export interface DashboardSection {
    title?: string;
    cols?: 1 | 2 | 3 | 4;
    panels: Panel[];
}
/** A declarative dashboard page. */
export interface DashboardSpec {
    /** Stable route id, e.g. 'durable.workflows'. Globally unique across extensions. */
    id: string;
    /** Nav label, e.g. 'Workflows'. */
    label: string;
    /** Optional nav grouping header. */
    navGroup?: string;
    /** Flat layout (back-compat). Prefer `sections` for hierarchy. */
    panels: Panel[];
    /** Sectioned layout. When present, the UI renders these instead of `panels`. */
    sections?: DashboardSection[];
}
/** A bind from a panel to a named server-side provider + an opaque query object. */
export interface DataBinding {
    /** Provider name, e.g. 'durable.timeseries'. Resolved on the server. */
    provider: string;
    /** Opaque query passed through to the provider's `resolve`. */
    query?: Record<string, unknown>;
}
/**
 * A deep-link out of a table cell (to the durable dashboard, a telescope trace, etc.).
 *
 * @remarks Two hrefs conventions:
 *  - **In-app hash route** — an `href` starting with `#/` (e.g. `'#/traces/{traceId}'`)
 *    is a route inside the Telescope SPA itself. The UI renders it as a plain
 *    anchor; browsers treat a same-document `#`-only href as a same-document
 *    navigation (URL hash update + `hashchange`, no page reload), which the
 *    dashboard's `HashRouter` picks up — the same mechanism the built-in Entries
 *    table and Entry detail page already use for their own trace links. Leave
 *    `external` unset for these.
 *  - **Host-console link** — an absolute path with no `#` (e.g.
 *    `'/durable/runs/{runId}'`) targets a page in the HOST application (the app
 *    embedding/linking to Telescope), not a Telescope route. This is a real
 *    top-level navigation; set `external: true` when it should open in a new tab.
 *
 * The one confirmed in-app hash route today is the trace waterfall view:
 * `#/traces/{traceId}` (`traceId` is the row key to substitute), which renders
 * `TracePage` — the single-trace waterfall. Bridges that want to deep-link a
 * table row to "show me this trace" should target that exact shape.
 */
export interface LinkSpec {
    /** A URL template with `{key}` placeholders filled from the row, e.g. '/durable/runs/{runId}'. */
    href: string;
    /** When true, open in a new tab. */
    external?: boolean;
}
export interface Column {
    key: string;
    label: string;
    link?: LinkSpec;
    /**
     * Turns this column's header into a sort control. Clicking it cycles
     * ascending → descending → unsorted and re-resolves the panel's provider with
     * `sort=<key>` + `dir=asc|desc` merged into the query — see
     * {@link readTableQuery}.
     *
     * Sorting is the provider's job, not the browser's: the UI holds one page, so
     * a client-side sort would order 50 rows out of 50,000 and present the result
     * as "the top of the list". Only mark a column sortable when the provider
     * actually honours `sort`; the header otherwise looks like a control that
     * silently does nothing.
     */
    sortable?: boolean;
    /**
     * Gives this column a filter box in the header. The typed text is committed on
     * Enter (or blur) and re-resolves the provider with `filter.<key>=<text>` —
     * see {@link TABLE_FILTER_PREFIX}. Matching semantics are entirely the
     * provider's to choose (substring, prefix, exact).
     */
    filterable?: boolean;
    /**
     * Lets a viewer hide this column from the table's column menu. Purely a
     * client-side display concern — a hidden column is not communicated to the
     * provider, which keeps returning it. The menu itself only appears when at
     * least one column opts in, so a table that declares none renders exactly as
     * it did before this flag existed.
     */
    hideable?: boolean;
}
/**
 * Drill-down: opt a chart-shaped panel into "clicking a bar/segment/bucket filters
 * this dashboard".
 *
 * The UI holds the current selection and re-resolves EVERY panel on the dashboard
 * with `param` set to the clicked item's id (or its label when the provider gave
 * no id) merged onto each panel's own `DataBinding.query`. So a provider opts in
 * by reading that one query key; a provider that ignores it renders exactly what
 * it renders today.
 *
 * Omit this and the panel is inert: the UI attaches no click handler at all, which
 * is the difference between "clicking does nothing" and "the cursor says it should".
 *
 * @example
 * { kind: 'topN', title: 'Busiest workflows', data: { provider: 'durable.top' },
 *   drilldown: { param: 'workflow' } }
 * // click "checkout" → every panel re-resolves with `?workflow=checkout`
 */
export interface PanelDrilldown {
    /** Query-parameter name the selection is written to. */
    param: string;
}
export type Panel = {
    kind: 'stat';
    title: string;
    data: DataBinding;
    format?: 'number' | 'percent' | 'duration' | 'rate';
    accent?: string;
    /** When true, the provider also returns `spark: number[]` and the card draws a sparkline. */
    spark?: boolean;
    thresholds?: PanelThresholds;
} | {
    kind: 'timeseries';
    title: string;
    data: DataBinding;
    series: string[];
    style?: 'area' | 'stacked';
    /** Clicking a bucket filters the dashboard by its label. See {@link PanelDrilldown}. */
    drilldown?: PanelDrilldown;
} | {
    kind: 'topN';
    title: string;
    data: DataBinding;
    limit?: number;
    /** Clicking a bar filters the dashboard by the item's `id` (or label). See {@link PanelDrilldown}. */
    drilldown?: PanelDrilldown;
} | {
    kind: 'table';
    title: string;
    data: DataBinding;
    columns: Column[];
    /**
     * Opt into paged-table mode: the UI renders prev/next controls (+ "page X
     * of Y") and re-resolves this panel's provider with `query.page` (1-based)
     * and `query.limit` merged in on top of the panel's own static `data.query`.
     * The provider MUST then return `{ rows, total, page, limit }` instead of
     * a bare `{ rows }` — see {@link DataProvider.resolve}. Omit (or `false`)
     * for the existing bare-rows table, unchanged.
     */
    paged?: boolean;
} | {
    kind: 'distribution';
    title: string;
    data: DataBinding;
    markers?: Array<'p50' | 'p95' | 'p99'>;
    format?: 'duration' | 'number';
    /** Clicking a bucket filters the dashboard by its label. See {@link PanelDrilldown}. */
    drilldown?: PanelDrilldown;
} | {
    kind: 'gauge';
    title: string;
    data: DataBinding;
    min?: number;
    max?: number;
    format?: 'number' | 'percent' | 'duration' | 'rate';
    thresholds?: PanelThresholds;
} | {
    kind: 'breakdown';
    title: string;
    data: DataBinding;
    style?: 'donut' | 'bar';
    /** Clicking a segment filters the dashboard by its label. See {@link PanelDrilldown}. */
    drilldown?: PanelDrilldown;
};
/** A named server-side query a panel binds to. */
export interface DataProvider {
    /** Stable name referenced by a panel's `DataBinding.provider`, e.g. 'durable.timeseries'. */
    name: string;
    /**
     * Resolve a panel's data. `query` is the panel's `DataBinding.query`. Return value
     * shape is per panel kind:
     *  - stat         → `{ value: number; delta?: number; deltaLabel?: string; spark?: number[] }`
     *  - timeseries   → `{ rows: Array<{ label: string } & Record<string, number>> }`
     *  - topN         → `{ items: Array<{ label: string; value: number; id?: string }> }`
     *  - table        → `{ rows: Array<Record<string, unknown>> }`, or — when the
     *                   panel declares `paged: true` — `{ rows, total, page, limit }`
     *                   (`page`/`limit` normally echo the requested `query.page` /
     *                   `query.limit`; `total` is the full, unpaginated row count so
     *                   the UI can compute "page X of Y")
     *
     * A `table` panel whose columns declare `sortable` / `filterable` additionally
     * merges `sort` + `dir` and `filter.<columnKey>` params into `query`. Read
     * them with {@link readTableQuery} rather than by hand — everything in `query`
     * arrives as a **string** off the URL, so `query.page > 1` is silently `false`
     * for `'2'`. A provider that ignores the new params is unaffected: the table
     * simply keeps returning rows in the provider's own order.
     *  - distribution → `{ buckets: Array<{ label: string; count: number }>; p50?: number; p95?: number; p99?: number }`
     *  - gauge        → `{ value: number; min?: number; max?: number }`
     *  - breakdown    → `{ segments: Array<{ label: string; value: number; color?: string }> }`
     */
    resolve(query: Record<string, unknown> | undefined, ctx: ExtensionContext): Promise<unknown>;
}
/** Identity helper for authoring extensions with full type inference. */
export declare function defineTelescopeExtension(ext: TelescopeExtension): TelescopeExtension;
//# sourceMappingURL=types.d.ts.map