<?php

namespace WeDevs\Dokan\Order;

use Exception;
use WC_Order;
use WC_Order_Item_Coupon;
use WC_Order_Refund;
use WeDevs\Dokan\Cache;
use WeDevs\Dokan\Utilities\OrderUtil;
use WeDevs\Dokan\Vendor\Coupon;
use WP_Error;

/**
 * Order Management API
 *
 * @since 2.8
 * @since 3.8.0 added HPOS support
 */
class Manager {

    /**
     * Get all orders
     *
     * @since 3.0.0
     * @since 3.6.3 rewritten to include filters
     * @since 3.8.0 added HPOS support
     *
     * @return WP_Error|int[]|WC_Order[]
     */
    public function all( $args = [] ) {
        $query_args = apply_filters( 'dokan_get_vendor_orders_args', wp_parse_args( $this->get_backward_compatibility_args( $args ), $args ), $args, [] );

        if ( ! OrderUtil::is_hpos_enabled() ) {
            $meta = $query_args['meta_query'] ?? [];
            unset( $query_args['meta_query'] );

            $handle_meta = function ( $query ) use ( $meta ) {
                if ( [] === $meta ) {
                    return $query;
                }

                if ( ! isset( $query['meta_query'] ) ) {
                    $query['meta_query'] = $meta; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
                } else {
                    $query['meta_query'] = array_merge( $query['meta_query'], $meta ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
                }

                return $query;
            };

            add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', $handle_meta, 10, 1 );
        }

        $orders = wc_get_orders( $query_args );

        if ( ! OrderUtil::is_hpos_enabled() ) {
            remove_filter( 'woocommerce_order_data_store_cpt_get_orders_query', $handle_meta, 10 );
        }

        if ( isset( $args['return'] ) && 'count' === $args['return'] ) {
            return apply_filters( 'dokan_get_vendor_orders_count', $orders->total, $args );
        }

        return apply_filters( 'dokan_get_vendor_orders', $orders, $args );
    }

    /**
     * Get backward compatibility args
     *
     * @since 3.8.0
     *
     * @param array $args
     *
     * @return array
     */
    protected function get_backward_compatibility_args( $args = [] ) {
        $default = [
            'type'                => 'shop_order',
            'seller_id'           => 0,
            'customer_id'         => 0,
            'order_id'            => 0,
            'status'              => 'all',
            'order_date'          => '', // only for backward compatibility, will be removed in the future, if date is passed in args, it will be ignored
            'date'                => [
                'from' => '',
                'to'   => '',
            ],
            'search'              => '',
            'include'             => [],
            'exclude'             => [],
            'orderby'             => 'date',
            'order'               => 'DESC',
            'limit'               => 10,
            'paged'               => 1, // do nothing if offset is used
            'offset'              => '',
            'return'              => 'objects', // objects, ids, count
            'meta_query'          => [], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
            'meta_query_relation' => 'AND',
        ];

        $args = wp_parse_args( $args, $default );

        $query_args = [
            'limit'      => $args['limit'],
            'paged'      => $args['paged'],
            'offset'     => $args['offset'],
            'order'      => $args['order'],
            'return'     => $args['return'],
            'meta_query' => $args['meta_query'] ?? [], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
        ];

        // fix meta query relation
        $query_args['meta_query']['relation'] = $args['meta_query_relation'];

        // now apply filtering on the field
        // filter by seller id
        if ( ! $this->is_empty( $args['seller_id'] ) ) {
            $seller_ids                 = array_filter( array_map( 'absint', (array) $args['seller_id'] ) );
            $query_args['meta_query'][] = [
                [
                    'key'     => '_dokan_vendor_id',
                    'value'   => $seller_ids,
                    'compare' => 'IN',
                    'type'    => 'NUMERIC',
                ],
            ];
        }

        // filter customer id
        if ( ! $this->is_empty( $args['customer_id'] ) ) {
            $customer_ids              = array_filter( array_map( 'absint', (array) $args['customer_id'] ) );
            $query_args['customer_id'] = $customer_ids;
        }

        // filter order id
        if ( ! $this->is_empty( $args['order_id'] ) ) {
            $order_ids = array_filter( array_map( 'absint', (array) $args['order_id'] ) );
            if ( OrderUtil::is_hpos_enabled() ) {
                $query_args['id'] = $order_ids;
            } else {
                $query_args['post__in'] = $order_ids;
            }
        }

        // filter status
        if ( ! $this->is_empty( $args['status'] ) && 'all' !== $args['status'] ) {
            $query_args['status'] = (array) $args['status'];
        }

        // include order ids
        if ( ! $this->is_empty( $args['include'] ) ) {
            $include = array_filter( array_map( 'absint', (array) $args['include'] ) );
            if ( OrderUtil::is_hpos_enabled() ) {
                $query_args['id'] = $include;
            } else {
                $query_args['post__in'] = $include;
            }
        }

        // exclude order ids
        if ( ! $this->is_empty( $args['exclude'] ) ) {
            $exclude               = array_filter( array_map( 'absint', (array) $args['exclude'] ) );
            $query_args['exclude'] = $exclude;
        }

        // date filter
        $date_from = false;
        $date_to   = false;
        // check if the start date is set
        if ( ! $this->is_empty( $args['date']['from'] ) ) {
            // convert date string to object
            $date_from = dokan_current_datetime()->modify( $args['date']['from'] );
        }

        // check if the end date is set
        if ( ! $this->is_empty( $args['date']['to'] ) ) {
            // convert date string to object
            $date_to = dokan_current_datetime()->modify( $args['date']['to'] );
        }

        if ( $date_from && $date_to ) {
            $date_from                  = $date_from->format( 'Y-m-d' );
            $date_to                    = $date_to->format( 'Y-m-d' );
            $query_args['date_created'] = $date_from . '...' . $date_to;
            // if only start date is set
        } elseif ( $date_from ) {
            $query_args['date_created'] = '>=' . $date_from->format( 'Y-m-d' );
            // if only end date is set
        } elseif ( $date_to ) {
            $query_args['date_created'] = '<=' . $date_to->format( 'Y-m-d' );
            // if only a single date is set
        } elseif ( is_string( $args['date'] ) && ! empty( $args['date'] ) ) {
            // backward compatibility for old filter
            $query_args['date_created'] = $args['date'];
        } elseif ( ! $this->is_empty( $args['order_date'] ) ) {
            // backward compatibility for old filter
            $query_args['date_created'] = $args['order_date'];
        }

        // filter by search parameter
        if ( ! $this->is_empty( $args['search'] ) ) {
            if ( OrderUtil::is_hpos_enabled() ) {
                $query_args['id'] = (int) $args['search'];
            } else {
                $query_args['post__in'] = [ (int) $args['search'] ];
            }
        }

        // fix order by parameter
        if ( ! in_array( strtoupper( $args['order'] ), [ 'ASC', 'DESC' ], true ) ) {
            $query_args['order'] = 'DESC';
        }

        // fix order by parameter
        if ( isset( $args['order_by'] ) ) {
            $args['orderby'] = $args['order_by'];
        }

        // get supported orderby paramter
        $supported_order_by = [
            'name'      => 'name',
            'type'      => 'type',
            'rand'      => 'rand',
            'modified'  => 'modified',
            'post_date' => 'date',
            'date'      => 'date',
            'id'        => 'ID',
            'order_id'  => 'ID',
            'seller_id' => 'seller_id',
        ];

        if ( ! array_key_exists( $args['orderby'], $supported_order_by ) ) {
            $args['orderby'] = 'date';
        }

        switch ( $args['orderby'] ) {
            case 'seller_id':
                $query_args = array_merge(
                    $query_args, [
                        'orderby'   => 'meta_value',
                        'meta_key'  => '_dokan_vendor_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
                        'meta_type' => 'NUMERIC',
                    ]
                );
                break;

            default:
                $query_args['orderby'] = $supported_order_by[ $args['orderby'] ];
                break;
        }

        // fix return parameter
        if ( 'count' === $query_args['return'] ) {
            $query_args['limit']    = 1;
            $query_args['return']   = 'ids';
            $query_args['paginate'] = true;
        }

        return $query_args;
    }

    /**
     * Get single order details
     *
     * @since 3.0.0
     *
     * @return bool|WC_Order|WC_Order_Refund
     */
    public function get( $id ) {
        return wc_get_order( $id );
    }

    /**
     * Count orders for a seller
     *
     * @since 3.8.0 moved this function from functions.php file
     *
     * @param int $seller_id
     *
     * @return array
     */
    public function count_orders( $seller_id ) {
        global $wpdb;

        $cache_group = "seller_order_data_{$seller_id}";
        $cache_key   = "count_orders_{$seller_id}";
        $counts      = Cache::get( $cache_key, $cache_group );

        if ( false === $counts ) {
            $counts = [
                'wc-pending'        => 0,
                'wc-completed'      => 0,
                'wc-on-hold'        => 0,
                'wc-processing'     => 0,
                'wc-refunded'       => 0,
                'wc-cancelled'      => 0,
                'wc-failed'         => 0,
                'wc-checkout-draft' => 0,
                'total'             => 0,
            ];
            $counts = apply_filters( 'dokan_order_status_count', $counts );

            $order_table_name = OrderUtil::get_order_table_name();
            if ( OrderUtil::is_hpos_enabled() ) {
                // HPOS usage is enabled.
                // phpcs:disable
                $query = $wpdb->prepare(
                    "SELECT do.order_status as order_status, count(do.id) as order_count
                    FROM {$wpdb->prefix}dokan_orders AS do
                    LEFT JOIN $order_table_name p ON do.order_id = p.id
                    WHERE
                        do.seller_id = %d AND
                        p.status != 'trash'
                    GROUP BY do.order_status",
                    [ $seller_id ]
                );
                // phpcs:enable
            } else {
                // traditional CPT-based orders are in use.
                // phpcs:disable
                $query = $wpdb->prepare(
                    "SELECT do.order_status as order_status, count(do.id) as order_count
                    FROM {$wpdb->prefix}dokan_orders AS do
                    LEFT JOIN $order_table_name p ON do.order_id = p.ID
                    WHERE
                        do.seller_id = %d AND
                        p.post_status != 'trash'
                    GROUP BY do.order_status",
                    [ $seller_id ]
                );
                // phpcs:enable
            }

            $results = $wpdb->get_results( $query ); // phpcs:ignore

            if ( $results ) {
                foreach ( $results as $count ) {
                    if ( isset( $counts[ $count->order_status ] ) ) {
                        $counts[ $count->order_status ] = intval( $count->order_count );
                        $counts['total']                += $counts[ $count->order_status ];
                    }
                }
            }

            $counts = (object) $counts;
            Cache::set( $cache_key, $counts, $cache_group );
        }

        return $counts;
    }

    /**
     * Check if an order with same id is exists in database
     *
     * @since 3.8.0
     *
     * @param int|WC_Order $order_id
     *
     * @return boolean
     */
    public function is_order_already_synced( $order_id ) {
        global $wpdb;

        if ( $order_id instanceof WC_Order ) {
            $order_id = $order_id->get_id();
        }

        if ( ! $order_id || ! is_numeric( $order_id ) ) {
            return false;
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $order_id = $wpdb->get_var( $wpdb->prepare( "SELECT 1 FROM {$wpdb->prefix}dokan_orders WHERE order_id=%d LIMIT 1", $order_id ) );

        return wc_string_to_bool( $order_id );
    }

    /**
     * Check if order is belonged to given seller
     *
     * @since 3.8.0
     *
     * @param int $seller_id
     * @param int $order_id
     *
     * @return bool
     */
    public function is_seller_has_order( $seller_id, $order_id ) {
        global $wpdb;

        return 1 === (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->prepare(
                "SELECT 1 FROM {$wpdb->prefix}dokan_orders WHERE seller_id = %d AND order_id = %d LIMIT 1",
                [ $seller_id, $order_id ]
            )
        );
    }

    /**
     * Get order of current logged-in users or by given customer id
     *
     * @since 3.8.0
     *
     * @param array $args
     *
     * @return \stdClass|WC_Order[]|int[]
     */
    public function get_customer_orders( $args ) {
        // fix customer id if not provided
        $customer_id = ! empty( $args['customer_id'] ) ? intval( $args['customer_id'] ) : dokan_get_current_user_id();
        $args        = [
            'customer_id' => $customer_id,
            'limit'       => ! empty( $args['limit'] ) ? intval( $args['limit'] ) : 10,
            'paged'       => ! empty( $args['paged'] ) ? absint( $args['paged'] ) : 1,
            'return'      => ! empty( $args['return_type'] ) && in_array( $args['return_type'], [ 'ids', 'objects' ], true ) ? sanitize_text_field( $args['return_type'] ) : 'ids',
        ];

        return $this->all(
            apply_filters( 'woocommerce_my_account_my_orders_query', $args )
        );
    }

    /**
     * Get Customer Order IDs by Seller
     *
     * @since 3.8.0
     *
     * @param int $customer_id
     * @param int $seller_id
     *
     * @return int[]|null on failure
     */
    public function get_customer_order_ids_by_seller( $customer_id, $seller_id ) {
        if ( ! $customer_id || ! $seller_id ) {
            return null;
        }

        $args = [
            'customer_id' => $customer_id,
            'seller_id'   => $seller_id,
            'return'      => 'ids',
            'limit'       => -1,
        ];

        $orders = $this->all( apply_filters( 'dokan_get_customer_orders_by_seller', $args ) );

        return ! empty( $orders ) ? $orders : null;
    }

    /**
     * Get all child orders of a parent order
     *
     * @param int|WC_Order $parent_order
     * @param array $args
     *
     * @return WC_Order[]
     */
    public function get_child_orders( $parent_order, array $args = [] ) {
        $parent_order_id = is_numeric( $parent_order ) ? $parent_order : $parent_order->get_id();
        $default_args = [
			'type'   => 'shop_order',
			'parent' => $parent_order_id,
			'limit'  => -1,
		];

        $args = apply_filters(
            'dokan_get_child_orders_args',
            wp_parse_args( $args, $default_args ),
            $parent_order_id
        );

        return wc_get_orders( $args );
    }

    /**
     * Delete dokan order
     *
     * @since 3.8.0
     *
     * @param int      $order_id
     * @param int|null $seller_id
     *
     * @return void
     */
    public function delete_seller_order( $order_id, $seller_id = null ) {
        global $wpdb;

        $where = [
            'order_id' => $order_id,
        ];

        $where_format = [ '%d' ];

        if ( is_numeric( $seller_id ) ) {
            $where['seller_id'] = $seller_id;
            $where_format[]     = '%d';
        }

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $deleted = $wpdb->delete( $wpdb->prefix . 'dokan_orders', $where, $where_format );
        if ( false === $deleted ) {
            dokan_log( sprintf( '[DeleteSellerOrder] Error while deleting dokan order table data, order_id: %d, Database Error: %s  ', $order_id, $wpdb->last_error ) );

            return; // since dokan_orders table data couldn't be deleted, returning from here
        }

        // delete from dokan refund table -> order_id
        $deleted = $wpdb->query(  // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->prepare(
                "DELETE FROM `{$wpdb->prefix}dokan_refund` WHERE order_id = %d",
                [ $order_id ]
            )
        );
        if ( false === $deleted ) {
            dokan_log( sprintf( '[DeleteSellerOrder] Error while deleting refund table data, order_id: %d, Database Error: %s  ', $order_id, $wpdb->last_error ) );
        }

        do_action( 'dokan_after_deleting_seller_order', $order_id );

        // delete data from vendor balance table -> trn_id, trn_type: dokan_orders, dokan_refund, dokan_withdraw
        $deleted = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->prepare(
                "DELETE FROM `{$wpdb->prefix}dokan_vendor_balance`
                WHERE trn_id = %d AND trn_type in ( %s, %s, %s )",
                [ $order_id, 'dokan_orders', 'dokan_refund', 'dokan_withdraw' ]
            )
        );
        if ( false === $deleted ) {
            dokan_log( sprintf( '[DeleteSellerOrder] Error while deleting vendor balance table data, order_id: %d, Database Error: %s  ', $order_id, $wpdb->last_error ) );
        }

        // delete data from reverse withdrawal table -> order_id, trn_type: order_commission, manual_order_commission, order_refund
        $deleted = $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->prepare(
                "DELETE FROM `{$wpdb->prefix}dokan_reverse_withdrawal`
                WHERE trn_id = %d AND trn_type in ( %s, %s, %s )",
                [ $order_id, 'order_commission', 'manual_order_commission', 'order_refund' ]
            )
        );
        if ( false === $deleted ) {
            dokan_log( sprintf( '[DeleteSellerOrder] Error while deleting dokan reverse withdrawal table data, order_id: %d, Database Error: %s  ', $order_id, $wpdb->last_error ) );
        }
    }

    /**
     * Delete dokan order with suborders
     *
     * @since 3.8.0
     *
     * @param int $order_id
     *
     * @return void
     */
    public function delete_seller_order_with_suborders( $order_id ) {
        // delete main order
        $this->delete_seller_order( $order_id );

        $sub_orders = $this->get_child_orders( $order_id );

        if ( $sub_orders ) {
            foreach ( $sub_orders as $sub_order ) {
                // delete_seller_order for sub_order will be called from Order/Admin/Hooks.php file
                $sub_order->delete( true );
            }
        }
    }

    /**
     * Creates a sub order
     *
     * @param WC_Order $parent_order
     * @param integer  $seller_id
     * @param array    $seller_products
     *
     * @return void|WP_Error
     */
    public function create_sub_order( $parent_order, $seller_id, $seller_products ) {
        dokan_log( 'Creating sub order for vendor: #' . $seller_id );

        $bill_ship = [
            'billing_country',
            'billing_first_name',
            'billing_last_name',
            'billing_company',
            'billing_address_1',
            'billing_address_2',
            'billing_city',
            'billing_state',
            'billing_postcode',
            'billing_email',
            'billing_phone',
            'shipping_country',
            'shipping_first_name',
            'shipping_last_name',
            'shipping_company',
            'shipping_address_1',
            'shipping_address_2',
            'shipping_city',
            'shipping_state',
            'shipping_postcode',
        ];

        try {
            $order = new WC_Order();

            // save other details
            $order->set_created_via( 'dokan' );
            $order->set_cart_hash( $parent_order->get_cart_hash() );
            $order->set_customer_id( $parent_order->get_customer_id() );
            $order->set_currency( $parent_order->get_currency() );
            $order->set_prices_include_tax( $parent_order->get_prices_include_tax() );
            $order->set_customer_ip_address( $parent_order->get_customer_ip_address() );
            $order->set_customer_user_agent( $parent_order->get_customer_user_agent() );
            $order->set_customer_note( $parent_order->get_customer_note() );
            $order->set_payment_method( $parent_order->get_payment_method() );
            $order->set_payment_method_title( $parent_order->get_payment_method_title() );
            $order->update_meta_data( '_dokan_vendor_id', $seller_id );

            // save billing and shipping address
            foreach ( $bill_ship as $key ) {
                if ( is_callable( [ $order, "set_{$key}" ] ) ) {
                    $order->{"set_{$key}"}( $parent_order->{"get_{$key}"}() );
                }
            }

            // save other meta data
            $order->save(); // need to save order data before passing it to a hook

            // now insert line items
            $this->create_line_items( $order, $seller_products );

            // do shipping
            $this->create_shipping( $order, $parent_order );

            // do tax
            $this->create_taxes( $order, $parent_order, $seller_products );

            // add coupons if any
            $this->create_coupons( $order, $parent_order );

            $order->save(); // need to save order data before passing it to a hook

            do_action( 'dokan_create_sub_order_before_calculate_totals', $order, $parent_order, $seller_products );

            // finally, let the order re-calculate itself and save
            $order->calculate_totals();
            $order->set_status( $parent_order->get_status() );
            $order->set_parent_id( $parent_order->get_id() );
            $order->save();

            // update total_sales count for sub-order
            wc_update_total_sales_counts( $order->get_id() );

            dokan_log( 'Created sub order : #' . $order->get_id() );

            do_action( 'dokan_checkout_update_order_meta', $order->get_id(), $seller_id );
        } catch ( \Throwable $e ) {
            dokan_log( 'Error in create_sub_order: ' . $e->getMessage() );
            dokan_log( 'Stack trace: ' . $e->getTraceAsString() );
            dokan_log( 'Backtrace at error: ' . wp_debug_backtrace_summary() );
            return new WP_Error( 'dokan-suborder-error', $e->getMessage() );
        }
    }

    /**
     * Create line items for order
     *
     * @param object $order wc_get_order
     * @param array  $products
     *
     * @return void
     */
    private function create_line_items( $order, $products ) {
        foreach ( $products as $item ) {
            $product_item = new \WC_Order_Item_Product();

            $product_item->set_name( $item->get_name() );
            $product_item->set_product_id( $item->get_product_id() );
            $product_item->set_variation_id( $item->get_variation_id() );
            $product_item->set_quantity( $item->get_quantity() );
            $product_item->set_tax_class( $item->get_tax_class() );
            $product_item->set_subtotal( $item->get_subtotal() );
            $product_item->set_subtotal_tax( $item->get_subtotal_tax() );
            $product_item->set_total_tax( $item->get_total_tax() );
            $product_item->set_total( $item->get_total() );
            $product_item->set_taxes( $item->get_taxes() );

            $metadata = $item->get_meta_data();
            if ( $metadata ) {
                foreach ( $metadata as $meta ) {
                    $product_item->add_meta_data( $meta->key, $meta->value );
                }
            }

            $product_item->add_meta_data( '_dokan_parent_order_item_id', $item->get_id() );

            $order->add_item( $product_item );
        }

        do_action( 'dokan_after_create_line_items', $order );
    }

    /**
     * Create tax line items
     *
     * @param WC_Order $order
     * @param WC_Order $parent_order
     * @param array    $products
     *
     * @return void
     */
    private function create_taxes( $order, $parent_order, $products ) {
        $shipping  = $order->get_items( 'shipping' );
        $tax_total = 0;

        foreach ( $products as $item ) {
            $tax_total += $item->get_total_tax();
        }

        $seller_shipping_total_taxes = array_map(
            function ( $item ) {
                return $item->get_total_tax();
            },
            $shipping
        );

        foreach ( $parent_order->get_taxes() as $tax ) {
            $item = new \WC_Order_Item_Tax();
            $item->set_props(
                [
                    'rate_id'            => $tax->get_rate_id(),
                    'label'              => $tax->get_label(),
                    'compound'           => $tax->get_compound(),
                    'rate_code'          => \WC_Tax::get_rate_code( $tax->get_rate_id() ),
                    'tax_total'          => $tax_total,
                    'shipping_tax_total' => empty( $seller_shipping_total_taxes ) ? '' : array_sum( $seller_shipping_total_taxes ),
                ]
            );

            $order->add_item( $item );
        }
    }

    /**
     * Create shipping for a sub-order if neccessary
     *
     * @param WC_Order $order
     * @param WC_Order $parent_order
     *
     * @return void
     */
    private function create_shipping( $order, $parent_order ) {
        dokan_log( sprintf( '#%d - Creating Shipping.', $order->get_id() ) );

        // Get all shipping methods for parent order
        $shipping_methods = $parent_order->get_shipping_methods();
        $order_seller_id  = (int) dokan_get_seller_id_by_order( $order->get_id() );

        $applied_shipping_methods = [];

        if ( $shipping_methods ) {
            foreach ( $shipping_methods as $method_item_id => $shipping_object ) {
                $shipping_seller_id = (int) wc_get_order_item_meta( $method_item_id, 'seller_id', true );

                if ( $order_seller_id === $shipping_seller_id ) {
                    $applied_shipping_methods[] = $shipping_object;
                }
            }
            $applied_shipping_methods = array_filter( $applied_shipping_methods );
        }

        $applied_shipping_methods[0] = apply_filters_deprecated(
            'dokan_shipping_method',
            [
                $applied_shipping_methods[0] ?? '',
                $order->get_id(),
                $parent_order,
            ],
            '3.7.19',
            'dokan_shipping_methods'
        );

        $shipping_methods = apply_filters( 'dokan_shipping_methods', $applied_shipping_methods, $order->get_id(), $parent_order );

        // bail out if no shipping methods found
        if ( empty( $shipping_methods ) ) {
            dokan_log( sprintf( '#%d - No shipping method found. Aborting.', $order->get_id() ) );

            return;
        }

        $shipping_totals = 0.0;
        foreach ( $shipping_methods as $shipping_method ) {
            if ( ! is_a( $shipping_method, 'WC_Order_Item_Shipping' ) ) {
                continue;
            }
            dokan_log( sprintf( '#%d - Adding shipping item.', $order->get_id() ) );

            $item = clone $shipping_method;
            $item->set_id( 0 );
            $shipping_totals += $shipping_method->get_total();
            $order->add_item( $item );
        }
        $order->set_shipping_total( $shipping_totals );
        $order->save();
    }

    /**
     * Create coupons for a sub-order if necessary
     *
     * @param WC_Order $order
     * @param WC_Order $parent_order
     *
     * @return void
     */
    private function create_coupons( $order, $parent_order ) {
        if ( dokan()->is_pro_exists() && property_exists( dokan_pro(), 'vendor_discount' ) ) {
            // remove vendor discount coupon code changes
            remove_filter( 'woocommerce_order_get_items', [ dokan_pro()->vendor_discount->woocommerce_hooks, 'replace_coupon_name' ], 10 );
        }

        $parent_coupons = $parent_order->get_items( 'coupon' );

        if ( ! $parent_coupons ) {
            return;
        }

        $seller_id = dokan_get_seller_id_by_order( $order->get_id() );

        if ( ! $seller_id ) {
            return;
        }

        $parent_coupons = array_map(
            function ( $coupon ) {
                /** @var WC_Order_Item_Coupon $coupon */
                return $coupon->get_code();
            },
            $parent_coupons
        );

        $order_items = $order->get_items();
        $used_coupons_data = [];
        foreach ( $order_items as $order_item ) {
            $item_coupons = $order_item->get_meta( Coupon::DOKAN_COUPON_META_KEY, true );
            if ( ! is_array( $item_coupons ) ) {
                continue;
            }
            foreach ( $item_coupons as $code => $item ) {
                if ( ! isset( $used_coupons_data[ $code ] ) ) {
                    $used_coupons_data[ $code ] = 0;
                }
                if ( in_array( $code, $parent_coupons, true ) ) {
                    $used_coupons_data[ $code ] += $item['discount'];
                }
            }
        }

        foreach ( $used_coupons_data as $code => $total_discount ) {
            $coupon = new \WC_Coupon( $code );
            $coupon_item = new WC_Order_Item_Coupon();
            $coupon_item->set_props(
                [
                    'code'         => $code,
                    'discount'     => $total_discount,
                    'discount_tax' => 0,
                ]
            );
            $coupon_info = $coupon->get_short_info();
            $coupon_item->add_meta_data( 'coupon_info', $coupon_info );
            $coupon_item->add_meta_data( 'coupon_data', $coupon->get_data() );
            $order->add_item( $coupon_item );
        }
    }

    /**
     * Monitors a new order and attempts to create sub-orders
     *
     * If an order contains products from multiple vendor, we can't show the order
     * to each seller dashboard. That's why we need to divide the main order to
     * some sub-orders based on the number of sellers.
     *
     * @since 3.8.0 added $force_create parameter
     *
     * @param bool $force_create if this parameter is true, if suborder is already created, they'd be deleted first
     *
     * @param int  $parent_order_id
     *
     * @return void
     */
    public function maybe_split_orders( $parent_order_id, $force_create = false ) {
        if ( is_a( $parent_order_id, 'WC_Order' ) ) {
            $parent_order    = $parent_order_id;
            $parent_order_id = $parent_order->get_id();
        } else {
            $parent_order = $this->get( $parent_order_id );
        }

        if ( ! $parent_order ) {
            //dokan_log( sprintf( 'Invalid Order ID #%d found. Skipping from here.', $parent_order_id ) );
            return;
        }

        // return if order already has been processed by checking if a vendor id has been assigned to it
        if ( (int) $parent_order->get_meta( '_dokan_vendor_id' ) > 0 ) {
            return;
        }

        //dokan_log( sprintf( 'New Order #%d created. Init sub order.', $parent_order_id ) );

        if ( $force_create || wc_string_to_bool( $parent_order->get_meta( 'has_sub_order' ) ) === true ) {
            $child_orders = $this->get_child_orders( $parent_order->get_id() );

            foreach ( $child_orders as $child_order ) {
                $child_order->delete( true );
            }
        }

        $vendors = dokan_get_sellers_by( $parent_order_id );
        if ( empty( $vendors ) ) {
            // we didn't find any vendor, so we can't create sub-order
            //dokan_log( 'No vendor found, skipping sub order.' );
            return;
        }

        // return if we've only ONE seller
        if ( count( $vendors ) === 1 ) {
            //dokan_log( '1 vendor only, skipping sub order.' );

            $temp      = array_keys( $vendors );
            $seller_id = reset( $temp );

            do_action( 'dokan_create_parent_order', $parent_order, $seller_id );

            $parent_order->update_meta_data( '_dokan_vendor_id', $seller_id );
            $parent_order->save();

            do_action( 'dokan_checkout_update_order_meta', $parent_order_id, $seller_id );

            return;
        }

        // flag it as it has a suborder
        $parent_order->update_meta_data( 'has_sub_order', true );
        $parent_order->save();

        //dokan_log( sprintf( 'Got %s vendors, starting sub order.', count( $vendors ) ) );

        // seems like we've got multiple sellers
        foreach ( $vendors as $seller_id => $seller_products ) {
            $this->create_sub_order( $parent_order, $seller_id, $seller_products );
        }

        //dokan_log( sprintf( 'Completed sub order for #%d.', $parent_order_id ) );
    }

    /**
     * This will check if given var is empty or not.
     *
     * @since 3.6.3
     *
     * @param mixed $item
     *
     * @return bool
     */
    protected function is_empty( $item ) {
        if ( empty( $item ) ) {
            return true;
        }

        // if var is an array, check if it's empty or not
        if ( is_array( $item ) && isset( $item[0] ) && intval( $item[0] ) === 0 ) {
            return true;
        }

        return false;
    }
}
