import type { ModelCatalogEntry, ModelFormat, ModelOutputFormat, CustomModelMetadata } from '../types/models.js';
/**
 * Configuration UI Schema -- declarative form definition for any element.
 *
 * Used by addons to declare their settings UI. The admin UI collects schemas
 * from all active addons, merges tabs with the same ID, and renders responsive forms.
 *
 * Layout hierarchy: Tab → Section → Field
 * Multiple addons can contribute sections to the same tab.
 */
/** Predefined tab identifiers that addons reference by ID. */
export type WellKnownTabId = 'overview' | 'general' | 'image' | 'light' | 'motion' | 'audio' | 'snapshot' | 'osd' | 'alarms' | 'pipeline' | 'stream-broker' | 'streaming' | 'zones' | 'live-stats' | 'recording' | 'notifications' | 'network' | 'storage' | 'engine' | 'scheduler' | 'decoder' | 'advanced';
export interface WellKnownTab {
    readonly id: WellKnownTabId;
    readonly label: string;
    readonly icon: string;
    readonly order: number;
}
/** Predefined tabs with standard label, icon, and sort order.
 *
 *  Pipeline (renamed Orchestrator in the UI) holds the four
 *  pipeline-orchestrator sections: General, Object Detection, Audio,
 *  and Cluster Assignment. Motion stays its own tab.
 *  `streaming` remains for older payloads that haven't been retagged. */
export declare const WELL_KNOWN_TABS: readonly WellKnownTab[];
/** Lookup map for well-known tabs by ID. */
export declare const WELL_KNOWN_TAB_MAP: Readonly<Record<WellKnownTabId, WellKnownTab>>;
/**
 * Custom tab declaration by an addon.
 * Use well-known tab IDs when possible. Custom tabs are for addon-specific UI.
 */
