import type { AvatarProps } from '@planview/pv-uikit'
import { Avatar, Badge, Tooltip } from '@planview/pv-uikit'
import {
    align,
    theme,
    iconSizes,
    overflow,
    sizePx,
    spacingPx,
    text,
    useIsFocusVisible,
    cursor,
    useLinkClick,
} from '@planview/pv-utilities'
import type { CSSProperties } from 'react'
import React from 'react'
import styled, { css } from 'styled-components'
import type { IconProps } from '@planview/pv-icons'
import { CaretDown } from '@planview/pv-icons'

const ShrinkableBadge = styled(Badge)`
    flex-shrink: 1;
`

const CaretWrapper = styled.div<{ $hasIcon?: boolean }>`
    display: none;
    box-sizing: border-box;
    position: absolute;
    inset: 0 0 0 auto;
    place-content: center;
    width: ${sizePx.small};
    overflow: visible;

    ${(props) =>
        props.$hasIcon
            ? css`
                  background-color: var(
                      --grid-cell-bg,
                      var(
                          --grid-row-background-color,
                          ${theme.backgroundNeutral0}
                      )
                  );
              `
            : css`
                  &::before {
                      z-index: 1;
                      content: '';
                      position: absolute;
                      inset: 0 0 0 auto;
                      display: block;
                      width: ${sizePx.medium};
                      height: 100%;
                      background-color: var(
                          --grid-cell-bg,
                          var(
                              --grid-row-background-color,
                              ${theme.backgroundNeutral0}
                          )
                      );
                      mask-image: linear-gradient(
                          90deg,
                          rgb(0 0 0 / 0%) 0%,
                          rgb(0 0 0 / 100%) 40%,
                          rgb(0 0 0 / 100%) 100%,
                          transparent
                      );
                  }
              `}
`

const StyledCaretDown = styled(CaretDown)`
    z-index: 2;
    ${cursor.pointer};
`

const CCell = styled.div<{
    $paddingLeft: boolean
    $focusVisible: boolean
    $paddingRight: boolean
    $aggregated: boolean
    $numeric: boolean
}>`
    ${(props) =>
        props.$aggregated
            ? props.$numeric
                ? text.numericSemibold
                : text.semibold
            : props.$numeric
              ? text.numeric
              : text.regular};
    min-height: ${sizePx.small};
    height: 100%;
    width: 100%;
    padding: 0 ${(props) => (props.$paddingRight ? spacingPx.small : 0)} 0
        ${(props) => (props.$paddingLeft ? spacingPx.small : 0)};
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    min-width: 0;
    position: relative;

    &:focus,
    *:focus {
        outline: none;
    }

    &:hover ${CaretWrapper} {
        display: grid;
    }

    ${(props) =>
        props.$focusVisible
            ? `${CaretWrapper} {
        display: grid;
    }`
            : ''}
`

const anchorStyles = css`
    ${cursor.pointer};
    color: ${theme.textLinkNormal};
    text-decoration: none;

    &:hover {
        text-decoration: underline;
        text-decoration-color: ${theme.textLinkHover};
        color: ${theme.textLinkHover};
    }

    &:focus {
        outline: none;
        text-decoration: underline;
        text-decoration-color: ${theme.textLinkHover};
        color: ${theme.textLinkHover};
    }
`

const Ellipsed = styled.div<{ $asLink?: boolean; $align: 'left' | 'right' }>`
    ${overflow.ellipsis};
    color: ${theme.textPrimary};

    ${(props) => (props.$asLink ? anchorStyles : '')};
    ${(props) =>
        props.$align === 'right' &&
        css`
            text-align: right;
            flex-grow: 1;
            justify-content: flex-end;
        `}
`

const LeftWrap = styled.div<{ $hasBadge: boolean; $align: 'left' | 'right' }>`
    ${align.centerV};
    min-height: ${sizePx.small};
    min-width: 0;
    gap: ${(props) => (props.$hasBadge ? spacingPx.small : 0)};
    ${(props) =>
        props.$align === 'right' &&
        css`
            display: flex;
            flex-grow: 1;
        `}
`

