/**
 * Keyboard navigation utilities for web components
 * @countermeasure/web-components/utils/keyboard
 *
 * Provides key constants, grid/tree/list navigation helpers
 * for accessible widget patterns.
 */

// ============================================================================
// Key Constants
// ============================================================================

export const Keys = {
  ARROW_UP: 'ArrowUp',
  ARROW_DOWN: 'ArrowDown',
  ARROW_LEFT: 'ArrowLeft',
  ARROW_RIGHT: 'ArrowRight',
  ENTER: 'Enter',
  SPACE: ' ',
  ESCAPE: 'Escape',
  TAB: 'Tab',
  HOME: 'Home',
  END: 'End',
  PAGE_UP: 'PageUp',
  PAGE_DOWN: 'PageDown',
  BACKSPACE: 'Backspace',
  DELETE: 'Delete',
} as const

export type KeyName = (typeof Keys)[keyof typeof Keys]

// ============================================================================
// Grid Navigation Types
// ============================================================================

export interface GridNavOptions {
  /** Number of columns in the grid */
  columns: number
  /** Number of rows in the grid */
  rows: number
  /** Current column index (0-based) */
  currentCol: number
  /** Current row index (0-based) */
  currentRow: number
  /** Whether to wrap at edges (default: false) */
  wrap?: boolean
  /** Callback to check if a cell is disabled */
  isCellDisabled?: (row: number, col: number) => boolean
}

export interface GridNavResult {
  /** Whether the key was handled */
  handled: boolean
  /** New column index */
  col: number
  /** New row index */
  row: number
}

// ============================================================================
// Tree Navigation Types
// ============================================================================

export interface TreeNavOptions {
  /** Array of visible node indices in DOM order */
  visibleNodes: number[]
  /** Currently focused node index */
  currentIndex: number
  /** Whether the current node is expanded */
  isExpanded: boolean
  /** Whether the current node has children */
  hasChildren: boolean
  /** Index of parent node (-1 if root) */
  parentIndex: number
  /** First child index (-1 if no children) */
  firstChildIndex: number
}

export interface TreeNavResult {
  /** Whether the key was handled */
  handled: boolean
  /** Action to perform */
  action:
    | 'focus' // Focus a new node
    | 'expand' // Expand current node
    | 'collapse' // Collapse current node
    | 'select' // Select current node
    | 'none' // No action
  /** Target index for focus action */
  targetIndex?: number
}

// ============================================================================
// List Navigation Types
// ============================================================================

export interface ListNavOptions {
  /** Current active index */
  activeIndex: number
  /** Total number of items */
  itemCount: number
  /** Whether to wrap at edges (default: true) */
  wrap?: boolean
  /** Orientation for arrow key handling */
  orientation?: 'horizontal' | 'vertical' | 'both'
  /** Callback to check if an item is disabled */
  isItemDisabled?: (index: number) => boolean
}

export interface ListNavResult {
  /** Whether the key was handled */
  handled: boolean
  /** New active index */
  index: number
  /** Whether Enter/Space was pressed for activation */
  activated: boolean
}

// ============================================================================
// Grid Navigation
// ============================================================================

/**
 * Handle keyboard navigation within a grid pattern (DataTable, Calendar, etc.)
 * Implements ARIA Grid pattern keyboard interactions.
 */
