import type { CSSProperties } from 'react';
import { CheckIcon, SparklesIcon } from '@/components/ui/icons';
import { cn } from '@/lib/cn';
import { cellName, cellsInLines, lineCoordinates } from './board-model';
import { CategoryIcon } from './category-icons';
import s from './game.module.css';

export interface BoardTile {
  position: number;
  category: string;
  activated: boolean;
  source?: 'QUESTION' | 'WILDCARD' | null;
}

export interface BoardProps {
  /** Nine tiles, `tiles[i].position === i` (engine BoardState.tiles). */
  tiles: readonly BoardTile[];
  /** Category key → label (PrivateState.board.categoryLabels). Keys are shown when missing. */
  categoryLabels?: Readonly<Record<string, string>>;
  /** Completed line ids (0-7). */
  lines?: readonly number[];
  /** Lines completed just now: drawn in with an animation. */
  animateLines?: readonly number[];
  /** Tile to spotlight, e.g. the category coming up next. */
  highlight?: number | null;
  /** Visible tag on the spotlighted tile. */
  highlightTag?: string;
  /** Tile activated just now: pops. */
  popPosition?: number | null;
  /** Staggered entrance (round intro). */
  reveal?: boolean;
  /**
   * Selection mode: positions that may be picked. When set, tiles render as toggle buttons
   * (wildcard claim, Category Swap); tiles outside the list are disabled.
   */
  selectable?: readonly number[];
  selected?: readonly number[];
  onSelect?: (position: number) => void;
  /** Badge on selected tiles: pick order (Category Swap) or a check (single pick, e.g. wildcard). */
  selectionMark?: 'number' | 'check';
  /** Accessible name for the whole board. */
  label: string;
  /** Hide the text labels (icons only) for very small renderings. */
  showLabels?: boolean;
  className?: string;
}

const LIT = 'bg-[linear-gradient(140deg,var(--color-violet-strong),var(--color-cyan-deep))] text-white shadow-[0_0_28px_-10px_rgb(124_92_255/0.95)]';
const UNLIT = 'border-line bg-night/80 text-muted';

/**
 * The 3×3 Rush board. Purely presentational and prop-driven so the phone, the TV and the host
 * dashboard can all render it; sizes scale with the container (container-query units), so it
 * reads on a 360 px phone and on a 1080p TV alike. Lit state is conveyed by fill AND a check /
 * sparkle badge, never by colour alone.
 */
