import * as React from 'react';
import { createComponent } from '@lit/react';
import Component from '../../components/spatialnavigationprovider';
import { TAG_NAME } from '../../components/spatialnavigationprovider/spatialnavigationprovider.constants';
/**
 * Spatial navigation focus manager
 *
 * [Spatial navigation](https://en.wikipedia.org/wiki/Spatial_navigation) lets users move focus among
 * elements on a 2D plane, common on TVs and game consoles with remotes or gamepads.
 *
 * It should have only one instance and it should placed at the root of the application.
 *
 * ## Focus management
 *
 * The provider listens to keyboard events and moves focus among elements based on arrow key input.
 * You can influence or override this behavior.
 *
 * ### Steps
 *
 * Spatial navigation goes trough the following steps after each keydown:
 *
 * 1. Handle `keydown` in the capture phase.
 *    When active element has `data-spatial-{direction}` attribute then prevent all component navigation and call the
 *    provider own `keydown` handler (see step 3).
 * 2. Component own `keydown` handler executed (bubble phase) (e.g., list moves focus internally) it it was not
 *    prevented.
 * 3. Spatial Navigation Provider's `keydown` handler executed (bubble phase)
 *    - If key event was not prevented in step 1. emit `navbeforeprocess` to check if any component want to handle
 *      the key event itself. If `navbeforeprocess` event is prevented, stop here.
 *    - If the component did not handle `keydown`, it calculate the next focusable item
 *      - if the active element has `data-spatial-{direction}` attribute, it will try to focus the element with the id.
 *      - Otherwise calculate the next focused item based on the direction and distances.
 *    - If there is no next item, it emits `navnotarget` event
 *    - Otherwise emit `navbeforefocus`,
 *      - If this event prevented, nothing happens
 *      - Otherwise the focus moves to the next element
 *
 * ### Determine next focus
 *
 * The provider use multiple ways to determine the next focused element. The order defined in the "Steps" section.
 *
 * #### Calculated focus
 *
 * By default, the next focus target is computed from element positions:
 *
 * 1. Find the nearest focus area (scrollable container or active focus trap) relative to the current element.
 * 2. Collect focusable elements in that area.
 * 3. Compute distances from the current element to candidates using the W3C "find the shortest
 *    distance" algorithm: https://www.w3.org/TR/css-nav-1/#find-the-shortest-distance
 * 4. If no candidates are found, repeat from step 1, skipping areas already checked.
 * 5. Focus the closest candidate.
 *
 * Elements with `data-spatial-focusable` are treated as focusable even if they would otherwise not be
 * (e.g., `tabindex="-1"`).
 *
 * Elements with `data-spatial-exclude` are excluded (with its subtree) from the navigation, even if they
 * are focusable.
 *
 * Note: The algorithm is distance-based, so the UI should be designed to focusable elements are
 * predictably reachable. Relative element positions should remain stable; responsive layouts can
 * make navigation unpredictable. This is less of an issue on fixed-size TV UIs but can show unexpected
 * behavior in Storybook when resizing. See the "Limitations" section.
 *
 * #### Overwrite next element
 *
 * Override calculated navigation by adding one of these attributes to a focusable element:
 *
 * - `data-spatial-up`
 * - `data-spatial-down`
 * - `data-spatial-left`
 * - `data-spatial-right`
 *
 * Each attribute value must be the id of the element to focus when the corresponding key is pressed.
 *
 * #### Element internal navigation
 *
 * Complex components (List, Combobox, Tree, etc.) may handle their own navigation. For example, a List moves
 * focus internally on Down until the last item, after which Down should fall back to provider navigation.
 *
 * To prevent the provider from handling a key, listen to `navbeforeprocess` and call `event.preventDefault()`.
 * This event fires after the component handles `keydown`.
 *
 * ### Cancel focus change
 *
 * Before focusing a computed target, the provider dispatches `navbeforefocus` on the current element. Call
 * `event.preventDefault()` on this event to cancel the focus change.
 *
 * ## Enter action
 *
 * Pressing Enter triggers `.click()` on the currently focused element.
 *
 * You can prevent this by listening to `navbeforeprocess` and calling `event.preventDefault()`.
 *
 * ## Escape/Back action
 *
 * Pressing Escape tries to find a focusable element with `data-spatial-go-back` and clicks it. If none exists,
 * the provider calls `history.back()`.
 *
 * You can prevent this by listening to `navbeforeprocess` and calling `event.preventDefault()`.
 *
 * You can also intercept the back click by listening to `navback` and calling `event.preventDefault()`.
 *
 * ## Control data attributes
 *
 * Supported data attributes:
 *
 * | Attribute                | Value                     | Default | Description                                                                   |
 * |--------------------------|---------------------------|---------|-------------------------------------------------------------------------------|
 * | `data-spatial-left`      | empty string / element id | N/A     | Prevent native navigation in Left direction and focus element if exists       |
 * | `data-spatial-up`        | empty string / element id | N/A     | Prevent native navigation in Up direction and focus element if exists         |
 * | `data-spatial-right`     | empty string / element id | N/A     | Prevent native navigation in Right direction and focus element if exists      |
 * | `data-spatial-down`      | empty string / element id | N/A     | Prevent native navigation in Down direction and focus element if exists       |
 * | `data-spatial-go-back`   | N/A                       | N/A     | First focusable element with this attribute is clicked on Back/Escape         |
 * | `data-spatial-focusable` | N/A                       | N/A     | Treat element as focusable even if it normally is not (e.g., `tabindex="-1"`) |
 * | `data-spatial-exclude`   | N/A                       | N/A     | Exclude focusable element (and its subtree) from the navigation               |
 *
 * ## Event emitting order
 *
 * On a navigation key press, events fire in this order:
 *
 * 1. `navbeforeprocess` on the currently focused element.
 * 2. If not prevented:
 * a. Arrow keys: `navbeforefocus` on the currently focused element.
 * b. Enter: `.click()` on the currently focused element.
 * c. Escape/Back: `navback` on the provider, then `.click()` on the go-back element or `history.back()`.
 * 3. If no target is found in the requested direction: `navnotarget` on the provider.
 *
 * ## Handle complex components
 *
 * ### Generic components
 *
 * Components that handle navigation internally should prevent the provider from acting. Handle `navbeforeprocess`
 * and call `event.preventDefault()` for keys you process yourself.
 *
 * ### Form inputs
 *
 * Native inputs often submit on Enter, which is not desirable here. Enter should toggle or activate the control
 * (e.g., check/uncheck). Provide a dedicated submit button users can navigate to and press Enter on.
 *
 * ### Utilities for complex components
 *
 * #### KeyToActionMixin
 *
 * Maps key events to action names. Call `getActionForKeyEvent` to get the action for a keyboard event. Also provides
 * `getKeyboardNavMode` to check whether navigation is spatial or default.
 *
 * #### KeyDownHandledMixin
 *
 * Notify the provider when a component handled `keydown` internally. Call `keyDownEventHandled` whenever you process
 * keydown yourself.
 *
 * ## Platform specific behaviors
 *
 * Consider remote/gamepad constraints. Often focus alone is not enough and users press Enter to "enter" an interactive mode:
 * - Select: Enter opens options rather than arrow keys opening a popover.
 * - Text inputs: see the next section.
 * - Slider: Enter to start adjusting, arrow keys to change value, Enter/Escape to stop.
 *
 * ### Text inputs
 *
 * On TV-like platforms without physical keyboards, Enter/focus on an input should open a virtual keyboard instead of submitting
 * the form. Users must close the keyboard (Escape) to continue spatial navigation.
 *
 * If navigation keys are mapped to letters (e.g., `w/a/s/d`), they should navigate, not change input values. Inputs should
 * be edited via the virtual keyboard.
 *
 * Note: Stories do not emulate virtual keyboards, so letter-based navigation may change input values in Storybook.
 *
 * ## Debugging
 *
 * ### Storybook toolbar
 *
 * Enable "Spatial navigation" in the toolbar. Key mapping:
 * - Up - ArrowUp
 * - Left - ArrowLeft
 * - Down - ArrowDown
 * - Right - ArrowRight
 * - Enter - Enter
 * - Escape - Escape
 *
 * With wrapper: wraps the component in a 3x3 grid with surrounding buttons for testing.
 * Without wrapper: renders the component alone.
 *
 * ### Visual debugger
 *
 * With spatial navigation enabled, press Shift + navigation key to visualize calculations:
 *
 * - Star: next active element
 * - `#{number}`: candidate order by distance
 * - `D: {distance}`: computed distance
 *
 * ## Limitations
 *
 * ### Completeness
 *
 * The algorithm cannot guarantee reachability to all elements using the four directions. Some components can be isolated.
 *
 * Workarounds:
 * - Use data attributes to explicitly link navigation targets.
 * - Arrange DOM to improve spatial consistency:
 * - Group focusable elements using dedicated components (lists, menus, etc.).
 * - Avoid complex grid-like layouts with variable-sized items.
 * - Avoid overlap along horizontal or vertical axes.
 * - Avoid nested focusable elements where possible.
 * - Tune algorithm weights to match your UI layout.
 *
 * ### Scrollable containers
 *
 * Content scrolling is not supported yet, e.g.:
 * - Focused element larger than the viewport.
 * - Scrollable content without interactive children.
 *
 * ### Nested providers
 *
 * Only one provider instance is supported in the application at a time.
 *
 * @event navbeforeprocess - (React: onNavBeforeProcess) This event dispatched before spatial navigation process any key event.
 *                           It can be canceled to prevent any action from spatial navigation, e.g.: back, click or calculating the next candidate.
 * @event navbeforefocus - (React: onNavBeforeFocus) This event is dispatched before the focus is changing to the next element.
 *                         It can be canceled to prevent the focus change. @see https://www.w3.org/TR/css-nav-1/#event-type-navbeforefocus
 * @event navback - (React: onNavBack) This event dispatched a back navigation triggered by the user.
 *                  The event's detail contains the goBackElement if any. It is cancelable to prevent click
 *                  action on the goBackElement.
 * @event navnotarget - (React: onNavNoTarget) This event is dispatched when there is no target to focus in the current focus area and
 *                      in the given direction .
 *
 * @tagname mdc-spatialnavigationprovider
 */
const reactWrapper = createComponent({
    tagName: TAG_NAME,
    elementClass: Component,
    react: React,
    events: {
        onNavBeforeProcess: 'navbeforeprocess',
        onNavBeforeFocus: 'navbeforefocus',
        onNavBack: 'navback',
        onNavNoTarget: 'navnotarget',
    },
    displayName: 'SpatialNavigationProvider',
});
export default reactWrapper;
