import { forwardRef, useMemo, type ReactNode } from 'react';
import { Text, View, type TextProps, type ViewProps } from 'react-native';
import {
  Slot,
  TileGroup as TileGroupPrimitive,
  TileRoot as TileRootPrimitive,
  dataAttributes,
  mergeDataAttributes,
  useTileContext,
  type ITileGroupMultipleProps,
  type ITileGroupSingleProps,
  type ITileIndicatorProps,
  type ITileLeadingSlotProps,
  type ITileProps,
  type ITileTrailingSlotProps,
} from '@cdx-ui/primitives';
import { Check } from '@cdx-ui/icons';
import { cn, ParentContext, useParentContext, useStyleContext } from '@cdx-ui/utils';
import { checkboxIconVariants, checkboxIndicatorVariants } from '../Checkbox/styles';
import { Icon } from '../Icon';
import { radioIndicatorVariants, radioInnerDotVariants } from '../Radio/styles';
import {
  tileContentVariants,
  tileDescriptionVariants,
  tileGroupVariants,
  tileIndicatorVariants,
  tileLeadingSlotVariants,
  tileRootVariants,
  tileTitleVariants,
  tileTrailingSlotVariants,
  type TileGroupVariantProps,
  type TileVariantProps,
} from './styles';

const TILE_SCOPE = 'TILE';
const TILE_GROUP_SCOPE = 'TILE_GROUP';

interface TileGroupStyleContext {
  shape?: TileVariantProps['shape'];
}

const useTileGroupStyleContext = (): TileGroupStyleContext => {
  // `useStyleContext` types the result as a `Record`, but at runtime the value is
  // `undefined` when no provider is in scope (e.g. standalone Tile). Cast through
  // `unknown` to acknowledge that.
  const ctx = useStyleContext(TILE_GROUP_SCOPE) as unknown as TileGroupStyleContext | undefined;
  return ctx ?? {};
};

// =============================================================================
// TILE GROUP
// =============================================================================

/**
 * Discriminated union mirroring `ITileGroupProps`, with `className` and layout variants added at
 * the styled layer. Distributing the intersection across each branch keeps `interface … extends`
 * ergonomics and avoids tripping `@typescript-eslint` on `(union) & { ... }`.
 *
 * `direction` and `spacing` constrain the group layout to the three supported patterns:
 * - `direction="column"` + `spacing="default"` — vertical stack with gaps (default).
 * - `direction="column"` + `spacing="none"` — tight vertical stack (rows touch).
 * - `direction="row"` + `spacing="default"` — side-by-side horizontal layout.
 *
 * `shape` (when set) is propagated to child `Tile`s via `ParentContext`, matching the
 * Avatar size-propagation pattern. A per-tile `shape` prop overrides the inherited value.
 */
export interface TileGroupSingleProps extends ITileGroupSingleProps, TileGroupVariantProps {
  className?: string;
  /** Visual shape inherited by child `Tile`s. Per-tile `shape` overrides this. */
  shape?: TileVariantProps['shape'];
}
export interface TileGroupMultipleProps extends ITileGroupMultipleProps, TileGroupVariantProps {
  className?: string;
  /** Visual shape inherited by child `Tile`s. Per-tile `shape` overrides this. */
  shape?: TileVariantProps['shape'];
}
export type TileGroupProps = TileGroupSingleProps | TileGroupMultipleProps;

const TileGroup = forwardRef<View, TileGroupProps>(
  ({ className, style, children, direction, spacing, shape, ...props }, ref) => {
    const parent = useParentContext();
    const ctx = useMemo(() => ({ ...parent, [TILE_GROUP_SCOPE]: { shape } }), [parent, shape]);

    return (
      <ParentContext.Provider value={ctx}>
        <TileGroupPrimitive
          ref={ref}
          className={cn(tileGroupVariants({ direction, spacing }), className)}
          style={style}
          {...(props as TileGroupProps)}
          {...mergeDataAttributes(props, dataAttributes({ slot: 'tile-group' }))}
        >
          {children}
        </TileGroupPrimitive>
      </ParentContext.Provider>
    );
  },
);

TileGroup.displayName = 'Tile.Group';

// =============================================================================
// TILE ROOT
// =============================================================================

export interface TileProps extends ITileProps, TileVariantProps {
  className?: string;
}

const TileRoot = forwardRef<View, TileProps>(
  ({ shape: shapeProp, showSeparator, className, style, ...props }, ref) => {
    const { shape: inheritedShape } = useTileGroupStyleContext();
    const shape = shapeProp ?? inheritedShape ?? 'card';
    const computedClassName = cn(tileRootVariants({ shape, showSeparator }), className);

    const parent = useParentContext();
    const ctx = useMemo(() => ({ ...parent, [TILE_SCOPE]: { shape } }), [parent, shape]);

    return (
      <ParentContext.Provider value={ctx}>
        <TileRootPrimitive
          ref={ref}
          className={computedClassName}
          style={style}
          {...props}
          {...mergeDataAttributes(props, dataAttributes({ slot: 'tile' }))}
        />
      </ParentContext.Provider>
    );
  },
);

TileRoot.displayName = 'Tile';

// =============================================================================
// SLOTS
// =============================================================================

const leadingSlotStyle = { flexShrink: 0 as const };

export interface TileLeadingSlotProps extends ITileLeadingSlotProps {
  asChild?: boolean;
  className?: string;
}

const TileLeadingSlot = forwardRef<View, TileLeadingSlotProps>(
  ({ asChild, className, style, 'aria-hidden': ariaHidden, ...props }, ref) => {
    const accessibilityElementsHidden = ariaHidden !== false;
    const Comp = asChild ? Slot : View;
    return (
      <Comp
        ref={ref}
        accessibilityElementsHidden={accessibilityElementsHidden}
        aria-hidden={ariaHidden ?? true}
        className={cn(tileLeadingSlotVariants(), className)}
        style={[leadingSlotStyle, style]}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'tile-leading' }))}
      />
    );
  },
);