type GridCellDefaultIcon = {
    /**
     * Icon component
     */
    icon?: React.ReactElement<IconProps>
    /**
     * URL to avatar image or Avatar component
     */
    avatar?: never
    /**
     * Badge to display
     */
    badge?: never
}

type GridCellDefaultAvatar = {
    /**
     * Icon component
     */
    icon?: never
    /**
     * URL to avatar image or Avatar component
     */
    avatar?: string | React.ReactElement<AvatarProps>
    /**
     * Badge to display
     */
    badge?: never
}

type GridCellDefaultBadge = {
    /**
     * Icon component
     */
    icon?: never
    /**
     * URL to avatar image or Avatar component
     */
    avatar?: never
    /**
     * Badge to display
     */
    badge?: string | number
}

type AnchorTagConfig = Pick<
    React.ComponentPropsWithoutRef<'a'>,
    'download' | 'rel' | 'target' | 'onClick'
> & {
    url: string
    preventNavigation?: boolean
}

export type GridCellDefaultProps = (
    | GridCellDefaultIcon
    | GridCellDefaultAvatar
    | GridCellDefaultBadge
) & {
    /**
     * Accessible tab index. You should generally pass through the tabIndex
     * provided to the Renderer component.
     */
    tabIndex: number
    /**
     * Display value
     */
    label?: string
    /**
     * String to pass to a tooltip around icon/avatar/badge. Note, if `align` is set
     * to `right` this content will appear on the right.
     */
    leftContentTooltip?: string
    /**
     * Background color to use. Should only use official colors in
     * the 0-200 range to be properly accessible with textPrimary unless
     * also setting a color to ensure accessible contrast.
     */
    backgroundColor?: string
    /**
     * Color to use for the text. Should use official text colors or custom
     * colors that have enough contrast against backgroundColor.
     */
    color?: string

    /** Renders a caret icon on the right on hover or focus */
    withCaret?: boolean

    /** Renders the value in bold to indicate aggregation */
    aggregated?: boolean

    /**
     * Using this prop will render the label as an anchor tag. Other elements
     * such as icon, avatar, and badge will be rendered outside the anchor tag.
     *
     * Accepts a `string` or a
     *
```
    type AnchorTagConfig = {
        url: string
        rel?: string
        target?: string
        download?: string
        preventNavigation?: boolean
        onClick?: React.MouseEventHandler<HTMLAnchorElement>
    }
```
     * When using the `AnchorTagConfig` the url (as href), rel, target, download and onClick will be passed to the anchor element
     * */
    href?: string | AnchorTagConfig

    /**
     * @deprecated Pass onClick to the `href` prop instead
     * @see {@link GridCellDefaultProps.href}
     *
     * Callback when link is clicked
     */
    onLinkClick?: React.MouseEventHandler<HTMLAnchorElement>

    /** Should the field use tabular (monospace) numbers? Defaults `align` to `right` if not overridden. */
    numeric?: boolean

    /** Should the content of the cell be right or left aligned. Defaults to `left` unless `numeric` is true. */
    align?: 'left' | 'right'
}

