import type { SearchTokenHint, WrappedTransportRegistryRow } from '@/utils/search'

const HUB_CHAIN_ID = 138

/** c* on hub ↔ cW* on destination — mirrors config/token-mapping-multichain.json */
export const HUB_TO_WRAPPED_SYMBOL: Record<string, string> = {
  cUSDT: 'cWUSDT',
  cUSDC: 'cWUSDC',
  cEURC: 'cWEURC',
  cEURT: 'cWEURT',
  cGBPC: 'cWGBPC',
  cGBPT: 'cWGBPT',
  cAUDC: 'cWAUDC',
  cJPYC: 'cWJPYC',
  cCHFC: 'cWCHFC',
  cCADC: 'cWCADC',
  cXAUC: 'cWXAUC',
  cXAUT: 'cWXAUT',
  cBTC: 'cWBTC',
}

const WRAPPED_TO_HUB_SYMBOL = Object.fromEntries(
  Object.entries(HUB_TO_WRAPPED_SYMBOL).map(([hub, wrapped]) => [wrapped.toLowerCase(), hub]),
) as Record<string, string>

const addressPattern = /^0x[a-f0-9]{40}$/i

export interface MeshSeed {
  hubChainId: number
  hubAddress?: string
  hubSymbol: string
  wrappedSymbol: string
  sourceChainId?: number
  sourceAddress?: string
  matchReason: string
  needsHubResolve: boolean
}

export interface MeshCounterpartRow {
  chainId: number
  chainName: string
  symbol: string
  address: string
  role: 'hub' | 'wrapped' | 'registry'
  mappedVia: 'token-mapping' | 'cw-registry' | 'curated'
}

function hubSymbolFor(rawSymbol: string): string | null {
  const sym = rawSymbol.trim()
  if (!sym) return null
  const lower = sym.toLowerCase()
  if (HUB_TO_WRAPPED_SYMBOL[sym]) return sym
  return WRAPPED_TO_HUB_SYMBOL[lower] ?? null
}

function wrappedSymbolFor(hubSymbol: string): string {
  return HUB_TO_WRAPPED_SYMBOL[hubSymbol] ?? hubSymbol
}

function findHubInCurated(hubSymbol: string, curatedTokens: SearchTokenHint[]): SearchTokenHint | undefined {
  const lower = hubSymbol.toLowerCase()
  return curatedTokens.find(
    (token) => token.chainId === HUB_CHAIN_ID && token.symbol?.toLowerCase() === lower && token.address,
  )
}

function findHubInRegistry(hubSymbol: string, rows: WrappedTransportRegistryRow[]): WrappedTransportRegistryRow | undefined {
  const lower = hubSymbol.toLowerCase()
  return rows.find((row) => row.chainId === HUB_CHAIN_ID && row.symbol.toLowerCase() === lower)
}

