#!/usr/bin/env tsx

// Downloads SVG icons for every hip-3 asset listed in the picker, saving
// to public/icons/<ASSET>.svg. Skips assets that already have a local
// icon.

import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import {
  isOurDex,
  SUPPORTED_DEXES,
} from '@kinetiq-research/markets-shared/dexes'

const HL_INFO = 'https://api.hyperliquid.xyz/info'
const HL_COINS = 'https://app.hyperliquid.xyz/coins'
const scriptDir = path.dirname(fileURLToPath(import.meta.url))
const ICONS_DIR = path.join(scriptDir, '..', 'public', 'icons')

const RATE_LIMIT_DELAY_MS = 75

const sleep = (ms: number): Promise<void> =>
  new Promise((resolve) => {
    setTimeout(resolve, ms)
  })

type Outcome = 'downloaded' | 'missing' | 'skipped'

interface UniverseEntry {
  name: string
}

interface DexMeta {
  universe: Array<UniverseEntry>
}

const fetchDexMeta = async (dex: string): Promise<DexMeta> => {
  const response = await fetch(HL_INFO, {
    body: JSON.stringify({ dex, type: 'metaAndAssetCtxs' }),
    headers: { 'content-type': 'application/json' },
    method: 'POST',
  })
  if (!response.ok) {
    throw new Error(
      `metaAndAssetCtxs({ dex: ${dex} }) failed: ${response.status}`,
    )
  }
  const [meta] = (await response.json()) as [DexMeta, unknown]
  return meta
}

const downloadIcon = async ({
  assetName,
  fullCoin,
}: {
  assetName: string
  fullCoin: string
}): Promise<Outcome> => {
  const target = path.join(ICONS_DIR, `${assetName}.svg`)
  if (fs.existsSync(target)) {
    return 'skipped'
  }

  const response = await fetch(`${HL_COINS}/${fullCoin}.svg`)
  if (!response.ok) {
    console.warn(`  ✗ ${fullCoin} (HTTP ${response.status})`)
    return 'missing'
  }

  const body = await response.text()
  if (!body.includes('<svg')) {
    console.warn(`  ✗ ${fullCoin} (not an SVG)`)
    return 'missing'
  }

  fs.writeFileSync(target, body)
  console.log(`  ✓ ${assetName}`)
  return 'downloaded'
}

const isExcluded = ({ dex }: { dex: string }): boolean => !isOurDex(dex)

const main = async (): Promise<void> => {
  fs.mkdirSync(ICONS_DIR, { recursive: true })

  const counts: Record<Outcome, number> = {
    downloaded: 0,
    missing: 0,
    skipped: 0,
  }

  for (const dex of SUPPORTED_DEXES) {
    console.log(`\nFetching ${dex} universe…`)
    // oxlint-disable-next-line no-await-in-loop
    const meta = await fetchDexMeta(dex)
    const coins = meta.universe.map(({ name }) => name)

    for (const fullCoin of coins) {
      const segments = fullCoin.split(':')
      const assetName = segments[segments.length - 1]

      if (isExcluded({ dex })) {
        continue
      }

      // oxlint-disable-next-line no-await-in-loop
      const outcome = await downloadIcon({ assetName, fullCoin })
      counts[outcome] += 1
      if (outcome === 'downloaded') {
        // Rate-limit to stay polite to the CDN.
        // oxlint-disable-next-line no-await-in-loop
        await sleep(RATE_LIMIT_DELAY_MS)
      }
    }
  }

  console.log(
    `\nDone. downloaded=${counts.downloaded} skipped=${counts.skipped} missing=${counts.missing}`,
  )
}

try {
  await main()
} catch (error) {
  console.error(error)
  process.exit(1)
}
