<?php
/**
 * Resolves the priority tier for a wp_mail() payload.
 *
 * @package NimblyMail\SMTP
 */

namespace NimblyMail\SMTP\Queue;

use NimblyMail\SMTP\Options;

defined( 'ABSPATH' ) || exit;

/**
 * Class PriorityResolver
 *
 * Determines whether a queued message should jump the line (password
 * resets, security alerts), drop to the back (newsletters, bulk drips),
 * or take the default lane.
 *
 * Two extension points:
 *
 *  - Headers: callers add `X-Nimbly-Mail-Priority: 0|10|20` to wp_mail()
 *    headers. This is the per-call form that survives serialisation cleanly.
 *  - Filter: `apply_filters( 'nimblymail_priority', int, array $atts )`
 *    runs after header parsing so site-wide rules (e.g. "all WC order
 *    emails are normal, all marketing emails are bulk") can override.
 *
 * Output is clamped to one of the three documented tiers; arbitrary
 * integer headers snap to the nearest tier.
 *
 * @since 1.0.0
 */
class PriorityResolver {

    /**
     * High-priority lane (drains first).
     *
     * @since 1.0.0
     *
     * @var int
     */
    public const HIGH = 0;

    /**
     * Normal lane. Default for most wp_mail() calls.
     *
     * @since 1.0.0
     *
     * @var int
     */
    public const NORMAL = 10;

    /**
     * Bulk lane (drains last).
     *
     * @since 1.0.0
     *
     * @var int
     */
    public const BULK = 20;

    /**
     * Header name callers use to mark per-message priority.
     *
     * @since 1.0.0
     *
     * @var string
     */
    private const HEADER_NAME = 'X-Nimbly-Mail-Priority';

    /**
     * Resolve the priority for a wp_mail() $atts array.
     *
     * @since 1.0.0
     *
     * @param array $atts The wp_mail() $atts array.
     *
     * @return int One of self::HIGH, self::NORMAL, self::BULK.
     */
    public static function resolve( array $atts ): int {
        $priority = self::resolve_default();

        $from_header = self::extract_header_priority( $atts['headers'] ?? '' );
        if ( null !== $from_header ) {
            $priority = $from_header;
        }

        /**
         * Filters the resolved priority for a wp_mail() call.
         *
         * Lets site-wide rules override per-call header values. Return
         * one of the public constants (HIGH=0, NORMAL=10, BULK=20) — any
         * other integer is snapped to the nearest tier.
         *
         * @since 1.0.0
         *
         * @param int   $priority Current priority (post-header parsing).
         * @param array $atts     The wp_mail() $atts array.
         *
         * @return int
         */
        $priority = (int) apply_filters( 'nimblymail_priority', $priority, $atts );

        return self::clamp( $priority );
    }

    /**
     * Resolve the configured fallback priority for messages with no explicit tier.
     *
     * Reads the `queue.default_priority` setting (high/normal/bulk) and
     * maps it to the matching constant. Unknown values fall back to NORMAL.
     *
     * @since 1.0.0
     *
     * @return int
     */
    private static function resolve_default(): int {
        $configured = (string) Options::init()->get( 'queue', 'default_priority' );
        switch ( $configured ) {
            case 'high':
                return self::HIGH;
            case 'bulk':
                return self::BULK;
            case 'normal':
            default:
                return self::NORMAL;
        }
    }

    /**
     * Snap an arbitrary integer to the nearest priority tier.
     *
     * @since 1.0.0
     *
     * @param int $value Raw priority value.
     *
     * @return int One of self::HIGH, self::NORMAL, self::BULK.
     */
    private static function clamp( int $value ): int {
        if ( $value <= self::HIGH ) {
            return self::HIGH;
        }
        if ( $value >= self::BULK ) {
            return self::BULK;
        }

        // Halfway point between HIGH and NORMAL is 5; between NORMAL and BULK is 15.
        if ( $value < ( self::HIGH + self::NORMAL ) / 2 ) {
            return self::HIGH;
        }
        if ( $value > ( self::NORMAL + self::BULK ) / 2 ) {
            return self::BULK;
        }
        return self::NORMAL;
    }

    /**
     * Extract the X-Nimbly-Mail-Priority header value from wp_mail() headers.
     *
     * Handles both the string form ("From: …\nX-Nimbly-Mail-Priority: 0\n…")
     * and the array form (["From: …", "X-Nimbly-Mail-Priority: 0"]).
     *
     * @since 1.0.0
     *
     * @param string|array $headers Raw headers as wp_mail() accepts them.
     *
     * @return int|null Parsed integer value, or null when the header is absent
     *                  or not numeric.
     */
    private static function extract_header_priority( $headers ): ?int {
        if ( is_string( $headers ) ) {
            $headers = explode( "\n", str_replace( "\r\n", "\n", $headers ) );
        }

        if ( ! is_array( $headers ) ) {
            return null;
        }

        $needle = strtolower( self::HEADER_NAME ) . ':';
        foreach ( $headers as $header ) {
            $header = (string) $header;
            if ( 0 !== stripos( $header, $needle ) ) {
                continue;
            }
            $value = trim( substr( $header, strlen( $needle ) ) );
            if ( '' === $value ) {
                return null;
            }
            if ( ! is_numeric( $value ) ) {
                return null;
            }
            return (int) $value;
        }

        return null;
    }
}