export function inferMeshSeed(
  query: string,
  curatedTokens: SearchTokenHint[] = [],
  wrappedRows: WrappedTransportRegistryRow[] = [],
): MeshSeed | null {
  const trimmed = query.trim()
  if (!trimmed) return null

  if (addressPattern.test(trimmed)) {
    const lower = trimmed.toLowerCase()

    const curated = curatedTokens.find(
      (token) => token.chainId === HUB_CHAIN_ID && token.address?.toLowerCase() === lower,
    )
    if (curated?.address && curated.symbol) {
      const hubSymbol = hubSymbolFor(curated.symbol) ?? curated.symbol
      return {
        hubChainId: HUB_CHAIN_ID,
        hubAddress: curated.address,
        hubSymbol,
        wrappedSymbol: wrappedSymbolFor(hubSymbol),
        sourceChainId: HUB_CHAIN_ID,
        sourceAddress: curated.address,
        matchReason: 'curated hub address',
        needsHubResolve: false,
      }
    }

    const registryHit = wrappedRows.find((row) => row.address.toLowerCase() === lower)
    if (registryHit) {
      const hubSym = hubSymbolFor(registryHit.symbol)
      if (!hubSym) return null
      const hubRow = findHubInRegistry(hubSym, wrappedRows) ?? findHubInCurated(hubSym, curatedTokens)
      if (registryHit.chainId === HUB_CHAIN_ID) {
        return {
          hubChainId: HUB_CHAIN_ID,
          hubAddress: registryHit.address,
          hubSymbol: hubSym,
          wrappedSymbol: wrappedSymbolFor(hubSym),
          sourceChainId: HUB_CHAIN_ID,
          sourceAddress: registryHit.address,
          matchReason: 'registry hub address',
          needsHubResolve: false,
        }
      }
      return {
        hubChainId: HUB_CHAIN_ID,
        hubAddress: hubRow?.address,
        hubSymbol: hubSym,
        wrappedSymbol: wrappedSymbolFor(hubSym),
        sourceChainId: registryHit.chainId,
        sourceAddress: registryHit.address,
        matchReason: 'registry wrapped address',
        needsHubResolve: !hubRow?.address,
      }
    }

    return null
  }

  const hubFromSymbol = hubSymbolFor(trimmed)
  if (hubFromSymbol) {
    const hubRow = findHubInRegistry(hubFromSymbol, wrappedRows) ?? findHubInCurated(hubFromSymbol, curatedTokens)
    return {
      hubChainId: HUB_CHAIN_ID,
      hubAddress: hubRow?.address,
      hubSymbol: hubFromSymbol,
      wrappedSymbol: wrappedSymbolFor(hubFromSymbol),
      matchReason: trimmed.toLowerCase() === hubFromSymbol.toLowerCase() ? 'hub symbol' : 'wrapped symbol',
      needsHubResolve: !hubRow?.address,
    }
  }

  return null
}

export function buildMeshCounterpartRows(
  seed: MeshSeed,
  resolvedByChain: Map<number, string>,
  wrappedRows: WrappedTransportRegistryRow[],
  chainNameById: Map<number, string>,
): MeshCounterpartRow[] {
  const rows: MeshCounterpartRow[] = []
  const seen = new Set<string>()

  const push = (row: MeshCounterpartRow) => {
    const key = `${row.chainId}:${row.address.toLowerCase()}`
    if (seen.has(key)) return
    seen.add(key)
    rows.push(row)
  }

  for (const [chainId, address] of resolvedByChain.entries()) {
    if (!/^0x[a-fA-F0-9]{40}$/.test(address)) continue
    push({
      chainId,
      chainName: chainNameById.get(chainId) ?? `Chain ${chainId}`,
      symbol: chainId === HUB_CHAIN_ID ? seed.hubSymbol : seed.wrappedSymbol,
      address,
      role: chainId === HUB_CHAIN_ID ? 'hub' : 'wrapped',
      mappedVia: 'token-mapping',
    })
  }

  for (const row of wrappedRows) {
    const sym = row.symbol.toLowerCase()
    if (sym !== seed.hubSymbol.toLowerCase() && sym !== seed.wrappedSymbol.toLowerCase()) continue
    push({
      chainId: row.chainId,
      chainName: row.chainName || chainNameById.get(row.chainId) || `Chain ${row.chainId}`,
      symbol: row.symbol,
      address: row.address,
      role: row.chainId === HUB_CHAIN_ID ? 'hub' : 'registry',
      mappedVia: 'cw-registry',
    })
  }

  return rows.sort((left, right) => {
    if (left.chainId === HUB_CHAIN_ID) return -1
    if (right.chainId === HUB_CHAIN_ID) return 1
    return left.chainId - right.chainId
  })
}

export function buildChainNameMap(wrappedRows: WrappedTransportRegistryRow[]): Map<number, string> {
  const map = new Map<number, string>()
  for (const row of wrappedRows) {
    if (!map.has(row.chainId) && row.chainName) {
      map.set(row.chainId, row.chainName)
    }
  }
  map.set(HUB_CHAIN_ID, map.get(HUB_CHAIN_ID) ?? 'Chain 138')
  return map
}
