<?php
/**
 * Shared helpers for HTTP transports.
 *
 * @package MailDoctor
 */

namespace MailDoctor\Transports;

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

/**
 * Utility helpers consumed by transport implementations.
 */
class Utils {
	/**
	 * Parse an address list into structured array.
	 *
	 * @param mixed $value Raw address data.
	 * @return array<int, array<string, string>>
	 */
	public static function parse_address_list( $value ): array {
		$items = array();

		if ( empty( $value ) ) {
			return $items;
		}

		if ( is_array( $value ) ) {
			$raw = $value;
		} else {
			$raw = array_map( 'trim', explode( ',', (string) $value ) );
		}

		foreach ( $raw as $entry ) {
			$entry = trim( (string) $entry );
			if ( '' === $entry ) {
				continue;
			}

			$name  = '';
			$email = '';

			if ( preg_match( '/^(.+)<(.+)>$/', $entry, $matches ) ) {
				$name  = trim( trim( $matches[1] ), '\"' );
				$email = sanitize_email( trim( $matches[2] ) );
			} else {
				$email = sanitize_email( $entry );
			}

			if ( ! $email ) {
				continue;
			}

			$items[] = array(
				'email' => $email,
				'name'  => $name,
			);
		}

		return $items;
	}

	/**
	 * Extract addresses from headers.
	 *
	 * @param array<int,string> $headers Normalized header array.
	 * @param string            $header  Header name to match.
	 * @return array<int, array<string,string>>
	 */
	public static function header_addresses( array $headers, string $header ): array {
		$matches = array();
		$target  = strtolower( $header );

		foreach ( $headers as $line ) {
			if ( false === strpos( $line, ':' ) ) {
				continue;
			}

			list( $name, $value ) = array_map( 'trim', explode( ':', $line, 2 ) );
			if ( strtolower( $name ) !== $target ) {
				continue;
			}

			$matches = array_merge( $matches, self::parse_address_list( $value ) );
		}

		return $matches;
	}

	/**
	 * Collect recipient buckets from mail args.
	 *
	 * @param array<string,mixed> $args Normalized mail args.
	 * @return array{to:array,cc:array,bcc:array,reply_to:array}
	 */
	public static function recipients_from_args( array $args ): array {
		$headers = isset( $args['headers'] ) && is_array( $args['headers'] ) ? $args['headers'] : array();

		$to            = self::parse_address_list( $args['to'] ?? array() );
		$cc            = self::header_addresses( $headers, 'Cc' );
		$bcc           = self::header_addresses( $headers, 'Bcc' );
		$reply_to_list = self::header_addresses( $headers, 'Reply-To' );

		$reply_to = array();
		if ( ! empty( $reply_to_list ) ) {
			$reply_to = $reply_to_list[0];
		}

		return array(
			'to'       => $to,
			'cc'       => $cc,
			'bcc'      => $bcc,
			'reply_to' => $reply_to,
		);
	}

	/**
	 * Determine message bodies.
	 *
	 * @param array<string,mixed> $args Normalized mail args.
	 * @return array{text:string,html:?string}
	 */
	public static function message_bodies( array $args ): array {
		$headers = isset( $args['headers'] ) && is_array( $args['headers'] ) ? $args['headers'] : array();
		$is_html = self::is_html_message( $headers );

		$body = (string) ( $args['message'] ?? '' );

		if ( $is_html ) {
			return array(
				'html' => $body,
				'text' => wp_strip_all_tags( $body ),
			);
		}

		return array(
			'html' => null,
			'text' => $body,
		);
	}

	/**
	 * Return sender information.
	 *
	 * @param array<string,mixed> $settings Plugin settings.
	 * @return array<string,string>
	 */
	public static function sender_from_settings( array $settings ): array {
		$email = isset( $settings['from_email'] ) && is_email( $settings['from_email'] ) ? $settings['from_email'] : get_option( 'admin_email' );
		$name  = isset( $settings['from_name'] ) && '' !== $settings['from_name'] ? $settings['from_name'] : wp_specialchars_decode( get_bloginfo( 'name' ), ENT_QUOTES );

		return array(
			'email' => $email,
			'name'  => $name,
		);
	}

	/**
	 * Prepare attachments for API delivery.
	 *
	 * @param array<int,mixed> $attachments Attachment list.
	 * @return array<int,array<string,string>>
	 */
	public static function attachment_payloads( array $attachments ): array {
		$prepared = array();

		foreach ( $attachments as $attachment ) {
			$path = self::normalize_attachment_path( $attachment );
			if ( '' === $path || ! file_exists( $path ) || ! is_readable( $path ) ) {
				continue;
			}

			$contents = file_get_contents( $path );
			if ( false === $contents ) {
				continue;
			}

			$filename = basename( $path );
			$type     = function_exists( 'mime_content_type' ) ? mime_content_type( $path ) : null;

			if ( ! $type ) {
				$guessed = wp_check_filetype( $filename );
				$type    = $guessed['type'] ?? 'application/octet-stream';
			}

			$prepared[] = array(
				'name'    => $filename,
				'type'    => $type,
				'content' => base64_encode( $contents ),
			);
		}

		return $prepared;
	}

	/**
	 * Convert address array to formatted string.
	 *
	 * @param array<string,string> $address Address data.
	 * @return string
	 */
	public static function format_address( array $address ): string {
		$email = $address['email'] ?? '';
		$name  = trim( (string) ( $address['name'] ?? '' ) );

		if ( '' === $email ) {
			return '';
		}

		return $name ? sprintf( '%s <%s>', $name, $email ) : $email;
	}

	/**
	 * Convert attachments input into normalized path.
	 *
	 * @param mixed $attachment Raw attachment entry.
	 * @return string
	 */
	protected static function normalize_attachment_path( $attachment ): string {
		if ( is_array( $attachment ) ) {
			if ( isset( $attachment['file'] ) ) {
				return wp_normalize_path( (string) $attachment['file'] );
			}
			if ( isset( $attachment['path'] ) ) {
				return wp_normalize_path( (string) $attachment['path'] );
			}
		}

		return wp_normalize_path( (string) $attachment );
	}

	/**
	 * Determine if message is HTML based on headers.
	 *
	 * @param array<int,string> $headers Header lines.
	 * @return bool
	 */
	protected static function is_html_message( array $headers ): bool {
		foreach ( $headers as $header ) {
			if ( stripos( $header, 'Content-Type:' ) === 0 && stripos( $header, 'text/html' ) !== false ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Convert headers array into associative map.
	 *
	 * @param array<int,string> $headers Header lines.
	 * @return array<string,string>
	 */
	public static function headers_to_map( array $headers ): array {
		$map = array();

		foreach ( $headers as $header ) {
			if ( false === strpos( $header, ':' ) ) {
				continue;
			}
			list( $name, $value ) = array_map( 'trim', explode( ':', $header, 2 ) );
			if ( '' === $name ) {
				continue;
			}
			$map[ $name ] = $value;
		}

		return $map;
	}
}
