import type { ReactNode } from "react";
import { tv } from "tailwind-variants";
import { useDensity } from "../../core/DensityContext";
import type { AccentScopeProps } from "../containers/AccentScope";
import { InteractiveBox, type InteractiveBoxProps } from "../containers/Box";
import { Icon, type SVGIconElement } from "../primitives/Icon";
import { InteractiveIcon } from "../primitives/InteractiveIcon";
import { Text } from "../primitives/Text";
import { View } from "../primitives/View";
import type {
  SegmentedOrientation,
  SegmentedVariant,
} from "./SelectionContext";

// chip — the selected layer is raised and cross-fades on opacity so the
// background and shadow animate together with no border. Swapping a bordered
// variant instead would flash a border on the outgoing segment mid-transition.
// segment — the visible chip (`control-chip`) is shorter than the pressable
// (`control-md`), so the lowered SegmentedBar shows around it as an inset frame
// while the tap target keeps the full height. Its border is permanently transparent and only animates color on the
// row's hover/active, driven by the `group` on the pressable. The focus ring
// lives here too: the pressable fills the bar's content box, and the bar clips
// (`surface` is overflow-hidden), so an outline drawn on the pressable is cut
// away — the chip's slack holds the 2px offset + 2px ring instead (6px a side
// on a comfortable row, 4px on a compact one, 2px on a stacked item, which the
// bar's own `py-xs` completes).
// foreground — label and icon share one color set, and the icon's optional
// `activeIcon` layer cross-fades on the same `group`. Native resolves the icon
// tint through useColorToken, which reads the base `text-*` only, so the hover
// tint and the stacking above the chip are web-only.
const segmentedItemVariants = tv({
  slots: {
    pressable: "group flex-center min-h-control-md rounded-full",
    segment:
      "relative flex-row flex-center gap-xxs min-h-control-chip rounded-full border border-transparent transition-[border-color] duration-fast ease-in group-focus-visible:focus-ring",
    chip: "absolute inset-0 rounded-full transition-opacity duration-fast ease-in",
    foreground: "z-1 transition-[color] duration-fast ease-in",
    label: "select-none font-body-bold text-base text-center",
    // indicator — a badge over the glyph's top-right, not beside it: the icon
    // chip is a circle and the lunes its glyph leaves in the corners are ~4px
    // wide, too narrow to hold anything. Its halo is the chip's own fill, so the
    // badge punches out of the glyph it overlaps.
    indicator:
      "absolute z-1 flex-center size-[14px] rounded-full transition-colors duration-fast ease-in",
  },
  variants: {
    // Comfortable, the insets keep the badge's painted circle inside the chip
    // in both sizes the icon variant takes (36px in a row, 40px stacked or
    // stretched): 1.1px and 0.3px of clearance. Compact, a 28px chip inset
    // that way would put the badge over most of the glyph, so it hangs off
    // the chip's corner into the 4px of bar around it, as far as the bar's clip.
    density: {
      comfortable: { indicator: "right-[4px] top-[4px]" },
      compact: { indicator: "-right-[4px] -top-[4px]" },
    },
    selected: {
      true: {
        chip: "opacity-100",
        foreground: "text-on-emphasis",
        indicator: "bg-emphasis",
      },
      false: {
        chip: "opacity-0",
        foreground: "text-muted group-hover:text-sharp",
        // The chip is transparent here, so the halo takes what shows through it:
        // the lowered SegmentedBar behind.
        indicator: "bg-lowered",
      },
    },
    disabled: {
      true: {
        chip: "bg-interactive-filled-disabled",
        foreground: "text-disabled-muted group-hover:text-disabled-muted",
        indicator: "bg-interactive-filled-disabled",
      },
      false: { chip: "bg-emphasis shadow-s" },
    },
    compact: {
      true: { segment: "px-xs" },
      false: { segment: "px-sm md:px-m" },
    },
    orientation: {
      horizontal: {},
      // A stacked item spans the bar's width, so the chip stretches with it
      // instead of shrinking to its own label, and stands taller: a rail reads
      // as rows, not as chips floating in a column.
      vertical: {
        pressable: "items-stretch",
        segment: "self-stretch min-h-control-chip-stacked",
      },
    },
    // A stretched bar hands its extra width to its items; a stacked one already
    // spans that width, so only a row shares it.
    stretch: { true: {}, false: {} },
    // Declared last so its radius and padding land after the ones `compact` and
    // `orientation` set, and win the merge.
    variant: {
      segmented: {},
      // The chip is a square with no label, so the pressable carries the tap
      // target's width the way it already carries its height. 36px in a row
      // leaves 4px on every side: exactly the focus ring (2px offset + 2px
      // width).
      icon: {
        pressable: "min-w-control-md",
        segment:
          "rounded-md self-center w-control-chip-icon min-h-control-chip-icon px-0",
        chip: "rounded-md",
      },
    },
  },
  defaultVariants: {
    compact: false,
    orientation: "horizontal",
    stretch: false,
    variant: "segmented",
  },
  compoundVariants: [
    {
      stretch: true,
      orientation: "horizontal",
      // `grow`, not `flex-1`: a zero basis would make every item an equal share
      // of the bar and truncate the longer labels the moment the bar is only as
      // wide as its content. Growing from the natural width instead leaves the
      // labels intact and only shares the space a stretched bar has to spare —
      // with the chip stretching too, so the row reads as adjacent segments
      // instead of labels floating in their own space.
      class: {
        pressable: "grow items-stretch",
        segment: "self-stretch",
      },
    },
    {
      // A square chip stays square whatever width the item is given, so a
      // stretched or stacked icon bar centers it instead of stretching it.
      variant: "icon",
      stretch: true,
      class: { segment: "self-center w-control-chip-stacked" },
    },
    {
      variant: "icon",
      orientation: "vertical",
      class: {
        segment:
          "self-center w-control-chip-stacked min-h-control-chip-stacked",
      },
    },
    {
      selected: false,
      disabled: false,
      class: {
        segment:
          "group-hover:border-interactive-outlined-hover group-active:border-interactive-outlined-active",
      },
    },
    {
      selected: true,
      disabled: true,
      class: {
        foreground: "text-disabled-sharp group-hover:text-disabled-sharp",
      },
    },
    {
      // `disabled` is declared after `selected`, so it would hand the halo the
      // disabled chip's fill on an item that has no chip showing at all.
      selected: false,
      disabled: true,
      class: { indicator: "bg-lowered" },
    },
  ],
});