export function gridNavigate(event: KeyboardEvent, options: GridNavOptions): GridNavResult {
  const { columns, rows, currentCol, currentRow, wrap = false, isCellDisabled } = options

  let newCol = currentCol
  let newRow = currentRow
  let handled = false

  const findNextValidCell = (
    startRow: number,
    startCol: number,
    rowDelta: number,
    colDelta: number
  ): { row: number; col: number } => {
    let r = startRow + rowDelta
    let c = startCol + colDelta

    // Bounds checking
    if (wrap) {
      if (c < 0) {
        c = columns - 1
      }
      if (c >= columns) {
        c = 0
      }
      if (r < 0) {
        r = rows - 1
      }
      if (r >= rows) {
        r = 0
      }
    } else {
      c = Math.max(0, Math.min(columns - 1, c))
      r = Math.max(0, Math.min(rows - 1, r))
    }

    // Skip disabled cells
    if (isCellDisabled !== undefined) {
      const maxIterations = rows * columns
      let iterations = 0
      while (isCellDisabled(r, c) && iterations < maxIterations) {
        r += rowDelta
        c += colDelta
        if (wrap) {
          if (c < 0) {
            c = columns - 1
          }
          if (c >= columns) {
            c = 0
          }
          if (r < 0) {
            r = rows - 1
          }
          if (r >= rows) {
            r = 0
          }
        } else {
          c = Math.max(0, Math.min(columns - 1, c))
          r = Math.max(0, Math.min(rows - 1, r))
        }
        iterations++
      }
    }

    return { row: r, col: c }
  }

  switch (event.key) {
    case Keys.ARROW_RIGHT: {
      const result = findNextValidCell(currentRow, currentCol, 0, 1)
      newCol = result.col
      newRow = result.row
      handled = true
      break
    }
    case Keys.ARROW_LEFT: {
      const result = findNextValidCell(currentRow, currentCol, 0, -1)
      newCol = result.col
      newRow = result.row
      handled = true
      break
    }
    case Keys.ARROW_DOWN: {
      const result = findNextValidCell(currentRow, currentCol, 1, 0)
      newCol = result.col
      newRow = result.row
      handled = true
      break
    }
    case Keys.ARROW_UP: {
      const result = findNextValidCell(currentRow, currentCol, -1, 0)
      newCol = result.col
      newRow = result.row
      handled = true
      break
    }
    case Keys.HOME:
      if (event.ctrlKey) {
        // Ctrl+Home: go to first cell
        newRow = 0
        newCol = 0
      } else {
        // Home: go to first cell in row
        newCol = 0
      }
      handled = true
      break
    case Keys.END:
      if (event.ctrlKey) {
        // Ctrl+End: go to last cell
        newRow = rows - 1
        newCol = columns - 1
      } else {
        // End: go to last cell in row
        newCol = columns - 1
      }
      handled = true
      break
    case Keys.PAGE_UP:
      // Move up by a page (typically visible rows)
      newRow = Math.max(0, currentRow - 10)
      handled = true
      break
    case Keys.PAGE_DOWN:
      // Move down by a page
      newRow = Math.min(rows - 1, currentRow + 10)
      handled = true
      break
  }

  return { handled, col: newCol, row: newRow }
}

// ============================================================================
// Tree Navigation
// ============================================================================

/**
 * Handle keyboard navigation within a tree view pattern.
 * Implements ARIA Tree pattern keyboard interactions.
 */
export function treeNavigate(event: KeyboardEvent, options: TreeNavOptions): TreeNavResult {
  const { visibleNodes, currentIndex, isExpanded, hasChildren, parentIndex, firstChildIndex } =
    options

  const currentVisibleIndex = visibleNodes.indexOf(currentIndex)

  switch (event.key) {
    case Keys.ARROW_DOWN: {
      // Move to next visible node
      if (currentVisibleIndex < visibleNodes.length - 1) {
        const nextIndex = visibleNodes[currentVisibleIndex + 1]
        if (nextIndex !== undefined) {
          return { handled: true, action: 'focus', targetIndex: nextIndex }
        }
      }
      return { handled: true, action: 'none' }
    }
    case Keys.ARROW_UP: {
      // Move to previous visible node
      if (currentVisibleIndex > 0) {
        const prevIndex = visibleNodes[currentVisibleIndex - 1]
        if (prevIndex !== undefined) {
          return { handled: true, action: 'focus', targetIndex: prevIndex }
        }
      }
      return { handled: true, action: 'none' }
    }
    case Keys.ARROW_RIGHT:
      // If closed and has children: expand
      // If open: move to first child
      // If no children: do nothing
      if (hasChildren) {
        if (!isExpanded) {
          return { handled: true, action: 'expand' }
        } else if (firstChildIndex >= 0) {
          return { handled: true, action: 'focus', targetIndex: firstChildIndex }
        }
      }
      return { handled: true, action: 'none' }
    case Keys.ARROW_LEFT:
      // If open: collapse
      // If closed: move to parent
      if (isExpanded && hasChildren) {
        return { handled: true, action: 'collapse' }
      } else if (parentIndex >= 0) {
        return { handled: true, action: 'focus', targetIndex: parentIndex }
      }
      return { handled: true, action: 'none' }
    case Keys.HOME: {
      // Move to first visible node
      const firstNode = visibleNodes[0]
      if (firstNode !== undefined && firstNode !== currentIndex) {
        return { handled: true, action: 'focus', targetIndex: firstNode }
      }
      return { handled: true, action: 'none' }
    }
    case Keys.END: {
      // Move to last visible node
      const lastNode = visibleNodes[visibleNodes.length - 1]
      if (lastNode !== undefined && lastNode !== currentIndex) {
        return { handled: true, action: 'focus', targetIndex: lastNode }
      }
      return { handled: true, action: 'none' }
    }
    case Keys.ENTER:
    case Keys.SPACE:
      // Select/activate the current node
      return { handled: true, action: 'select' }
    default:
      return { handled: false, action: 'none' }
  }
}

// ============================================================================
// List Navigation
// ============================================================================

/**
 * Handle keyboard navigation within a list (menu, combobox options, etc.)
 * Returns the new active index based on key press.
 */