export interface ConfigTabDeclaration {
    /** Unique tab identifier. Use WellKnownTabId for standard tabs. */
    readonly id: string;
    /** Display label (ignored for well-known tabs — their label is used). */
    readonly label: string;
    /** Icon identifier (ignored for well-known tabs — their icon is used). */
    readonly icon: string;
    /** Sort order within the tab bar. Lower = left. */
    readonly order?: number;
}
export interface ConfigUISchema {
    /**
     * Tabs contributed by this addon.
     * Multiple addons can reference the same tab ID — the UI merges them.
     * For well-known tabs, label/icon from WELL_KNOWN_TABS takes precedence.
     * Omit to use only well-known tabs referenced by sections.
     */
    readonly tabs?: readonly ConfigTabDeclaration[];
    readonly sections: readonly ConfigSection[];
}
export interface ConfigSection {
    readonly id: string;
    readonly title: string;
    readonly description?: string;
    readonly style?: 'card' | 'accordion';
    readonly defaultCollapsed?: boolean;
    readonly columns?: 1 | 2 | 3 | 4;
    /** Which tab this section belongs to. Default: 'general'. */
    readonly tab?: string;
    /**
     * Where the section is rendered in the device page. Default
     * `'settings'` keeps the section under the device Settings panel
     * (existing behavior). `'top-tab'` hoists the section into a
     * top-level device tab (sibling of Live / Streams / Recordings /
     * Settings) keyed by the section's `tab` value. Multiple addons
     * targeting the same `(tab, location: 'top-tab')` pair render
     * inside the same top tab in `order` sequence — used for the
     * Zones tab where orchestrator (geometry CRUD) + motion-wasm
     * (motion rules) + detection-pipeline (detection rules) all
     * contribute to one operator surface.
     */
    readonly location?: 'settings' | 'top-tab';
    /** Sort order within the tab. Lower = top. */
    readonly order?: number;
    /**
     * When true, every field in this section saves immediately on change
     * (no Save button needed). Per-field `immediate: false` overrides this.
     */
    readonly immediate?: boolean;
    readonly fields: readonly ConfigField[];
}
export type ConfigField = ConfigTextField | ConfigNumberField | ConfigBooleanField | ConfigSelectField | ConfigMultiSelectField | ConfigColorField | ConfigPasswordField | ConfigTextAreaField | ConfigSliderField | ConfigTagsField | ConfigGroupField | ConfigSubTabsField | ConfigSeparatorField | ConfigInfoField | ConfigObjectArrayField | ConfigEditableArrayField | ConfigModelSelectorField | ConfigStorageLocationField | ConfigProbeField | ConfigButtonField | ConfigPipelineEditorField | ConfigNodeSelectField | ConfigNodeMultiSelectField | ConfigZoneEditorField | ConfigLiveStatsField | ConfigLiveStreamViewerField | ConfigPipelineStatsField | ConfigStreamNetworkStatsField | ConfigDoorbellRecentField | ConfigDeviceInfoField | ConfigWidgetField | ConfigAddonActionSelectField | ConfigAddonActionButtonField;
export interface ConfigFieldBase {
    readonly key: string;
    readonly label: string;
    readonly description?: string;
    readonly required?: boolean;
    readonly disabled?: boolean;
    readonly placeholder?: string;
    /** Default value used when the current value is undefined */
    readonly default?: unknown;
    readonly span?: 1 | 2 | 3 | 4;
    readonly showWhen?: ConfigCondition;
    /**
     * Save behavior when value changes.
     * - true: save immediately on change (toggles, selects, checkboxes)
     * - false (default): debounce 2 seconds for text/number inputs before saving
     *
     * The UI FormBuilder reads this flag and applies debounce accordingly.
     */
    readonly immediate?: boolean;
    /**
     * Declares that changes to this field can only take effect after the
     * owning addon is (re)started — the field is bound at boot time and
     * cannot be hot-reloaded from `onConfigChanged()`. Example: Python
     * inference-pool `concurrency` (pool threads are allocated at pool
     * spawn). The UI surfaces a "Restart required" hint, and the
     * framework auto-restarts the addon after the update lands on a flag
     * transition — see `BaseAddon.updateGlobalSettings` /
     * `updateAddonSettings`.
     */
    readonly requiresRestart?: boolean;
    /**
     * Override the default placement of this field.
     * Moves it to a different tab and/or section than its parent section.
     * Useful for placing a single setting in a shared tab (e.g., 'advanced').
     */
    readonly placement?: {
        /** Target tab ID (well-known or custom). */
        readonly tab?: string;
        /** Target section ID within the tab. Created if it doesn't exist. */
        readonly section?: string;
    };
    /**
     * Device-aggregator metadata — only meaningful when this field is part of
     * a `ConfigUISchemaWithValues` merged by `deviceManager.getDeviceSettingsAggregate`
     * / `getDeviceLiveInfoAggregate`.
     *
     * - `readonlyField: true` — render as display-only, no mutation handler
     * - `source: 'live'`    — the value comes from the fast-poll live
     *                         aggregator; the UI re-queries it on its own
     *                         cadence and swaps the value in place. Default:
     *                         'settings' (slow, cached, invalidated on save)
     * - `writerCapName`     — when editable, the capability whose provider
     *                         accepts the mutation via
     *                         `deviceManager.updateDeviceField`.
     * - `writerAddonId`     — when editable, the addon id of the provider
     *                         for `writerCapName`. Ignored for
     *                         `readonlyField: true`.
     * - `minRole`           — minimum user role required to edit this
     *                         field. UI uses it to disable the input and
     *                         show a lock icon; the server still enforces
     *                         cap-level auth (`admin` for the aggregator
     *                         writers today). Default: 'admin'.
     *
     * The aggregator tags fields with `writerCapName` + `writerAddonId` before
     * returning them — providers produce clean schemas; provenance is injected
     * based on which (cap, provider) contributed the section.
     *
     * Named `readonlyField` instead of `readonly` to avoid colliding with the
     * TS modifier already implied by the interface fields.
     */
    readonly readonlyField?: boolean;
    readonly source?: 'settings' | 'live';
    readonly writerCapName?: string;
    readonly writerAddonId?: string;
    readonly minRole?: 'viewer' | 'admin' | 'super_admin';
    /**
     * Optional inline action icons shown on the right of the field
     * control. Each action is a compact icon button that dispatches
     * through the same `onAction(action, key)` handler the button-field
     * type uses — keeps the action pipeline single-sourced. Common use:
     * readonly URL field with a "regenerate" icon, probe field with a
     * "copy" shortcut, etc.
     */
    readonly actions?: readonly FieldInlineAction[];
    /**
     * Declare this field as a *collection* of its underlying primitive.
     * When set, the stored value is an array (`T[]`) instead of a scalar
     * (`T`), and the form renderer shows:
     *   - `min` initial instances (empty slots if no value stored yet)
     *   - an `+ Add` button when the current count < `max`
     *   - a remove icon on each instance when the current count > `min`
     *   - an item counter badge (e.g. `2/3`) next to the section header
     *
     * Applies uniformly to every scalar-valued field type (text, number,
     * boolean, select, color, probe, password, textarea, slider, tags,
     * model-selector, storage-location). Structural fields (separator,
     * info, button) and group fields ignore this flag.
     *
     * Probe fields hit `testCreationField` / settings-contribution
     * probing per-index — the handler receives the full array and an
     * `index` hint so it can resolve just that slot.
     */
    readonly multiple?: ConfigFieldMultiple;
}
/**
 * Array-widget metadata for a `ConfigFieldBase`. See `multiple` on
 * `ConfigFieldBase` for the activation semantics.
 */
