import type { MoneyLayer } from './types';
import { GL_CODES } from './money-supply';

export type M2TokenCapabilities = {
  symbol: string;
  name: string;
  address: string;
  decimals: number;
  native?: boolean;
  currencyCode?: string;
  moneyLayer: MoneyLayer;
  omnlLine: string;
  loadFromGl: string;
  swappable: boolean;
  convertible: boolean;
  transferableInternal: boolean;
  transferableExternal: boolean;
};

export type M2TokenRegistryFile = {
  version: string;
  chainId: number;
  defaultMoneyLayer: MoneyLayer;
  defaultLoadFromGl: string;
  tokens: M2TokenCapabilities[];
};

const CURRENCY_FROM_SYMBOL: Record<string, string> = {
  USD: 'USD',
  EUR: 'EUR',
  GBP: 'GBP',
  AUD: 'AUD',
  JPY: 'JPY',
  CHF: 'CHF',
  CAD: 'CAD',
  XAU: 'XAU',
  BTC: 'BTC',
  ETH: 'ETH',
  BNB: 'BNB',
  POL: 'POL',
  AVAX: 'AVAX',
  CRO: 'CRO',
  XDAI: 'XDAI',
  CELO: 'CELO',
  WEMIX: 'WEMIX',
};

export function inferCurrencyCode(symbol: string): string {
  const s = symbol.toUpperCase();
  for (const [key, code] of Object.entries(CURRENCY_FROM_SYMBOL)) {
    if (s.includes(key)) return code;
  }
  if (s.startsWith('C') && s.length > 1) {
    for (const [key, code] of Object.entries(CURRENCY_FROM_SYMBOL)) {
      if (s.slice(1).includes(key)) return code;
    }
  }
  return 'USD';
}

export function resolveOmnlLineM2(currencyCode?: string, symbol?: string): string {
  const ccy = (currencyCode || inferCurrencyCode(symbol ?? 'USD')).toUpperCase();
  return `${ccy}-M2`;
}

export function withM2Capabilities(
  token: {
    symbol: string;
    name: string;
    address: string;
    decimals: number;
    native?: boolean;
    currencyCode?: string;
    omnlLine?: string;
  },
  overrides: Partial<M2TokenCapabilities> = {},
): M2TokenCapabilities {
  const currencyCode = token.currencyCode ?? inferCurrencyCode(token.symbol);
  return {
    symbol: token.symbol,
    name: token.name,
    address: token.address,
    decimals: token.decimals,
    native: token.native,
    currencyCode,
    moneyLayer: 'M2',
    omnlLine: token.omnlLine ?? resolveOmnlLineM2(currencyCode, token.symbol),
    loadFromGl: GL_CODES.M2_BROAD,
    swappable: true,
    convertible: true,
    transferableInternal: true,
    transferableExternal: true,
    ...overrides,
  };
}

export function mergeExchangeTokensWithM2Registry(
  baseTokens: {
    symbol: string;
    name: string;
    address: string;
    decimals: number;
    native?: boolean;
    omnlLine?: string;
  }[],
  registry: M2TokenRegistryFile,
): M2TokenCapabilities[] {
  const byAddress = new Map<string, M2TokenCapabilities>();
  for (const t of registry.tokens) {
    byAddress.set(t.address.toLowerCase(), t);
  }
  for (const t of baseTokens) {
    const key = t.address.toLowerCase();
    if (!byAddress.has(key)) {
      byAddress.set(key, withM2Capabilities(t));
    }
  }
  return Array.from(byAddress.values()).sort((a, b) => a.symbol.localeCompare(b.symbol));
}

export function findM2TokenByAddress(
  registry: M2TokenRegistryFile,
  address: string,
): M2TokenCapabilities | undefined {
  const lower = address.toLowerCase();
  return registry.tokens.find((t) => t.address.toLowerCase() === lower);
}

export function findM2TokenBySymbol(
  registry: M2TokenRegistryFile,
  symbol: string,
): M2TokenCapabilities | undefined {
  const s = symbol.trim().toLowerCase();
  return registry.tokens.find((t) => t.symbol.toLowerCase() === s);
}
