import * as fs from 'fs';
import * as path from 'path';
import { detectActiveWindow } from './active-window';
import {
  ENGINE_SIGNATURE_FILES,
  GameCandidateInput,
  MIN_GAME_CONFIDENCE,
  ScoredGameCandidate,
  scoreCandidate,
  shouldIgnoreProcess
} from './heuristics';
import { listRunningProcessNames } from '../process-list';
import { readDesktopSettings } from '../desktop-settings';

/**
 * Public result of a detection scan. The renderer prefers `candidate` and only
 * falls back to `fallbackProcessNames` when no focused candidate clears the
 * minimum confidence threshold. The fallback list is intentionally trimmed and
 * pre-filtered so the renderer never sees obvious non-games like Spotify.
 */
export interface GameDetectionResult {
  candidate: ScoredGameCandidate | null;
  /**
   * Filtered list of plausible game process names. Empty when the focused
   * candidate already crossed the threshold (so the renderer skips fallback
   * matching). Capped to keep RAWG quota usage predictable.
   */
  fallbackProcessNames: string[];
}

const MAX_FALLBACK_PROCESSES = 8;

export async function detectActiveGame(): Promise<GameDetectionResult> {
  const ignoredProcessNames = getUserIgnoredProcesses();
  const active = await detectActiveWindow();

  let candidate: ScoredGameCandidate | null = null;

  if (active) {
    const hasEngineSignature = await detectEngineSignature(active.executablePath);
    const input: GameCandidateInput = {
      processName: active.processName,
      rawProcessName: active.processName,
      executablePath: active.executablePath,
      windowTitle: active.windowTitle,
      pid: active.pid,
      bounds: active.bounds,
      isFullscreen: active.isFullscreen,
      source: 'foreground',
      ignoredProcessNames,
      hasEngineSignature
    };

    candidate = scoreCandidate(input);
  }

  if (candidate && candidate.confidence >= MIN_GAME_CONFIDENCE) {
    return { candidate, fallbackProcessNames: [] };
  }

  const fallbackProcessNames = await collectFallbackProcessNames(ignoredProcessNames);

  return { candidate, fallbackProcessNames };
}

async function collectFallbackProcessNames(ignoredProcessNames: ReadonlySet<string>): Promise<string[]> {
  try {
    const names = await listRunningProcessNames();
    const filtered: string[] = [];

    for (const name of names) {
      if (filtered.length >= MAX_FALLBACK_PROCESSES) {
        break;
      }

      if (!shouldIgnoreProcess(name, ignoredProcessNames)) {
        filtered.push(name);
      }
    }

    return filtered;
  } catch {
    return [];
  }
}

async function detectEngineSignature(executablePath: string | undefined): Promise<boolean> {
  if (!executablePath) {
    return false;
  }

  try {
    const directory = path.dirname(executablePath);
    const entries = await fs.promises.readdir(directory).catch(() => []);
    const lowerEntries = new Set(entries.map((entry) => entry.toLowerCase()));

    if (ENGINE_SIGNATURE_FILES.some((file) => lowerEntries.has(file.toLowerCase()))) {
      return true;
    }

    // Unreal Engine ships executables ending in "-Win64-Shipping.exe" or
    // "-Linux-Shipping" inside <Game>/Binaries/<Platform>/.
    return entries.some((entry) => /-(win64|win32|linux)-shipping(\.exe)?$/i.test(entry));
  } catch {
    return false;
  }
}

function getUserIgnoredProcesses(): ReadonlySet<string> {
  try {
    const stored = readDesktopSettings().ignoredGameProcesses ?? [];

    return new Set(stored.map((entry) => entry.trim().toLowerCase()).filter(Boolean));
  } catch {
    return new Set();
  }
}

export type { ScoredGameCandidate } from './heuristics';