export interface ConfigFieldMultiple {
    /** Minimum item count — always rendered, no remove button when count == min. */
    readonly min: number;
    /** Maximum item count — Add button hidden when count == max. */
    readonly max: number;
    /** Label for the "add" affordance. Default: "Add". */
    readonly addLabel?: string;
    /** Template for each item's label (${n} = 1-based index). Default: none. */
    readonly itemLabel?: string;
    /**
     * Default value each newly-added instance starts with. Falls back to
     * the field-level `default`, then to the empty string for string
     * fields or `null` for everything else.
     */
    readonly itemDefault?: unknown;
}
/** Inline action icon attached to any ConfigField. */
export interface FieldInlineAction {
    /** Action identifier forwarded to the FormBuilder `onAction` handler. */
    readonly action: string;
    /** Lucide icon name (e.g. 'refresh-cw', 'copy', 'external-link'). */
    readonly icon: string;
    /** Tooltip shown on hover. */
    readonly tooltip: string;
    /** Visual variant — `danger` flags destructive actions in red. */
    readonly variant?: 'default' | 'danger' | 'primary';
    /** Optional confirmation prompt before dispatching. */
    readonly confirmMessage?: string;
}
export interface ConfigCondition {
    field: string;
    equals?: unknown;
    notEquals?: unknown;
    in?: unknown[];
    notIn?: unknown[];
    /**
     * For `multiselect` / `tags` / array-valued fields: matches when the
     * referenced field's array value INCLUDES the given primitive. Example:
     * `{ field: 'motionSources', includes: 'analyzer' }` shows the
     * dependent field only when the analyzer source is selected.
     */
    includes?: unknown;
    /** Inverse of `includes` — show when the field's array value does NOT
     *  contain the given primitive. */
    notIncludes?: unknown;
}
export interface ConfigTextField extends ConfigFieldBase {
    type: 'text';
    maxLength?: number;
    pattern?: string;
    inputType?: 'text' | 'url' | 'email';
}
export interface ConfigNumberField extends ConfigFieldBase {
    type: 'number';
    min?: number;
    max?: number;
    step?: number;
    unit?: string;
}
export interface ConfigBooleanField extends ConfigFieldBase {
    type: 'boolean';
    style?: 'switch' | 'checkbox';
}
export interface ConfigSelectField extends ConfigFieldBase {
    type: 'select';
    options: ConfigOption[];
    dependsOn?: Record<string, unknown>;
}
export interface ConfigMultiSelectField extends ConfigFieldBase {
    type: 'multiselect';
    options: ConfigOption[];
    maxItems?: number;
}
export interface ConfigColorField extends ConfigFieldBase {
    type: 'color';
    presets?: string[];
}
/** Select a single cluster node from the live topology. Options are
 * populated dynamically by the UI via useClusterNodes — no server-side
 * option list needed. */
