/**
 * List components with keyboard navigation.
 * 
 * Provides:
 * - ScrollableList: Full-featured list with grouping, filtering, and custom rendering
 * - List: Basic selectable list (legacy, kept for backward compatibility)
 * - SimpleList: Non-selectable list for display only
 */

import React, { useState, useMemo, useCallback } from 'react';
import { Box, Text, useInput } from 'ink';
import TextInput from './TextInput.js';
import { colors } from '../theme.js';

// =============================================================================
// Types
// =============================================================================

/**
 * Base list item data interface.
 * Used by ScrollableList for item data.
 */
export interface ListItemData<T = unknown> {
  /** Unique key for the item */
  key: string;
  /** Display label */
  label: string;
  /** Optional secondary text/description */
  description?: string;
  /** Optional value associated with item */
  value?: T;
  /** Whether item is disabled (can't be activated) */
  disabled?: boolean;
  /** Whether item should be hidden (not rendered, skipped in navigation) */
  hidden?: boolean;
  /** Custom color name for the item (semantic: 'info', 'warning', 'success', 'error', 'muted') */
  color?: string;
  /** Group identifier for grouping items */
  group?: string;
}

/**
 * Group definition for organizing list items.
 */
export interface ListGroup {
  /** Unique group identifier */
  id: string;
  /** Optional header text to display above group */
  label?: string;
  /** Whether to show a separator after this group */
  separator?: boolean;
}

/**
 * Props for ScrollableList component.
 */
export interface ScrollableListProps<T> {
  /** Array of list items */
  items: ListItemData<T>[];
  /** Currently selected index */
  selectedIndex: number;
  /** Handler called when selection changes */
  onSelect: (index: number) => void;
  /** Handler called when item is activated (Enter key) */
  onActivate?: (item: ListItemData<T>, index: number) => void;
  /** Whether the list is focused for keyboard input */
  focus?: boolean;
  /** Maximum number of visible items (enables scrolling). Default: 10 */
  maxVisible?: number;
  /** Whether to show a border around the list */
  border?: boolean;
  /** Optional label/title for the list */
  label?: string;
  /** Message to show when list is empty */
  emptyMessage?: string;
  /** Group definitions for organizing items */
  groups?: ListGroup[];
  /** Whether to enable filtering/search */
  filterable?: boolean;
  /** Placeholder text for filter input */
  filterPlaceholder?: string;
  /** Handler called when filter text changes */
  onFilterChange?: (filter: string) => void;
  /** Custom render function for items */
  renderItem?: (item: ListItemData<T>, isSelected: boolean, isFocused: boolean) => React.ReactNode;
  /** Whether to wrap around when navigating past ends */
  wrapNavigation?: boolean;
  /** Whether to show the scroll position indicator (e.g., "1-5 of 10"). Default: true */
  showScrollIndicator?: boolean;
}

// =============================================================================
// Helper Functions
// =============================================================================

/**
 * Map semantic color names to theme colors.
 */
function getColorFromName(colorName: string | undefined): string {
  switch (colorName) {
    case 'info':
      return colors.info as string;
    case 'warning':
      return colors.warning as string;
    case 'success':
      return colors.success as string;
    case 'error':
      return colors.error as string;
    case 'muted':
      return colors.textMuted as string;
    case 'accent':
      return colors.accent as string;
    case 'primary':
      return colors.primary as string;
    default:
      return colors.text as string;
  }
}

/**
 * Find the next valid (non-hidden) index in a direction.
 * 
 * @param items - Array of items
 * @param currentIndex - Current index
 * @param direction - Direction to search (1 for down, -1 for up)
 * @param wrap - Whether to wrap around at ends
 * @returns Next valid index, or current if none found
 */
function findNextValidIndex<T>(
  items: ListItemData<T>[],
  currentIndex: number,
  direction: 1 | -1,
  wrap: boolean = false
): number {
  if (items.length === 0) return 0;
  
  // Count visible items
  const visibleIndices = items
    .map((item, idx) => ({ item, idx }))
    .filter(({ item }) => !item.hidden)
    .map(({ idx }) => idx);
  
  if (visibleIndices.length === 0) return currentIndex;
  
  // Find current position in visible indices
  const currentVisiblePos = visibleIndices.indexOf(currentIndex);
  
  if (currentVisiblePos === -1) {
    // Current index is hidden, find nearest visible
    return visibleIndices[0] ?? 0;
  }
  
  // Calculate next position
  let nextVisiblePos = currentVisiblePos + direction;
  
  if (wrap) {
    if (nextVisiblePos < 0) nextVisiblePos = visibleIndices.length - 1;
    if (nextVisiblePos >= visibleIndices.length) nextVisiblePos = 0;
  } else {
    nextVisiblePos = Math.max(0, Math.min(visibleIndices.length - 1, nextVisiblePos));
  }
  
  return visibleIndices[nextVisiblePos] ?? 0;
}

