<?php

/**
 * Base class for all business rule validations.
 *
 * @package WooCommerce\PayPalCommerce\StoreSync\Validation
 */
declare (strict_types=1);
namespace WooCommerce\PayPalCommerce\StoreSync\Validation;

use WooCommerce\PayPalCommerce\StoreSync\Enums\ErrorCode;
use WooCommerce\PayPalCommerce\StoreSync\Enums\ErrorType;
use WooCommerce\PayPalCommerce\StoreSync\Validation\Resolution\ResolutionOption;
use WooCommerce\PayPalCommerce\StoreSync\Validation\Context\IssueContext;
/**
 * Implements the ValidationIssue schema.
 *
 * @see https://github.com/paypal/agent-commerce/blob/28b799b0d11b6fb62f423e203de6ea4b9f2ce122/v1/docs/SCHEMA_REFERENCE.md#validationissue
 */
class ValidationIssue
{
    private const MAX_MESSAGE_LENGTH = 255;
    private const MAX_USER_MESSAGE_LENGTH = 500;
    private const MAX_RESOLUTION_OPTIONS = 5;
    /**
     * @readonly Must only be changed via the constructor!
     */
    private string $issue_code;
    /**
     * @readonly Must only be changed via the constructor!
     */
    private string $issue_type;
    private string $message;
    private string $user_message = '';
    private string $field = '';
    private string $item_id = '';
    private array $context = array();
    private array $resolution_options = array();
    private function __construct(string $message, string $issue_code, string $issue_type)
    {
        $this->message = trim(substr($message, 0, self::MAX_MESSAGE_LENGTH));
        $this->issue_code = $issue_code;
        $this->issue_type = $issue_type;
    }
    /**
     * A generic business rule issue, intended for third-party code or cases
     * not covered by the more specific factory methods below.
     */
    public static function create_business_rule_violation(string $message): self
    {
        return new self($message, ErrorCode::BUSINESS_RULE_ERROR, ErrorType::BUSINESS_RULE);
    }
    /**
     * When to use:
     * - Coupon code is invalid or expired.
     * - Coupon not applicable to cart items.
     * - Coupon usage limit reached.
     */
    public static function create_coupon_invalid(string $message): self
    {
        return new self($message, ErrorCode::PRICING_ERROR, ErrorType::BUSINESS_RULE);
    }
    /**
     * When to use:
     * - Cart items have different currencies (mixed currency not supported).
     * - Cart currency does not match WooCommerce store currency.
     */
    public static function create_currency_mismatch(string $message): self
    {
        return new self($message, ErrorCode::PRICING_ERROR, ErrorType::BUSINESS_RULE);
    }
    /**
     * When to use:
     * - Requested quantity exceeds available stock.
     * - Stock reduced between cart creation and checkout.
     * - High-demand item with limited availability.
     */
    public static function create_insufficient_quantity(string $message): self
    {
        return new self($message, ErrorCode::INVENTORY_ISSUE, ErrorType::BUSINESS_RULE);
    }
    /**
     * When to use:
     * - Product is currently unavailable.
     * - No stock remaining.
     * - Item temporarily out of inventory.
     */
    public static function create_item_out_of_stock(string $message): self
    {
        return new self($message, ErrorCode::INVENTORY_ISSUE, ErrorType::BUSINESS_RULE);
    }
    /**
     * When to use:
     * - Product price does not match the cart value.
     * - Promotional pricing ended.
     * - Dynamic pricing adjustments occurred.
     */
    public static function create_price_mismatch(string $message): self
    {
        return new self($message, ErrorCode::PRICING_ERROR, ErrorType::BUSINESS_RULE);
    }
    /**
     * When to use:
     * - Shipping not available to a specified location.
     * - Regional restrictions apply.
     * - No shipping methods available for this address.
     */
    public static function create_shipping_unavailable(string $message): self
    {
        return new self($message, ErrorCode::SHIPPING_ERROR, ErrorType::BUSINESS_RULE);
    }
    /**
     * A generic invalid-data issue, intended for third-party code or cases
     * not covered by the more specific factory methods below.
     *
     * When to use:
     * - Provided data is incorrect, e.g., malformed email.
     * - Unexpected data format, e.g., non-numeric price.
     */
    public static function create_invalid_data(string $message): self
    {
        return new self($message, ErrorCode::DATA_ERROR, ErrorType::INVALID_DATA);
    }
    /**
     * When to use:
     * - Shipping address cannot be validated.
     * - Address is incomplete or malformed.
     * - Postal code format is invalid.
     */
    public static function create_invalid_address(string $message): self
    {
        return new self($message, ErrorCode::SHIPPING_ERROR, ErrorType::INVALID_DATA);
    }
    /**
     * When to use:
     * - Product ID doesn't exist in WooCommerce.
     * - Invalid or malformed item_id.
     */
    public static function create_invalid_product(string $message): self
    {
        return new self($message, ErrorCode::INVENTORY_ISSUE, ErrorType::INVALID_DATA);
    }
    /**
     * When to use:
     * - Required information missing, e.g., missing shipping address.
     */
    public static function create_missing_field(string $message): self
    {
        return new self($message, ErrorCode::DATA_ERROR, ErrorType::MISSING_FIELD);
    }
    /**
     * When to use:
     * - Payment was declined by the processor.
     */
    public static function create_payment_error(string $message): self
    {
        return new self($message, ErrorCode::PAYMENT_ERROR, ErrorType::BUSINESS_RULE);
    }
    /**
     * Returns the error code, which is a high-level description of the problem.
     * Possible values are defined in the `Enums/ErrorCode` class.
     */
    public function code(): string
    {
        return $this->issue_code;
    }
    /**
     * Returns the error type, which classifies the issue.
     * Possible values are defined in the `Enums/ErrorType` class.
     */
    public function type(): string
    {
        return $this->issue_type;
    }
    /**
     * Returns the field, which caused the issue, in a dot-notation.
     * For example "shipping_address.postal_code"
     */
    public function field(): string
    {
        return $this->field;
    }
    /**
     * Sets the field that triggered the issue.
     *
     * @param string $field Field path, e.g. "shipping_address.postal_code".
     * @return static
     */
    public function for_field(string $field): self
    {
        $this->field = $field;
        return $this;
    }
    /**
     * Sets the customer-friendly error message.
     *
     * @param string $user_message Customer-facing message.
     * @return static
     */
    public function user_message(string $user_message): self
    {
        $this->user_message = trim(substr($user_message, 0, self::MAX_USER_MESSAGE_LENGTH));
        return $this;
    }
    /**
     * Sets the cart item ID that triggered the issue.
     *
     * @param string $item_id Cart item identifier.
     * @return static
     */
    public function item_id(string $item_id): self
    {
        $this->item_id = $item_id;
        return $this;
    }
    /**
     * Adds one or more context instances to the validation issue.
     *
     * Accepts either a single IssueContext or an array of IssueContext objects.
     * Non-IssueContext values are silently ignored.
     *
     * @param IssueContext|array $context A context instance or array of instances.
     * @return static
     */
    public function add_context($context): self
    {
        if ($context instanceof IssueContext) {
            $this->context[] = $context;
            return $this;
        }
        if (is_array($context)) {
            foreach ($context as $item) {
                $this->add_context($item);
            }
        }
        return $this;
    }
    /**
     * Adds one or more resolution options to the validation issue.
     *
     * Accepts either a single ResolutionOption or an array of ResolutionOption objects.
     * Non-ResolutionOption values are silently ignored.
     * A maximum of 5 resolution options is allowed in total.
     *
     * @param ResolutionOption|array $resolution A resolution option or array of options.
     * @return static
     */
    public function add_resolution($resolution): self
    {
        if (count($this->resolution_options) >= self::MAX_RESOLUTION_OPTIONS) {
            return $this;
        }
        if ($resolution instanceof ResolutionOption) {
            $this->resolution_options[] = $resolution;
            return $this;
        }
        if (is_array($resolution)) {
            foreach ($resolution as $item) {
                $this->add_resolution($item);
            }
        }
        return $this;
    }
    public function to_array(): array
    {
        $data = array('code' => $this->code(), 'type' => $this->type(), 'message' => $this->message);
        if ($this->user_message) {
            $data['user_message'] = $this->user_message;
        }
        if ($this->field) {
            $data['field'] = $this->field;
        }
        if ($this->item_id) {
            $data['item_id'] = $this->item_id;
        }
        $context = $this->get_context();
        if ($context !== null) {
            $data['context'] = $context;
        }
        if (!empty($this->resolution_options)) {
            $data['resolution_options'] = array_map(static fn($option) => $option instanceof ResolutionOption ? $option->to_array() : $option, $this->resolution_options);
        }
        return $data;
    }
    /**
     * Returns the first context as an object for the API response.
     * Schema allows exactly one context object per issue; additional contexts are ignored.
     */
    private function get_context(): ?array
    {
        if (empty($this->context)) {
            return null;
        }
        return $this->context[0]->to_array();
    }
}
