import { type Accessor, type JSX, Show, createEffect, createSignal, on } from "solid-js";

export interface PresenceChildProps {
  /** Whether the content is currently present (tracks props.present directly). */
  present: Accessor<boolean>;
  /** True briefly after present transitions from false→true. For enter animations. */
  opening: Accessor<boolean>;
  /** True briefly after present transitions from true→false. For exit animations. */
  closing: Accessor<boolean>;
}

export interface PresenceProps {
  /** Whether the content should be visible/mounted. */
  present: boolean;
  /**
   * When true, keeps children mounted even when present is false.
   * Use this when an external animation library controls unmounting.
   */
  forceMount?: boolean;
  children: JSX.Element | ((props: PresenceChildProps) => JSX.Element);
}

/**
 * Controls mount/unmount of children with enter/exit animation support.
 * Exposes opening and closing signals via the children-as-function API.
 *
 * Usage with children-as-function:
 *   <Presence present={open()}>
 *     {({ opening, closing }) => (
 *       <div classList={{ opening: opening(), closing: closing() }}>…</div>
 *     )}
 *   </Presence>
 */
export function Presence(props: PresenceProps): JSX.Element {
  const [mounted, setMounted] = createSignal(props.present);
  const [opening, setOpening] = createSignal(false);
  const [closing, setClosing] = createSignal(false);

  // defer: true skips the initial run — only react to changes after mount.
  createEffect(
    on(
      () => props.present,
      (isPresent) => {
        if (isPresent) {
          setMounted(true);
          setClosing(false);
          setOpening(true);
          // Clear opening after a microtask — gives consumers one reactive tick
          // to read the opening state before it resets.
          queueMicrotask(() => setOpening(false));
        } else {
          setOpening(false);
          setClosing(true);
          // In the baseline, unmount immediately (no animation duration known).
          // With forceMount, the consumer controls the lifecycle externally.
          if (!props.forceMount) {
            setMounted(false);
          }
          queueMicrotask(() => setClosing(false));
        }
      },
      { defer: true },
    ),
  );

  const shouldMount = () => props.forceMount || mounted();

  const childProps: PresenceChildProps = {
    // Reflect props.present directly — no reactive lag from the mounted signal.
    present: () => props.present,
    opening,
    closing,
  };

  // Wrap in a function so Show evaluates children lazily (only when mounted).
  return (
    <Show when={shouldMount()}>
      {
        (() =>
          typeof props.children === "function"
            ? (props.children as (p: PresenceChildProps) => JSX.Element)(childProps)
            : props.children) as unknown as JSX.Element
      }
    </Show>
  );
}
