import type { DataTag } from "./dataTag"; import type { Layer } from "./layer"; import type { LayerClient } from "./layerClient"; import type { LayerStack } from "./layerStack"; import type { CancelQueuedArgs, DefaultLayerError, HandleDismissArgs, LayerKey, LayerOptions, PayloadArg, } from "./types"; import { keySignature } from "./utils"; import type { InferValidatorOutput, OpenValidatePayload, Validator, } from "./validators"; interface OpenOpts { key: LayerKey; payload: P; component?: unknown; enteringDelay?: number; exitingDelay?: number; upsert?: boolean; loadFn?: (ctx: { payload: P; signal: AbortSignal }) => Promise | D; validate?: Validator

; } /** Prevents validated options from falling through to the unvalidated overload. */ type NoValidateOptions = Opts extends { validate: Validator } ? never : Opts; /** * Identity-bound layer ops + escapes (`stack` / `client` / `options` / `current`). * `open`/`upsert` take payload only; stack-level ops route via `.stack`. */ export interface LayerHandle { open: (payload: PayloadArg

["payload"]) => Promise; upsert: (payload: PayloadArg

["payload"]) => Promise; /** * Dismiss the bound instance (or `{ id }`). * Response optional iff `undefined extends R` ({@link HandleDismissArgs} / * {@link EndArgs} gate). */ dismiss: (...args: HandleDismissArgs) => Promise; update: (patch: Partial

, opts?: { id?: string }) => void; /** * Resolves and removes a serially queued layer without mounting (skips blockers). * No `id` → FIFO head for this key; `{ id }` → exact queued match. * Response may be omitted when `undefined extends R` ({@link CancelQueuedArgs} / * {@link EndArgs} gate). */ cancelQueued: (...args: CancelQueuedArgs) => boolean; readonly client: LayerClient; readonly stack: LayerStack; readonly options: LayerOptions & { key: DataTag; }; /** Live-checked bound instance (`null` when not in the stack). */ readonly current: Layer | null; } /** * Validated handle: `open`/`upsert` take schema **input** ({@link OpenValidatePayload}); * `current`/`update` use parsed **output**. */ export interface ValidatedLayerHandle< V extends Validator, R, E, D, RP, > extends Omit< LayerHandle, R, E, D, RP>, "open" | "upsert" > { open: (payload: OpenValidatePayload) => Promise; upsert: (payload: OpenValidatePayload) => Promise; } /** * Wire `layerOptions` + a {@link LayerClient} into a headless {@link LayerHandle}. * * @example * ```ts * const c = createLayer(confirm, client); * const ok = await c.open({ title: "Remove?" }); * ``` */ export function createLayer< V extends Validator, R, E = DefaultLayerError, D = unknown, RP = unknown, >( options: LayerOptions, R, E, D, RP> & { key: LayerKey; validate: V; }, client: LayerClient, ): ValidatedLayerHandle; export function createLayer< P, R, E = DefaultLayerError, D = unknown, RP = unknown, >( options: NoValidateOptions & { key: LayerKey }>, client: LayerClient, ): LayerHandle; export function createLayer< P, R, E = DefaultLayerError, D = unknown, RP = unknown, >( options: LayerOptions & { key: LayerKey }, client: LayerClient, ): LayerHandle { const stackId = options.stack ?? "default"; const stack = client.getStack(stackId) as unknown as LayerStack; const opts = options as LayerOptions & { key: DataTag; }; const sig = keySignature(opts.key); let mine: Layer | undefined; const toOpenOpts = (payload: P): OpenOpts => ({ key: opts.key, payload, component: opts.component, enteringDelay: opts.enteringDelay, exitingDelay: opts.exitingDelay, upsert: opts.upsert, loadFn: opts.loadFn, validate: opts.validate, }); const target = (id?: string): Layer | undefined => { if (id) { const l = stack.getLayer(id); return l && keySignature(l.key) === sig ? l : undefined; } return stack.find(opts.key); }; return { open: (payload) => { mine = stack.open(toOpenOpts(payload as P)); return mine.promise.promise as Promise; }, upsert: (payload) => { mine = stack.open({ ...toOpenOpts(payload as P), upsert: true }); return mine.promise.promise as Promise; }, dismiss: (...args) => { const [response, o] = args; const l = target(o?.id); return l ? stack.dismiss(l, response as R, { force: o?.force }) : Promise.resolve(false); }, update: (patch, o) => { const l = target(o?.id); if (l) stack.update(l, patch); }, cancelQueued: (...args) => { const [response, o] = args; return stack.cancelQueued(opts.key, response as R, o); }, client, stack, options: opts, get current(): Layer | null { return (mine ? (stack.getLayer(mine.id) ?? null) : null) as Layer< P, R, E, D > | null; }, }; }