import * as React from 'react'

import { cn } from './utils'

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface JsonPathBrowserProps extends Omit<React.ComponentProps<'div'>, 'onSelect'> {
  data: unknown
  expanded?: boolean
  searchable?: boolean
  showPath?: boolean
  showTypes?: boolean
  onSelect?: (path: string, value: unknown) => void
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function getValueType(value: unknown): string {
  if (value === null) return 'null'
  if (Array.isArray(value)) return 'array'
  return typeof value
}

function formatValue(value: unknown): string {
  if (value === null) return 'null'
  if (typeof value === 'string') return `"${value}"`
  if (typeof value === 'number' || typeof value === 'boolean') return String(value)
  return ''
}

function getTypeBadge(value: unknown): string {
  if (value === null) return 'null'
  if (Array.isArray(value)) return `[${value.length}]`
  if (typeof value === 'object') return `{${Object.keys(value).length}}`
  if (typeof value === 'string') return 'str'
  if (typeof value === 'number') return '123'
  if (typeof value === 'boolean') return 'bool'
  return typeof value
}

function isComposite(value: unknown): value is Record<string, unknown> | unknown[] {
  return value !== null && typeof value === 'object'
}

function copyText(text: string): void {
  void navigator.clipboard.writeText(text).catch(() => {})
}

// ---------------------------------------------------------------------------
// JsonNode (recursive renderer)
// ---------------------------------------------------------------------------

interface JsonNodeProps {
  path: string
  nodeKey: string
  value: unknown
  depth: number
  showTypes: boolean
  searchQuery: string
  expandedPaths: Set<string>
  selectedPath: string
  onToggle: (path: string) => void
  onSelectPath: (path: string, value: unknown) => void
}

function matchesSearch(key: string, value: unknown, query: string): boolean {
  if (query.length === 0) return false
  if (key.toLowerCase().includes(query)) return true
  if (!isComposite(value)) {
    return formatValue(value).toLowerCase().includes(query)
  }
  return false
}

function JsonNode({
  path,
  nodeKey,
  value,
  depth,
  showTypes,
  searchQuery,
  expandedPaths,
  selectedPath,
  onToggle,
  onSelectPath,
}: JsonNodeProps) {
  const isMatch = matchesSearch(nodeKey, value, searchQuery)

  if (isComposite(value)) {
    const expanded = expandedPaths.has(path)
    const entries = Array.isArray(value)
      ? value.map((v, i) => [String(i), v] as [string, unknown])
      : Object.entries(value)

    return (
      <div style={{ paddingLeft: `${depth * 16}px` }}>
        <div
          className={cn(
            'flex items-center gap-1 rounded px-1 py-0.5 text-sm',
            isMatch && 'bg-accent-amber/10',
            selectedPath === path && 'bg-surface-secondary'
          )}
        >
          <button
            onClick={() => onToggle(path)}
            className="text-foreground-secondary shrink-0 font-mono text-xs"
          >
            {expanded ? '\u25BE' : '\u25B8'}
          </button>
          {nodeKey.length > 0 && (
            <>
              <span
                className="text-accent-cyan cursor-pointer font-mono"
                onClick={() => onSelectPath(path, value)}
              >
                {nodeKey}
              </span>
              <span className="text-foreground-secondary">: </span>
            </>
          )}
          {showTypes && (
            <span
              className={cn(
                'rounded px-1 py-0.5 font-mono text-[10px]',
                'bg-surface-secondary text-foreground-secondary'
              )}
            >
              {getTypeBadge(value)}
            </span>
          )}
        </div>
        {expanded && (
          <div>
            {entries.map(([childKey, childValue]) => {
              const childPath = Array.isArray(value)
                ? `${path}[${childKey}]`
                : `${path}.${childKey}`
              return (
                <JsonNode
                  key={childKey}
                  path={childPath}
                  nodeKey={childKey}
                  value={childValue}
                  depth={depth + 1}
                  showTypes={showTypes}
                  searchQuery={searchQuery}
                  expandedPaths={expandedPaths}
                  selectedPath={selectedPath}
                  onToggle={onToggle}
                  onSelectPath={onSelectPath}
                />
              )
            })}
          </div>
        )}
      </div>
    )
  }

  // Leaf node
  const valueType = getValueType(value)

  return (
    <div style={{ paddingLeft: `${depth * 16}px` }}>
      <div
        className={cn(
          'flex items-center gap-1 rounded px-1 py-0.5 text-sm',
          isMatch && 'bg-accent-amber/10',
          selectedPath === path && 'bg-surface-secondary'
        )}
      >
        <span className="w-3.5 shrink-0" />
        {nodeKey.length > 0 && (
          <>
            <span
              className="text-accent-cyan cursor-pointer font-mono"
              onClick={() => onSelectPath(path, value)}
            >
              {nodeKey}
            </span>
            <span className="text-foreground-secondary">: </span>
          </>
        )}
        <span
          className={cn(
            'cursor-pointer font-mono',
            valueType === 'string' && 'text-accent-amber',
            valueType === 'number' && 'text-accent-violet',
            valueType === 'boolean' && 'text-accent-rose',
            valueType === 'null' && 'text-foreground-secondary italic'
          )}
          title="Click to copy value"
          onClick={() => {
            onSelectPath(path, value)
            const text = typeof value === 'object' ? JSON.stringify(value) : String(value)
            copyText(text)
          }}
        >
          {formatValue(value)}
        </span>
        {showTypes && (
          <span
            className={cn(
              'rounded px-1 py-0.5 font-mono text-[10px]',
              'bg-surface-secondary text-foreground-secondary'
            )}
          >
            {getTypeBadge(value)}
          </span>
        )}
      </div>
    </div>
  )
}

// ---------------------------------------------------------------------------
// Helpers for expanding paths
// ---------------------------------------------------------------------------

const MAX_RECURSION_DEPTH = 100

function collectAllPaths(value: unknown, path: string, set: Set<string>, depth = 0): void {
  if (depth > MAX_RECURSION_DEPTH || !isComposite(value)) return
  set.add(path)
  if (Array.isArray(value)) {
    value.forEach((v, i) => collectAllPaths(v, `${path}[${i}]`, set, depth + 1))
  } else {
    for (const [key, child] of Object.entries(value)) {
      collectAllPaths(child, `${path}.${key}`, set, depth + 1)
    }
  }
}

function expandMatchingPaths(
  value: unknown,
  path: string,
  query: string,
  set: Set<string>,
  depth = 0
): boolean {
  if (depth > MAX_RECURSION_DEPTH) return false
  if (!isComposite(value)) {
    return formatValue(value).toLowerCase().includes(query)
  }
  let hasMatch = false
  if (Array.isArray(value)) {
    value.forEach((v, i) => {
      if (expandMatchingPaths(v, `${path}[${i}]`, query, set, depth + 1)) hasMatch = true
    })
  } else {
    for (const [key, child] of Object.entries(value)) {
      if (key.toLowerCase().includes(query)) hasMatch = true
      if (expandMatchingPaths(child, `${path}.${key}`, query, set, depth + 1)) hasMatch = true
    }
  }
  if (hasMatch) set.add(path)
  return hasMatch
}

// ---------------------------------------------------------------------------
// JsonPathBrowser
// ---------------------------------------------------------------------------

function JsonPathBrowser({
  data,
  expanded = false,
  searchable = true,
  showPath = true,
  showTypes = true,
  onSelect,
  className,
  ...props
}: JsonPathBrowserProps) {
  const [expandedPaths, setExpandedPaths] = React.useState<Set<string>>(() => {
    if (expanded) {
      const set = new Set<string>()
      collectAllPaths(data, '$', set)
      return set
    }
    return new Set<string>()
  })
  const [selectedPath, setSelectedPath] = React.useState('')
  const [searchQuery, setSearchQuery] = React.useState('')

  const handleToggle = React.useCallback((path: string) => {
    setExpandedPaths(prev => {
      const next = new Set(prev)
      if (next.has(path)) {
        next.delete(path)
      } else {
        next.add(path)
      }
      return next
    })
  }, [])

  const handleSelectPath = React.useCallback(
    (path: string, value: unknown) => {
      setSelectedPath(path)
      onSelect?.(path, value)
    },
    [onSelect]
  )

  const handleExpandAll = () => {
    const set = new Set<string>()
    collectAllPaths(data, '$', set)
    setExpandedPaths(set)
  }

  const handleCollapseAll = () => {
    setExpandedPaths(new Set())
  }

  const handleSearch = (query: string) => {
    setSearchQuery(query.toLowerCase())
    if (query.length > 0) {
      const set = new Set<string>()
      expandMatchingPaths(data, '$', query.toLowerCase(), set)
      setExpandedPaths(set)
    }
  }

  const handlePathCopy = () => {
    const pathText = selectedPath.length > 0 ? selectedPath : '$'
    copyText(pathText)
  }

  return (
    <div
      data-slot="json-path-browser"
      className={cn(
        'bg-glass border-border-glass flex flex-col rounded-lg border backdrop-blur-[20px]',
        className
      )}
      {...props}
    >
      {/* Toolbar */}
      <div className="border-border-glass flex items-center gap-2 border-b px-3 py-2">
        <button
          onClick={handleExpandAll}
          className="bg-surface-secondary text-foreground-secondary hover:text-foreground rounded px-2 py-1 text-xs transition-colors"
        >
          Expand All
        </button>
        <button
          onClick={handleCollapseAll}
          className="bg-surface-secondary text-foreground-secondary hover:text-foreground rounded px-2 py-1 text-xs transition-colors"
        >
          Collapse All
        </button>
        {searchable && (
          <input
            type="text"
            placeholder="Search keys or values..."
            className="bg-surface-secondary text-foreground placeholder:text-foreground-secondary ml-auto rounded px-2 py-1 text-xs outline-none"
            onChange={e => handleSearch(e.target.value)}
          />
        )}
      </div>

      {/* Path bar */}
      {showPath && (
        <div
          className="border-border-glass text-foreground-secondary cursor-pointer border-b px-3 py-1.5 font-mono text-xs transition-colors hover:text-foreground"
          title="Click to copy path"
          onClick={handlePathCopy}
        >
          {selectedPath.length > 0 ? selectedPath : '$'}
        </div>
      )}

      {/* Tree */}
      <div className="flex-1 overflow-auto p-2">
        <JsonNode
          path="$"
          nodeKey=""
          value={data}
          depth={0}
          showTypes={showTypes}
          searchQuery={searchQuery}
          expandedPaths={expandedPaths}
          selectedPath={selectedPath}
          onToggle={handleToggle}
          onSelectPath={handleSelectPath}
        />
      </div>
    </div>
  )
}
JsonPathBrowser.displayName = 'JsonPathBrowser'

export { JsonPathBrowser }
