<script setup lang="ts">
/**
 * Renders a single DisplayCard by dispatching its JSON definition into
 * the matching composition under `components/compositions/cards/`.
 *
 * The renderer is the bridge layer:
 *   - resolves the composition by `card.cardType` via the registry
 *   - evaluates the card's visibility (`shouldRender`)
 *   - applies responsive layout + classList chrome at the wrapper level
 *   - resolves ID-referenced child lists against the page-level
 *     `elements` / `cards` maps:
 *       - `card.elementList: string[]` → pass as `elements: CardElement[]`
 *       - `card.cardList: string[]`    → pass as `cards: DisplayCard[]`
 *       - `card.tabList: string[]`     → pass as `tabs: DisplayCard[]`
 *       - `card.elementId: string`     → pass as `element: CardElement`
 *   - strips schema-only keys (cardType, conditionParams, columns*, ...)
 *     and passes the rest through as Vue props
 *
 * Compositions don't know about the page-level maps or the JSON schema —
 * they receive resolved children + display props and emit normal Vue
 * events. They're directly mountable in any app.
 */
import { computed } from "vue"
import type { CardElement } from "@/definitions/elements"
import type { DisplayCard } from "@/definitions/cards"
import { cn } from "@/lib/utils"
import { getCardComponent } from "./useCardLoader"
import {
  shouldRender,
  type EvaluationContext,
} from "./useDisplayConditions"

interface Props {
  card: DisplayCard
  item?: Record<string, unknown>
  /** Page-level element registry: id → CardElement JSON. */
  elements?: Record<string, CardElement>
  /** Page-level card registry: id → DisplayCard JSON (for nested cards). */
  cards?: Record<string, DisplayCard>
  evaluationContext?: EvaluationContext
}

const props = withDefaults(defineProps<Props>(), {
  item: () => ({}),
  elements: () => ({}),
  cards: () => ({}),
  evaluationContext: () => ({}),
})

const visible = computed(() =>
  shouldRender(props.card, props.item, props.evaluationContext),
)

const resolved = computed(() => getCardComponent(props.card.cardType))

/**
 * Keys that live on every DisplayCard and are NOT meant to flow
 * through to the composition as props. These are either consumed by
 * this renderer (`columns*`, `conditionParams`, `cardType`, ...) or
 * resolved into their concrete child arrays below.
 */
const STRIPPED_KEYS = new Set<string>([
  "cardType",
  "conditionParams",
  "conditionGroupLogic",
  "condition",
  "features",
  "permissions",
  "columns",
  "columnsSm",
  "columnsMd",
  "columnsLg",
  "columnsXl",
  "columnsXxl",
  "wrapperClassList",
  "columnClassList",
  "order",
  // ID-referenced child lists — resolved below into concrete arrays.
  "elementList",
  "cardList",
  "tabList",
  "elementId",
])

/**
 * Compute the props object to pass to the resolved composition.
 *
 * - Strips schema-only keys (`cardType`, `conditionParams`, ...).
 * - Resolves `elementList` / `cardList` / `tabList` / `elementId` against
 *   the page-level registries and exposes them as concrete arrays /
 *   objects under composition-facing prop names.
 * - Forwards `item` as-is for child renderers to use.
 */
const compositionProps = computed<Record<string, unknown>>(() => {
  const c = props.card as unknown as Record<string, unknown>
  const result: Record<string, unknown> = {}
  for (const [key, value] of Object.entries(c)) {
    if (!STRIPPED_KEYS.has(key)) {
      result[key] = value
    }
  }

  // Pass through the data model.
  result.item = props.item

  // Resolve `elementList: string[]` → `elements: CardElement[]`.
  if (Array.isArray((c as { elementList?: unknown }).elementList)) {
    const ids = (c as { elementList: string[] }).elementList
    const out: CardElement[] = []
    for (const id of ids) {
      const el = props.elements[id]
      if (el) {
        out.push(el)
      }
    }
    result.elements = out
  }

  // Resolve `cardList: string[]` → `cards: DisplayCard[]`.
  if (Array.isArray((c as { cardList?: unknown }).cardList)) {
    const ids = (c as { cardList: string[] }).cardList
    const out: DisplayCard[] = []
    for (const id of ids) {
      const child = props.cards[id]
      if (child) {
        out.push(child)
      }
    }
    result.cards = out
  }

  // Resolve `tabList: string[]` → `tabs: DisplayCard[]`.
  if (Array.isArray((c as { tabList?: unknown }).tabList)) {
    const ids = (c as { tabList: string[] }).tabList
    const out: DisplayCard[] = []
    for (const id of ids) {
      const child = props.cards[id]
      if (child) {
        out.push(child)
      }
    }
    result.tabs = out
  }

  // For GridViewCard: resolve element refs that appear inside
  // `gridLayout[breakpoint][n].i` against the page-level elements
  // map. Card refs at the same position already work because the
  // composition resolves them against `cards` (resolved above).
  // Elements need a separate `gridElements` array on the composition.
  if (
    props.card.cardType === "grid-view" &&
    typeof (c as { gridLayout?: unknown }).gridLayout === "object" &&
    (c as { gridLayout?: unknown }).gridLayout !== null
  ) {
    const layout = (c as { gridLayout: Record<string, unknown> }).gridLayout
    const elementIds = new Set<string>()
    for (const breakpoint of Object.values(layout)) {
      if (Array.isArray(breakpoint)) {
        for (const item of breakpoint) {
          if (item && typeof item === "object" && "i" in item) {
            const id = (item as { i: unknown }).i
            if (typeof id === "string" && props.elements[id]) {
              elementIds.add(id)
            }
          }
        }
      }
    }
    if (elementIds.size > 0) {
      result.gridElements = Array.from(elementIds)
        .map((id) => props.elements[id]!)
        .filter(Boolean)
    }
  }

  // Resolve `elementId: string` → `element: CardElement`.
  const elementId = (c as { elementId?: unknown }).elementId
  if (typeof elementId === "string" && elementId.length > 0) {
    const el = props.elements[elementId]
    if (el) {
      result.element = el
    }
  }

  return result
})

const wrapperClass = computed(() =>
  cn(
    "gxp-card",
    `gxp-card--${props.card.cardType}`,
    columnClass("col", props.card.columns),
    columnClass("col-sm", props.card.columnsSm),
    columnClass("col-md", props.card.columnsMd),
    columnClass("col-lg", props.card.columnsLg),
    columnClass("col-xl", props.card.columnsXl),
    normalizeClasses(props.card.wrapperClassList),
  ),
)

function columnClass(prefix: string, n?: number): string | undefined {
  if (typeof n !== "number") {
    return undefined
  }
  const pct = Math.max(0, Math.min(12, n)) / 12
  return prefix === "col"
    ? `basis-[${(pct * 100).toFixed(4)}%]`
    : `${prefix}:basis-[${(pct * 100).toFixed(4)}%]`
}

function normalizeClasses(value: string | string[] | undefined): string {
  if (!value) {
    return ""
  }
  return Array.isArray(value) ? value.join(" ") : value
}
</script>

<template>
  <div
    v-if="visible"
    :class="wrapperClass"
    :data-card-id="card.id"
    :data-card-type="card.cardType"
  >
    <component :is="resolved" v-bind="compositionProps" />
  </div>
</template>
