<?php
/**
 * Simple CAPTCHA Image Generator
 *
 * Generates image CAPTCHAs using PHP GD. Supports distorted and simple styles.
 *
 * @package SimpleCaptcha
 * @since 1.0.0
 */

namespace SimpleCaptcha;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * CAPTCHA Image Generator Class
 *
 * @since 1.0.0
 */
class CaptchaGenerator {

	/**
	 * Generate a random alphanumeric string for image CAPTCHA.
	 *
	 * @param int $length Number of characters.
	 * @return string Random string (letters and numbers only, no ambiguous chars).
	 */
	public static function generateCode( int $length = 5 ): string {
		$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789';
		$code  = '';
		$max   = strlen( $chars ) - 1;
		for ( $i = 0; $i < $length; $i++ ) {
			$code .= $chars[ random_int( 0, $max ) ];
		}
		return $code;
	}

	/**
	 * Generate and output CAPTCHA image.
	 *
	 * @param string $code The code to display.
	 * @param string $style 'distorted' or 'simple'.
	 * @return void Outputs PNG image and exits.
	 */
	public static function outputImage( string $code, string $style = 'distorted' ): void {
		// Only the charset/length from self::generateCode (defense in depth for any caller).
		$code   = (string) preg_replace( '/[^A-Za-z0-9]/', '', $code );
		$length = strlen( $code );
		if ( $length < 4 || $length > 8 ) {
			status_header( 404 );
			exit;
		}

		if ( ! extension_loaded( 'gd' ) || ! function_exists( 'imagecreatetruecolor' ) ) {
			status_header( 404 );
			exit;
		}

		$style = ( $style === 'simple' ) ? 'simple' : 'distorted';

		// Compact canvas: built-in font 5 is ~9×13px per char; avoid 2× upscale (large PNG + slow GD work).
		$char_w_est = imagefontwidth( 5 );
		$pad        = 22;
		$base_w     = max( 88, $length * $char_w_est + $pad );
		$base_h     = 42;

		$img = imagecreatetruecolor( $base_w, $base_h );
		if ( ! $img ) {
			status_header( 500 );
			exit;
		}

		$bg = imagecolorallocate( $img, 245, 245, 245 );
		imagefill( $img, 0, 0, $bg );

		if ( $style === 'distorted' ) {
			self::addDistortion( $img, $base_w, $base_h );
		}

		$text_color = imagecolorallocate( $img, 50, 50, 50 );
		$font       = 5;
		$char_w     = imagefontwidth( $font );
		$char_h     = imagefontheight( $font );
		$total_w    = $char_w * $length;
		$start_x    = (int) ( ( $base_w - $total_w ) / 2 );
		$start_y    = (int) ( ( $base_h - $char_h ) / 2 );

		for ( $i = 0; $i < $length; $i++ ) {
			$x = $start_x + ( $i * $char_w );
			$y = $start_y;
			if ( $style === 'distorted' ) {
				$x += random_int( -2, 2 );
				$y += random_int( -1, 1 );
			}
			imagestring( $img, $font, $x, $y, $code[ $i ], $text_color );
		}

		if ( $style === 'distorted' ) {
			self::addNoise( $img, $base_w, $base_h );
		}

		header( 'Content-Type: image/png' );
		header( 'Cache-Control: no-store, no-cache, must-revalidate, max-age=0' );
		header( 'Pragma: no-cache' );
		// Compression 6: smaller payload, faster download; single-pass render (no upscale).
		imagepng( $img, null, 6 );
		imagedestroy( $img );
		exit;
	}

	/**
	 * Add distortion lines to image.
	 *
	 * @param \GdImage $img Image resource.
	 * @param int      $width Image width.
	 * @param int      $height Image height.
	 * @return void
	 */
	private static function addDistortion( \GdImage $img, int $width, int $height ): void {
		$line_color = imagecolorallocate( $img, 180, 180, 180 );
		for ( $i = 0; $i < 3; $i++ ) {
			imageline(
				$img,
				random_int( 0, $width ),
				random_int( 0, $height ),
				random_int( 0, $width ),
				random_int( 0, $height ),
				$line_color
			);
		}
	}

	/**
	 * Add noise dots to image.
	 *
	 * @param \GdImage $img Image resource.
	 * @param int      $width Image width.
	 * @param int      $height Image height.
	 * @return void
	 */
	private static function addNoise( \GdImage $img, int $width, int $height ): void {
		$noise_color = imagecolorallocate( $img, 150, 150, 150 );
		$dots        = max( 12, (int) ( $width * $height / 120 ) );
		for ( $i = 0; $i < $dots; $i++ ) {
			imagesetpixel( $img, random_int( 0, $width - 1 ), random_int( 0, $height - 1 ), $noise_color );
		}
	}
}