/**
 * Calculate scroll window for visible items.
 */
function calculateScrollWindow(
  selectedIndex: number,
  totalItems: number,
  maxVisible: number
): { startIndex: number; endIndex: number } {
  const halfWindow = Math.floor(maxVisible / 2);
  let startIndex = Math.max(0, selectedIndex - halfWindow);
  let endIndex = Math.min(totalItems, startIndex + maxVisible);
  
  // Adjust start if we're near the end
  if (endIndex - startIndex < maxVisible) {
    startIndex = Math.max(0, endIndex - maxVisible);
  }
  
  return { startIndex, endIndex };
}

// =============================================================================
// ScrollableList Component
// =============================================================================

/**
 * Full-featured scrollable list with grouping, filtering, and custom rendering.
 */
export function ScrollableList<T>({
  items,
  selectedIndex,
  onSelect,
  onActivate,
  focus = true,
  maxVisible = 10,
  border = false,
  label,
  emptyMessage = 'No items',
  groups,
  filterable = false,
  filterPlaceholder = 'Filter...',
  onFilterChange,
  renderItem,
  wrapNavigation = false,
  showScrollIndicator = true,
}: ScrollableListProps<T>): React.ReactElement {
  // Filter state
  const [filterText, setFilterText] = useState('');
  const [isFiltering, setIsFiltering] = useState(false);

  // Filter items based on filter text
  const filteredItems = useMemo(() => {
    if (!filterText.trim()) return items;
    const lowerFilter = filterText.toLowerCase();
    return items.map(item => ({
      ...item,
      hidden: item.hidden || !item.label.toLowerCase().includes(lowerFilter),
    }));
  }, [items, filterText]);

  // Get visible (non-hidden) items count
  const visibleCount = useMemo(() => 
    filteredItems.filter(item => !item.hidden).length,
    [filteredItems]
  );

  // Handle keyboard navigation
  useInput((input, key) => {
    if (!focus) return;

    // Toggle filter mode with '/'
    if (filterable && input === '/' && !isFiltering) {
      setIsFiltering(true);
      return;
    }

    // Exit filter mode with Escape
    if (isFiltering && key.escape) {
      setIsFiltering(false);
      setFilterText('');
      onFilterChange?.('');
      return;
    }

    // Don't process navigation when filtering
    if (isFiltering) return;

    // Navigation
    if (key.upArrow || input === 'k') {
      const newIndex = findNextValidIndex(filteredItems, selectedIndex, -1, wrapNavigation);
      onSelect(newIndex);
    } else if (key.downArrow || input === 'j') {
      const newIndex = findNextValidIndex(filteredItems, selectedIndex, 1, wrapNavigation);
      onSelect(newIndex);
    } else if (key.return && onActivate) {
      const item = filteredItems[selectedIndex];
      if (item && !item.disabled && !item.hidden) {
        onActivate(item, selectedIndex);
      }
    }
  }, { isActive: focus && !isFiltering });

  // Handle filter text change
  const handleFilterChange = useCallback((value: string) => {
    setFilterText(value);
    onFilterChange?.(value);
  }, [onFilterChange]);

  // Handle filter submit (Enter in filter mode)
  const handleFilterSubmit = useCallback(() => {
    setIsFiltering(false);
  }, []);

  // Render a single item
  const renderListItem = (item: ListItemData<T>, index: number) => {
    if (item.hidden) return null;

    const isSelected = index === selectedIndex;
    const isFocused = focus && isSelected;

    // Use custom render if provided
    if (renderItem) {
      return (
        <Box key={item.key}>
          {renderItem(item, isSelected, isFocused)}
        </Box>
      );
    }

    // Default rendering
    const itemColor = isFocused 
      ? colors.focus 
      : item.disabled 
        ? colors.textMuted 
        : getColorFromName(item.color);

    return (
      <Box key={item.key}>
        <Text
          color={itemColor as string}
          bold={isSelected}
          dimColor={item.disabled}
        >
          {isFocused ? '▸ ' : '  '}
          {item.label}
        </Text>
        {item.description && (
          <Text color={colors.textMuted} dimColor> {item.description}</Text>
        )}
      </Box>
    );
  };

  // Get all visible (non-hidden) items with their original indices
  const visibleItemsWithIndices = useMemo(() => {
    return filteredItems
      .map((item, idx) => ({ item, idx }))
      .filter(({ item }) => !item.hidden);
  }, [filteredItems]);

  // Calculate scroll window based on visible items only
  const scrollWindow = useMemo(() => {
    // Find position of selected index in visible items
    const selectedVisiblePos = visibleItemsWithIndices.findIndex(({ idx }) => idx === selectedIndex);
    const effectivePos = selectedVisiblePos >= 0 ? selectedVisiblePos : 0;
    
    const halfWindow = Math.floor(maxVisible / 2);
    let start = Math.max(0, effectivePos - halfWindow);
    let end = Math.min(visibleItemsWithIndices.length, start + maxVisible);
    
    // Adjust start if we're near the end
    if (end - start < maxVisible) {
      start = Math.max(0, end - maxVisible);
    }
    
    return { start, end };
  }, [visibleItemsWithIndices, selectedIndex, maxVisible]);

  // Get the slice of visible items to display
  const displayItems = useMemo(() => {
    return visibleItemsWithIndices.slice(scrollWindow.start, scrollWindow.end);
  }, [visibleItemsWithIndices, scrollWindow]);

  // Render content based on grouping
  const renderContent = () => {
    // Show empty message if no visible items
    if (visibleCount === 0) {
      return <Text color={colors.textMuted} dimColor>{emptyMessage}</Text>;
    }

    // If groups are defined, render grouped (but still respect maxVisible)
    if (groups && groups.length > 0) {
      // Get display item indices for quick lookup
      const displayIndices = new Set(displayItems.map(({ idx }) => idx));
      
      return (
        <Box flexDirection="column">
          {groups.map((group, groupIndex) => {
            // Filter to only items that are in this group AND in the display window
            const groupItems = displayItems.filter(({ item }) => item.group === group.id);

            if (groupItems.length === 0) return null;

            return (
              <Box key={group.id} flexDirection="column">
                {/* Group label */}
                {group.label && (
                  <Text color={colors.textMuted} bold>{group.label}</Text>
                )}
                
                {/* Group items */}
                {groupItems.map(({ item, idx }) => renderListItem(item, idx))}
                
                {/* Separator - only show if there are more groups with items after this */}
                {group.separator && groupIndex < groups.length - 1 && (
                  <Box marginY={1}>
                    <Text color={colors.textMuted}>────────────────────────</Text>
                  </Box>
                )}
              </Box>
            );
          })}
        </Box>
      );
    }

    // No grouping - render with scroll window
    return (
      <Box flexDirection="column">
        {displayItems.map(({ item, idx }) => renderListItem(item, idx))}
      </Box>
    );
  };

  const borderColor = focus ? colors.focus : colors.border;

  const content = (
    <Box flexDirection="column">
      {/* Filter input */}
      {filterable && isFiltering && (
        <Box marginBottom={1}>
          <Text color={colors.info}>Filter: </Text>
          <TextInput
            value={filterText}
            onChange={handleFilterChange}
            onSubmit={handleFilterSubmit}
            placeholder={filterPlaceholder}
            focus={isFiltering}
          />
        </Box>
      )}
      
      {/* List content */}
      {renderContent()}
      
      {/* Scroll indicator */}
      {showScrollIndicator && visibleCount > maxVisible && (
        <Text color={colors.textMuted} dimColor>
          {scrollWindow.start + 1}-{scrollWindow.end} of {visibleCount}
        </Text>
      )}
      
      {/* Filter hint */}
      {filterable && !isFiltering && (
        <Text color={colors.textMuted} dimColor>
          Press '/' to filter
        </Text>
      )}
    </Box>
  );

  return (
    <Box flexDirection="column">
      {label && <Text color={colors.primary} bold>{label}</Text>}
      {border ? (
        <Box
          borderStyle="single"
          borderColor={borderColor}
          paddingX={1}
          flexDirection="column"
        >
          {content}
        </Box>
      ) : content}
    </Box>
  );
}

