<?php

/**
 * BookiFlex
 *
 * @package   BookiFlex
 * @author    BookiFlex
 * @copyright Copyright (c) 2025 BookiFlex
 * @license   GPL-2.0-or-later
 *
 * @link      https://bookiflex.com
 */
declare (strict_types = 1);
namespace BookiFlex\Plugin\Admin\CPT;

use BookiFlex\Core\Admin\CPT\AccommodationType;
use BookiFlex\Core\Admin\CPT\CancellationPolicy;
use BookiFlex\Core\Admin\CPT\PaymentType;
use BookiFlex\Core\Admin\CPT\Event\RatePlanContainerReady;
use BookiFlex\Core\Admin\CPT\RatePlan as BaseCPT;
use BookiFlex\Core\Admin\Field\SectionNotice;
use BookiFlex\Core\Contract\PluginInterface;
use BookiFlex\Core\Model\AccommodationType\AccommodationPlacementTypeInterface;
use BookiFlex\Core\Model\DateUnit\DateUnitTypeInterface;
use BookiFlex\Core\Model\RatePlan\BoardTypeInterface;
use BookiFlex\Core\Util\ProFeature;
use BookiFlex\Core\Admin\Field\RadioCard;
use BookiFlex\Extensions\Onboarding\Contract\OnboardingPresetsProviderInterface;
use Carbon_Fields\Container;
use Carbon_Fields\Field;
defined( 'ABSPATH' ) || exit;
class RatePlan extends BaseCPT {
    private const PRESET_META_KEY = '_bflex_preset_key';

    public function __construct( private readonly OnboardingPresetsProviderInterface $presetsProvider ) {
    }

    public function register( PluginInterface $plugin ) : void {
        parent::register( $plugin );
        $this->customizeMenuOrder( __( 'Display order', 'bookiflex' ), __( 'Controls the display order of this rate plan. Lower numbers appear first.', 'bookiflex' ) );
        if ( $this->supportInheritance() ) {
            $this->customizeParentSelector( __( 'Parent rate plan', 'bookiflex' ), __( 'Select a parent rate plan to inherit prices and restrictions from.', 'bookiflex' ) );
        }
        $this->customizeExcerpt( __( 'Description', 'bookiflex' ), __( 'A brief description of this rate plan. This text may be shown to guests during the booking process.', 'bookiflex' ) );
        $this->plugin->addAction(
            'carbon_fields_post_meta_container_saved',
            [$this, 'handleBookingTermsSave'],
            15,
            1
        );
        $this->plugin->addAction( 'add_meta_boxes', [$this, 'syncBookingTermsOnLoad'], 1 );
    }

    protected function supportInheritance() : bool {
        return false;
    }

