import * as path from 'path';

/**
 * Pure scoring/filtering helpers for game detection. Lives in the main process
 * and is exercised by both the foreground-window detector and the legacy
 * process-name scanner.
 *
 * The goal is to dramatically reduce the false-positive rate compared to the
 * previous "send every running process name to RAWG" approach by combining
 * multiple signals (window focus, executable path, engine markers, blacklist,
 * user-managed ignore list) into a confidence score.
 */

export interface GameCandidateInput {
  /** Lower-cased base name without extension (e.g. "stardewvalley"). */
  processName: string;
  /** Original process name as reported by the OS (for display). */
  rawProcessName?: string;
  executablePath?: string;
  windowTitle?: string;
  pid?: number;
  bounds?: { width: number; height: number } | undefined;
  isFullscreen?: boolean;
  source: 'foreground' | 'process-scan';
  /** User-managed ignore list, already lower-cased. */
  ignoredProcessNames: ReadonlySet<string>;
  /** True when an engine signature file was found beside the executable. */
  hasEngineSignature?: boolean;
}

export interface ScoredGameCandidate {
  processName: string;
  rawProcessName: string;
  executablePath?: string;
  windowTitle?: string;
  pid?: number;
  isFullscreen: boolean;
  bounds?: { width: number; height: number };
  confidence: number;
  source: 'foreground' | 'process-scan';
  reasons: string[];
}

export const MIN_GAME_CONFIDENCE = 55;

/**
 * Processes that are commonly misclassified as games. Lower-cased base names.
 * Note: we deliberately blacklist Electron/Chromium/IDE/launcher/comm apps.
 */
export const HARDCODED_IGNORED_PROCESSES: ReadonlySet<string> = new Set([
  '1password',
  '7zfm',
  'agent',
  'audiodg',
  'bash',
  'baloo',
  'baloo_file',
  'baloorunner',
  'bluetoothuiservice',
  'brave',
  'brave-browser',
  'chrome',
  'cmd',
  'code',
  'code-insiders',
  'conhost',
  'cursor',
  'csrss',
  'ctfmon',
  'dbus',
  'dbus-daemon',
  'discord',
  'discordcanary',
  'discordptb',
  'dolphin',
  'dwm',
  'electron',
  'epicgameslauncher',
  'epicgames',
  'explorer',
  'fcitx5',
  'firefox',
  'fontdrvhost',
  'gameoverlayui',
  'gamemoded',
  'gamemode-launcher',
  'gamescopereaper',
  'gnome-shell',
  'gnome-software',
  'gnome-terminal',
  'init',
  'java',
  'javaw',
  'kdeconnect',
  'kdeconnectd',
  'kded5',
  'kded6',
  'keepass',
  'keepassxc',
  'kernel_task',
  'krunner',
  'ksmserver',
  'lockapp',
  'logioptionsplus',
  'logitechg',
  'login',
  'metoyou',
  'toju',
  'msedge',
  'msedgewebview2',
  'msteams',
  'node',
  'npm',
  'nvcontainer',
  'nvidia-broadcast',
  'nvidia-share',
  'nvidia-smi',
  'obs',
  'obs64',
  'obs-studio',
  'pipewire',
  'plasmashell',
  'pluma',
  'powershell',
  'pwsh',
  'pulseaudio',
  'remoteapps-service',
  'rundll32',
  'runtimebroker',
  'screen',
  'searchapp',
  'searchhost',
  'shellexperiencehost',
  'signal',
  'slack',
  'spotify',
  'spotifywebhelper',
  'sshd',
  'startmenuexperiencehost',
  'steam',
  'steamservice',
  'steamwebhelper',
  'svchost',
  'system',
  'systemd',
  'systemsettings',
  'systemsoundsservice',
  'taskhost',
  'taskhostw',
  'taskmgr',
  'teams',
  'telegram',
  'telegramdesktop',
  'textinputhost',
  'thunderbird',
  'tracker-miner-fs',
  'tray',
  'utilman',
  'vivaldi',
  'whatsapp',
  'wininit',
  'winlogon',
  'xdg-desktop-portal',
  'xorg',
  'xwayland',
  'yakuake',
  'zoom'
]);