export interface SegmentedItemProps extends Omit<
  InteractiveBoxProps,
  "aria-label" | "children" | "className" | "withFocusVisibleOutline"
> {
  label: string;
  icon?: SVGIconElement;
  /**
   * Replaces `icon` while the item is hovered, focused or pressed, and for as
   * long as it is selected.
   */
  activeIcon?: SVGIconElement;
  /** Accent tinting `activeIcon`, so the glyph changes color as well as weight. */
  activeAccent?: AccentScopeProps["accent"];
  /**
   * Badge glyph pinned over the chip's top-right, in the foreground's own
   * color: a secondary state the item's `label` spells out (following the
   * system, unread changes). It adds to `icon`, it never replaces it, and it
   * renders in `variant="icon"` only — a text chip has no room for it.
   */
  indicator?: SVGIconElement;
  selected: boolean;
  /** Tighter horizontal padding, set by a compact group. */
  compact?: boolean;
  /** Set by a vertical group: the item stretches to the bar's width. */
  orientation?: SegmentedOrientation;
  /** Set by a stretched group: the item grows into the bar's spare width. */
  stretch?: boolean;
  /**
   * Set by the group. `icon` hides the label — it stays the accessible name —
   * so an item in an icon group must carry an `icon` to render anything.
   */
  variant?: SegmentedVariant;
  /**
   * react-native's types have no `aria-current` / `aria-controls` / `href`, but
   * react-native-web forwards all three (an `href` makes it render an `<a>`) and
   * native ignores unknown props — declared here for `NavBarItem` and `Tab`.
   */
  "aria-current"?: "page";
  "aria-controls"?: string;
  href?: string;
}

export function SegmentedItem({
  label,
  icon,
  activeIcon,
  activeAccent,
  indicator,
  selected,
  disabled,
  compact,
  orientation,
  stretch,
  variant,
  ...props
}: SegmentedItemProps): ReactNode {
  const density = useDensity();
  const styles = segmentedItemVariants({
    density,
    selected,
    disabled: disabled === true,
    compact,
    orientation,
    stretch,
    variant,
  });

  return (
    <InteractiveBox
      aria-label={label}
      // The chip below draws the ring (`group-focus-visible:focus-ring`), so the
      // pressable carries none — a zero-width one, because react-native-css
      // drops `outline-style: none` and the browser's own ring would stay.
      withFocusVisibleOutline={false}
      disabled={disabled}
      className={styles.pressable()}
      {...props}
    >
      <View className={styles.segment()}>
        <View className={styles.chip()} />
        {icon ? (
          <InteractiveIcon
            icon={icon}
            activeIcon={activeIcon}
            activeAccent={activeAccent}
            active={selected}
            disabled={disabled === true}
            size={20}
            className={styles.foreground()}
          />
        ) : null}
        {variant === "icon" ? null : (
          <Text
            numberOfLines={1}
            className={styles.label({ class: styles.foreground() })}
          >
            {label}
          </Text>
        )}
        {variant === "icon" && indicator ? (
          <View className={styles.indicator()}>
            <Icon icon={indicator} size={10} className={styles.foreground()} />
          </View>
        ) : null}
      </View>
    </InteractiveBox>
  );
}
