import {
  createContext,
  HTMLAttributes,
  useCallback,
  useContext,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react'

const MAX_LINES = 2

const getLineHeightPx = (element: HTMLElement): number => {
  const style = window.getComputedStyle(element)
  const lineHeight = parseFloat(style.lineHeight)
  if (!Number.isNaN(lineHeight) && lineHeight > 0) return lineHeight

  // `line-height: normal` fallback (approx. 1.2 * font-size)
  const fontSize = parseFloat(style.fontSize)
  return (!Number.isNaN(fontSize) && fontSize > 0 ? fontSize : 16) * 1.2
}

const elementOverflowsLines = (measureEl: HTMLElement, lineHeightPx: number, maxLines: number): boolean => {
  const maxHeight = lineHeightPx * maxLines
  const height = measureEl.getBoundingClientRect().height
  return height > maxHeight + 0.5
}

export interface IMiddleTruncate extends HTMLAttributes<HTMLSpanElement> {
  children: string
  tail?: number
}

type TTruncateContext = { tail: number | undefined }
export const TruncateContext = createContext<TTruncateContext>({ tail: 5 })

export const Truncate = ({ children, tail: tailProp, ...props }: IMiddleTruncate): JSX.Element => {
  const firstPart = children
  const context = useContext(TruncateContext)
  const tail = tailProp !== undefined ? tailProp : context.tail || 0
  const secondPart = children.length <= tail + 3 ? null : children.slice(-tail)
  const showingSecondPart = secondPart !== null && tail > 0
  const wrapperRef = useRef<HTMLSpanElement>(null)
  const measureRef = useRef<HTMLSpanElement>(null)
  const [isOverflowing, setIsOverflowing] = useState(false)

  const computeOverflow = useCallback(() => {
    const wrapper = wrapperRef.current
    const measureEl = measureRef.current
    if (!wrapper || !measureEl) return

    const lineHeightPx = getLineHeightPx(wrapper)
    setIsOverflowing(elementOverflowsLines(measureEl, lineHeightPx, MAX_LINES))
  }, [])

  const resizeObserver = useMemo(() => {
    return new ResizeObserver((entries) => {
      if (entries.some((entry) => entry.target === wrapperRef.current)) {
        computeOverflow()
      }
    })
  }, [computeOverflow])
  useLayoutEffect(() => {
    if (wrapperRef.current) {
      resizeObserver.observe(wrapperRef.current)
    }
    computeOverflow()
    return () => resizeObserver.disconnect()
  }, [children, resizeObserver, computeOverflow])
  return (
    <span
      ref={wrapperRef}
      {...props}
      data-pkt-truncate-part="wrapper"
      style={{
        maxWidth: '100%',
        width: '100%',
        display: 'grid',
        gridTemplateColumns: 'minmax(0, 1fr) auto',
        alignItems: 'end',
        columnGap: '0.25rem',
        overflow: 'hidden',
        position: 'relative',
        ...props.style,
      }}
    >
      <span
        data-pkt-truncate-part="first"
        style={{
          minWidth: 0,
          display: '-webkit-box',
          WebkitBoxOrient: 'vertical',
          WebkitLineClamp: MAX_LINES,
          overflow: 'hidden',
          textOverflow: 'ellipsis',
          whiteSpace: 'normal',
          overflowWrap: 'anywhere',
        }}
      >
        {firstPart}
      </span>
      {tail !== 0 && (
        <span
          data-pkt-truncate-part="tail"
          style={{
            whiteSpace: 'nowrap',
            display: isOverflowing ? 'inline' : 'none',
            paddingRight: '0.5rem',
          }}
          aria-hidden={true}
        >
          {secondPart}
        </span>
      )}

      {/* Hidden measuring element (full text, wraps normally) */}
      <span
        ref={measureRef}
        aria-hidden={true}
        data-pkt-truncate-part="measure"
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%',
          visibility: 'hidden',
          pointerEvents: 'none',
          whiteSpace: 'normal',
          overflowWrap: 'anywhere',
        }}
      >
        {children}
      </span>
    </span>
  )
}
