/*
 * This file is part of the xPack project (http://xpack.github.io).
 * Copyright (c) 2021-2026 Liviu Ionescu. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software
 * for any purpose is hereby granted, under the terms of the MIT license.
 *
 * If a copy of the license was not distributed with this file, it can
 * be obtained from https://opensource.org/license/mit.
 */

// ----------------------------------------------------------------------------

/**
 * Platform information structure for runtime environment detection.
 *
 * @remarks
 * This interface encapsulates operating system and architecture information
 * used throughout the xPack library for platform-specific behaviour,
 * path filtering, and binary distribution selection.
 */
export interface PlatformInfo {
  /**
   * The operating system platform identifier.
   *
   * @remarks
   * Common values include:
   *
   * <ul>
   * <li><code>darwin</code> (macOS)</li>
   * <li><code>linux</code> (Linux)</li>
   * <li><code>win32</code> (Windows)</li>
   * </ul>
   *
   * Corresponds to <code>process.platform</code> from Node.js.
   */
  platform: string

  /**
   * The CPU architecture identifier.
   *
   * @remarks
   * Common values include:
   *
   * <ul>
   * <li><code>x64</code> (64-bit Intel/AMD)</li>
   * <li><code>arm64</code> (64-bit ARM)</li>
   * <li><code>ia32</code> (32-bit Intel/AMD)</li>
   * <li><code>arm</code> (32-bit ARM)</li>
   * </ul>
   *
   * Corresponds to <code>process.arch</code> from Node.js.
   */
  arch: string
}

/**
 * Options for platform information retrieval.
 *
 * @remarks
 * This interface defines configuration options that control how platform
 * information is retrieved and processed, particularly for architecture
 * coercion scenarios.
 */
export interface PlatformInfoOptions {
  /**
   * Whether to coerce 64-bit architectures to their 32-bit equivalents.
   *
   * @remarks
   * When <code>true</code>, applies the following architecture mappings:
   *
   * <ul>
   * <li><code>x64 → ia32</code></li>
   * <li><code>arm64 → arm</code></li>
   * </ul>
   *
   * This is useful for backward compatibility scenarios where only 32-bit
   * binaries are available but can run on 64-bit systems.
   *
   * @defaultValue `false`
   */
  doForce32bit?: boolean
}

// ============================================================================

/**
 * Platform detection service for runtime environment identification.
 *
 * @remarks
 * This class encapsulates platform and architecture detection logic,
 * providing a mockable abstraction over Node.js process properties.
 * It enables testable platform-specific behaviour without requiring
 * execution on multiple operating systems or architectures.
 *
 * Key features:
 *
 * <ol>
 * <li><b>Dependency injection:</b> Accepts a custom process object via
 *    constructor, enabling test environments to inject mock process
 *    implementations.</li>
 * <li><b>Architecture coercion:</b> Provides optional 64-bit to 32-bit
 *    architecture mapping for backward compatibility scenarios.</li>
 * <li><b>Consistent interface:</b> Returns standardised
 *    {@link PlatformInfo} objects for use throughout the xPack library.</li>
 * </ol>
 *
 * This abstraction eliminates direct <code>process.platform</code> and
 * <code>process.arch</code> access in business logic, making platform-specific
 * code paths testable via mocked platform information.
 *
 * @example
 * Default usage with actual runtime platform:
 * ```typescript
 * const detector = new PlatformDetector()
 * const info = detector.getPlatformInfo()
 * console.log(info.platform) // 'darwin', 'linux', or 'win32'
 * console.log(info.arch)     // 'x64', 'arm64', etc.
 * ```
 *
 * @example
 * Testing with mocked platform:
 * ```typescript
 * const mockProcess = {
 *   platform: 'darwin',
 *   arch: 'arm64'
 * } as NodeJS.Process
 *
 * const detector = new PlatformDetector(mockProcess)
 * const info = detector.getPlatformInfo({ doForce32bit: true })
 * // info.platform === 'darwin'
 * // info.arch === 'arm' (coerced from arm64)
 * ```
 */
export class PlatformDetector {
  // --------------------------------------------------------------------------
  // Private Members.

  /**
   * The Node.js process object for accessing platform information.
   */
  private readonly process: NodeJS.Process

  // --------------------------------------------------------------------------
  // Constructor.

  /**
   * Constructs a platform detector instance.
   *
   * @remarks
   * This constructor accepts an optional process object parameter, enabling
   * dependency injection for testing scenarios. When no process object is
   * provided, the global Node.js <code>process</code> is used automatically.
   *
   * The injected process object must implement the <code>platform</code> and
   * <code>arch</code> properties from the <code>NodeJS.Process</code>
   * interface.
   *
   * @param _process - The Node.js process object providing platform and
   * architecture information. Defaults to the global <code>process</code>
   * object.
   */
  constructor(_process: NodeJS.Process = globalThis.process) {
    this.process = _process
  }

  // --------------------------------------------------------------------------
  // Public Methods.

  /**
   * Retrieves current platform and architecture information.
   *
   * @remarks
   * This method returns a {@link PlatformInfo} object containing the
   * operating system platform and CPU architecture. When the
   * <code>doForce32bit</code> option is enabled, 64-bit architectures
   * are coerced to their 32-bit equivalents.
   *
   * Architecture coercion rules (when <code>doForce32bit</code> is
   * <code>true</code>):
   *
   * <ul>
   * <li><b>x64 → ia32:</b> Coerces 64-bit Intel/AMD to 32-bit.</li>
   * <li><b>arm64 → arm:</b> Coerces 64-bit ARM to 32-bit.</li>
   * </ul>
   *
   * This coercion is useful for backward compatibility scenarios where
   * only 32-bit binaries are available but can run on 64-bit systems via
   * compatibility layers.
   *
   * The platform identifier is never modified and always reflects the actual
   * operating system (<code>darwin</code>, <code>linux</code>,
   * <code>win32</code>).
   *
   * @param options - Configuration options controlling platform information
   * retrieval.
   * @returns Platform and architecture information.
   */
  getPlatformInfo(options: PlatformInfoOptions = {}): PlatformInfo {
    const { doForce32bit = false } = options

    let arch = this.process.arch

    if (doForce32bit) {
      // https://nodejs.org/docs/latest/api/process.html#processarch
      if (arch === 'x64') {
        arch = 'ia32'
      } else if (arch === 'arm64') {
        arch = 'arm'
      }
    }

    return {
      platform: this.process.platform,
      arch,
    }
  }

  /**
   * Checks whether the current platform is Windows.
   *
   * @remarks
   * This convenience method provides a simple boolean check for Windows
   * platform detection, commonly used for path handling and command
   * formatting decisions.
   *
   * Equivalent to checking <code>platform === 'win32'</code>.
   *
   * @returns <code>true</code> if running on Windows,
   * <code>false</code> otherwise.
   */
  isWindows(): boolean {
    return this.process.platform === 'win32'
  }
}

// ----------------------------------------------------------------------------