    public function registerMetaBoxes() : void {
        $generalFields = [Field::make( 'hidden', self::FIELD_BOARD_TYPE, '' )->set_default_value( BoardTypeInterface::ROOM_ONLY ), SectionNotice::make( 'bflex__rp__accommodation_types-html', sprintf( '<strong>%s</strong> %s', __( 'Select the rental units this rate plan applies to.', 'bookiflex' ), __( 'You\'ll be able to define prices and restrictions for each one separately.', 'bookiflex' ) ), 'https://bookiflex.com/support/knowledge-base/setting-base-price/?utm_source=plugin&utm_medium=rate_plan&utm_campaign=documentation#rental-units' ), Field::make( 'association', self::FIELD_ACCOMMODATION_TYPES, '' )->set_required()->set_types( [[
            'type'      => 'post',
            'post_type' => AccommodationType::POST_TYPE,
        ]] )];
        Container::make( 'post_meta', 'rate-plan-short-code', __( 'Short code', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->set_context( 'side' )->add_fields( [Field::make( 'text', self::FIELD_SHORT_CODE, '' )->set_attribute( 'maxLength', '10' )->set_attribute( 'readOnly', 'true' )->help_text( __( 'A short identifier for this rate plan (generated automatically). For example: BRP for Base rate plan, SAM for Summer vacation rate plan. This code may be used internally or in reports.', 'bookiflex' ) )] );
        $fieldsContainer = Container::make( 'post_meta', 'rate-plan-fields', __( 'Configuration', 'bookiflex' ) )->set_layout( 'tabbed-vertical' )->where( 'post_type', '=', self::POST_TYPE )->add_tab( __( 'General', 'bookiflex' ), $generalFields )->add_tab( __( 'Booking terms', 'bookiflex' ), $this->buildBookingTermsFields() );
        $this->plugin->getEventDispatcher()->dispatch( new RatePlanContainerReady($fieldsContainer) );
        $this->showNotice();
    }

    private function showNotice() : void {
        Container::make( 'post_meta', 'rate-plan-inheritance-fields', __( 'Inheritance rules', 'bookiflex' ) )->where( 'post_type', '=', self::POST_TYPE )->add_fields( [ProFeature::notice( 'pro_notice_inheritance', __( 'Create <strong>child rate plans</strong> with inheritance rules. Automatically calculate prices and restrictions based on parent rate plans.', 'bookiflex' ) )] );
    }

    /**
     * Build Carbon Fields for booking terms: radio + conditional descriptions + conditional associations.
     *
     * @return array<\Carbon_Fields\Field\Field>
     */
    private function buildBookingTermsFields() : array {
        $fields = [];
        $fields[] = SectionNotice::make( 'booking-terms-html', __( 'Choose how guests pay and what cancellation rules apply to this rate plan.', 'bookiflex' ), 'https://bookiflex.com/support/knowledge-base/setting-base-price/?utm_source=plugin&utm_medium=rate_plan&utm_campaign=documentation#booking-terms' );
        // Build RadioCard options from presets
        $options = [];
        $presets = $this->presetsProvider->getBookingPresets();
        foreach ( $presets as $preset ) {
            $paymentTypeId = $this->findCptByPresetKey( PaymentType::POST_TYPE, $preset['paymentTypeKey'] );
            $cancellationPolicyId = $this->findCptByPresetKey( CancellationPolicy::POST_TYPE, $preset['cancellationPolicyKey'] );
            if ( $paymentTypeId && $cancellationPolicyId ) {
                $title = $preset['title'];
                if ( !empty( $preset['popular'] ) ) {
                    $title .= ' ' . __( '(recommended)', 'bookiflex' );
                }
                $description = $preset['shortDescription'];
                if ( !empty( $preset['recommendation'] ) ) {
                    $description .= ' ' . $preset['recommendation'];
                }
                $options[$preset['id']] = [
                    'title'       => $title,
                    'description' => $description,
                ];
            }
        }
        $options['custom'] = [
            'title'       => __( 'Custom', 'bookiflex' ),
            'description' => __( 'Manually select the payment method(s) and cancellation policy for this rate plan.', 'bookiflex' ),
        ];
        $default = $this->getDefaultBookingTermsValue();
        array_push( $fields, ...RadioCard::make(
            self::FIELD_BOOKING_TERMS,
            __( 'Booking terms', 'bookiflex' ),
            $options,
            $default,
            true
        ) );
        // Association fields — visible only for Custom
        $fields[] = Field::make( 'separator', 'bflex__cp__strict_separator', __( 'Configuration', 'bookiflex' ) )->set_conditional_logic( [[
            'field' => self::FIELD_BOOKING_TERMS,
            'value' => 'custom',
        ]] );
        $fields[] = Field::make( 'association', self::FIELD_PAYMENT_TYPES, __( 'Payment types', 'bookiflex' ) )->set_types( [[
            'type'      => 'post',
            'post_type' => PaymentType::POST_TYPE,
        ]] )->set_conditional_logic( [[
            'field' => self::FIELD_BOOKING_TERMS,
            'value' => 'custom',
        ]] );
        $fields[] = Field::make( 'association', self::FIELD_CANCELLATION_POLICY, __( 'Cancellation policy', 'bookiflex' ) )->set_types( [[
            'type'      => 'post',
            'post_type' => CancellationPolicy::POST_TYPE,
        ]] )->set_max( 1 )->set_conditional_logic( [[
            'field' => self::FIELD_BOOKING_TERMS,
            'value' => 'custom',
        ]] );
        return $fields;
    }

    /**
     * Get default value for booking terms radio.
     *
     * Returns the first available preset id, or 'custom' if none available.
     *
     * @return string
     */
    private function getDefaultBookingTermsValue() : string {
        $presets = $this->presetsProvider->getBookingPresets();
        foreach ( $presets as $preset ) {
            $paymentTypeId = $this->findCptByPresetKey( PaymentType::POST_TYPE, $preset['paymentTypeKey'] );
            $cancellationPolicyId = $this->findCptByPresetKey( CancellationPolicy::POST_TYPE, $preset['cancellationPolicyKey'] );
            if ( $paymentTypeId && $cancellationPolicyId ) {
                return $preset['id'];
            }
        }
        return 'custom';
    }

    /**
     * Handle save: resolve preset to CPT associations.
     *
     * When a preset is selected, find the matching PaymentType and CancellationPolicy
     * CPTs by their `_bflex_preset_key` and write them as associations.
     *
     * @internal Called via carbon_fields_post_meta_container_saved hook
     */
    public function handleBookingTermsSave( int $postId ) : void {
        if ( get_post_type( $postId ) !== self::POST_TYPE ) {
            return;
        }
        $bookingTerms = carbon_get_post_meta( $postId, self::FIELD_BOOKING_TERMS );
        if ( empty( $bookingTerms ) || $bookingTerms === 'custom' ) {
            return;
        }
        // Find the matching preset
        $preset = $this->findPresetById( $bookingTerms );
        if ( !$preset ) {
            return;
        }
        // Resolve to CPT IDs
        $paymentTypeId = $this->findCptByPresetKey( PaymentType::POST_TYPE, $preset['paymentTypeKey'] );
        $cancellationPolicyId = $this->findCptByPresetKey( CancellationPolicy::POST_TYPE, $preset['cancellationPolicyKey'] );
        if ( !$paymentTypeId || !$cancellationPolicyId ) {
            return;
        }
        // Write associations
        carbon_set_post_meta( $postId, self::FIELD_PAYMENT_TYPES, [$paymentTypeId] );
        carbon_set_post_meta( $postId, self::FIELD_CANCELLATION_POLICY, [$cancellationPolicyId] );
    }

    /**
     * Sync booking terms value when loading the edit screen.
     *
     * For existing Rate Plans that don't have a booking_terms value stored
     * (created before this feature), detect the preset from current associations.
     * Runs before Carbon Fields renders fields.
     *
     * @internal Called via add_meta_boxes hook at priority 1
     */
    public function syncBookingTermsOnLoad() : void {
        global $post;
        if ( !$post || get_post_type( $post ) !== self::POST_TYPE ) {
            return;
        }
        if ( $post->post_status === 'auto-draft' ) {
            return;
        }
        // Check if booking_terms is already stored
        $stored = get_post_meta( $post->ID, '_' . self::FIELD_BOOKING_TERMS, true );
        if ( !empty( $stored ) ) {
            return;
        }
        // Detect preset from existing associations and store it
        $detected = $this->detectPresetForPost( $post->ID );
        carbon_set_post_meta( $post->ID, self::FIELD_BOOKING_TERMS, $detected );
    }

    /**
     * Detect the current preset for a Rate Plan post based on its associations.
     *
     * @param int $postId
     * @return string Preset id or 'custom'
     */
    public function detectPresetForPost( int $postId ) : string {
        $paymentTypes = carbon_get_post_meta( $postId, self::FIELD_PAYMENT_TYPES );
        $cancellationPolicy = carbon_get_post_meta( $postId, self::FIELD_CANCELLATION_POLICY );
        // Normalize to arrays of IDs
        $ptIds = $this->extractAssociationIds( $paymentTypes );
        $cpIds = $this->extractAssociationIds( $cancellationPolicy );
        if ( count( $ptIds ) !== 1 || count( $cpIds ) !== 1 ) {
            return 'custom';
        }
        $ptId = $ptIds[0];
        $cpId = $cpIds[0];
        // Check preset keys on the associated CPTs
        $ptPresetKey = get_post_meta( $ptId, self::PRESET_META_KEY, true );
        $cpPresetKey = get_post_meta( $cpId, self::PRESET_META_KEY, true );
        if ( empty( $ptPresetKey ) || empty( $cpPresetKey ) ) {
            return 'custom';
        }
        // Find matching preset
        $presets = $this->presetsProvider->getBookingPresets();
        foreach ( $presets as $preset ) {
            if ( $preset['paymentTypeKey'] === $ptPresetKey && $preset['cancellationPolicyKey'] === $cpPresetKey ) {
                return $preset['id'];
            }
        }
        return 'custom';
    }

    /**
     * Find a CPT post ID by its preset key.
     *
     * @param string $postType CPT post type
     * @param string $presetKey The preset key to search for
     * @return int|null Post ID or null if not found
     */
    private function findCptByPresetKey( string $postType, string $presetKey ) : ?int {
        $posts = get_posts( [
            'post_type'      => $postType,
            'post_status'    => 'publish',
            'meta_key'       => self::PRESET_META_KEY,
            'meta_value'     => $presetKey,
            'posts_per_page' => 1,
            'fields'         => 'ids',
        ] );
        return ( !empty( $posts ) ? (int) $posts[0] : null );
    }

    /**
     * Find a preset definition by its id.
     *
     * @param string $presetId
     * @return array<string, mixed>|null
     */
    private function findPresetById( string $presetId ) : ?array {
        $presets = $this->presetsProvider->getBookingPresets();
        foreach ( $presets as $preset ) {
            if ( $preset['id'] === $presetId ) {
                return $preset;
            }
        }
        return null;
    }

    /**
     * Extract post IDs from Carbon Fields association value.
     *
     * @param mixed $value Carbon Fields association value
     * @return array<int>
     */
    private function extractAssociationIds( mixed $value ) : array {
        if ( empty( $value ) || !is_array( $value ) ) {
            return [];
        }
        $ids = [];
        foreach ( $value as $item ) {
            if ( is_array( $item ) && isset( $item['id'] ) ) {
                $ids[] = (int) $item['id'];
            } elseif ( is_numeric( $item ) ) {
                $ids[] = (int) $item;
            }
        }
        return $ids;
    }

}