export function listNavigate(event: KeyboardEvent, options: ListNavOptions): ListNavResult {
  const { activeIndex, itemCount, wrap = true, orientation = 'vertical', isItemDisabled } = options

  if (itemCount === 0) {
    return { handled: false, index: -1, activated: false }
  }

  let newIndex = activeIndex
  let handled = false
  let activated = false

  const findNextValidIndex = (start: number, delta: number): number => {
    let index = start + delta
    const maxIterations = itemCount

    for (let i = 0; i < maxIterations; i++) {
      if (wrap) {
        if (index < 0) {
          index = itemCount - 1
        }
        if (index >= itemCount) {
          index = 0
        }
      } else {
        index = Math.max(0, Math.min(itemCount - 1, index))
      }

      if (isItemDisabled?.(index) !== true) {
        return index
      }

      index += delta
    }

    return start // Return original if no valid index found
  }

  const isVertical = orientation === 'vertical' || orientation === 'both'
  const isHorizontal = orientation === 'horizontal' || orientation === 'both'

  switch (event.key) {
    case Keys.ARROW_DOWN:
      if (isVertical) {
        newIndex = findNextValidIndex(activeIndex, 1)
        handled = true
      }
      break
    case Keys.ARROW_UP:
      if (isVertical) {
        newIndex = findNextValidIndex(activeIndex, -1)
        handled = true
      }
      break
    case Keys.ARROW_RIGHT:
      if (isHorizontal) {
        newIndex = findNextValidIndex(activeIndex, 1)
        handled = true
      }
      break
    case Keys.ARROW_LEFT:
      if (isHorizontal) {
        newIndex = findNextValidIndex(activeIndex, -1)
        handled = true
      }
      break
    case Keys.HOME:
      newIndex = findNextValidIndex(-1, 1)
      handled = true
      break
    case Keys.END:
      newIndex = findNextValidIndex(itemCount, -1)
      handled = true
      break
    case Keys.ENTER:
    case Keys.SPACE:
      activated = true
      handled = true
      break
  }

  return { handled, index: newIndex, activated }
}

// ============================================================================
// Type-ahead Search
// ============================================================================

export interface TypeAheadOptions {
  /** Items to search (should have a text/label property) */
  items: { label: string; disabled?: boolean }[]
  /** Timeout for resetting search buffer (default: 500ms) */
  timeout?: number
}

export interface TypeAheadController {
  /** Handle a keypress and return matching index or -1 */
  handleKey: (char: string) => number
  /** Reset the search buffer */
  reset: () => void
}

/**
 * Create a type-ahead search controller for navigating lists by typing.
 */
export function createTypeAhead(options: TypeAheadOptions): TypeAheadController {
  const { items, timeout = 500 } = options
  let searchBuffer = ''
  let searchTimeout: ReturnType<typeof setTimeout> | null = null

  const handleKey = (char: string): number => {
    // Only handle printable characters
    if (char.length !== 1 || char === ' ') {
      return -1
    }

    // Clear previous timeout
    if (searchTimeout !== null) {
      clearTimeout(searchTimeout)
    }

    // Add to buffer
    searchBuffer += char.toLowerCase()

    // Set reset timeout
    searchTimeout = setTimeout(() => {
      searchBuffer = ''
    }, timeout)

    // Find matching item
    for (let i = 0; i < items.length; i++) {
      const item = items[i]
      if (item === undefined || item.disabled === true) {
        continue
      }
      if (item.label.toLowerCase().startsWith(searchBuffer)) {
        return i
      }
    }

    return -1
  }

  const reset = (): void => {
    searchBuffer = ''
    if (searchTimeout !== null) {
      clearTimeout(searchTimeout)
      searchTimeout = null
    }
  }

  return { handleKey, reset }
}

// ============================================================================
// Keyboard Event Helpers
// ============================================================================

/**
 * Check if an event is a printable character key.
 */
export function isPrintableKey(event: KeyboardEvent): boolean {
  return event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey
}

/**
 * Check if the event target is an input element.
 */
export function isInputElement(target: EventTarget | null): boolean {
  if (target === null) {
    return false
  }
  const element = target as HTMLElement
  const tagName = element.tagName.toLowerCase()
  return (
    tagName === 'input' ||
    tagName === 'textarea' ||
    tagName === 'select' ||
    element.isContentEditable
  )
}

/**
 * Get a keyboard shortcut string from an event (e.g., "Ctrl+Shift+S").
 */
export function getShortcutFromEvent(event: KeyboardEvent): string {
  const parts: string[] = []

  if (event.ctrlKey || event.metaKey) {
    parts.push('Ctrl')
  }
  if (event.altKey) {
    parts.push('Alt')
  }
  if (event.shiftKey) {
    parts.push('Shift')
  }

  const key = event.key.length === 1 ? event.key.toUpperCase() : event.key
  if (!['Control', 'Alt', 'Shift', 'Meta'].includes(event.key)) {
    parts.push(key)
  }

  return parts.join('+')
}
