import { XMLParser } from 'fast-xml-parser';

export type DatRom = {
  name: string;
  size?: number;
  crc?: string;
  md5?: string;
  sha1?: string;
};

export type DatGame = {
  name: string;
  roms: DatRom[];
};

export type DatDatabase = {
  games: DatGame[];
};

function ensureArray<T>(v: T | T[] | undefined): T[] {
  if (v === undefined || v === null) return [];
  return Array.isArray(v) ? v : [v];
}

function normalizeHex(v?: string): string | undefined {
  if (!v) return undefined;
  return v.trim().toLowerCase();
}

export function parseDat(xml: string): DatDatabase {
  const parser = new XMLParser({
    ignoreAttributes: false,
    attributeNamePrefix: '',
    trimValues: true,
  });

  const parsed = parser.parse(xml as any) as any;
  const datafile = parsed?.datafile ?? parsed;

  const rawGames = ensureArray(datafile?.game);

  const games: DatGame[] = rawGames.map((g: any) => {
    // game name may be an attribute or a child node
    const nameAttr = g?.name ?? g?.['@_name'] ?? (g?.$?.name as any);

    const romNodes = ensureArray(g?.rom);

    const roms: DatRom[] = romNodes.map((r: any) => {
      const rname = r?.name ?? r?.['@_name'] ?? r?.['@name'];
      const sizeRaw = r?.size ?? r?.['@_size'];
      const parsedSize = sizeRaw != null ? Number(sizeRaw) : undefined;

      return {
        name: String(rname ?? ''),
        size: typeof parsedSize === 'number' && !Number.isNaN(parsedSize) ? parsedSize : undefined,
        crc: normalizeHex(r?.crc ?? r?.['@_crc'] ?? r?.CRC ?? r?.['CRC']),
        md5: normalizeHex(r?.md5 ?? r?.['@_md5'] ?? r?.MD5 ?? r?.['MD5']),
        sha1: normalizeHex(r?.sha1 ?? r?.['@_sha1'] ?? r?.SHA1 ?? r?.['SHA1']),
      };
    });

    return {
      name: String(nameAttr ?? ''),
      roms,
    };
  });

  return { games };
}

export function verifyHashesAgainstDat(
  datDb: DatDatabase,
  hashes: { crc?: string; md5?: string; sha1?: string; size?: number }
): { gameName: string; romName: string; matchedOn: 'crc' | 'md5' | 'sha1' | 'size' } | null {
  const cmp = {
    crc: normalizeHex(hashes.crc),
    md5: normalizeHex(hashes.md5),
    sha1: normalizeHex(hashes.sha1),
    size: hashes.size,
  } as any;

  for (const g of datDb.games) {
    for (const r of g.roms) {
      if (cmp.crc && r.crc && cmp.crc === r.crc) {
        return { gameName: g.name, romName: r.name, matchedOn: 'crc' };
      }
      if (cmp.md5 && r.md5 && cmp.md5 === r.md5) {
        return { gameName: g.name, romName: r.name, matchedOn: 'md5' };
      }
      if (cmp.sha1 && r.sha1 && cmp.sha1 === r.sha1) {
        return { gameName: g.name, romName: r.name, matchedOn: 'sha1' };
      }
      if (cmp.size !== undefined && r.size !== undefined && Number(cmp.size) === Number(r.size)) {
        return { gameName: g.name, romName: r.name, matchedOn: 'size' };
      }
    }
  }

  return null;
}
