const { execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');

let cachedState = null;
let artFiles = new Map();

function exists(targetPath) {
  try {
    return fs.existsSync(targetPath);
  } catch (error) {
    return false;
  }
}

function isDirectory(targetPath) {
  try {
    return fs.statSync(targetPath).isDirectory();
  } catch (error) {
    return false;
  }
}

function isFile(targetPath) {
  try {
    return fs.statSync(targetPath).isFile();
  } catch (error) {
    return false;
  }
}

function normalizeDir(targetPath) {
  return path.normalize(String(targetPath || '').trim());
}

function uniquePaths(values) {
  const seen = new Set();
  const output = [];

  values.forEach((value) => {
    const normalized = normalizeDir(value);
    if (!normalized) return;
    const key = normalized.toLowerCase();
    if (seen.has(key)) return;
    seen.add(key);
    output.push(normalized);
  });

  return output;
}

function decodeKeyValueString(rawValue) {
  return String(rawValue || '')
    .replace(/\\"/g, '"')
    .replace(/\\\\/g, '\\');
}

function parseKeyValues(text) {
  const tokens = [];
  const matcher = /"((?:\\.|[^"\\])*)"|([{}])/g;
  let match = null;

  while ((match = matcher.exec(String(text || '')))) {
    if (match[1] !== undefined) {
      tokens.push({ type: 'string', value: decodeKeyValueString(match[1]) });
    } else if (match[2]) {
      tokens.push({ type: match[2] });
    }
  }

  let index = 0;

  function parseObject() {
    const output = {};

    while (index < tokens.length) {
      const token = tokens[index];
      if (!token) break;

      if (token.type === '}') {
        index += 1;
        break;
      }

      if (token.type !== 'string') {
        index += 1;
        continue;
      }

      const key = token.value;
      index += 1;
      const next = tokens[index];
      if (!next) break;

      if (next.type === 'string') {
        output[key] = next.value;
        index += 1;
      } else if (next.type === '{') {
        index += 1;
        output[key] = parseObject();
      } else {
        index += 1;
      }
    }

    return output;
  }

  if (!tokens.length) return {};

  if (tokens[0].type === 'string' && tokens[1] && tokens[1].type === '{') {
    const rootKey = tokens[0].value;
    index = 2;
    const rootValue = parseObject();
    return { [rootKey]: rootValue };
  }

  if (tokens[0].type === '{') {
    index = 1;
    return parseObject();
  }

  return parseObject();
}

