import type { DeploymentStatusFile } from './deployment-status';
import { loadDeploymentStatusFile, resolveDeploymentStatusPath } from './deployment-status';

export type GruV2PmmSection = 'pmmPools' | 'pmmPoolsVolatile' | 'gasPmmPools';

export interface GruV2DeploymentPoolRow {
  chainId: number;
  chainName: string;
  section: GruV2PmmSection;
  status: 'live' | 'routing_enabled' | 'configured' | 'proof_required';
  statusReason: string;
  baseSymbol: string;
  quoteSymbol: string;
  baseAddress: string;
  quoteAddress: string;
  poolAddress: string;
  feeBps?: number;
  role?: string;
  publicRoutingEnabled?: boolean;
  familyKey?: string;
  venue?: string;
}

interface ChainTokenMaps {
  cwTokens?: Record<string, string>;
  gasMirrors?: Record<string, string>;
  anchorAddresses?: Record<string, string>;
  gasQuoteAddresses?: Record<string, string>;
}

/** Resolve a pool leg symbol to an address using deployment-status chain maps (cW*, anchors, gas quotes). */
export function resolveDeploymentTokenAddress(chain: ChainTokenMaps, symbol: string): string | null {
  const candidates = [
    chain.cwTokens?.[symbol],
    chain.gasMirrors?.[symbol],
    chain.anchorAddresses?.[symbol],
    chain.gasQuoteAddresses?.[symbol],
  ];
  for (const a of candidates) {
    if (typeof a === 'string' && a.startsWith('0x')) {
      return a.toLowerCase();
    }
  }
  return null;
}

function pushTokenPoolIndex(
  byChainTokenPools: Map<string, Set<string>>,
  chainId: number,
  tokenAddress: string,
  poolAddress: string
): void {
  const key = `${chainId}:${tokenAddress.toLowerCase()}`;
  let set = byChainTokenPools.get(key);
  if (!set) {
    set = new Set();
    byChainTokenPools.set(key, set);
  }
  set.add(poolAddress.toLowerCase());
}

/**
 * Build GRU v2 PMM pool rows and a (chainId:token) → official pool addresses index from deployment-status data.
 * Includes stable mesh (`pmmPools`), volatile (`pmmPoolsVolatile`), and gas (`gasPmmPools`) sections.
 */
export function buildGruV2PoolRegistryFromDeploymentData(data: DeploymentStatusFile): {
  rows: GruV2DeploymentPoolRow[];
  byChainTokenPools: Map<string, Set<string>>;
} {
  const rows: GruV2DeploymentPoolRow[] = [];
  const byChainTokenPools = new Map<string, Set<string>>();

  const sections: GruV2PmmSection[] = ['pmmPools', 'pmmPoolsVolatile', 'gasPmmPools'];

  for (const [cid, rawChain] of Object.entries(data.chains ?? {})) {
    const chainId = Number(cid);
    if (Number.isNaN(chainId)) continue;
    const chain = rawChain as ChainTokenMaps & {
      name?: string;
      pmmPools?: unknown;
      pmmPoolsVolatile?: unknown;
      gasPmmPools?: unknown;
    };
    const chainName = typeof chain.name === 'string' ? chain.name : `Chain ${cid}`;

    for (const section of sections) {
      const arr = chain[section];
      if (!Array.isArray(arr)) continue;

      for (const raw of arr) {
        if (!raw || typeof raw !== 'object') continue;
        const pool = raw as Record<string, unknown>;
        const poolAddress = typeof pool.poolAddress === 'string' ? pool.poolAddress.trim() : '';
        const baseSymbol = typeof pool.base === 'string' ? pool.base : '';
        const quoteSymbol = typeof pool.quote === 'string' ? pool.quote : '';
        if (!poolAddress.startsWith('0x') || !baseSymbol || !quoteSymbol) continue;

        const baseAddress = resolveDeploymentTokenAddress(chain, baseSymbol);
        const quoteAddress = resolveDeploymentTokenAddress(chain, quoteSymbol);
        if (!baseAddress || !quoteAddress) continue;

        const row: GruV2DeploymentPoolRow = {
          chainId,
          chainName,
          section,
          status:
            pool.publicRoutingEnabled === true
              ? 'routing_enabled'
              : poolAddress.startsWith('0x')
                ? 'live'
                : 'configured',
          statusReason:
            pool.publicRoutingEnabled === true
              ? 'Pool address is configured in deployment-status and public routing is enabled.'
              : 'Pool address is configured in deployment-status; routing enablement is not asserted.',
          baseSymbol,
          quoteSymbol,
          baseAddress,
          quoteAddress,
          poolAddress: poolAddress.toLowerCase(),
          feeBps: typeof pool.feeBps === 'number' ? pool.feeBps : undefined,
          role: typeof pool.role === 'string' ? pool.role : undefined,
          publicRoutingEnabled:
            typeof pool.publicRoutingEnabled === 'boolean' ? pool.publicRoutingEnabled : undefined,
          familyKey: typeof pool.familyKey === 'string' ? pool.familyKey : undefined,
          venue: typeof pool.venue === 'string' ? pool.venue : undefined,
        };
        rows.push(row);

        pushTokenPoolIndex(byChainTokenPools, chainId, baseAddress, poolAddress);
        pushTokenPoolIndex(byChainTokenPools, chainId, quoteAddress, poolAddress);
      }
    }
  }

  rows.sort((a, b) => {
    const c = a.chainId - b.chainId;
    if (c !== 0) return c;
    return a.poolAddress.localeCompare(b.poolAddress);
  });

  return { rows, byChainTokenPools };
}