export interface ConfigNodeSelectField extends ConfigFieldBase {
    type: 'node-select';
    /** When true, include offline nodes (greyed out). Default: false. */
    showOffline?: boolean;
}
/** Select multiple cluster nodes from the live topology. */
export interface ConfigNodeMultiSelectField extends ConfigFieldBase {
    type: 'node-multiselect';
    showOffline?: boolean;
    maxItems?: number;
}
export interface ConfigPasswordField extends ConfigFieldBase {
    type: 'password';
    showToggle?: boolean;
}
export interface ConfigTextAreaField extends ConfigFieldBase {
    type: 'textarea';
    rows?: number;
    maxLength?: number;
    /**
     * When true, the stored value is a JSON-serializable object/array and
     * the UI shows it as pretty-printed JSON, with parse-on-save semantics.
     * `hydrateSchema` takes care of stringifying on the way out; the
     * FormBuilder is expected to parse before emitting the patch.
     *
     * Used by `zodEntriesToConfigUI` when the Zod schema is an
     * array/object — falls back gracefully when the FormBuilder hasn't
     * implemented parse-on-save yet (the field still renders the JSON
     * string instead of `[object Object],…`).
     */
    isJson?: boolean;
}
export interface ConfigSliderField extends ConfigFieldBase {
    type: 'slider';
    min: number;
    max: number;
    step?: number;
    showValue?: boolean;
    unit?: string;
    default?: number;
    /** Display scale factor: displayed = stored / displayScale. E.g., displayScale=1000 shows ms as seconds. */
    displayScale?: number;
    /** When true, allows a "not set" state where the value is null (e.g., "Auto" mode) */
    nullable?: boolean;
    /** Label shown when the value is null (default: "Auto") */
    nullLabel?: string;
    /**
     * When true, render +/− stepper buttons flanking the numeric input.
     * Each click increments/decrements by `step` (default 1) and clamps
     * to `[min, max]`. Pairs with `showValue` so the operator can fine-
     * tune from the keyboard / pointer without scrubbing the slider.
     */
    showStepper?: boolean;
}
export interface ConfigTagsField extends ConfigFieldBase {
    type: 'tags';
    suggestions?: string[];
    maxTags?: number;
}
export interface ConfigGroupField extends ConfigFieldBase {
    type: 'group';
    fields: readonly ConfigField[];
    style?: 'card' | 'inline' | 'accordion';
    defaultCollapsed?: boolean;
}
/** Visual separator between fields */
export interface ConfigSeparatorField {
    type: 'separator';
    key: string;
}
/** A single sub-tab inside a `ConfigSubTabsField`. */
export interface ConfigSubTabDefinition {
    readonly id: string;
    readonly label: string;
    readonly icon?: string;
    /** Short text shown next to the label (e.g. live status / counter). */
    readonly badge?: string;
    readonly fields: readonly ConfigField[];
}
export interface ConfigSubTabsField extends ConfigFieldBase {
    type: 'sub-tabs';
    /** Tab definitions; rendered in array order. The first tab is active by default. */
    tabs: readonly ConfigSubTabDefinition[];
}
/** Read-only informational text */
export interface ConfigInfoField {
    type: 'info';
    key: string;
    label: string;
    content: string;
    variant?: 'info' | 'warning' | 'success' | 'danger';
    /**
     * Rendering mode for `content`. Default `'text'` keeps the legacy
     * "plain string with auto-newlines" behaviour. `'html'` interprets
     * `content` as inline HTML (sanitised in the renderer to a small
     * allowlist: a, ul, ol, li, strong, em, code, br) so addons can ship
     * clickable links and bulleted steps without inventing markdown.
     *
     * HTML mode is for addon-authored copy that lives in the bundle (no
     * user-supplied input flows here), so the surface stays narrow.
     */
    format?: 'text' | 'html';
    /** Same semantics as `ConfigFieldBase.showWhen` — lets info banners
     *  conditionally appear (e.g. "Cloudflare API token instructions"
     *  only when mode === 'custom'). */
    showWhen?: ConfigCondition;
}
/**
 * Read-only structured table for diagnostic data. Renders as a row-per-
 * entry list with column headers. Use when a section needs to surface
 * multiple parallel records (live sessions, socket-pool entries, recent
 * doorbell presses, …) — replaces the older "joined-string in an info
 * field" pattern that lost structure on long entries.
 *
 * Columns describe how to render each cell. Each entry in `value` is a
 * record keyed by the column `key`. Missing keys render as em-dash.
 *
 * NOT editable — the FormBuilder ignores the field for save patches.
 */
export interface ConfigObjectArrayField {
    type: 'object-array';
    key: string;
    label: string;
    description?: string;
    /**
     * Column definitions in render order. The `kind` hint controls cell
     * formatting:
     *   - `text` (default): plain string; null/undefined → em-dash
     *   - `monospace`: monospace font (good for IPs, sessionIds)
     *   - `status`: small colored dot + label, value should be one of
     *     'ok' | 'warn' | 'danger' | 'idle' | 'unknown'
     *   - `duration`: number of ms, formatted as "1m 23s"
     *   - `timestamp`: number (ms epoch), formatted as locale time
     */
    columns: ReadonlyArray<{
        readonly key: string;
        readonly label: string;
        readonly kind?: 'text' | 'monospace' | 'status' | 'duration' | 'timestamp';
        /** Optional fixed width hint (CSS values: '120px', '20%', etc). */
        readonly width?: string;
    }>;
    /** Rows. Empty array renders `emptyMessage`. */
    value: ReadonlyArray<Record<string, unknown>>;
    /** Shown when `value` is empty. Default: "No entries". */
    emptyMessage?: string;
}
export interface ConfigOption {
    value: string;
    label: string;
    description?: string;
    icon?: string;
}
/**
 * Editable array of records. Each row exposes the fields declared in
 * `itemFields`; the form renderer reuses the standard scalar field
 * components (text / number / select / password / boolean) for each
 * cell. Add/Remove row buttons mutate `value` (the FormBuilder picks
 * up the new array via the standard onChange flow).
 *
 * Use for "configure N items" cases — e.g. a backup-sftp addon
 * exposing a list of remote destinations the operator can add or
 * remove. Don't use for fixed-cardinality data; a flat group of
 * fields reads better.
 *
 * Constraints inside `itemFields`:
 *   - Only scalar field types are supported (text/number/select/
 *     multiselect/password/boolean/textarea/color/tags/slider). Nested
 *     groups, sub-tabs, probes, button actions, and other arrays are
 *     intentionally rejected — they conflate row-state with section-
 *     state and the FormBuilder's flat onChange contract can't carry
 *     the nesting cleanly. Build a custom-renderer if you need that.
 *   - Field `key`s are scoped to the row (no global collisions with
 *     the surrounding section).
 *   - `showWhen` inside a row references siblings within the same row.
 */