TileLeadingSlot.displayName = 'Tile.LeadingSlot';

const contentStyle = {
  flex: 1,
  flexDirection: 'column' as const,
  minWidth: 0,
};

export interface TileContentProps extends ViewProps {
  asChild?: boolean;
  className?: string;
}

const TileContent = forwardRef<View, TileContentProps>(
  ({ asChild, className, style, ...props }, ref) => {
    const Comp = asChild ? Slot : View;
    return (
      <Comp
        ref={ref}
        className={cn(tileContentVariants(), className)}
        style={[contentStyle, style]}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'tile-content' }))}
      />
    );
  },
);

TileContent.displayName = 'Tile.Content';

const noUnderlineStyle = { textDecorationLine: 'none' as const };

export interface TileTitleProps extends TextProps {
  asChild?: boolean;
  className?: string;
}

const TileTitle = forwardRef<Text, TileTitleProps>(
  ({ asChild, className, style, numberOfLines = 1, ...props }, ref) => {
    const Comp = asChild ? Slot : Text;
    return (
      <Comp
        ref={ref}
        className={cn(tileTitleVariants(), className)}
        style={[noUnderlineStyle, style]}
        numberOfLines={numberOfLines}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'tile-title' }))}
      />
    );
  },
);

TileTitle.displayName = 'Tile.Title';

export interface TileDescriptionProps extends TextProps {
  asChild?: boolean;
  className?: string;
}

const TileDescription = forwardRef<Text, TileDescriptionProps>(
  ({ asChild, className, style, ...props }, ref) => {
    const Comp = asChild ? Slot : Text;
    return (
      <Comp
        ref={ref}
        className={cn(tileDescriptionVariants(), className)}
        style={[noUnderlineStyle, style]}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'tile-description' }))}
      />
    );
  },
);

TileDescription.displayName = 'Tile.Description';

/**
 * Decorative selection affordance. Renders pure CVA-styled Views — no interactive hooks,
 * no nested Pressable, zero runtime overhead. The Tile root owns the press target and
 * ARIA role; this subtree is `aria-hidden`.
 *
 * The visual is inferred from the tile context: `single` (radio semantics) → radio ring,
 * `multiple` (checkbox semantics) → checkbox square. An explicit `indicatorType` prop
 * overrides — primarily for standalone tiles that want a radio-style visual.
 */
function TileIndicatorVisual({ indicatorType }: { indicatorType?: 'radio' | 'checkbox' }) {
  const { isSelected, isDisabled, selectionType } = useTileContext();
  const effectiveType: 'radio' | 'checkbox' =
    indicatorType ?? (selectionType === 'single' ? 'radio' : 'checkbox');
  const stateAttrs = dataAttributes({ checked: isSelected, disabled: isDisabled });

  if (effectiveType === 'radio') {
    return (
      <View className={radioIndicatorVariants()} {...stateAttrs}>
        <View
          className={radioInnerDotVariants()}
          {...dataAttributes({ checked: isSelected, invalid: false })}
        />
      </View>
    );
  }

  return (
    <View className={cn(checkboxIndicatorVariants())} {...stateAttrs}>
      {isSelected && <Icon as={Check} className={checkboxIconVariants()} />}
    </View>
  );
}

const indicatorStyle = { flexShrink: 0 as const, pointerEvents: 'none' as const };

export interface TileIndicatorProps extends ITileIndicatorProps {
  asChild?: boolean;
  className?: string;
  children?: ReactNode;
}

const TileIndicator = forwardRef<View, TileIndicatorProps>(
  ({ asChild, className, children, style, indicatorType, ...props }, ref) => {
    const Comp = asChild ? Slot : View;
    return (
      <Comp
        ref={ref}
        accessibilityElementsHidden
        aria-hidden
        className={cn(tileIndicatorVariants(), className)}
        style={[indicatorStyle, style]}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'tile-indicator' }))}
      >
        {children ?? <TileIndicatorVisual indicatorType={indicatorType} />}
      </Comp>
    );
  },
);

TileIndicator.displayName = 'Tile.Indicator';

const trailingSlotStyle = { flexShrink: 0 as const };

export interface TileTrailingSlotProps extends ITileTrailingSlotProps {
  asChild?: boolean;
  className?: string;
}

const TileTrailingSlot = forwardRef<View, TileTrailingSlotProps>(
  ({ asChild, className, style, ...props }, ref) => {
    const Comp = asChild ? Slot : View;
    return (
      <Comp
        ref={ref}
        className={cn(tileTrailingSlotVariants(), className)}
        style={[trailingSlotStyle, style]}
        {...props}
        {...mergeDataAttributes(props, dataAttributes({ slot: 'tile-trailing' }))}
      />
    );
  },
);

TileTrailingSlot.displayName = 'Tile.TrailingSlot';

type TileCompound = typeof TileRoot & {
  Group: typeof TileGroup;
  LeadingSlot: typeof TileLeadingSlot;
  Content: typeof TileContent;
  Title: typeof TileTitle;
  Description: typeof TileDescription;
  Indicator: typeof TileIndicator;
  TrailingSlot: typeof TileTrailingSlot;
};

export const Tile = Object.assign(TileRoot, {
  Group: TileGroup,
  LeadingSlot: TileLeadingSlot,
  Content: TileContent,
  Title: TileTitle,
  Description: TileDescription,
  Indicator: TileIndicator,
  TrailingSlot: TileTrailingSlot,
}) as TileCompound;

export { useTileContext };
