import { execFile } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';

const execFileAsync = promisify(execFile);

/**
 * Structured snapshot of the currently focused window. Returned by
 * detectActiveWindow() and consumed by the game-detection orchestrator.
 *
 * Field availability varies by platform/compositor; consumers must treat all
 * optional fields as best-effort. `processName` is required because the
 * heuristic engine refuses to score a candidate without it.
 */
export interface ActiveWindowSnapshot {
  processName: string;
  executablePath?: string;
  windowTitle?: string;
  pid?: number;
  bounds?: { width: number; height: number };
  isFullscreen?: boolean;
  /** Where the snapshot came from, for diagnostics. */
  source: 'get-windows' | 'hyprctl' | 'swaymsg' | 'xprop';
}

let cachedDynamicImport: ((specifier: string) => Promise<unknown>) | null = null;

function importEsm<T>(specifier: string): Promise<T> {
  if (!cachedDynamicImport) {
    // Built via the Function constructor so the TypeScript compiler does not
    // down-level the `import()` call to `require()` under module: commonjs.
    cachedDynamicImport = new Function('s', 'return import(s)') as (specifier: string) => Promise<unknown>;
  }

  return cachedDynamicImport(specifier) as Promise<T>;
}

interface GetWindowsModule {
  activeWindow: (options?: { accessibilityPermission?: boolean; screenRecordingPermission?: boolean }) => Promise<GetWindowsResult | undefined>;
}

interface GetWindowsResult {
  platform: 'macos' | 'linux' | 'windows';
  title: string;
  id: number;
  bounds: { x: number; y: number; width: number; height: number };
  owner: { name: string; processId: number; path: string };
}

export async function detectActiveWindow(): Promise<ActiveWindowSnapshot | null> {
  const getWindowsResult = await tryGetWindows();

  if (getWindowsResult) {
    return getWindowsResult;
  }

  if (process.platform === 'linux') {
    return await detectLinuxActiveWindowViaCompositor();
  }

  return null;
}

async function tryGetWindows(): Promise<ActiveWindowSnapshot | null> {
  try {
    const mod = await importEsm<GetWindowsModule>('get-windows');
    const result = await mod.activeWindow({
      accessibilityPermission: false,
      screenRecordingPermission: false
    });

    if (!result || !result.owner?.name) {
      return null;
    }

    return {
      processName: result.owner.name,
      executablePath: result.owner.path || undefined,
      windowTitle: result.title || undefined,
      pid: result.owner.processId,
      bounds: result.bounds
        ? { width: result.bounds.width, height: result.bounds.height }
        : undefined,
      isFullscreen: isFullscreenFromBounds(result.bounds),
      source: 'get-windows'
    };
  } catch {
    return null;
  }
}

function isFullscreenFromBounds(bounds: { x?: number; y?: number; width?: number; height?: number } | undefined): boolean {
  if (!bounds || typeof bounds.width !== 'number' || typeof bounds.height !== 'number') {
    return false;
  }

  // Cheap proxy: anything ≥1920x1080 is treated as fullscreen-ish. This is
  // intentionally loose because we already gate on focus and exe path.
  return bounds.width >= 1920 && bounds.height >= 1080;
}

async function detectLinuxActiveWindowViaCompositor(): Promise<ActiveWindowSnapshot | null> {
  const hypr = await tryHyprctl();

  if (hypr) {
    return hypr;
  }

  const sway = await trySwaymsg();

  if (sway) {
    return sway;
  }

  return null;
}

interface HyprlandActiveWindow {
  address?: string;
  pid?: number;
  title?: string;
  class?: string;
  initialClass?: string;
  fullscreen?: number | boolean;
  fullscreenClient?: number | boolean;
  size?: [number, number];
}

async function tryHyprctl(): Promise<ActiveWindowSnapshot | null> {
  if (!process.env.HYPRLAND_INSTANCE_SIGNATURE) {
    return null;
  }

  try {
    const { stdout } = await execFileAsync('hyprctl', ['activewindow', '-j'], {
      timeout: 2_000,
      maxBuffer: 256 * 1024
    });
    const parsed = JSON.parse(stdout) as HyprlandActiveWindow;

    if (!parsed?.pid) {
      return null;
    }

    return await snapshotFromPid(parsed.pid, {
      windowTitle: parsed.title,
      processNameHint: parsed.class || parsed.initialClass,
      bounds: parsed.size ? { width: parsed.size[0], height: parsed.size[1] } : undefined,
      isFullscreen: !!parsed.fullscreen || !!parsed.fullscreenClient,
      source: 'hyprctl'
    });
  } catch {
    return null;
  }
}

interface SwayTreeNode {
  focused?: boolean;
  pid?: number;
  name?: string;
  app_id?: string;
  window_properties?: { class?: string };
  fullscreen_mode?: number;
  rect?: { width?: number; height?: number };
  nodes?: SwayTreeNode[];
  floating_nodes?: SwayTreeNode[];
}

async function trySwaymsg(): Promise<ActiveWindowSnapshot | null> {
  if (!process.env.SWAYSOCK && !process.env.I3SOCK) {
    return null;
  }

  try {
    const { stdout } = await execFileAsync('swaymsg', ['-t', 'get_tree'], {
      timeout: 2_000,
      maxBuffer: 2 * 1024 * 1024
    });
    const tree = JSON.parse(stdout) as SwayTreeNode;
    const focused = findFocusedSwayNode(tree);

    if (!focused?.pid) {
      return null;
    }

    return await snapshotFromPid(focused.pid, {
      windowTitle: focused.name,
      processNameHint: focused.app_id || focused.window_properties?.class,
      bounds: focused.rect
        ? { width: focused.rect.width ?? 0, height: focused.rect.height ?? 0 }
        : undefined,
      isFullscreen: (focused.fullscreen_mode ?? 0) > 0,
      source: 'swaymsg'
    });
  } catch {
    return null;
  }
}

function findFocusedSwayNode(node: SwayTreeNode): SwayTreeNode | null {
  if (node.focused && node.pid) {
    return node;
  }

  for (const child of node.nodes ?? []) {
    const found = findFocusedSwayNode(child);

    if (found) {
      return found;
    }
  }

  for (const child of node.floating_nodes ?? []) {
    const found = findFocusedSwayNode(child);

    if (found) {
      return found;
    }
  }

  return null;
}

interface SnapshotFromPidOptions {
  windowTitle?: string;
  processNameHint?: string;
  bounds?: { width: number; height: number };
  isFullscreen?: boolean;
  source: ActiveWindowSnapshot['source'];
}

async function snapshotFromPid(pid: number, options: SnapshotFromPidOptions): Promise<ActiveWindowSnapshot | null> {
  let executablePath: string | undefined;
  let processName = options.processNameHint?.trim() || '';

  try {
    executablePath = await fs.promises.readlink(`/proc/${pid}/exe`);

    if (!processName) {
      processName = path.basename(executablePath);
    }
  } catch {
    /* /proc/<pid>/exe is restricted for foreign-uid processes; that's fine. */
  }

  if (!processName) {
    try {
      processName = (await fs.promises.readFile(`/proc/${pid}/comm`, 'utf8')).trim();
    } catch {
      /* ignored */
    }
  }

  if (!processName) {
    return null;
  }

  return {
    processName,
    executablePath,
    windowTitle: options.windowTitle?.trim() || undefined,
    pid,
    bounds: options.bounds,
    isFullscreen: options.isFullscreen,
    source: options.source
  };
}