// =============================================================================
// Legacy List Component (kept for backward compatibility)
// =============================================================================

/**
 * Legacy list item type.
 * @deprecated Use ListItemData instead
 */
export interface ListItem<T = unknown> {
  /** Unique key for the item */
  key: string;
  /** Display label */
  label: string;
  /** Optional secondary text */
  description?: string;
  /** Optional value associated with item */
  value?: T;
  /** Whether item is disabled */
  disabled?: boolean;
}

/**
 * Props for the legacy List component.
 * @deprecated Use ScrollableListProps instead
 */
interface ListProps<T> {
  /** List items */
  items: ListItem<T>[];
  /** Currently selected index */
  selectedIndex: number;
  /** Selection change handler */
  onSelect: (index: number) => void;
  /** Item activation handler (Enter key) */
  onActivate?: (item: ListItem<T>, index: number) => void;
  /** Whether list is focused */
  focus?: boolean;
  /** Maximum visible items (for scrolling) */
  maxVisible?: number;
  /** Optional label */
  label?: string;
  /** Show border */
  border?: boolean;
}

/**
 * Selectable list with keyboard navigation.
 * @deprecated Use ScrollableList instead
 */
export function List<T>({
  items,
  selectedIndex,
  onSelect,
  onActivate,
  focus = true,
  maxVisible = 10,
  label,
  border = true,
}: ListProps<T>): React.ReactElement {
  // Handle keyboard input
  useInput((input, key) => {
    if (!focus) return;

    if (key.upArrow || input === 'k') {
      const newIndex = selectedIndex > 0 ? selectedIndex - 1 : items.length - 1;
      onSelect(newIndex);
    } else if (key.downArrow || input === 'j') {
      const newIndex = selectedIndex < items.length - 1 ? selectedIndex + 1 : 0;
      onSelect(newIndex);
    } else if (key.return && onActivate && items[selectedIndex]) {
      const item = items[selectedIndex];
      if (item && !item.disabled) {
        onActivate(item, selectedIndex);
      }
    }
  }, { isActive: focus });

  // Calculate visible range for scrolling
  const startIndex = Math.max(0, Math.min(selectedIndex - Math.floor(maxVisible / 2), items.length - maxVisible));
  const visibleItems = items.slice(startIndex, startIndex + maxVisible);

  const borderColor = focus ? colors.focus : colors.border;

  const content = (
    <Box flexDirection="column">
      {visibleItems.map((item, visibleIndex) => {
        const actualIndex = startIndex + visibleIndex;
        const isSelected = actualIndex === selectedIndex;
        
        return (
          <Box key={item.key}>
            <Text
              color={item.disabled ? colors.textMuted : isSelected ? colors.bg : colors.text}
              backgroundColor={isSelected ? colors.focus : undefined}
              bold={isSelected}
              dimColor={item.disabled}
            >
              {isSelected ? '▸ ' : '  '}
              {item.label}
            </Text>
            {item.description && (
              <Text color={colors.textMuted} dimColor> - {item.description}</Text>
            )}
          </Box>
        );
      })}
      {items.length === 0 && (
        <Text color={colors.textMuted} dimColor>No items</Text>
      )}
    </Box>
  );

  return (
    <Box flexDirection="column">
      {label && <Text color={colors.text} bold>{label}</Text>}
      {border ? (
        <Box
          borderStyle="single"
          borderColor={borderColor}
          paddingX={1}
          flexDirection="column"
        >
          {content}
        </Box>
      ) : content}
      {items.length > maxVisible && (
        <Text color={colors.textMuted} dimColor>
          {startIndex + 1}-{Math.min(startIndex + maxVisible, items.length)} of {items.length}
        </Text>
      )}
    </Box>
  );
}

// =============================================================================
// SimpleList Component
// =============================================================================

/**
 * Props for SimpleList component.
 */
interface SimpleListProps {
  items: string[];
  label?: string;
  bullet?: string;
}

/**
 * Simple inline list for displaying items without selection.
 */
export function SimpleList({ 
  items, 
  label, 
  bullet = '•' 
}: SimpleListProps): React.ReactElement {
  return (
    <Box flexDirection="column">
      {label && <Text color={colors.text} bold>{label}</Text>}
      {items.map((item, index) => (
        <Text key={index} color={colors.text}>
          {bullet} {item}
        </Text>
      ))}
    </Box>
  );
}