function escapeRegExp(value) {
  return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function queryRegistryValue(key, valueName) {
  try {
    const raw = execFileSync('reg', ['query', key, '/v', valueName], {
      encoding: 'utf8',
      stdio: ['ignore', 'pipe', 'ignore'],
      windowsHide: true,
    });
    const pattern = new RegExp(`^\\s*${escapeRegExp(valueName)}\\s+REG_\\w+\\s+(.*)$`, 'mi');
    const match = raw.match(pattern);
    return match ? normalizeDir(match[1]) : '';
  } catch (error) {
    return '';
  }
}

function getSteamBaseCandidates() {
  const env = process.env;
  const candidates = [
    queryRegistryValue('HKCU\\Software\\Valve\\Steam', 'SteamPath'),
    queryRegistryValue('HKLM\\SOFTWARE\\WOW6432Node\\Valve\\Steam', 'InstallPath'),
    queryRegistryValue('HKLM\\SOFTWARE\\Valve\\Steam', 'InstallPath'),
    env['PROGRAMFILES(X86)'] ? path.join(env['PROGRAMFILES(X86)'], 'Steam') : '',
    env.PROGRAMFILES ? path.join(env.PROGRAMFILES, 'Steam') : '',
  ];

  return uniquePaths(candidates).filter((candidate) => isDirectory(path.join(candidate, 'steamapps')));
}

function getSteamLibraryPaths(steamBasePath) {
  const libraries = [];
  const steamappsDir = path.join(steamBasePath, 'steamapps');

  if (isDirectory(steamappsDir)) {
    libraries.push(steamBasePath);
  }

  const libraryFile = path.join(steamappsDir, 'libraryfolders.vdf');
  if (!isFile(libraryFile)) {
    return uniquePaths(libraries);
  }

  let parsed = {};
  try {
    parsed = parseKeyValues(fs.readFileSync(libraryFile, 'utf8'));
  } catch (error) {
    return uniquePaths(libraries);
  }

  const root = parsed.libraryfolders || parsed.LibraryFolders || parsed;
  Object.keys(root || {}).forEach((key) => {
    const entry = root[key];
    if (typeof entry === 'string') {
      libraries.push(entry);
      return;
    }
    if (entry && typeof entry === 'object' && entry.path) {
      libraries.push(entry.path);
    }
  });

  return uniquePaths(libraries).filter((candidate) => isDirectory(path.join(candidate, 'steamapps')));
}

function registerLocalArt(appId, slot, filePath) {
  if (!filePath || !isFile(filePath)) return '';
  const token = `${appId}-${slot}`;
  artFiles.set(token, filePath);
  return `/steam-art/${encodeURIComponent(token)}`;
}

function collectFilesRecursive(rootDir, maxDepth) {
  if (!rootDir || !isDirectory(rootDir) || maxDepth < 0) return [];

  let entries = [];
  try {
    entries = fs.readdirSync(rootDir, { withFileTypes: true });
  } catch (error) {
    return [];
  }

  const files = [];
  entries.forEach((entry) => {
    const entryPath = path.join(rootDir, entry.name);
    if (entry.isFile()) {
      files.push(entryPath);
      return;
    }

    if (entry.isDirectory() && maxDepth > 0) {
      files.push(...collectFilesRecursive(entryPath, maxDepth - 1));
    }
  });

  return files;
}

function getUserLibraryCacheRoots(steamBasePath) {
  const userdataDir = path.join(steamBasePath, 'userdata');
  if (!isDirectory(userdataDir)) return [];

  let entries = [];
  try {
    entries = fs.readdirSync(userdataDir, { withFileTypes: true });
  } catch (error) {
    return [];
  }

  return entries
    .filter((entry) => entry.isDirectory())
    .map((entry) => path.join(userdataDir, entry.name, 'config', 'librarycache'))
    .filter((candidate) => isDirectory(candidate));
}

function findArtInFiles(files, preferredNames) {
  const lowered = preferredNames.map((name) => String(name || '').toLowerCase());
  for (const name of lowered) {
    const match = files.find((filePath) => path.basename(filePath).toLowerCase() === name);
    if (match) return match;
  }
  return '';
}

function findSteamArt(steamBasePath, appId) {
  const libraryCacheDir = path.join(steamBasePath, 'appcache', 'librarycache');
  if (!isDirectory(libraryCacheDir)) {
    return { coverUrl: '', bannerUrl: '' };
  }

  const flatCoverCandidates = [
    path.join(libraryCacheDir, `${appId}_library_600x900.jpg`),
    path.join(libraryCacheDir, `${appId}_library_600x900.png`),
    path.join(libraryCacheDir, `${appId}_library_600x900.webp`),
    path.join(libraryCacheDir, `${appId}_library_600x900_2x.jpg`),
    path.join(libraryCacheDir, `${appId}_library_600x900_2x.png`),
    path.join(libraryCacheDir, `${appId}_library_600x900_2x.webp`),
  ];

  const flatBannerCandidates = [
    path.join(libraryCacheDir, `${appId}_hero.jpg`),
    path.join(libraryCacheDir, `${appId}_hero.png`),
    path.join(libraryCacheDir, `${appId}_hero.webp`),
    path.join(libraryCacheDir, `${appId}_library_hero.jpg`),
    path.join(libraryCacheDir, `${appId}_library_hero.png`),
    path.join(libraryCacheDir, `${appId}_library_hero.webp`),
    path.join(libraryCacheDir, `${appId}_library_header.jpg`),
    path.join(libraryCacheDir, `${appId}_library_header.png`),
    path.join(libraryCacheDir, `${appId}_library_header.webp`),
    path.join(libraryCacheDir, `${appId}_header.jpg`),
    path.join(libraryCacheDir, `${appId}_header.png`),
    path.join(libraryCacheDir, `${appId}_header.webp`),
  ];

  const nestedRoots = [
    path.join(libraryCacheDir, appId),
    ...getUserLibraryCacheRoots(steamBasePath).map((rootDir) => path.join(rootDir, appId)),
  ].filter((candidate) => exists(candidate));

  const nestedFiles = uniquePaths(
    nestedRoots.flatMap((rootDir) => collectFilesRecursive(rootDir, 2)),
  ).filter((candidate) => isFile(candidate));

  const coverFile = flatCoverCandidates.find((candidate) => isFile(candidate))
    || findArtInFiles(nestedFiles, [
      'library_600x900.jpg',
      'library_600x900.png',
      'library_600x900.webp',
      'library_600x900_2x.jpg',
      'library_600x900_2x.png',
      'library_600x900_2x.webp',
      'library_capsule.jpg',
      'library_capsule.png',
      'library_capsule.webp',
      'library_header.jpg',
      'library_header.png',
      'library_header.webp',
      'header.jpg',
      'header.png',
      'header.webp',
      'logo.png',
      'logo.jpg',
      'logo.webp',
    ])
    || '';

  const bannerFile = flatBannerCandidates.find((candidate) => isFile(candidate))
    || findArtInFiles(nestedFiles, [
      'library_hero.jpg',
      'library_hero.png',
      'library_hero.webp',
      'library_header.jpg',
      'library_header.png',
      'library_header.webp',
      'header.jpg',
      'header.png',
      'header.webp',
      'library_capsule.jpg',
      'library_capsule.png',
      'library_capsule.webp',
      'logo.png',
      'logo.jpg',
      'logo.webp',
    ])
    || coverFile;

  return {
    coverUrl: registerLocalArt(appId, 'cover', coverFile),
    bannerUrl: registerLocalArt(appId, 'banner', bannerFile),
  };
}

function readInstalledApps(steamBasePath, libraryPaths) {
  const apps = {};

  libraryPaths.forEach((libraryPath) => {
    const manifestsDir = path.join(libraryPath, 'steamapps');
    if (!isDirectory(manifestsDir)) return;

    let files = [];
    try {
      files = fs.readdirSync(manifestsDir);
    } catch (error) {
      files = [];
    }

    files
      .filter((name) => /^appmanifest_\d+\.acf$/i.test(name))
      .forEach((name) => {
        const manifestPath = path.join(manifestsDir, name);
        let parsed = {};

        try {
          parsed = parseKeyValues(fs.readFileSync(manifestPath, 'utf8'));
        } catch (error) {
          return;
        }

        const appState = parsed.AppState || parsed.appstate || parsed;
        const appId = String(appState.appid || '').trim();
        if (!appId || apps[appId]) return;

        const art = findSteamArt(steamBasePath, appId);
        const installDir = String(appState.installdir || '').trim();

        apps[appId] = {
          appId,
          name: String(appState.name || `Steam App ${appId}`),
          libraryPath,
          installDir,
          installPath: installDir ? path.join(libraryPath, 'steamapps', 'common', installDir) : '',
          coverUrl: art.coverUrl,
          bannerUrl: art.bannerUrl,
        };
      });
  });

  return apps;
}

function scanSteamLibrary() {
  artFiles = new Map();

  const bases = getSteamBaseCandidates();
  if (!bases.length) {
    return {
      ok: true,
      installed: false,
      basePath: '',
      libraryPaths: [],
      appCount: 0,
      apps: {},
    };
  }

  const basePath = bases[0];
  const libraryPaths = getSteamLibraryPaths(basePath);
  const apps = readInstalledApps(basePath, libraryPaths);

  return {
    ok: true,
    installed: true,
    basePath,
    libraryPaths,
    appCount: Object.keys(apps).length,
    apps,
  };
}

function getSteamLibraryState(forceRefresh) {
  if (forceRefresh || !cachedState) {
    try {
      cachedState = scanSteamLibrary();
    } catch (error) {
      cachedState = {
        ok: false,
        installed: false,
        error: error.message,
        basePath: '',
        libraryPaths: [],
        appCount: 0,
        apps: {},
      };
    }
  }

  return cachedState;
}

function getArtFilePath(token) {
  return artFiles.get(String(token || '')) || '';
}

module.exports = {
  getArtFilePath,
  getSteamLibraryState,
};