export interface ConfigEditableArrayField extends ConfigFieldBase {
    readonly type: 'editable-array';
    /** Per-row schema. Same shape as section.fields, scalar-only. */
    readonly itemFields: readonly ConfigField[];
    /** Default values used when the operator clicks "Add". */
    readonly defaultItem?: Record<string, unknown>;
    /** Minimum number of rows allowed. Remove disabled below this count. */
    readonly minRows?: number;
    /** Maximum number of rows allowed. Add disabled at this count. */
    readonly maxRows?: number;
    /** Label for the "Add" button. Defaults to "Add". */
    readonly addLabel?: string;
    /** Shown when the array is empty. Default: "No entries". */
    readonly emptyMessage?: string;
    /**
     * Optional template for each row's title (e.g. "{displayName}" or
     * "{host}:{port}"). Resolved against the row's values; missing keys
     * collapse to an em-dash. Falls back to `Row N` when not provided.
     */
    readonly rowTitleTemplate?: string;
}
export interface ConfigModelSelectorField extends ConfigFieldBase {
    type: 'model-selector';
    /** Filter models by type */
    modelType?: string;
    /** Allow user to enter a custom local path or URL (default: true) */
    allowCustomPath?: boolean;
    /** Allow file upload via UI (default: true) */
    allowUpload?: boolean;
    /** Accepted file extensions for upload (default: ['.onnx']) */
    acceptExtensions?: string[];
    /** URL to model conversion documentation/guide */
    conversionGuideUrl?: string;
    /** Brief text explaining what models are compatible */
    compatibilityNote?: string;
    /** Legacy: inline catalog entries */
    catalog?: readonly ModelCatalogEntry[];
    allowCustom?: boolean;
    acceptFormats?: readonly ModelFormat[];
    allowConversion?: boolean;
    requiredMetadata?: readonly (keyof CustomModelMetadata)[];
    outputFormatHint?: ModelOutputFormat;
}
/** Result of probing a single field value (returned by IDeviceProvider.testField) */
export interface FieldProbeResult {
    status: 'ok' | 'error';
    /** Labels to display as chips (e.g. "1920×1080", "H.265", "25fps") */
    labels?: readonly string[];
    /** Error message when status is 'error' */
    error?: string;
    /**
     * Optional values the provider suggests applying to other form fields
     * after a successful probe. The form-builder merges these into the
     * working snapshot ONLY for fields the operator hasn't filled yet,
     * never overwriting user input. Typical use: a host-probe that
     * autodetects the camera model and proposes it as the device `name`.
     */
    suggestedValues?: Readonly<Record<string, unknown>>;
}
/**
 * A probeable input field. Renders as a text input with a test button.
 * The provider implements the actual probing logic via IDeviceProvider.testField.
 *
 * UX states:
 * - idle: muted icon, no labels
 * - probing: spinner icon
 * - ok: green check icon + provider-returned labels as chips
 * - error: red icon + error message
 */
export interface ConfigProbeField extends ConfigFieldBase {
    type: 'probe';
    /** Input type hint for the text input (default: 'url') */
    inputType?: 'url' | 'text';
}
import type { StorageLocationType } from './storage.js';
export interface ConfigStorageLocationField extends ConfigFieldBase {
    type: 'storage-location';
    /** Which location type this field selects a provider for */
    locationType: StorageLocationType;
    /** Show available space indicator next to each provider option */
    showAvailableSpace?: boolean;
}
export interface ConfigButtonField extends ConfigFieldBase {
    type: 'button';
    /** Button label text */
    buttonLabel: string;
    /** Visual variant */
    variant?: 'default' | 'danger' | 'primary';
    /** tRPC action identifier — the form handler calls this action when pressed */
    action: string;
    /** Confirmation prompt before executing (optional) */
    confirmMessage?: string;
}
/**
 * Select whose options are pulled from a customAction on an addon at
 * render time. Generalises the "user pastes a token, server returns
 * the list of zones / buckets / dirs they can pick from" UX pattern
 * across every addon that integrates with an external API.
 *
 * Wiring at render time (admin-ui side):
 *   1. Collect `paramsFromForm` keys from the current form values.
 *   2. Merge with `paramsStatic`.
 *   3. Call `trpc.addons.custom({addonId, action, input}).query()`.
 *   4. Map each result item via `mapOption` → `{value, label, description?}`.
 *   5. If `refreshOn` is set, re-fetch when any listed form key changes.
 *
 * Example (cloudflare-tunnel zone picker):
 *   { type: 'addon-action-select',
 *     key: 'zoneId', label: 'Zone',
 *     addonId: 'cloudflare-tunnel',
 *     action: 'listZones',
 *     paramsFromForm: { token: 'apiToken' },
 *     refreshOn: ['apiToken'],
 *     mapOption: { value: 'id', label: 'name' } }
 */
