import { type Component, defineAsyncComponent, h } from "vue"
import { resolveElementPath } from "./registry"

/**
 * Vite glob entry point. Each value is a function returning a Promise
 * that resolves to the .vue module. Vite turns each into its own chunk
 * so the consumer only downloads the elements that get rendered.
 */
const elementModules = import.meta.glob("../components/compositions/elements/**/*.vue")

/**
 * Component cache so repeated lookups of the same (type, style) return
 * the same `defineAsyncComponent` instance — important for Vue's
 * keep-alive and for stability across re-renders.
 */
const componentCache = new Map<string, Component>()

/**
 * Resolves the Vue component for a given element type + style.
 *
 * Returns an async-wrapped component that lazy-loads the underlying
 * .vue file on first render. If the (type, style) pair isn't
 * registered or the file is missing, returns a Fallback component that
 * renders a small warning instead of throwing — this lets a page with
 * one bad element still render the rest.
 */
export function getElementComponent(type: string): Component {
  const cached = componentCache.get(type)
  if (cached) {
    return cached
  }

  const path = resolveElementPath(type)
  if (!path) {
    const fallback = createMissingFallback("element", type)
    componentCache.set(type, fallback)
    return fallback
  }

  const importer = elementModules[`../components/compositions/elements/${path}.vue`]
  if (!importer) {
    const fallback = createMissingFallback("element", type, path)
    componentCache.set(type, fallback)
    return fallback
  }

  const component = defineAsyncComponent({
    loader: async () => {
      const mod = (await (importer as () => Promise<{ default: Component }>)())
      return mod.default ?? (mod as unknown as Component)
    },
    errorComponent: createMissingFallback("element", type, path),
    delay: 0,
    suspensible: false,
  })
  componentCache.set(type, component)
  return component
}

/**
 * Eagerly preload an element module (e.g. for above-the-fold inputs).
 * Returns a Promise that resolves when the chunk is loaded.
 */
export function preloadElement(type: string): Promise<unknown> {
  const path = resolveElementPath(type)
  if (!path) {
    return Promise.resolve()
  }
  const importer = elementModules[`../components/compositions/elements/${path}.vue`]
  return importer ? importer() : Promise.resolve()
}

/**
 * Test-only: lets vitest reset the cache between cases.
 */
export function __clearElementCache(): void {
  componentCache.clear()
}

function createMissingFallback(
  kind: "element" | "card",
  type: string,
  path?: string,
): Component {
  return {
    name: "MissingRendererFallback",
    setup() {
      return () =>
        h(
          "div",
          {
            class:
              "rounded-md border border-dashed border-destructive/40 bg-destructive/5 px-3 py-2 text-sm text-destructive",
            "data-testid": "missing-renderer",
            "data-kind": kind,
            "data-type": type,
          },
          [
            h(
              "strong",
              { class: "font-semibold" },
              `Unknown ${kind}: ${type}`,
            ),
            path
              ? h(
                  "div",
                  { class: "text-xs opacity-70" },
                  `Expected file at src/${kind === "element" ? "elements" : "cards"}/${path}.vue`,
                )
              : null,
          ],
        )
    },
  }
}

// Re-export so the card loader can use the same fallback signature.
export { createMissingFallback as __createMissingFallback }
