import * as React from 'react'
import { theme } from '@planview/pv-utilities'
import { ButtonEmptyInverse, ListItem, Tooltip } from '@planview/pv-uikit'
import { Cancel, Search } from '@planview/pv-icons'
import {
    ToolbarItem,
    ToolbarItemCollapsedView,
    ToolbarItemView,
} from '../wrapper'
import { useMergeRefs } from '../utils/refs'
import ResizeContext, { useMainNavigationContext } from '../utils/context'
import type { DisplayOnType } from '../utils'
import { DISPLAY_ON_TABLET_PORTRAIT } from '../utils'
import { defineMessages, useIntl } from 'react-intl'
import { SearchContainer, SearchInnerInput } from './styles'

const messages = defineMessages({
    search: {
        id: 'pvds.toolbar.navigation.search',
        defaultMessage: 'Search',
        description: 'Label for search input in navigation',
    },
    clearSearch: {
        id: 'pvds.toolbar.navigation.clearSearch',
        defaultMessage: 'Clear search',
        description: 'Label for clear search button in navigation search input',
    },
})

export type SearchInputProps = {
    /**
     * force expanded mode (controlled mode)
     */
    expanded?: boolean

    /**
     * call-back on expanded state change request (if controlled state will not change)
     */
    onExpandedChange?: (nextState: boolean) => void
    /**
     * Target display of this item. Choose 'phone' to prevent collapsing.
     */
    displayOn?: DisplayOnType

    /** value of input in controlled mode */
    value?: string

    /** default value of input in uncontrolled mode */
    defaultValue?: string

    /** Callback triggered after the value changes. */
    onChange?: (
        value: string,
        event: React.ChangeEvent<HTMLInputElement>
    ) => void

    /**
     * call-back when list item is activated in collapsed mode from more menu
     * (This can be useful in case you want to open search modal in phone resolution)
     */
    onActivateMoreMenu?: () => void
} & Omit<React.ComponentPropsWithoutRef<'input'>, 'onChange'>

/* Adapted from https://hustle.bizongo.in/simulate-react-on-change-on-controlled-components-baa336920e04 */
function setValueImperative(input: HTMLInputElement, value: string) {
    const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
        window.HTMLInputElement.prototype,
        'value'
    )?.set
    nativeInputValueSetter?.call(input, value)

    const inputEvent = new InputEvent('input', { bubbles: true })
    input.dispatchEvent(inputEvent)
}

const SearchInputImpl: React.ForwardRefRenderFunction<
    HTMLInputElement | null,
    SearchInputProps
