import { getGruCatalogPosture } from '@/services/api/gruCatalog'

export type DirectSearchTarget =
  | { kind: 'address'; href: string; label: string }
  | { kind: 'transaction'; href: string; label: string }
  | { kind: 'block'; href: string; label: string }
  | { kind: 'token'; href: string; label: string }

export interface SearchTokenHint {
  chainId?: number
  symbol?: string
  address?: string
  name?: string
  tags?: string[]
}

export interface RawExplorerSearchItem {
  type?: string | null
  address?: string | null
  block_number?: number | string | null
  transaction_hash?: string | null
  priority?: number | null
  name?: string | null
  symbol?: string | null
  token_type?: string | null
}

export interface ExplorerSearchResult {
  type: string
  chain_id: number
  data: {
    hash?: string
    address?: string
    number?: number
  }
  score: number
  href?: string
  label: string
  name?: string
  symbol?: string
  token_type?: string
  is_curated_token?: boolean
  is_gru_token?: boolean
  is_x402_ready?: boolean
  is_wrapped_transport?: boolean
  currency_code?: string
  match_reason?: string
  matched_tags?: string[]
}

const addressPattern = /^0x[a-f0-9]{40}$/i
const transactionHashPattern = /^0x[a-f0-9]{64}$/i
const blockNumberPattern = /^\d+$/

export function inferDirectSearchTarget(query: string): DirectSearchTarget | null {
  const trimmed = query.trim()
  if (!trimmed) {
    return null
  }

  if (addressPattern.test(trimmed)) {
    return {
      kind: 'address',
      href: `/addresses/0x${trimmed.slice(2)}`,
      label: 'Open address',
    }
  }

  if (transactionHashPattern.test(trimmed)) {
    return {
      kind: 'transaction',
      href: `/transactions/0x${trimmed.slice(2)}`,
      label: 'Open transaction',
    }
  }

  if (blockNumberPattern.test(trimmed)) {
    return {
      kind: 'block',
      href: `/blocks/${trimmed}`,
      label: 'Open block',
    }
  }

  return null
}

export function inferTokenSearchTarget(query: string, tokens: SearchTokenHint[] = []): DirectSearchTarget | null {
  const trimmed = query.trim()
  if (!trimmed) {
    return null
  }

  const lower = trimmed.toLowerCase()
  const matched = tokens.find((token) => {
    if (token.chainId !== 138) return false
    return token.address?.toLowerCase() === lower || token.symbol?.toLowerCase() === lower
  })

  if (!matched?.address) {
    return null
  }

  return {
    kind: 'token',
    href: `/tokens/${matched.address}`,
    label: `Open token${matched.symbol ? ` (${matched.symbol})` : ''}`,
  }
}

function normalizeNumber(value: string | number | null | undefined): number | undefined {
  if (typeof value === 'number' && Number.isFinite(value)) {
    return value
  }
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value)
    if (Number.isFinite(parsed)) {
      return parsed
    }
  }
  return undefined
}

function getTypeWeight(type: string): number {
  switch (type) {
    case 'token':
      return 40
    case 'transaction':
      return 30
    case 'address':
      return 20
    case 'block':
      return 10
    default:
      return 0
  }
}

function getExactnessBoost(query: string, item: RawExplorerSearchItem): number {
  const trimmed = query.trim().toLowerCase()
  if (!trimmed) {
    return 0
  }

  const candidates = [
    item.address?.toLowerCase(),
    item.transaction_hash?.toLowerCase(),
    item.symbol?.toLowerCase(),
    item.name?.toLowerCase(),
    normalizeNumber(item.block_number)?.toString(),
  ].filter((value): value is string => Boolean(value))

  return candidates.includes(trimmed) ? 1000 : 0
}

function getCuratedMatchReason(query: string, token?: SearchTokenHint): string | undefined {
  if (!token) return undefined
  const trimmed = query.trim().toLowerCase()
  if (!trimmed) return undefined
  if (token.address?.toLowerCase() === trimmed) return 'exact curated token address'
  if (token.symbol?.toLowerCase() === trimmed) return 'exact curated token symbol'
  if (token.name?.toLowerCase() === trimmed) return 'exact curated token name'
  if (token.symbol?.toLowerCase().includes(trimmed)) return 'symbol match'
  if (token.name?.toLowerCase().includes(trimmed)) return 'name match'
  if (token.tags?.some((tag) => tag.toLowerCase().includes(trimmed))) return 'tag match'
  return undefined
}

function getHref(type: string, item: RawExplorerSearchItem, curatedToken: SearchTokenHint | undefined): string | undefined {
  if ((type === 'token' || curatedToken) && item.address) {
    return `/tokens/${item.address}`
  }
  if (type === 'address' && item.address) {
    return `/addresses/${item.address}`
  }
  if (type === 'transaction' && item.transaction_hash) {
    return `/transactions/${item.transaction_hash}`
  }
  const blockNumber = normalizeNumber(item.block_number)
  if (type === 'block' && blockNumber != null) {
    return `/blocks/${blockNumber}`
  }
  return undefined
}