export function Board({
  tiles,
  categoryLabels,
  lines = [],
  animateLines = [],
  highlight = null,
  highlightTag = 'Next',
  popPosition = null,
  reveal = false,
  selectable,
  selected = [],
  onSelect,
  selectionMark = 'number',
  label,
  showLabels = true,
  className,
}: BoardProps) {
  const inLine = cellsInLines(lines);
  const selecting = selectable !== undefined;
  const drawn = [...new Set([...lines, ...animateLines])];

  return (
    <div className={cn('@container relative isolate w-full', className)} role="group" aria-label={label}>
      <ul className="grid grid-cols-3 gap-[2.4cqw]">
        {tiles.map((tile, i) => {
          const name = categoryLabels?.[tile.category] ?? tile.category;
          const isNext = highlight === tile.position && !tile.activated;
          const isSelected = selected.includes(tile.position);
          const canSelect = selecting && selectable.includes(tile.position);
          const pop = popPosition === tile.position;
          const status = tile.activated ? (tile.source === 'WILDCARD' ? 'lit with the wildcard' : 'lit') : 'not lit';
          const order = isSelected ? selected.indexOf(tile.position) + 1 : 0;
          const style: CSSProperties | undefined = reveal ? { animationDelay: `${i * 70}ms` } : undefined;

          const face = (
            <>
              <CategoryIcon category={tile.category} className={cn('relative z-[2] size-[8.5cqw] shrink-0', !tile.activated && 'opacity-80')} />
              {showLabels && (
                <span className="relative z-[2] line-clamp-2 px-[1cqw] text-center text-[clamp(0.625rem,3.5cqw,2.25rem)] leading-[1.1] font-bold break-words hyphens-auto">
                  {name}
                </span>
              )}
              {tile.activated && (
                <span
                  aria-hidden="true"
                  className={cn(
                    'absolute top-[1.6cqw] right-[1.6cqw] z-[2] grid size-[5.6cqw] place-items-center rounded-full [&_svg]:size-[3.8cqw]',
                    tile.source === 'WILDCARD' ? 'bg-amber text-on-bright' : 'bg-white/90 text-violet-deep',
                  )}
                >
                  {tile.source === 'WILDCARD' ? <SparklesIcon strokeWidth={2.6} /> : <CheckIcon strokeWidth={3} />}
                </span>
              )}
              {isNext && (
                <span className="absolute bottom-[1.4cqw] left-1/2 z-[2] -translate-x-1/2 rounded-full bg-cyan px-[2cqw] py-[0.3cqw] text-[clamp(0.5625rem,2.6cqw,1.5rem)] font-extrabold tracking-[0.12em] text-on-bright uppercase">
                  {highlightTag}
                </span>
              )}
              {isSelected && (
                <span
                  aria-hidden="true"
                  className="absolute top-[1.6cqw] left-[1.6cqw] z-[2] grid size-[6cqw] place-items-center rounded-full bg-amber font-display text-[clamp(0.625rem,3.2cqw,1.75rem)] font-extrabold text-on-bright"
                >
                  {selectionMark === 'check' ? <CheckIcon className="size-[3.8cqw]" strokeWidth={3} /> : order}
                </span>
              )}
            </>
          );

          // Exactly one skin per tile (cn() does not resolve conflicting utilities, so never stack two).
          const skin = isSelected
            ? 'border-amber bg-amber/20 text-fg shadow-glow-amber'
            : isNext
              ? cn('border-cyan bg-cyan/10 text-fg', s.nextGlow)
              : tile.activated
                ? cn(LIT, inLine.has(tile.position) ? 'border-amber/80' : 'border-white/35')
                : canSelect
                  ? 'border-dashed border-line-strong bg-night/80 text-muted hover:border-cyan'
                  : UNLIT;
          const tileClass = cn(
            'relative flex aspect-square w-full flex-col items-center justify-center gap-[1.6cqw] rounded-[clamp(0.75rem,4cqw,1.75rem)] border-2 p-[1.5cqw]',
            skin,
            pop && 'motion-safe:animate-tile-pop',
            reveal && 'motion-safe:animate-rise',
          );

          if (!selecting) {
            return (
              <li key={tile.position} className={tileClass} style={style}>
                {face}
                <span className="sr-only">
                  {`${showLabels ? '' : name}, ${cellName(tile.position)}, ${status}${isNext ? `, ${highlightTag}` : ''}`}
                </span>
              </li>
            );
          }
          return (
            <li key={tile.position} style={style} className={cn(reveal && 'motion-safe:animate-rise')}>
              <button
                type="button"
                disabled={!canSelect}
                aria-pressed={canSelect ? isSelected : undefined}
                aria-label={`${name}, ${cellName(tile.position)}, ${status}${isSelected ? `, selected ${order}` : ''}`}
                onClick={() => onSelect?.(tile.position)}
                className={cn(
                  tileClass,
                  'cursor-pointer transition-[transform,border-color,background-color] duration-150 motion-safe:active:scale-95',
                  !canSelect && 'cursor-not-allowed opacity-55',
                )}
              >
                {face}
              </button>
            </li>
          );
        })}
      </ul>
      {drawn.length > 0 && (
        <svg
          viewBox="0 0 100 100"
          preserveAspectRatio="none"
          aria-hidden="true"
          className="pointer-events-none absolute inset-0 z-[1] size-full overflow-visible"
        >
          {drawn.map((line) => {
            const c = lineCoordinates(line);
            if (!c) return null;
            return (
              <line
                key={line}
                x1={c[0]}
                y1={c[1]}
                x2={c[2]}
                y2={c[3]}
                pathLength={100}
                className={animateLines.includes(line) ? s.lineDraw : undefined}
                stroke="var(--color-amber)"
                strokeWidth={4}
                strokeLinecap="round"
                vectorEffect="non-scaling-stroke"
                style={{ filter: 'drop-shadow(0 0 5px rgb(255 176 32 / 0.9))' }}
              />
            );
          })}
        </svg>
      )}
    </div>
  );
}
