<?php

/**
 * Settings: registers a WooCommerce settings tab and exposes option getters.
 *
 * @package CartGiftWrapping
 */
defined( 'ABSPATH' ) || exit;
/**
 * Class CGWFW_Settings
 */
class CGWFW_Settings {
    /**
     * Option key prefix.
     */
    const PREFIX = 'cgwfw_';

    /**
     * Cached defaults (null until first built — see defaults()).
     *
     * @var array|null
     */
    private $defaults = null;

    /**
     * Cached premium-access result (null until first resolved).
     *
     * @var bool|null
     */
    private $is_pro = null;

    /**
     * Constructor.
     */
    public function __construct() {
        add_filter( 'woocommerce_get_settings_pages', array($this, 'register_settings_page') );
    }

    /**
     * The default option values, built lazily on first access.
     *
     * Built here rather than in the constructor because the labels are
     * translated: the constructor runs on plugins_loaded, and calling __()
     * before init triggers WP 6.7+'s "translation loading was triggered too
     * early" notice. By the time an option is actually read (render, AJAX,
     * admin), init has run and translations may load.
     *
     * @return array
     */
    public function defaults() {
        if ( null !== $this->defaults ) {
            return $this->defaults;
        }
        $this->defaults = array(
            'mode'                      => 'per_order',
            'enable_product_page'       => 'no',
            'fee'                       => '5.00',
            'fee_per'                   => 'unit',
            'taxable'                   => 'no',
            'tax_class'                 => '',
            'targeting'                 => 'all',
            'categories'                => array(),
            'enable_product_exclusions' => 'no',
            'exclude_products'          => array(),
            'enable_included'           => 'no',
            'included_categories'       => array(),
            'included_products'         => array(),
            'included_text'             => __( 'Gift wrapping included', 'tinyplugs-gift-wrapping' ),
            'wrap_options'              => array(),
            'designs_display'           => 'flat',
            'designs_label'             => __( 'Choose gift wrapping', 'tinyplugs-gift-wrapping' ),
            'designs_none_label'        => __( 'No gift wrapping', 'tinyplugs-gift-wrapping' ),
            'enable_message'            => 'yes',
            'message_limit'             => 200,
            'enable_tooltip'            => 'no',
            'tooltip_text'              => '',
            'item_style'                => 'button',
            'pp_style'                  => 'checkbox',
            'button_size'               => 'normal',
            'button_label'              => __( 'Gift wrap this item', 'tinyplugs-gift-wrapping' ),
            'button_label_added'        => __( '✓ Gift wrapped', 'tinyplugs-gift-wrapping' ),
            'order_label'               => __( 'Add gift wrapping to my order', 'tinyplugs-gift-wrapping' ),
            'order_style'               => 'card',
            'order_show_on'             => 'both',
            'order_subtitle'            => __( 'Make your gift extra special', 'tinyplugs-gift-wrapping' ),
            'card_add_label'            => __( 'Add', 'tinyplugs-gift-wrapping' ),
            'card_added_label'          => __( 'Added', 'tinyplugs-gift-wrapping' ),
            'message_label'             => __( 'Gift message (optional)', 'tinyplugs-gift-wrapping' ),
            'fee_label'                 => __( 'Gift Wrapping', 'tinyplugs-gift-wrapping' ),
            'btn_bg'                    => '#1e3a5f',
            'btn_bg_added'              => '#2e7d32',
            'btn_color'                 => '#ffffff',
            'btn_color_added'           => '#ffffff',
            'btn_radius'                => 4,
            'delete_on_uninstall'       => 'no',
        );
        return $this->defaults;
    }

    /**
     * Register our settings page with WooCommerce.
     *
     * @param array $pages Existing settings pages.
     * @return array
     */
    public function register_settings_page( $pages ) {
        require_once CGWFW_PATH . 'includes/class-cgwfw-settings-page.php';
        $pages[] = new CGWFW_Settings_Page($this);
        return $pages;
    }

    /**
     * Whether premium (Pro) features may run. False in the free version, and in
     * the premium version until a valid license/trial is active. Cached per request.
     *
     * @return bool
     */
    public function is_pro() {
        if ( null === $this->is_pro ) {
            $this->is_pro = function_exists( 'cgwfw_' ) && cgwfw_()->can_use_premium_code();
        }
        return $this->is_pro;
    }

    /**
     * Get a single option with default fallback.
     *
     * Pro features are not gated here: each Pro feature's usage site is wrapped
     * in a can_use_premium_code__premium_only() block, so the free build simply
     * does not contain that code, and the premium build evaluates the license
     * where the feature runs.
     *
     * @param string $key Option key without prefix.
     * @return mixed
     */
    public function get( $key ) {
        return $this->raw( $key );
    }

    /**
     * Get the stored option value. Saved Pro values are preserved in the
     * database across up/downgrades; nothing is ever deleted by a plan change.
     *
     * @param string $key Option key without prefix.
     * @return mixed
     */
    public function raw( $key ) {
        $defaults = $this->defaults();
        $default = ( isset( $defaults[$key] ) ? $defaults[$key] : '' );
        return get_option( self::PREFIX . $key, $default );
    }

