import type { AllPerpMetasResponse } from '@nktkas/hyperliquid'
import type { PerpsAsset } from './asset'
import {
  HIP3_ASSET_INDEX_BASE,
  HIP3_DEX_INDEX_MULTIPLIER,
} from './asset-indices'
import { getDexNameFromMeta } from './get-dex-name-from-meta'
import { parsePerpsHyperliquidSymbol } from './parse-perps-hyperliquid-symbol'
import { toDisplaySymbol } from './to-display-symbol'
import { toTradeUrlSymbol } from './to-trade-url-symbol'

// Collateral token index -> quote asset symbol. Indices come from the
// Hyperliquid spot meta and are stable, so they are hardcoded here to avoid
// threading spot meta through perps normalization.
//
// This is a DISPLAY LABEL, not a key. It disagrees with the token names
// Hyperliquid publishes (index 268 is named `USDT0` in the spot meta), and an
// index absent from this map is labelled `USDC` by the fallback below. Anything
// that has to identify the settlement token — matching a balance, a fee, or a
// collateral figure — keys on `collateralTokenIndex` instead.
const COLLATERAL_TOKEN_SYMBOLS: Record<number, PerpsAsset['quoteAssetSymbol']> =
  {
    0: 'USDC',
    235: 'USDE',
    268: 'USDT',
    360: 'USDH',
  }

const getQuoteAssetSymbol = (
  collateralToken: number,
): PerpsAsset['quoteAssetSymbol'] =>
  COLLATERAL_TOKEN_SYMBOLS[collateralToken] ?? 'USDC'

type NormalizedPerpsMetaUniverseItem =
  AllPerpMetasResponse[number]['universe'][number] & {
    asset: PerpsAsset
  }

type NormalizedPerpsMetaDex = Omit<AllPerpMetasResponse[number], 'universe'> & {
  universe: Array<NormalizedPerpsMetaUniverseItem>
}

export type NormalizedPerpsMeta = Array<NormalizedPerpsMetaDex>

export const normalizePerpsMeta = ({
  perpsAnnotations,
  perpsMeta,
}: {
  perpsAnnotations?: Record<string, string>
  perpsMeta: AllPerpMetasResponse
}): NormalizedPerpsMeta =>
  perpsMeta.map((dexMeta, dexIndex) => {
    const dex = getDexNameFromMeta({ meta: dexMeta })
    const quoteAssetSymbol = getQuoteAssetSymbol(dexMeta.collateralToken)

    const universe = dexMeta.universe.map((universeItem, positionInDex) => {
      const { symbol } = parsePerpsHyperliquidSymbol({
        hyperliquidSymbol: universeItem.name,
      })
      // Curated override → live annotation → the raw cleaned name. `symbol`
      // stays the raw cleaned name. The precedence lives in `toDisplaySymbol`
      // because Pulse's `buildTickerUniverse` resolves the same question from
      // the same payloads, and when the two disagreed the trade app called one
      // listing `QQQ` while Pulse briefs called it `USTECH`.
      const preferredSymbol = toDisplaySymbol({
        // `Object.hasOwn`, not a bare index: `perpsAnnotations` is a plain
        // object literal typed `Record<string, string>`, so a listing named
        // `constructor` would read a FUNCTION off the prototype chain, and the
        // typechecker types the read as `string`. Same guard, same reason as
        // `toOverride` in `to-display-symbol.ts`.
        annotationDisplayName:
          perpsAnnotations !== undefined &&
          Object.hasOwn(perpsAnnotations, universeItem.name)
            ? perpsAnnotations[universeItem.name]
            : undefined,
        hyperliquidSymbol: universeItem.name,
        symbol,
      })
      const display = preferredSymbol
      // `dex` is passed because a listing whose ticker is live on a second dex
      // we surface keeps the prefix in its url — see `PREFIXED_URL_SYMBOLS`.
      const tradeUrlSymbol = toTradeUrlSymbol({ dex, symbol: preferredSymbol })
      // Main HL (dex index 0) uses the position directly; HIP-3 dexes use the
      // computed offset formula.
      const assetIndex =
        dexIndex === 0
          ? positionInDex
          : HIP3_ASSET_INDEX_BASE +
            dexIndex * HIP3_DEX_INDEX_MULTIPLIER +
            positionInDex

      return {
        ...universeItem,
        asset: {
          assetIndex,
          collateralTokenIndex: dexMeta.collateralToken,
          dex,
          display,
          hyperliquidSymbol: universeItem.name,
          maxLeverage: Number(universeItem.maxLeverage),
          onlyIsolated: !!universeItem.onlyIsolated,
          quoteAssetSymbol,
          symbol,
          szDecimals: universeItem.szDecimals,
          tradeUrlSymbol,
          type: 'perps' as const,
        },
      }
    })

    return {
      ...dexMeta,
      universe,
    }
  })
