/**
 * Reactive signals — the primitive that UIElement's property system is built on.
 *
 * @see ../USAGE.md#property-reactivity-signal-backed
 */

/** Read-only signal. Reading `.value` inside an `effect()` subscribes that effect. */
export interface ReadonlySignal<T> {
  readonly value: T;
  /** Read without creating a subscription. */
  peek(): T;
}

/** Mutable signal. Writing `.value` notifies all subscribers. */
export interface Signal<T> extends ReadonlySignal<T> {
  value: T;
}

/** Computed signal — derived from other signals; recomputes lazily. */
export type ComputedSignal<T> = ReadonlySignal<T>;

/** Effect options. */
export interface EffectOptions {
  /** Host tag name for diagnostic logging. */
  host?: string;
  /** Called when the effect body throws. Receives the error. */
  onError?: ((err: unknown) => void) | null;
}

/** Cleanup function returned by `effect()`. Call to dispose the subscription. */
export type EffectDisposer = () => void;

/**
 * Create a mutable signal. Reading `.value` inside an `effect()` subscribes it.
 *
 * @example
 *   const count = signal(0);
 *   effect(() => console.log(count.value));  // subscribes
 *   count.value = 1;                          // logs 1
 */
export function signal<T>(initial: T): Signal<T>;
export function signal<T = undefined>(): Signal<T | undefined>;

/**
 * Create a computed signal — re-derives when any dependency changes. Lazy:
 * recomputes only when read.
 *
 * @example
 *   const a = signal(2);
 *   const b = signal(3);
 *   const sum = computed(() => a.value + b.value);
 *   sum.value;  // 5
 */
export function computed<T>(fn: () => T): ComputedSignal<T>;

/**
 * Run `fn` inside a reactive context. Re-runs when any signal read during the
 * last run changes. Returns a disposer.
 *
 * §221h (v0.5.9) — re-run scheduling notes. Re-runs are scheduled via
 * `queueMicrotask` so multiple signal mutations in the same synchronous tick
 * batch into ONE re-run. This is the same semantic the render scheduler in
 * `core/element.js` uses. Two consequences worth knowing:
 *
 * 1. **Reads of mutated signals between write and re-run see the new value
 *    immediately** — only effect re-execution is deferred:
 *    ```
 *    count.value = 1;
 *    count.value;  // 1 (read returns new value)
 *    // effect hasn't run yet
 *    ```
 *
 * 2. **Test isolation requires awaiting two microtasks** — one for the
 *    effect's own re-run + one for any downstream effects it schedules:
 *    ```
 *    const tick = () => new Promise(r => queueMicrotask(r));
 *    count.value = 5;
 *    await tick();   // primary effect runs
 *    await tick();   // downstream effects flush
 *    ```
 *
 * See [`USAGE.md` §221h](../USAGE.md#221h--effect-dispatch-timing-signals) for
 * a worked example. The double-`queueMicrotask` test-flush idiom is the
 * canonical fix; don't reach for `await Promise.resolve()` (single microtask
 * window) or `setTimeout(_, 0)` (different task queue) — neither matches the
 * scheduler's batching shape reliably.
 *
 * @example
 *   const dispose = effect(() => console.log(count.value));
 *   // later
 *   dispose();
 */
export function effect(fn: () => void, opts?: EffectOptions): EffectDisposer;

/**
 * Batch multiple signal mutations into one effect re-run. Useful for atomic
 * updates that touch several signals.
 *
 * @example
 *   batch(() => {
 *     a.value = 1;
 *     b.value = 2;
 *   });  // effect runs once after both mutations
 */
export function batch(fn: () => void): void;

/**
 * Read signals inside `fn` without subscribing the surrounding effect.
 *
 * @example
 *   effect(() => {
 *     const snapshot = untracked(() => other.value);  // does NOT subscribe
 *     console.log(active.value, snapshot);            // only `active` subscribes
 *   });
 */
export function untracked<T>(fn: () => T): T;

/** Type guard — true if `v` looks like a signal. */
export function isSignal(v: unknown): v is ReadonlySignal<unknown>;

/** Brand symbol — identifies signal objects. Exported for advanced introspection. */
export const SIGNAL: unique symbol;