export interface ConfigAddonActionSelectField extends ConfigFieldBase {
    type: 'addon-action-select';
    addonId: string;
    action: string;
    /** Map form-value keys → action input keys. */
    paramsFromForm?: Readonly<Record<string, string>>;
    /** Static params merged into the action input. */
    paramsStatic?: Readonly<Record<string, unknown>>;
    /** Form keys that trigger an options refresh when changed. */
    refreshOn?: readonly string[];
    /** Map a result row to the dropdown option shape. */
    mapOption: {
        readonly value: string;
        readonly label: string;
        readonly description?: string;
    };
    /** Optional message shown when the form is missing required params (e.g. token). */
    emptyParamsMessage?: string;
    /** Optional message shown when the action returns no results. */
    emptyResultsMessage?: string;
}
/**
 * Button that invokes an addon customAction. Used for "Verify token /
 * Test connection / Refresh credentials" affordances inside addon
 * settings panels. The form renderer surfaces success + error inline.
 */
export interface ConfigAddonActionButtonField extends ConfigFieldBase {
    type: 'addon-action-button';
    addonId: string;
    action: string;
    /** Visible label. */
    buttonLabel: string;
    /** Visual variant. */
    variant?: 'default' | 'primary' | 'danger';
    /** Map form-value keys → action input keys. */
    paramsFromForm?: Readonly<Record<string, string>>;
    /** Static params merged into the action input. */
    paramsStatic?: Readonly<Record<string, unknown>>;
    /** Toast/inline success message template. `{key}` placeholders are
     *  resolved against the action result. */
    successMessage?: string;
    /** Confirmation prompt before executing. */
    confirmMessage?: string;
}
/**
 * Describes an async resolver that the form renderer should call to
 * fetch deferred-load data a field depends on (e.g. the addon catalog
 * for a pipeline-editor, a dynamic options list for a select, …).
 *
 * The renderer dispatches through `client.trpc[capName][methodName]
 * .query(input)` — the field declares WHICH cap + method + input to
 * call, and the renderer stays oblivious to the endpoint naming.
 * Results are cached client-side keyed by `(capName, methodName,
 * input)` so two fields pointing at the same resolver deduplicate
 * automatically.
 *
 * Not an endpoint to arbitrary HTTP — the tRPC proxy gates it to the
 * server's declared cap surface + each cap's auth rules, same as
 * every other `.query()` call the UI makes.
 */
export interface AsyncResolverRef {
    /** tRPC cap namespace (e.g. `pipelineExecutor`, `pipelineOrchestrator`). */
    readonly capName: string;
    /** Method on that cap — must be a `.query()` (no mutations via resolvers). */
    readonly methodName: string;
    /** Input payload passed verbatim; serialised into the React Query key. */
    readonly input?: Readonly<Record<string, unknown>>;
}
/**
 * Camera pipeline editor field (stateless-pipeline Phase 6, option B).
 *
 * The FormBuilder renders this as the rich `PipelineBuilder` composite
 * (step tree + engine picker + template picker) instead of a flat
 * input. `value` is always a fully-resolved `CameraPipelineConfig` —
 * when the camera has no persisted pipeline, the contributor
 * (orchestrator) fills it with the cluster default server-side so
 * the renderer stays dumb about nullability.
 *
 * `nodeId` names the runner node whose addon catalog powers the
 * editor. It's exposed explicitly (rather than buried inside the
 * resolver) for diagnostics, UI labels, and cache keying. The
 * catalog itself isn't embedded — `catalogResolver` tells the
 * renderer how to lazy-load it via the shared resolver hook, which
 * React Query caches once per `(capName, methodName, input)`. That
 * way 50 cameras assigned to the same node don't each trigger 50
 * catalog fetches.
 *
 * Templates are NOT described by a resolver — they're a hub-wide
 * library fetched in one place by the editor directly.
 */