    /**
     * Build inline CSS from the styling options, exposed as CSS variables.
     *
     * @return string
     */
    public function get_inline_css() {
        return sprintf(
            ':root{--cgwfw-btn-bg:%s;--cgwfw-btn-bg-added:%s;--cgwfw-btn-color:%s;--cgwfw-btn-color-added:%s;--cgwfw-btn-radius:%spx;}',
            esc_attr( $this->get_hex( 'btn_bg' ) ),
            esc_attr( $this->get_hex( 'btn_bg_added' ) ),
            esc_attr( $this->get_hex( 'btn_color' ) ),
            esc_attr( $this->get_hex( 'btn_color_added' ) ),
            (int) $this->get( 'btn_radius' )
        );
    }

    /**
     * Get a colour option as a validated hex string, falling back to its default
     * when the stored value is not a valid #rgb / #rrggbb colour. This keeps
     * arbitrary input out of the inline CSS (no CSS injection via a colour field).
     *
     * Uses a self-contained regex rather than sanitize_hex_color(), which is not
     * guaranteed to be loaded on the front end where this CSS is generated.
     *
     * @param string $key Colour option key without prefix.
     * @return string A valid hex colour, e.g. "#1e3a5f".
     */
    private function get_hex( $key ) {
        $value = (string) $this->get( $key );
        if ( !preg_match( '/^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{6})$/', $value ) ) {
            $defaults = $this->defaults();
            $value = ( isset( $defaults[$key] ) ? $defaults[$key] : '#000000' );
        }
        return $value;
    }

    /**
     * The flat fee amount as a float.
     *
     * @return float
     */
    public function get_fee() {
        return (float) $this->get( 'fee' );
    }

    /**
     * The configured wrap designs, each: [ id, name, image (attachment id), price ].
     *
     * @return array
     */
    public function get_wrap_options() {
        $opts = $this->get( 'wrap_options' );
        return ( is_array( $opts ) ? array_values( $opts ) : array() );
    }

    /**
     * How wrap designs are presented: 'flat' (single flat fee, no designs),
     * 'dropdown' or 'lightbox'.
     *
     * @return string
     */
    public function designs_display() {
        $display = $this->get( 'designs_display' );
        return ( in_array( $display, array('flat', 'dropdown', 'lightbox'), true ) ? $display : 'flat' );
    }

    /**
     * Whether multi-design mode is active: a design display style is selected
     * AND at least one design exists. Otherwise the single flat-fee behaviour
     * applies.
     *
     * @return bool
     */
    public function has_wrap_options() {
        return false;
    }

    /**
     * Whether the product-page wrapping control should be offered.
     *
     * It rides on the per-item wrapped-map/fee path, so it only takes effect in
     * per-item mode (the per-order option lives below the cart totals, which has
     * no product-page equivalent).
     *
     * @return bool
     */
    public function product_page_enabled() {
        return false;
    }

    /**
     * Look up a single wrap design by id.
     *
     * @param string $id Option id.
     * @return array|null
     */
    public function get_wrap_option( $id ) {
        foreach ( $this->get_wrap_options() as $option ) {
            if ( (string) $option['id'] === (string) $id ) {
                return $option;
            }
        }
        return null;
    }

    /**
     * The price for a given wrap design id, or 0 if unknown.
     *
     * @param string $id Option id.
     * @return float
     */
    public function get_option_price( $id ) {
        $option = $this->get_wrap_option( $id );
        return ( $option ? (float) $option['price'] : 0.0 );
    }

    /**
     * Whether a given product is eligible for wrapping, based on targeting settings.
     *
     * Product/variation lists (exclusions, "included" products) are matched
     * against both the parent product id and the variation id, since the admin
     * product search (woocommerce_json_search_products_and_variations) lets the
     * merchant pick either. Category targeting always resolves on the parent,
     * where product_cat terms live.
     *
     * @param int $product_id   Parent product ID.
     * @param int $variation_id Variation ID, or 0 for a simple product.
     * @return bool
     */
    public function product_is_eligible( $product_id, $variation_id = 0 ) {
        $product_id = (int) $product_id;
        $variation_id = (int) $variation_id;
        // Virtual / downloadable products have nothing physical to wrap. Check
        // the most specific product (the variation when there is one).
        $product = wc_get_product( ( $variation_id ? $variation_id : $product_id ) );
        if ( $product && ($product->is_virtual() || $product->is_downloadable()) ) {
            if ( !apply_filters( 'cgwfw_allow_virtual_products', false, $product ) ) {
                return false;
            }
        }
        return true;
    }

    /**
     * Whether a product already includes gift wrapping (e.g. arrives in a gift
     * box from the supplier). Such products show an "included" badge instead of
     * the paid wrap option.
     *
     * @param int $product_id   Parent product ID.
     * @param int $variation_id Variation ID, or 0 for a simple product.
     * @return bool
     */
    public function product_includes_wrapping( $product_id, $variation_id = 0 ) {
        return false;
    }

}
