import { Tree, Typography } from 'antd'
import type { DataNode } from 'antd/es/tree'
import { useMemo } from 'react'

export type JsonViewerProps = {
  value: unknown
  height?: number
  defaultExpandAll?: boolean
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return !!value && typeof value === 'object' && !Array.isArray(value)
}

function preview(value: unknown) {
  if (value === null) return 'null'
  if (value === undefined) return 'undefined'
  if (typeof value === 'string') return JSON.stringify(value)
  if (typeof value === 'number' || typeof value === 'boolean') return String(value)
  if (Array.isArray(value)) return `[${value.length}]`
  if (isRecord(value)) return `{${Object.keys(value).length}}`
  return String(value)
}

function toTreeData(value: unknown, path: string): DataNode[] {
  if (Array.isArray(value)) {
    return value.map((item, idx) => {
      const nextPath = `${path}[${idx}]`
      const hasChildren = Array.isArray(item) || isRecord(item)
      return {
        key: nextPath,
        title: (
          <span>
            <Typography.Text code>{idx}</Typography.Text>
            <Typography.Text type="secondary" style={{ marginLeft: 8 }}>
              {preview(item)}
            </Typography.Text>
          </span>
        ),
        children: hasChildren ? toTreeData(item, nextPath) : undefined,
      }
    })
  }

  if (isRecord(value)) {
    return Object.entries(value).map(([k, v]) => {
      const nextPath = path ? `${path}.${k}` : k
      const hasChildren = Array.isArray(v) || isRecord(v)
      return {
        key: nextPath,
        title: (
          <span>
            <Typography.Text code>{k}</Typography.Text>
            <Typography.Text type="secondary" style={{ marginLeft: 8 }}>
              {preview(v)}
            </Typography.Text>
          </span>
        ),
        children: hasChildren ? toTreeData(v, nextPath) : undefined,
      }
    })
  }

  return [
    {
      key: path || 'value',
      title: <Typography.Text type="secondary">{preview(value)}</Typography.Text>,
    },
  ]
}

export default function JsonViewer({
  value,
  height = 360,
  defaultExpandAll = true,
}: JsonViewerProps) {
  const treeData = useMemo<DataNode[]>(() => toTreeData(value, ''), [value])

  if (value === null || value === undefined) {
    return <Typography.Text type="secondary">空</Typography.Text>
  }

  return (
    <Tree
      blockNode
      showLine
      height={height}
      defaultExpandAll={defaultExpandAll}
      treeData={treeData}
    />
  )
}