export interface ConfigPipelineEditorField extends ConfigFieldBase {
    type: 'pipeline-editor';
    /** Runner node whose catalog powers the editor. */
    readonly nodeId: string;
    /** The camera this field edits — used by the renderer to fetch per-camera settings. */
    readonly deviceId: number;
    /** How to fetch the addon catalog for this node. */
    readonly catalogResolver: AsyncResolverRef;
}
/**
 * Polygon zone CRUD editor field. Renders a canvas with a backdrop
 * (stream snapshot or live preview) where the operator draws polygon
 * zones, plus the list of existing zones (user + onboard) with
 * editing affordances.
 *
 * Stateless from the form's point of view: bound directly to
 * `api.zones.*` mutations, NOT to the form's `value`. The renderer
 * subscribes to `dev.state.zones.onChanged` for live updates so two
 * operators editing the same camera see each other's changes
 * immediately. `value` is therefore not consumed by the renderer
 * (kept on the discriminated union only so the form layout engine
 * stays homogeneous).
 *
 * Every zone is operator-drawn and fully editable — firmware-reported
 * onboard zones are deliberately out of scope for the first iteration.
 */
export interface ConfigZoneEditorField extends ConfigFieldBase {
    type: 'zone-editor';
    /**
     * Optional backdrop hint. `'snapshot'` (default) fetches a still
     * via the snapshot cap; `'stream'` overlays the live WebRTC view.
     *
     * The renderer reads the `deviceId` from the device-details page
     * context — the field doesn't carry it inline so the schema stays
     * device-agnostic and addons can ship a static contribution.
     */
    readonly backdrop?: 'snapshot' | 'stream';
}
/**
 * Live statistics field — bound to the "Live Stats" top-tab declared
 * by the analytics addon. Renders occupancy snapshots, audio metrics
 * (current + history), and rolling-window charts. The form's `value`
 * is unused; the renderer reads `deviceId` from the device-details
 * page context and pulls live data through DeviceProxy.
 */
export interface ConfigLiveStatsField extends ConfigFieldBase {
    type: 'live-stats';
}
/**
 * Live-stream player block — snapshot poster + play/stop + quality
 * selector + detection overlay. Contributed by `addon-stream-broker`.
 */
export interface ConfigLiveStreamViewerField extends ConfigFieldBase {
    type: 'live-stream-viewer';
}
/**
 * Pipeline-quick-stats block — Phase / Detection FPS / Inference time /
 * Active Tracks. Contributed by `addon-pipeline-orchestrator`. The
 * adapter pulls live values from `pipelineRunner.getCameraMetrics` +
 * `pipelineAnalytics.getActiveTracks` via the device proxy.
 */
export interface ConfigPipelineStatsField extends ConfigFieldBase {
    type: 'pipeline-stats';
}
/**
 * Stream network-quality block — per-stream bitrate / packet loss
 * table sourced from the network-quality cap.
 */
export interface ConfigStreamNetworkStatsField extends ConfigFieldBase {
    type: 'stream-network-stats';
}
/**
 * Doorbell recent-press history block. Contributed by
 * `addon-provider-reolink` only when the device advertises the
 * `DoorbellButton` feature; absent on every other camera.
 */
export interface ConfigDoorbellRecentField extends ConfigFieldBase {
    type: 'doorbell-recent';
    /** Max history rows to render (default: 10). */
    readonly historyLimit?: number;
}
/**
 * Device-info block — id / stableId / name / type / provider / capability
 * badges. Contributed by `device-manager`'s base section, redirected to
 * the overview tab via `placement.tab = 'overview'`.
 */
export interface ConfigDeviceInfoField extends ConfigFieldBase {
    type: 'device-info';
}
/**
 * Generic widget field — addon-contributed widget mounted by the
 * shared `<WidgetSlot>` runtime via the `WidgetRegistry`.
 *
 * The form-builder reads `widgetId` as the public widget identifier
 * (`<addonId>/<stableId>`) and forwards `widgetConfig` as `props.config`
 * to the widget. Different from the legacy bespoke field types
 * (`'live-stats'`, `'zone-editor'`, `'pipeline-stats'`, …) which are now
 * DEPRECATED — those required per-type renderer registration in
 * admin-ui; widget fields go through the generic registry without a
 * code change in admin-ui.
 *
 * `key` is the form-field's unique identifier (kept distinct from the
 * widget identifier so the same widget can be mounted twice in the
 * same form). `widgetId` is the public id resolved against the
 * WidgetRegistry.
 *
 * Field carries no stored value — the widget owns its own state via
 * DeviceProxy / runtime-state hooks. Placed in
 * `ConfigFieldWithValue` only so the discriminated-union round-trip
 * through `hydrateSchema` stays homogeneous (`value: null`).
 */
