import React, { createContext, useContext, useMemo } from 'react'
import { resolveInputComponentChain } from '@ossy/schema'
import { resolveInputSlotFromMap, resolveSlotFromMap } from './slotLookup.js'

const ComponentSlotsContext = createContext(null)

/**
 * Nested providers shallow-merge: inner `slots` override keys from the parent map.
 *
 * @param {{ slots?: Record<string, import('react').ComponentType<any> | null | undefined>, children: import('react').ReactNode }} props
 */
export function ComponentSlotsProvider ({ slots, children }) {
  const parent = useContext(ComponentSlotsContext)
  const value = useMemo(() => {
    const local = slots ?? {}
    if (!parent) {
      return { slots: local }
    }
    return { slots: { ...parent.slots, ...local } }
  }, [parent, slots])
  return (
    <ComponentSlotsContext.Provider value={value}>
      {children}
    </ComponentSlotsContext.Provider>
  )
}

/**
 * Returns the component for `name` from the nearest provider map, or `undefined` if
 * there is no provider, the key is missing, or the value is explicitly `undefined`.
 * A value of `null` is returned as-is (slot intentionally empty).
 *
 * Accepts canonical ADR 0006 ids; bare app region names normalize to `app:*`.
 *
 * @param {string} name
 * @returns {import('react').ComponentType<any> | null | undefined}
 */
export function useSlot (name) {
  const ctx = useContext(ComponentSlotsContext)
  if (!ctx) {
    return undefined
  }
  return resolveSlotFromMap(ctx.slots, name)
}

/**
 * Resolve a form field control using ADR 0006 input id chain (feature → design-system).
 *
 * @param {string | null | undefined} schemaId
 * @param {string} fieldType
 * @returns {import('react').ComponentType<any> | null | undefined}
 */
export function useInputSlot (schemaId, fieldType) {
  const ctx = useContext(ComponentSlotsContext)
  const inputIds = useMemo(
    () => resolveInputComponentChain(schemaId, fieldType),
    [schemaId, fieldType],
  )
  if (!ctx) return undefined
  return resolveInputSlotFromMap(ctx.slots, inputIds)
}

/**
 * @param {string | undefined} region
 * @param {import('react').ReactNode} node
 * @param {Record<string, any>} forwardedProps
 */
function renderRegionFallback (region, node, forwardedProps) {
  if (node == null || node === false) return null

  const regionProps = {
    ...forwardedProps,
    ...(region ? { 'data-region': region } : {}),
  }

  if (React.isValidElement(node)) {
    return React.cloneElement(node, regionProps)
  }

  if (region || Object.keys(forwardedProps).length > 0) {
    return <div {...regionProps}>{node}</div>
  }

  return node
}

/**
 * Renders the component registered under `view` in the nearest
 * `ComponentSlotsProvider`. Falls back to `fallback` (default `null`) when the
 * view is not registered or is explicitly `null`.
 *
 * When `region` is set, `data-region` is forwarded to the resolved component
 * (or fallback) so layout CSS can place it in a grid cell. All other props are
 * spread onto the resolved component as well.
 *
 * @param {{ view: string, region?: string, fallback?: import('react').ReactNode } & Record<string, any>} props
 */
export function Slot ({ view, region, fallback = null, children, ...forwardedProps }) {
  const Component = useSlot(view)
  const mergedProps = {
    ...forwardedProps,
    ...(region ? { 'data-region': region } : {}),
  }

  // undefined = not registered; null = intentionally empty
  if (Component === null) return fallback

  if (Component === undefined) {
    return renderRegionFallback(region, fallback ?? children, forwardedProps)
  }

  return React.createElement(Component, mergedProps)
}
