"use client"
import useMergedRef from "@react-hook/merged-ref"
import React, {
  ElementRef,
  forwardRef,
  useCallback,
  useLayoutEffect,
  useRef,
  useState,
} from "react"
import { useCallbackRef, useIsOverflowed } from "../../hooks"
import {
  LineClampVariantProps,
  lineClampVariants,
} from "../../styles/utils/lineclamp"
import { classNames } from "../../utils/classNames"
import { Flex, FlexProps } from "../Flex"
import { MARKDOWN_SELECTOR } from "../Markdown/constants"
import { Text } from "../Text"
import { UnstyledButton } from "../UnstyledButton"

type BaseTextOverflowProps = {
  children: React.ReactNode
  className?: string
  open?: boolean
  onOpenChange?: (open: boolean) => unknown
  inlineSeeMore?: boolean
} & LineClampVariantProps &
  FlexProps

type ControlledTextOverflowProps = {
  open: boolean
} & BaseTextOverflowProps

type UncontrolledTextOverflowProps = {
  open?: never
} & BaseTextOverflowProps

export type TextOverflowProps =
  | ControlledTextOverflowProps
  | UncontrolledTextOverflowProps

const isControlledTextOverflow = (
  props: TextOverflowProps,
): props is ControlledTextOverflowProps => {
  return "open" in props
}

/**
 * Renders a See more/See less button when the text overflows.
 */
export const TextOverflow = (props: TextOverflowProps) => {
  if (isControlledTextOverflow(props)) {
    return <ControlledTextOverflow {...props} />
  }
  return <UncontrolledTextOverflowProps {...props} />
}

const UncontrolledTextOverflowProps = ({
  onOpenChange,
  ...rest
}: UncontrolledTextOverflowProps) => {
  const [open, setOpen] = useState(false)

  const handleOnOpenChange = useCallback(() => {
    setOpen(!open)
    onOpenChange?.(!open)
  }, [onOpenChange, open])

  return (
    <ControlledTextOverflow
      open={open}
      onOpenChange={handleOnOpenChange}
      {...rest}
    />
  )
}

const ControlledTextOverflow = ({
  children,
  className,
  open,
  onOpenChange,
  lines = 1,
  inlineSeeMore,
  ...rest
}: ControlledTextOverflowProps) => {
  const [ref, setRef] = useCallbackRef<HTMLDivElement>()

  const isOverflowed = useIsOverflowed(ref)

  const showSeeMore = isOverflowed || open

  return (
    <Flex
      className={classNames(
        "text-sm",
        inlineSeeMore && !open ? "flex-row" : "flex-col",
        className,
      )}
      {...rest}
    >
      <OverflowContainer lines={lines} open={open} ref={setRef}>
        {children}
      </OverflowContainer>

      {showSeeMore && (
        <UnstyledButton
          className={classNames(
            "w-fit items-end",
            inlineSeeMore && !open && "ml-1",
          )}
          onClick={() => onOpenChange?.(!open)}
        >
          <Text className="whitespace-nowrap" weight="semibold">
            {open ? "See less" : "See more"}
          </Text>
        </UnstyledButton>
      )}
    </Flex>
  )
}

const OverflowContainer = forwardRef<
  ElementRef<"div">,
  { children: React.ReactNode; open?: boolean } & LineClampVariantProps
>(function OverflowContainer({ children, lines, open }, ref) {
  const containerRef = useRef<HTMLDivElement>(null)
  const mergedRef = useMergedRef(ref, containerRef)
  const [width, setWidth] = useState<number | undefined>(undefined)

  return (
    <>
      <div
        className={classNames(
          !open && lineClampVariants({ lines }),
          "max-w-full break-normal",
        )}
        ref={mergedRef}
        style={{ width }}
      >
        {children}
      </div>
      <MeasuringContainer enabled={!open} lines={lines} onMeasure={setWidth}>
        {children}
      </MeasuringContainer>
    </>
  )
})

const MeasuringContainer = ({
  enabled,
  children,
  lines,
  onMeasure,
}: {
  enabled: boolean
  children: React.ReactNode
  lines?: number
  onMeasure: (width: number | undefined) => void
}) => {
  const [containerRef, setContainerRef] = useCallbackRef<HTMLDivElement>()

  // Calculate width based on markdown content
  useLayoutEffect(() => {
    if (!enabled || !containerRef.current) {
      return
    }

    const markdownContent =
      containerRef.current.querySelector(MARKDOWN_SELECTOR)

    if (!markdownContent) {
      onMeasure(undefined)
      return
    }

    const childrenLines = Array.from(markdownContent.children).slice(0, lines)
    if (!childrenLines.length) {
      onMeasure(undefined)
      return
    }

    const TRUNCATE_BUFFER = 12
    const measuredWidth =
      Math.max(
        ...childrenLines.map(el => el.getBoundingClientRect().width || 0),
      ) + TRUNCATE_BUFFER

    onMeasure(measuredWidth)
  }, [enabled, lines, onMeasure, containerRef])

  return (
    <div className="invisible absolute" ref={setContainerRef}>
      {children}
    </div>
  )
}
