"use client"

import { InformationCircleSolid } from "@medusajs/icons"

import { PropData, PropDataMap, PropSpecType } from "@/types/ui"
import { useCallback, useMemo } from "react"
import { InlineCode, MarkdownContent, Table, Tooltip } from "docs-ui"

type PropTableProps = {
  props: PropDataMap
}

const PropTable = ({ props }: PropTableProps) => {
  return (
    <Table className="!mb-0">
      <Table.Header className="border-t-0">
        <Table.Row>
          <Table.HeaderCell>Prop</Table.HeaderCell>
          <Table.HeaderCell>Type</Table.HeaderCell>
          <Table.HeaderCell className="!text-right">Default</Table.HeaderCell>
        </Table.Row>
      </Table.Header>
      <Table.Body className="border-b-0 [&_tr:last-child]:border-b-0">
        {Object.entries(props).map(([propName, propData]) => (
          <Row key={propName} propName={propName} propData={propData} />
        ))}
      </Table.Body>
    </Table>
  )
}

type RowProps = {
  propName: string
  propData: PropData
}

type TypeNode = {
  text: string
  tooltipContent?: string
  canBeCopied?: boolean
}

const Row = ({
  propName,
  propData: { tsType: tsType, defaultValue, description },
}: RowProps) => {
  const normalizeRaw = (str: string): string => {
    return str
      .replaceAll("\\|", "|")
      .replaceAll("&#60;", "<")
      .replaceAll("&#62;", ">")
  }
  const getTypeRaw = useCallback((type: PropSpecType): string => {
    let raw = "raw" in type ? type.raw || type.name : type.name
    if ("type" in type) {
      if (type.type === "object") {
        raw = `{\n  ${type.signature.properties
          .map((property) => `${property.key}: ${property.value.name}`)
          .join("\n  ")}\n}`
      } else {
        raw = type.raw
      }
    } else if (type.name === "Array" && "elements" in type) {
      raw = type.elements.map((element) => getTypeRaw(element)).join(" | ")
    } else if (raw.startsWith("React.")) {
      return ""
    }

    return normalizeRaw(raw)
  }, [])
  const getTypeText = useCallback((type: PropSpecType): string => {
    if (type?.name === "signature" && "type" in type) {
      return type.type
    } else if (type?.name === "Array" && type.raw) {
      return normalizeRaw(type.raw) || "array"
    } else if ("raw" in type && type.raw?.startsWith("React.")) {
      return type.name.replace(/^React/, "")
    }

    return type.name || ""
  }, [])
  const getTypeTooltipContent = useCallback(
    (type: PropSpecType): string | undefined => {
      if (
        (type?.name === "signature" && "type" in type) ||
        (type?.name === "Array" && type.raw) ||
        ("raw" in type && type.raw)
      ) {
        return getTypeRaw(type)
      }

      return undefined
    },
    [getTypeRaw]
  )

  const typeNodes = useMemo((): TypeNode[] => {
    const typeNodes: TypeNode[] = []
    if (tsType?.name === "union" && "elements" in tsType) {
      tsType.elements.forEach((element) => {
        if (
          ("elements" in element && element.elements.length) ||
          "signature" in element
        ) {
          const elementTypeText = getTypeText(element)
          const elementTooltipContent = getTypeTooltipContent(element)
          typeNodes.push({
            text: elementTypeText,
            tooltipContent:
              elementTypeText !== elementTooltipContent
                ? elementTooltipContent
                : undefined,
          })
        } else if ("value" in element) {
          typeNodes.push({
            text: element.value,
            canBeCopied: true,
          })
        } else if ("raw" in element) {
          typeNodes.push({
            text: getTypeText(element),
            tooltipContent: getTypeTooltipContent(element),
          })
        } else {
          typeNodes.push({
            text: element.name,
          })
        }
      })
    } else if (tsType) {
      typeNodes.push({
        text: getTypeText(tsType),
        tooltipContent: getTypeTooltipContent(tsType),
      })
    }

    return typeNodes
  }, [tsType, getTypeText, getTypeTooltipContent])

  const defaultVal: string | undefined = defaultValue?.value as string

  return (
    <Table.Row>
      <Table.Cell>
        <div className="flex items-center gap-x-1">
          <InlineCode>{propName}</InlineCode>
          {description && (
            <Tooltip
              tooltipChildren={
                <MarkdownContent
                  allowedElements={["a", "code"]}
                  unwrapDisallowed={true}
                >
                  {description}
                </MarkdownContent>
              }
            >
              <InformationCircleSolid className="text-medusa-fg-subtle" />
            </Tooltip>
          )}
        </div>
      </Table.Cell>
      <Table.Cell>
        <div className="flex items-center flex-wrap gap-1 py-1">
          {typeNodes.map((typeNode, index) => (
            <div key={index} className="flex items-center gap-x-1">
              {index > 0 && <span>|</span>}
              {typeNode.tooltipContent && (
                <Tooltip
                  tooltipChildren={<pre>{typeNode.tooltipContent}</pre>}
                  className="font-mono !max-w-none"
                  tooltipClassName="!text-left"
                >
                  <div className="flex items-center gap-x-1">
                    <code>{typeNode.text}</code>
                    <InformationCircleSolid className="text-medusa-fg-subtle" />
                  </div>
                </Tooltip>
              )}
              {!typeNode.tooltipContent && (
                <>
                  {typeNode.canBeCopied && (
                    <InlineCode>{typeNode.text}</InlineCode>
                  )}
                  {!typeNode.canBeCopied && <code>{typeNode.text}</code>}
                </>
              )}
            </div>
          ))}
        </div>
      </Table.Cell>
      <Table.Cell className="text-right">
        {defaultVal && <InlineCode>{defaultVal}</InlineCode>}
        {!defaultVal && " - "}
      </Table.Cell>
    </Table.Row>
  )
}

export { PropTable }
