'use strict';

/**
 * listfile.js
 *
 * Last-resort icon resolution: FileDataID -> icon name.
 *
 * Most slots resolve through Wowhead by spell or item ID. A few report neither
 * (flyouts, equipment sets, macros that cast nothing) and only give us the
 * icon's FileDataID, which no CDN can serve directly. The community listfile
 * maps those IDs to icon names, which the CDN *can* serve.
 *
 * The download is large (~70MB) so it happens lazily — only the first time a
 * slot actually needs it — and only the `interface/icons/` rows are kept,
 * cached as a small JSON (~2MB) that every later run reads instead.
 */

const fs = require('fs');
const path = require('path');
const { Readable } = require('stream');

const SOURCE_URL =
  'https://github.com/wowdev/wow-listfile/releases/latest/download/verified-listfile.csv';
const ICON_PREFIX = 'interface/icons/';
const CACHE_FILE = path.join(__dirname, 'icon_cache', '_fileid-icons.json');
const RETRY_AFTER_MS = 10 * 60_000;

/** @type {Object<number,string>|null} */
let map = null;
let building = null;
let lastFailure = 0;

function parseLine(line, into) {
  const sep = line.indexOf(';');
  if (sep === -1) return;
  const filePath = line.slice(sep + 1);
  if (!filePath.startsWith(ICON_PREFIX)) return;

  const id = Number(line.slice(0, sep));
  if (!Number.isFinite(id)) return;

  const base = filePath.slice(ICON_PREFIX.length).trim();
  const dot = base.lastIndexOf('.');
  into[id] = dot === -1 ? base : base.slice(0, dot);
}

/** Stream the listfile, keeping only icon rows. */
async function download() {
  console.log('[Listfile] Downloading icon lookup (one time, ~70MB)…');
  const started = Date.now();

  const res = await fetch(SOURCE_URL, {
    redirect: 'follow',
    headers: { 'User-Agent': 'RockyCoreWoWDeck/2.0' },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}`);

  const built = {};
  let partial = '';
  for await (const chunk of Readable.fromWeb(res.body)) {
    partial += chunk.toString('utf8');
    const lines = partial.split('\n');
    partial = lines.pop();          // keep the incomplete tail for the next chunk
    for (const line of lines) parseLine(line, built);
  }
  parseLine(partial, built);

  const count = Object.keys(built).length;
  if (count === 0) throw new Error('no icon rows found');

  fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
  fs.writeFileSync(CACHE_FILE, JSON.stringify(built));
  console.log(
    `[Listfile] ${count} icon names cached in ${((Date.now() - started) / 1000).toFixed(1)}s`
  );
  return built;
}

function loadFromDisk() {
  try {
    if (fs.existsSync(CACHE_FILE)) {
      return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
    }
  } catch (err) {
    console.warn('[Listfile] Cache unreadable, will rebuild:', err.message);
  }
  return null;
}

/** Kick off (or resume) building the map. Never throws. */
function ensureReady() {
  if (map || building) return;

  map = loadFromDisk();
  if (map) return;

  if (Date.now() - lastFailure < RETRY_AFTER_MS) return;

  building = download()
    .then((built) => { map = built; })
    .catch((err) => {
      lastFailure = Date.now();
      console.warn('[Listfile] Download failed:', err.message);
    })
    .finally(() => { building = null; });
}

/**
 * Icon name for a FileDataID, or null if the map isn't ready yet or the ID
 * is unknown. Starts the download on first miss; callers should retry later.
 */
function lookupIconName(fileDataId) {
  ensureReady();
  if (!map) return null;
  return map[fileDataId] ?? null;
}

module.exports = { lookupIconName, ensureReady, CACHE_FILE };
