import {
  createElement,
  type ElementType,
  type FunctionComponent,
  type ReactNode,
} from 'react';
import {
  useC42,
  useC42Events,
  type C42ControllerClass,
  type C42EventHandler,
} from './useC42';

export interface C42Props<O = unknown> {
  /** The headless controller class from `@42/core`. */
  controller: C42ControllerClass<O>;
  /** Options forwarded to the controller constructor / `update()`. */
  options?: O;
  /** Values that trigger an options re-sync (see {@link useC42}). */
  deps?: unknown[];
  /** Element/tag to render as the controller root. Defaults to `"div"`. */
  as?: ElementType;
  /**
   * Map of `data-c42-*` DOM event names to handlers, e.g.
   * `{ "accordion:change": (detail) => ... }`. Each handler receives the
   * unwrapped `event.detail` and the original `CustomEvent`.
   */
  events?: Record<string, C42EventHandler | undefined>;
  /** Children rendered inside the root. */
  children?: ReactNode;
  /**
   * Raw HTML used as the root's initial markup when no `children` are given.
   * Useful for rendering a `data-c42-*` skeleton the controller enhances.
   */
  html?: string;
  className?: string;
  // Any extra props are forwarded to the rendered element.
  [key: string]: unknown;
}

/**
 * Generic bridge that mounts any `@42/core` controller onto a rendered element.
 *
 * ```tsx
 * import { Accordion } from "@42/core/accordion";
 * <C42 controller={Accordion} options={{ multiple: true }}
 *      events={{ "accordion:change": (d) => console.log(d) }}>
 *   …markup…
 * </C42>
 * ```
 */
export function C42<O = unknown>(props: C42Props<O>) {
  const {
    controller,
    options,
    deps = [],
    as = 'div',
    events,
    children,
    html,
    className,
    ...rest
  } = props;

  const ref = useC42<O>(controller, options, deps);
  useC42Events(ref, events ?? {});

  const content =
    children != null
      ? { children }
      : html != null
        ? { dangerouslySetInnerHTML: { __html: html } }
        : {};

  // `createElement`'s overloads don't accept a bare `ElementType`; casting the
  // tag to a component shape is the standard escape hatch for a dynamic `as`.
  const Component = as as unknown as FunctionComponent<Record<string, unknown>>;
  return createElement(Component, { ref, className, ...rest, ...content });
}