> = (
    {
        value,
        defaultValue = '',
        expanded,
        displayOn = DISPLAY_ON_TABLET_PORTRAIT,
        onActivateMoreMenu,
        onExpandedChange,
        ...rest
    },
    ref
) => {
    const { formatMessage } = useIntl()
    const { shouldDisplay, initialized } = React.useContext(ResizeContext)
    const shouldCollapse = shouldDisplay('desktop-hd')
    const { wrappedInNavigation } = useMainNavigationContext()
    const [stateValue, setStateValue] = React.useState(value || defaultValue)
    const [hasFocus, setHasFocus] = React.useState(false)
    const [expandedState, setExpandedState] = React.useState<boolean | null>(
        expanded !== undefined ? expanded : null
    )
    const showClear = !!stateValue.length
    const inputRef = React.useRef<HTMLInputElement | null>(null)
    const parentRef = React.useRef<HTMLDivElement>(null)
    const mergedRefs = useMergeRefs<HTMLInputElement>([ref, inputRef])

    React.useEffect(() => {
        if (!initialized) {
            return
        }
        const inputExpanded =
            !shouldCollapse || !!stateValue.trim().length || hasFocus
        if (
            inputExpanded !== expandedState ||
            (expanded !== undefined && expanded !== expandedState)
        ) {
            setExpandedState(expanded === undefined ? inputExpanded : expanded)
            onExpandedChange?.(inputExpanded)
        }
    }, [
        expanded,
        expandedState,
        hasFocus,
        initialized,
        onExpandedChange,
        shouldCollapse,
        stateValue,
    ])

    React.useEffect(() => {
        if (value !== undefined) {
            setStateValue(value)
        }
    }, [value])

    return expandedState !== null ? (
        <ToolbarItem displayOn={displayOn}>
            <ToolbarItemView<HTMLInputElement> refOverride={inputRef}>
                {(
                    ref,
                    {
                        toolbarItemProps: {
                            focused: _focused,
                            ...toolbarItemProps
                        },
                    }
                ) => (
                    <Tooltip
                        text={formatMessage(messages.search)}
                        show={!expandedState}
                    >
                        <SearchContainer
                            ref={parentRef}
                            $expanded={expandedState}
                            $focusVisible={hasFocus}
                            role="search"
                            $hasClear={showClear}
                            onClick={() => {
                                inputRef.current?.focus()
                            }}
                            onFocus={() => {
                                setHasFocus(true)
                            }}
                            onBlur={(e) => {
                                if (
                                    !parentRef.current?.contains(
                                        e.relatedTarget
                                    )
                                ) {
                                    setHasFocus(false)
                                }
                            }}
                        >
                            <Search color={theme.iconInverse} />
                            <SearchInnerInput
                                {...rest}
                                {...toolbarItemProps}
                                tabIndex={
                                    wrappedInNavigation
                                        ? toolbarItemProps.tabIndex
                                        : 0
                                }
                                $expanded={expandedState}
                                type="search"
                                aria-label={
                                    rest['aria-label'] ||
                                    formatMessage(messages.search)
                                }
                                ref={mergedRefs}
                                placeholder={
                                    rest.placeholder ||
                                    formatMessage(messages.search)
                                }
                                value={stateValue}
                                onKeyDown={(ev) => {
                                    rest.onKeyDown?.(ev)
                                    const pos =
                                        ev.currentTarget.selectionStart || 0
                                    const len = ev.currentTarget.value.length
                                    if (ev.key === 'ArrowRight' && pos < len) {
                                        return
                                    } else if (
                                        ev.key === 'ArrowLeft' &&
                                        pos > 0
                                    ) {
                                        return
                                    }
                                    toolbarItemProps.onKeyDown(ev)
                                }}
                                onChange={(e) => {
                                    if (value === undefined) {
                                        setStateValue(e.target.value)
                                    }

                                    rest.onChange?.(e.target.value, e)
                                }}
                            />
                            {showClear ? (
                                <ButtonEmptyInverse
                                    icon={<Cancel />}
                                    tabIndex={-1}
                                    onClick={() => {
                                        if (inputRef.current) {
                                            setValueImperative(
                                                inputRef.current,
                                                ''
                                            )
                                            inputRef.current?.focus()
                                        }
                                    }}
                                    aria-label={formatMessage(
                                        messages.clearSearch
                                    )}
                                />
                            ) : null}
                        </SearchContainer>
                    </Tooltip>
                )}
            </ToolbarItemView>
            <ToolbarItemCollapsedView>
                <ListItem
                    icon={<Search />}
                    label={rest['aria-label'] || formatMessage(messages.search)}
                    onActivate={onActivateMoreMenu}
                />
            </ToolbarItemCollapsedView>
        </ToolbarItem>
    ) : null
}

/**
 *
 * [Design system spec](https://design.planview.com/components/navigation/navigation-bar#navigation-bar-search-)
 *
 * `import { SearchInput } from '@planview/pv-toolbar'`
 *
 * ### API
 * Props type extends `React.ComponentPropsWithoutRef<'input'>`
 *
 * ### Adaptive layout
 * The `SearchInput` component will be auto-expanded when in breakpoint `desktop-hd`. Below that breakpoint it will contract into a button and
 * expand on user interaction. The expanded state can be forced by using the `expanded` prop.
 *
 * Like other components in `pv-toolbar`, the `SearchInput` can collapse into a more menu.
 * When it should be removed from the bar and put into the more menu is determined by the `displayOn` property.
 *
 *
 * ### Accessibility
 * By using the `role="search"`, this component adds a [search landmark](https://www.w3.org/WAI/ARIA/apg/patterns/landmarks/examples/search.html) which will help users using assistive technology jump quicker between the different regions on the page.
 *
 */
export const SearchInput = React.forwardRef(SearchInputImpl)