let cachedSnapshot: {
  sourcePath: string;
  lastModified: string;
  rows: GruV2DeploymentPoolRow[];
  byChainTokenPools: Map<string, Set<string>>;
} | null = null;

function ensureRegistry(): void {
  const loaded = loadDeploymentStatusFile();
  const sourcePath = resolveDeploymentStatusPath() ?? '';
  const lm = loaded?.lastModified ?? '';
  if (cachedSnapshot && cachedSnapshot.lastModified === lm && cachedSnapshot.sourcePath === sourcePath) {
    return;
  }
  if (!loaded) {
    cachedSnapshot = { sourcePath, lastModified: lm, rows: [], byChainTokenPools: new Map() };
    return;
  }
  const built = buildGruV2PoolRegistryFromDeploymentData(loaded.data);
  cachedSnapshot = {
    sourcePath,
    lastModified: lm,
    rows: built.rows,
    byChainTokenPools: built.byChainTokenPools,
  };
}

/** All resolved GRU v2 PMM pools from `deployment-status.json` (when available). */
export function getGruV2DeploymentPoolRows(): GruV2DeploymentPoolRow[] {
  ensureRegistry();
  return cachedSnapshot?.rows ?? [];
}

/**
 * Official pool contract addresses for this token on this chain (from deployment-status).
 * Returns `null` when the registry is empty or this token has no GRU pools — callers should use all DexScreener pairs.
 */
export function getOfficialGruV2PoolAddressesForToken(chainId: number, normalizedTokenAddress: string): Set<string> | null {
  ensureRegistry();
  if (!cachedSnapshot || cachedSnapshot.rows.length === 0) {
    return null;
  }
  const key = `${chainId}:${normalizedTokenAddress.toLowerCase()}`;
  const set = cachedSnapshot.byChainTokenPools.get(key);
  if (!set || set.size === 0) {
    return null;
  }
  return set;
}

/**
 * When DexScreener returns multiple pairs, prefer rows whose `pairAddress` is an official GRU v2 pool; if none match, keep full list.
 */
export function preferGruV2OfficialDexPairs<T extends { pairAddress: string }>(
  chainId: number,
  tokenAddress: string,
  pairs: T[]
): T[] {
  const official = getOfficialGruV2PoolAddressesForToken(chainId, tokenAddress.toLowerCase());
  if (!official || official.size === 0) {
    return pairs;
  }
  const preferred = pairs.filter((p) => official.has(p.pairAddress.toLowerCase()));
  return preferred.length > 0 ? preferred : pairs;
}
