/**
 * Controller pattern — host-attached state objects that reflect into
 * the DOM + notify subscribers on change. `BaseController` is the
 * minimal contract; `RouteController` is the canonical routing-state
 * implementation used by `<router-ui>` (see `core/provider.js`).
 *
 * Runtime exports mirror `core/controller.js`. `.d.ts` authored in
 * v0.5.6 §191 to close the §178 audit-script baseline missing-sibling
 * gap.
 *
 * @see ./controller.js (runtime SoT)
 * @see ./provider.js (`<router-ui>` consumer of `RouteController`)
 */

/**
 * Static schema each {@link BaseController} subclass should declare so
 * the dev-warn at `connect()` time can identify the controller in
 * diagnostics. Optional but conventional.
 */
export interface ControllerSchema {
  /** Stable lowercase controller identifier (e.g. `"route"`). */
  name: string;
  /** Map of public state field names → human-readable type label. */
  state?: Record<string, string>;
  /** List of canonical command names available on the controller. */
  commands?: ReadonlyArray<string>;
  /** Host attributes the controller writes via `reflect()`. */
  attributes?: ReadonlyArray<string>;
}

/**
 * Base class for host-attached controllers.
 *
 * Subclasses MUST implement `getState()` and SHOULD declare a static
 * `schema`; the constructor warns when these are missing the first
 * time the controller is connected.
 */
export class BaseController {
  /** Optional schema (subclasses declare via `static schema = …`). */
  static schema?: ControllerSchema;

  /** The host element this controller is attached to, or `null`. */
  readonly host: Element | null;

  /**
   * Wire the controller to a host element. Calls
   * {@link onConnect} + {@link reflect} once.
   */
  connect(host: Element): void;

  /**
   * Detach from `host` (or the currently-connected host if omitted).
   * Calls {@link onDisconnect}.
   */
  disconnect(host?: Element): void;

  /**
   * Subscribe to state-change notifications. Returns an unsubscribe
   * function.
   */
  subscribe(fn: () => void): () => void;

  /**
   * Run subscriber callbacks + `reflect()` after a state mutation.
   * Subclasses call this from their command implementations.
   */
  notify(): void;

  /** Lifecycle hook — override to wire listeners at connect time. */
  onConnect(host: Element): void;

  /** Lifecycle hook — override to release listeners at disconnect time. */
  onDisconnect(host: Element): void;

  /**
   * Override to write controller state into the host (attributes,
   * data-* properties, etc.). Called on every {@link notify} + at
   * `connect()`.
   */
  reflect(): void;

  /**
   * Return the controller's current public state. Subclasses MUST
   * override. The default implementation throws.
   */
  getState(): unknown;
}

/**
 * A single route declaration consumed by {@link RouteController}.
 */
export interface Route {
  /**
   * Path pattern. Static segments match literally; segments prefixed
   * with `:` are captured into `params`. e.g. `/users/:id`.
   */
  path: string;
  /** Optional content URL for `<router-ui>` to fetch + inject. */
  content?: string;
  /** Optional document title to set when the route matches. */
  title?: string;
  /** Optional section identifier (consumer-defined). */
  section?: string;
  /** Open-ended — route definitions can carry arbitrary metadata. */
  [key: string]: unknown;
}

/**
 * Public state shape returned by {@link RouteController.getState}.
 */
export interface RouteState {
  /** Current pathname being matched. */
  path: string;
  /** Captured `:param` values from the matched route. */
  params: Record<string, string>;
  /** The matched route declaration, or `null` if no match. */
  route: Route | null;
  /** Previous pathname (before the most recent navigation). */
  previous: string;
}

/**
 * Imperative commands exposed by {@link RouteController}. Wire-up
 * stable across both code-driven navigation + DOM-driven link clicks
 * (the latter via `<router-ui>`'s click delegation).
 */
export interface RouteCommands {
  /** Push a new pathname onto the history stack + re-match routes. */
  navigate(path: string): void;
  /** Replace the current history entry's pathname + re-match. */
  replace(path: string): void;
  /** Equivalent to `history.back()` when `historySync` is on. */
  back(): void;
  /** Equivalent to `history.forward()` when `historySync` is on. */
  forward(): void;
  /** Swap the routes table at runtime (re-matches the current path). */
  setRoutes(routes: ReadonlyArray<Route>): void;
}

/**
 * Constructor options for {@link RouteController}.
 */
export interface RouteControllerOptions {
  /** Initial routes table. May be empty + updated later via `setRoutes`. */
  routes?: ReadonlyArray<Route>;
  /** Initial pathname (defaults to `location.pathname`). */
  initial?: string;
  /**
   * When `true` (default), navigate/replace push to History API +
   * `popstate` syncs state on browser back/forward.
   */
  historySync?: boolean;
}

/**
 * Routing-state controller. Used by `<router-ui>` (see
 * `core/provider.js`) as the canonical history-syncing route matcher.
 * Can also be used standalone — `notify()` fires whenever the path
 * changes; `getState()` returns the current match.
 */
export class RouteController extends BaseController {
  static schema: ControllerSchema;

  constructor(options?: RouteControllerOptions);

  /** Public state — path, params, matched route, previous path. */
  getState(): RouteState;

  /** Imperative API; wire from command palette / link handlers. */
  commands: RouteCommands;
}