const GENERIC_SUFFIX_NAMES = [
  'agent',
  'browser',
  'daemon',
  'helper',
  'indexer',
  'launcher',
  'monitor',
  'renderer',
  'runner',
  'service',
  'tray',
  'updater',
  'watcher',
  'worker',
  'portal',
  'sync',
  'broker',
  'host'
].join('|');
const IGNORE_NAME_PATTERNS: readonly RegExp[] = [
  new RegExp(`(^|[-_\\s.])(${GENERIC_SUFFIX_NAMES})([-_\\s.]|$)`, 'iu'),
  /^kworker/i,
  /^kthread/i,
  /^kpipefs/i,
  /^(at-spi|gvfs|ibus|kded|kglobalaccel|knotify|polkit|pulse|systemd)/i
];
/** Known game install root markers, case-insensitive substrings of the exe path. */
const KNOWN_GAME_PATH_MARKERS: readonly RegExp[] = [
  /[\\/]steamapps[\\/]common[\\/]/i,
  /[\\/]steamlibrary[\\/]/i,
  /[\\/]epic games[\\/]/i,
  /[\\/]epicgameslauncher[\\/]/i,
  /[\\/]gog galaxy[\\/]games[\\/]/i,
  /[\\/]gog\.com[\\/]games[\\/]/i,
  /[\\/]gog games[\\/]/i,
  /[\\/]ea games[\\/]/i,
  /[\\/]origin games[\\/]/i,
  /[\\/]battle\.net[\\/]/i,
  /[\\/]ubisoft[\\/]/i,
  /[\\/]riot games[\\/]/i,
  /[\\/]itch[\\/]apps[\\/]/i,
  /[\\/]\.itch[\\/]apps[\\/]/i,
  /[\\/]heroic[\\/]games[\\/]/i,
  /[\\/]lutris[\\/]/i,
  /[\\/]games[\\/]/i,
  // Proton / Wine prefixes used by Steam/Lutris/Heroic
  /[\\/]proton[\\/]/i,
  /[\\/]pfx[\\/]drive_c[\\/]/i,
  /[\\/]\.wine[\\/]drive_c[\\/]program files[\\/]/i
];
/** Path segments that strongly indicate the process is NOT a game. */
const NON_GAME_PATH_MARKERS: readonly RegExp[] = [
  /[\\/]appdata[\\/]local[\\/]temp[\\/]/i,
  /[\\/]temp[\\/]/i,
  /[\\/]node_modules[\\/]/i,
  /[\\/]chromium[\\/]/i,
  /[\\/]appdata[\\/]roaming[\\/]discord[\\/]/i,
  /[\\/]appdata[\\/]roaming[\\/]spotify[\\/]/i,
  /[\\/]windows[\\/]system32[\\/]/i,
  /[\\/]windows[\\/]syswow64[\\/]/i,
  /[\\/]\.cache[\\/]/i,
  /[\\/]snap[\\/]firefox[\\/]/i,
  /[\\/]snap[\\/]spotify[\\/]/i
];

/** File names placed beside a game's executable that reveal its engine. */
export const ENGINE_SIGNATURE_FILES: readonly string[] = [
  'UnityPlayer.dll',
  'libUnityPlayer.so',
  'UnityCrashHandler64.exe',
  'UnityCrashHandler32.exe',
  // Unreal Engine: foo-Win64-Shipping.exe sits in <Game>/Binaries/Win64/
  'UnrealEditor.exe',
  'UE4PrereqSetup_x64.exe',
  'UE4Game.dll',
  'UE5Game.dll',
  // Godot
  'Godot.exe',
  'libgodot.so',
  // Source engine
  'tier0.dll',
  'engine.dll',
  'hl2.exe',
  // RPG Maker
  'nw.dll',
  // CryEngine
  'CryGameSDK.dll'
];