export interface ConfigWidgetField extends ConfigFieldBase {
    readonly type: 'widget';
    /** `<addonId>/<stableId>` — resolved via the WidgetRegistry. */
    readonly widgetId: string;
    /** Per-instance config payload forwarded to the widget as `props.config`. */
    readonly widgetConfig?: Readonly<Record<string, unknown>>;
}
/** A `ConfigField` with its current value attached inline. */
export type ConfigFieldWithValue = (ConfigTextField & {
    readonly value: unknown;
}) | (ConfigNumberField & {
    readonly value: unknown;
}) | (ConfigBooleanField & {
    readonly value: unknown;
}) | (ConfigSelectField & {
    readonly value: unknown;
}) | (ConfigMultiSelectField & {
    readonly value: unknown;
}) | (ConfigColorField & {
    readonly value: unknown;
}) | (ConfigPasswordField & {
    readonly value: unknown;
}) | (ConfigTextAreaField & {
    readonly value: unknown;
}) | (ConfigSliderField & {
    readonly value: unknown;
}) | (ConfigTagsField & {
    readonly value: unknown;
}) | (ConfigGroupFieldWithValue) | (ConfigSubTabsFieldWithValue) | ConfigSeparatorField | ConfigInfoField | ConfigObjectArrayField | (ConfigEditableArrayField & {
    readonly value: unknown;
}) | (ConfigModelSelectorField & {
    readonly value: unknown;
}) | (ConfigStorageLocationField & {
    readonly value: unknown;
}) | (ConfigProbeField & {
    readonly value: unknown;
}) | (ConfigButtonField) | (ConfigPipelineEditorField & {
    readonly value: unknown;
}) | (ConfigNodeSelectField & {
    readonly value: unknown;
}) | (ConfigNodeMultiSelectField & {
    readonly value: unknown;
}) | (ConfigZoneEditorField & {
    readonly value: unknown;
}) | (ConfigLiveStatsField & {
    readonly value: unknown;
}) | (ConfigLiveStreamViewerField & {
    readonly value: unknown;
}) | (ConfigPipelineStatsField & {
    readonly value: unknown;
}) | (ConfigStreamNetworkStatsField & {
    readonly value: unknown;
}) | (ConfigDoorbellRecentField & {
    readonly value: unknown;
}) | (ConfigDeviceInfoField & {
    readonly value: unknown;
}) | ConfigWidgetField | (ConfigAddonActionSelectField & {
    readonly value: unknown;
}) | ConfigAddonActionButtonField;
/** Recursive version of `ConfigGroupField` with hydrated child fields. */
export interface ConfigGroupFieldWithValue extends ConfigFieldBase {
    readonly type: 'group';
    readonly fields: readonly ConfigFieldWithValue[];
    readonly style?: 'card' | 'inline' | 'accordion';
    readonly defaultCollapsed?: boolean;
    readonly value?: undefined;
}
/** Hydrated sibling of `ConfigSubTabDefinition` — fields inside each
 *  sub-tab carry their resolved values. */
export interface ConfigSubTabDefinitionWithValue {
    readonly id: string;
    readonly label: string;
    readonly icon?: string;
    readonly badge?: string;
    readonly fields: readonly ConfigFieldWithValue[];
}
/** Recursive version of `ConfigSubTabsField` with hydrated child fields. */
export interface ConfigSubTabsFieldWithValue extends ConfigFieldBase {
    readonly type: 'sub-tabs';
    readonly tabs: readonly ConfigSubTabDefinitionWithValue[];
    readonly value?: undefined;
}
export interface ConfigSectionWithValues extends Omit<ConfigSection, 'fields'> {
    /**
     * Array kept mutable (not `readonly`) to stay assignable to the Zod-inferred
     * wire shape produced by `DEVICE_SETTINGS_CONTRIBUTION_METHODS.getDeviceSettingsContribution.output`.
     * Consumers treat it as immutable by convention — mutation is never
     * expected and would be a bug.
     */
    fields: ConfigFieldWithValue[];
}
export interface ConfigUISchemaWithValues {
    /** See `ConfigSectionWithValues.fields` — kept mutable for Zod wire compat. */
    tabs?: ConfigTabDeclaration[];
    sections: ConfigSectionWithValues[];
}
/**
 * Merge a `ConfigUISchema` with a raw `values` record into a
 * `ConfigUISchemaWithValues`. Used by every `get*Settings` backend endpoint
 * before returning to the admin UI. Groups are walked recursively.
 *
 * For each leaf field:
 *   - structural fields (`separator`, `info`, `button`) pass through unchanged
 *   - other fields get `value = values[key] ?? field.default ?? null`
 *
 * Unknown keys in `values` (keys not declared in the schema) are silently
 * ignored — they don't contribute to the output. Missing keys fall back to
 * the schema default or `null`. This mirrors the old backend behaviour where
 * `FormBuilder` rendered `values[key] ?? field.default`.
 */
export declare function hydrateSchema(schema: ConfigUISchema, values: Record<string, unknown>): ConfigUISchemaWithValues;
//# sourceMappingURL=config-ui.d.ts.map