import { useEffect, useState, type ReactNode } from "react";
import type { VariantLoader, VariantRenderOptions } from "./types.js";

/**
 * Minimal contract for rendering a preview variant.
 * Compatible with fragment variants and Storybook-adapted variants.
 */
export interface PreviewVariantLike {
  render: (options?: VariantRenderOptions) => ReactNode;
  loaders?: VariantLoader[];
}

export interface PreviewRuntimeState {
  content: ReactNode | null;
  isLoading: boolean;
  error: Error | null;
  loadedData?: Record<string, unknown>;
}

export interface PreviewRuntimeOptions {
  variant?: PreviewVariantLike | null;
  loadedData?: Record<string, unknown>;
}

const EMPTY_STATE: PreviewRuntimeState = {
  content: null,
  isLoading: false,
  error: null,
  loadedData: undefined,
};

function toError(error: unknown): Error {
  return error instanceof Error ? error : new Error(String(error));
}

/**
 * Execute all variant loaders and merge their payloads.
 * `loadedData` is applied last so host-level overrides win.
 */
export async function executeVariantLoaders(
  loaders: VariantLoader[] | undefined,
  loadedData?: Record<string, unknown>,
): Promise<Record<string, unknown> | undefined> {
  const hasLoaders = !!loaders && loaders.length > 0;
  if (!hasLoaders) {
    return loadedData;
  }

  const results = await Promise.all(loaders.map((loader) => loader()));
  const mergedFromLoaders = results.reduce<Record<string, unknown>>(
    (acc, result) => ({ ...acc, ...result }),
    {},
  );

  return loadedData ? { ...mergedFromLoaders, ...loadedData } : mergedFromLoaders;
}

/**
 * Resolve a full runtime state (loader execution + render) in one async call.
 * This is useful for testing and for hook/component orchestration.
 */
export async function resolvePreviewRuntimeState(
  options: PreviewRuntimeOptions,
): Promise<PreviewRuntimeState> {
  const { variant, loadedData } = options;
  if (!variant) {
    return EMPTY_STATE;
  }

  try {
    const mergedLoadedData = await executeVariantLoaders(variant.loaders, loadedData);
    const content = variant.render({ loadedData: mergedLoadedData });
    return {
      content,
      isLoading: false,
      error: null,
      loadedData: mergedLoadedData,
    };
  } catch (error) {
    return {
      content: null,
      isLoading: false,
      error: toError(error),
      loadedData: undefined,
    };
  }
}

/**
 * Hook for rendering a preview variant with loader support.
 */
export function usePreviewVariantRuntime(
  options: PreviewRuntimeOptions,
): PreviewRuntimeState {
  const { variant, loadedData } = options;
  const [state, setState] = useState<PreviewRuntimeState>(EMPTY_STATE);

  useEffect(() => {
    let cancelled = false;

    if (!variant) {
      setState(EMPTY_STATE);
      return () => {
        cancelled = true;
      };
    }

    const hasLoaders = !!variant.loaders && variant.loaders.length > 0;
    setState({
      content: null,
      isLoading: hasLoaders,
      error: null,
      loadedData: undefined,
    });

    resolvePreviewRuntimeState({ variant, loadedData }).then((nextState) => {
      if (!cancelled) {
        setState(nextState);
      }
    });

    return () => {
      cancelled = true;
    };
  }, [variant, loadedData]);

  return state;
}

interface PreviewVariantRuntimeProps extends PreviewRuntimeOptions {
  children: (state: PreviewRuntimeState) => ReactNode;
}

/**
 * Render-prop component wrapper around `usePreviewVariantRuntime`.
 */
export function PreviewVariantRuntime({
  variant,
  loadedData,
  children,
}: PreviewVariantRuntimeProps) {
  const state = usePreviewVariantRuntime({ variant, loadedData });
  return <>{children(state)}</>;
}