function getLabel(type: string, item: RawExplorerSearchItem, curatedToken: SearchTokenHint | undefined): string {
  if ((type === 'token' || curatedToken) && item.symbol) {
    return `Token${item.symbol ? ` · ${item.symbol}` : ''}`
  }
  if ((type === 'token' || curatedToken) && item.name) {
    return 'Token'
  }
  switch (type) {
    case 'transaction':
      return 'Transaction'
    case 'block':
      return 'Block'
    case 'address':
      return 'Address'
    default:
      return 'Search Result'
  }
}

function getDeduplicationKey(type: string, item: RawExplorerSearchItem): string {
  if ((type === 'token' || type === 'address') && item.address) {
    return `entity:${item.address.toLowerCase()}`
  }
  if (type === 'transaction' && item.transaction_hash) {
    return `tx:${item.transaction_hash.toLowerCase()}`
  }
  const blockNumber = normalizeNumber(item.block_number)
  if (type === 'block' && blockNumber != null) {
    return `block:${blockNumber}`
  }
  return `${type}:${item.address || item.transaction_hash || item.block_number || item.name || item.symbol || 'unknown'}`
}

export function normalizeExplorerSearchResults(
  query: string,
  items: RawExplorerSearchItem[] = [],
  tokens: SearchTokenHint[] = [],
): ExplorerSearchResult[] {
  const curatedLookup = new Map<string, SearchTokenHint>()
  for (const token of tokens) {
    if (token.chainId !== 138 || !token.address) continue
    curatedLookup.set(token.address.toLowerCase(), token)
  }

  const deduped = new Map<string, ExplorerSearchResult & { _ranking: number }>()

  for (const item of items) {
    const type = item.type || 'unknown'
    const blockNumber = normalizeNumber(item.block_number)
    const curatedToken = item.address ? curatedLookup.get(item.address.toLowerCase()) : undefined
    const normalizedType = type === 'address' && curatedToken ? 'token' : type
    const gruPosture =
      normalizedType === 'token' || normalizedType === 'address'
        ? getGruCatalogPosture({
            symbol: item.symbol || curatedToken?.symbol,
            address: item.address,
            tags: curatedToken?.tags,
          })
        : null
    const ranking =
      getExactnessBoost(query, item) +
      (item.priority ?? 0) * 10 +
      getTypeWeight(normalizedType) +
      (curatedToken ? 15 : 0) +
      (gruPosture?.isGru ? 8 : 0) +
      (gruPosture?.isX402Ready ? 5 : 0)

    const result: ExplorerSearchResult & { _ranking: number } = {
      type: normalizedType,
      chain_id: 138,
      data: {
        hash: item.transaction_hash || undefined,
        address: item.address || undefined,
        number: blockNumber,
      },
      score: item.priority ?? 0,
      href: getHref(normalizedType, item, curatedToken),
      label: getLabel(normalizedType, item, curatedToken),
      name: item.name || curatedToken?.name || undefined,
      symbol: item.symbol || curatedToken?.symbol || undefined,
      token_type: item.token_type || undefined,
      is_curated_token: Boolean(curatedToken),
      is_gru_token: gruPosture?.isGru || false,
      is_x402_ready: gruPosture?.isX402Ready || false,
      is_wrapped_transport: gruPosture?.isWrappedTransport || false,
      currency_code: gruPosture?.currencyCode,
      match_reason:
        getCuratedMatchReason(query, curatedToken) ||
        (getExactnessBoost(query, item) > 0 ? 'exact match' : undefined),
      matched_tags: curatedToken?.tags?.filter((tag) => tag.toLowerCase().includes(query.trim().toLowerCase())) || [],
      _ranking: ranking,
    }

    const key = getDeduplicationKey(normalizedType, item)
    const existing = deduped.get(key)
    if (!existing || result._ranking > existing._ranking) {
      deduped.set(key, result)
    }
  }

  return Array.from(deduped.values())
    .sort((left, right) => right._ranking - left._ranking)
    .map(({ _ranking, ...result }) => result)
}

export function suggestCuratedTokens(query: string, tokens: SearchTokenHint[] = []): SearchTokenHint[] {
  const trimmed = query.trim().toLowerCase()
  if (!trimmed) return []

  return tokens
    .filter((token) => token.chainId === 138)
    .filter((token) =>
      token.symbol?.toLowerCase().includes(trimmed) ||
      token.name?.toLowerCase().includes(trimmed) ||
      token.tags?.some((tag) => tag.toLowerCase().includes(trimmed)),
    )
    .sort((left, right) => {
      const leftExact = left.symbol?.toLowerCase() === trimmed || left.name?.toLowerCase() === trimmed
      const rightExact = right.symbol?.toLowerCase() === trimmed || right.name?.toLowerCase() === trimmed
      if (leftExact !== rightExact) return leftExact ? -1 : 1
      return (left.symbol || left.name || '').localeCompare(right.symbol || right.name || '')
    })
    .slice(0, 5)
}