const GridCellDefaultImpl = ({
    label,
    tabIndex = 0,
    avatar,
    aggregated,
    icon,
    badge,
    leftContentTooltip = '',
    backgroundColor,
    color,
    withCaret,
    href,
    onLinkClick,
    numeric,
    align = numeric ? 'right' : 'left',
}: GridCellDefaultProps) => {
    const badgeRef = React.useRef<HTMLSpanElement | null>(null)
    const tabIndexForIconContent = label ? undefined : tabIndex
    const {
        innerRef: _innerRef,
        focusVisible,
        ...focusProps
    } = useIsFocusVisible()

    const hrefProps = React.useMemo(() => {
        if (typeof href === 'string') {
            return { href }
        } else if (href) {
            return {
                href: href.url,
                rel: href.rel,
                target: href.target,
                download: href.download,
            }
        }
        return {}
    }, [href])

    const badgeRefHandler = React.useCallback((ref: HTMLDivElement | null) => {
        /* We'd need a special ref to get access to the ellipsed content otherwise */
        badgeRef.current = ref?.querySelector<HTMLSpanElement>('span') || null
    }, [])

    const iconContent = React.useMemo(() => {
        const c = icon ? (
            React.cloneElement(icon, {
                size: iconSizes.SIZE_SMALL,
                tabIndex: tabIndexForIconContent,
                color: icon.props.color || theme.iconNormal,
            })
        ) : avatar ? (
            //Need to wrap this avatar in a focusable element since tooltip will not work if avatar receives tabIndex
            <span tabIndex={tabIndexForIconContent}>
                {typeof avatar === 'string' ? (
                    <Avatar src={avatar} size="small" />
                ) : (
                    React.cloneElement(avatar, {
                        size: 'small',
                    })
                )}
            </span>
        ) : badge != null ? (
            <ShrinkableBadge
                tabIndex={tabIndexForIconContent}
                ref={badgeRefHandler}
            >
                {badge}
            </ShrinkableBadge>
        ) : null

        if (c) {
            return (
                <Tooltip
                    unwrapped
                    text={leftContentTooltip || badge}
                    ellipsedElement={badge ? badgeRef : undefined}
                    show={
                        leftContentTooltip ? true : badge ? 'ellipsed' : false
                    }
                >
                    {c}
                </Tooltip>
            )
        }
        return null
    }, [
        avatar,
        icon,
        badge,
        badgeRefHandler,
        leftContentTooltip,
        tabIndexForIconContent,
    ])

    const hasBadge = !!badge && !icon && !avatar

    const handleClick = React.useCallback(
        (e: React.MouseEvent<HTMLAnchorElement>) => {
            e.stopPropagation()
            if (typeof href === 'object' && href.onClick) {
                href.onClick(e)
            } else {
                /* Fallback for deprecated onLinkClick method */
                onLinkClick?.(e)
            }
        },
        [onLinkClick, href]
    )

    const handleLinkClick = useLinkClick(
        handleClick,
        typeof href === 'object' && href.preventNavigation
    )

    return (
        <CCell
            {...focusProps}
            $focusVisible={focusVisible}
            $paddingLeft={align === 'right' || (!avatar && !icon)}
            $paddingRight={align === 'left' || (!avatar && !icon)}
            $aggregated={!!aggregated}
            $numeric={!!numeric}
            tabIndex={!iconContent && !label ? tabIndex : undefined}
            style={{ backgroundColor }}
        >
            <LeftWrap $hasBadge={hasBadge} $align={align}>
                {align === 'left' && iconContent}
                {label ? (
                    <Tooltip unwrapped text={label} show="ellipsed">
                        <Ellipsed
                            as={href ? 'a' : 'div'}
                            $asLink={!!href}
                            {...hrefProps}
                            tabIndex={tabIndex}
                            onClick={href ? handleLinkClick : undefined}
                            $align={align}
                            style={{ color }}
                        >
                            {label}
                        </Ellipsed>
                    </Tooltip>
                ) : null}
                {align === 'right' && iconContent}
            </LeftWrap>
            {withCaret ? (
                <CaretWrapper
                    $hasIcon={align === 'right' && !!iconContent && !hasBadge}
                    style={
                        { '--grid-cell-bg': backgroundColor } as CSSProperties
                    }
                >
                    <StyledCaretDown
                        size={iconSizes.SIZE_SMALL}
                        color={theme.iconNormal}
                    />
                </CaretWrapper>
            ) : null}
        </CCell>
    )
}
/**
 * Implementation of [grid renderer default](https://design.planview.com/components/grid/grid-renderer-default)
 * which is used by the default to render all cells.
 *
 * Though a lot is supported by this component, the default cell renderer only
 * leverages one aspect: rendering content that is is ellipsed when it would
 * overflow the cell (and provides a tooltip when this happens).
 *
 * To fully leverage this component, use a cell Renderer function that returns this
 * component.
 *
 * ```tsx
 * const calendarColumn: Column<User> = {
 *      id: "date",
 *      cell: {
 *          Renderer: ({ label, tabIndex }) => (
 *              <GridCellDefault
 *                  label={label}
 *                  icon={<Calendar />}
 *                  leftContentTooltip="Tooltip for calendar icon"
 *                  tabIndex={tabIndex} />
 *          )
 *      }
 * }
 * ```
 */
export const GridCellDefault = React.memo(
    GridCellDefaultImpl
) as typeof GridCellDefaultImpl
