/**
 * UIElement — light-DOM reactive base class.
 *
 * Every primitive in `@adia-ai/web-components` extends this (or `UIFormElement`
 * for form-bearing primitives). Provides signal-backed property reactivity,
 * lifecycle hooks (`connected`/`render`/`updated`/`disconnected`), and
 * tagged-template rendering.
 *
 * @see ../USAGE.md#lifecycle
 * @see ../USAGE.md#property-reactivity-signal-backed
 */

import type { ReadonlySignal, Signal } from './signals.js';
import type { TemplateResult } from './template.js';

// Mirror the runtime re-exports from element.js so a single import of
// `@adia-ai/web-components/core/element` resolves both the UIElement
// class AND the reactive/template helpers — matches the canonical
// pattern documented in USAGE.md and the `element.js` runtime export
// surface. Closes FEEDBACK-05 P1 (drift caught post-v0.4.9 upgrade).
export { signal, computed, effect, batch, untracked, isSignal } from './signals.js';
export type { ReadonlySignal, Signal, ComputedSignal, EffectOptions, EffectDisposer } from './signals.js';
export { html, css, repeat } from './template.js';
export type { TemplateResult } from './template.js';

/** Property declaration in a component's `static properties = { … }` map. */
export interface PropertyConfig<T = unknown> {
  /** Coercion target — `Boolean`, `Number`, `String`, or custom. */
  type?: BooleanConstructor | NumberConstructor | StringConstructor | (new (...args: unknown[]) => T);
  /** Initial value. Defaults to `undefined`. */
  default?: T;
  /** Mirror property changes to an HTML attribute. */
  reflect?: boolean;
  /** Custom attribute name. Default: kebab-case of property key. */
  attribute?: string;
}

/** Map of property name → config. The shape consumed by `static properties`. */
export type PropertiesMap = Record<string, PropertyConfig>;

/** Trait name + config tuple passed via `static traits`. */
export type TraitDeclaration = string | { name: string; [opt: string]: unknown };

/** Map of slot name → markup string. Consumed by `static parts`. */
export type PartsMap = Record<string, string>;

/**
 * Light-DOM reactive base class. Subclass to author a primitive.
 *
 * @example
 *   class MyWidget extends UIElement {
 *     static properties = { label: { type: String, default: '' } };
 *     render() { this.textContent = this.label; }
 *   }
 *   customElements.define('my-widget', MyWidget);
 */
export class UIElement extends HTMLElement {
  /**
   * Property declarations — signal-backed getters/setters wired by the
   * constructor. Override in subclasses.
   */
  static properties: PropertiesMap;

  /** Trait declarations — applied during `connectedCallback`. */
  static traits: readonly TraitDeclaration[];

  /** Slot blueprints used by `ensure(slotName)`. */
  static parts?: PartsMap;

  /** Stylesheets to adopt onto the document on first render. */
  static styles?: CSSStyleSheet | readonly CSSStyleSheet[];

  /**
   * Pure-function template — called inside the host's render effect. Reading
   * `this.foo` inside subscribes the effect to the `foo` signal.
   *
   * Override OR return `null` and update DOM imperatively in `render()`.
   */
  static template: <T extends UIElement>(host: T) => TemplateResult | null;

  /** Observed attribute names — derived from `static properties`. */
  static readonly observedAttributes: readonly string[];

  /** ElementInternals — attached in the constructor; carries form state etc. */
  readonly internals: ElementInternals;

  /**
   * Setup hook — called after construction + insertion, before the first render
   * effect installs. Set up listeners on document / external targets here.
   *
   * Override in subclasses; default is a no-op. Runs inside `untracked()` so
   * reads don't leak subscriptions to outer effects.
   */
  connected(): void;

  /**
   * Imperative update hook — called every time any property signal mutates.
   * Reading `this.foo` here subscribes the effect to `foo`.
   *
   * Override in subclasses; default is a no-op.
   */
  render(): void;

  /**
   * Post-render hook — called after `render()`. `changed` is a map of every
   * property that mutated this tick, keyed by property name with old value.
   *
   * Override in subclasses; default is a no-op.
   */
  updated(changed: ReadonlyMap<string, unknown>): void;

  /**
   * Teardown hook — called on element removal. Clean up listeners + intervals
   * + observers here.
   *
   * Override in subclasses; default is a no-op.
   */
  disconnected(): void;

  /**
   * Error hook — called when the host's render effect throws. Receives the
   * thrown value. Default behavior: rethrow.
   */
  onError?(err: unknown): void;

  // ── Standard custom-element callbacks (overridable but rarely needed) ──

  connectedCallback(): void;
  disconnectedCallback(): void;
  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;

  // ── Convenience helpers ──

  /**
   * Create a local signal bound to this element's lifetime. Disposed on
   * `disconnectedCallback`.
   */
  signal<T>(initial: T): Signal<T>;

  /**
   * Get or stamp a slot child by name. If a `[slot="name"]` child exists,
   * returns it; otherwise stamps the blueprint from `static parts[name]`,
   * appends it, and returns the new node.
   */
  ensure(slotName: string): Element | null;

  /** Remove the `[slot="name"]` child if present. */
  drop(slotName: string): void;

  /**
   * Keyed-reconcile a parent's children against `items`. Used internally by
   * the `repeat` directive; consumers rarely call directly.
   */
  reconcile<T>(
    parent: Element,
    items: readonly T[],
    keyFn: (item: T, i: number) => string | number,
    stampFn: (item: T, i: number, existing: Element | null) => Element,
  ): void;

  // ── Controller pattern (optional advanced) ──

  /** Set the controller; calls connect/disconnect on it through lifecycle. */
  set controller(c: UIController | null);
  get controller(): UIController | null;

  /**
   * Factory — create an instance of this element with the given props applied.
   * Tag must already be registered.
   */
  static create<T extends typeof UIElement>(
    this: T,
    props?: Partial<InstanceType<T>>,
  ): InstanceType<T>;
}

/**
 * Controller — optional pluggable behavior attached to a UIElement via
 * `element.controller = …`. Lifecycle mirrors the host.
 */
export interface UIController {
  connect?(host: UIElement): void;
  disconnect?(host: UIElement): void;
}