export function normalizeProcessKey(value: string): string {
  return path.basename(value.trim())
    .replace(/\.(exe|bin|app|out)$/iu, '')
    .replace(/[_-]+/gu, ' ')
    .replace(/\s+/gu, ' ')
    .trim()
    .toLowerCase();
}

export function shouldIgnoreProcess(
  processName: string,
  userIgnored: ReadonlySet<string>
): boolean {
  const key = normalizeProcessKey(processName);

  if (!key) {
    return true;
  }

  if (userIgnored.has(key) || HARDCODED_IGNORED_PROCESSES.has(key)) {
    return true;
  }

  if (/^\d+$/.test(key)) {
    return true;
  }

  if (key.length < 4) {
    return true;
  }

  return IGNORE_NAME_PATTERNS.some((pattern) => pattern.test(key));
}

export function pathMatchesKnownGameRoot(executablePath: string | undefined): boolean {
  if (!executablePath) {
    return false;
  }

  return KNOWN_GAME_PATH_MARKERS.some((pattern) => pattern.test(executablePath));
}

export function pathMatchesNonGameRoot(executablePath: string | undefined): boolean {
  if (!executablePath) {
    return false;
  }

  return NON_GAME_PATH_MARKERS.some((pattern) => pattern.test(executablePath));
}

interface ConfidenceScore {
  confidence: number;
  reasons: string[];
}

function computeConfidence(input: GameCandidateInput, rawProcessName: string): ConfidenceScore {
  let confidence = 0;

  const reasons: string[] = [];
  const add = (points: number, reason: string): void => {
    confidence += points;
    reasons.push(reason);
  };

  if (input.source === 'foreground') {
    add(35, 'foreground-window');
  }

  if (pathMatchesKnownGameRoot(input.executablePath)) {
    add(30, 'known-game-folder');
  }

  if (input.hasEngineSignature) {
    add(25, 'engine-signature');
  }

  if (input.isFullscreen) {
    add(15, 'fullscreen');
  }

  const width = input.bounds?.width ?? 0;
  const height = input.bounds?.height ?? 0;

  if (width >= 800 && height >= 600) {
    add(5, 'large-window');
  }

  const title = input.windowTitle?.trim() ?? '';

  if (title.length >= 3 && /[A-Za-z]/u.test(title)) {
    add(10, 'window-title');
  }

  if (/[A-Z]/u.test(rawProcessName) && /[a-z]/u.test(rawProcessName)) {
    add(3, 'mixed-case-name');
  }

  if (input.executablePath && /\.exe$/iu.test(input.executablePath)) {
    confidence += 2;
  }

  return { confidence: Math.min(100, confidence), reasons };
}

export function scoreCandidate(input: GameCandidateInput): ScoredGameCandidate | null {
  const rawProcessName = input.rawProcessName ?? input.processName;
  const normalizedKey = normalizeProcessKey(input.processName);

  if (!normalizedKey) {
    return null;
  }

  if (shouldIgnoreProcess(normalizedKey, input.ignoredProcessNames)) {
    return null;
  }

  if (pathMatchesNonGameRoot(input.executablePath)) {
    return null;
  }

  const { confidence, reasons } = computeConfidence(input, rawProcessName);
  const title = input.windowTitle?.trim() ?? '';

  // Process-scan candidates must clear a higher bar: without a foreground or
  // path signal the confidence will stay below the threshold, which is the
  // whole point - no more silent RAWG lookups for arbitrary processes.
  return {
    processName: normalizedKey,
    rawProcessName,
    executablePath: input.executablePath,
    windowTitle: title || undefined,
    pid: input.pid,
    isFullscreen: !!input.isFullscreen,
    bounds: input.bounds,
    confidence,
    source: input.source,
    reasons
  };
}

/** Returns whether a confidence score clears the "report to peers" threshold. */
export function meetsGameConfidence(candidate: ScoredGameCandidate | null): boolean {
  return !!candidate && candidate.confidence >= MIN_GAME_CONFIDENCE;
}
