<?php

/**
 * Plugin Name: Sensible Stock
 * Description: Smart WooCommerce purchase advice based on sales history, stock levels and supplier data.
 * Version: 2.4.31
 * Author: Sensible Stock
 * Author URI: https://sensiblestock.com
 * Requires Plugins: woocommerce
 * License: GPLv2 or later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain: sensible-stock
 * Domain Path: /languages
 *
 */
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
if ( !function_exists( 'sensst_fs' ) ) {
    /**
     * Freemius SDK helper.
     *
     * This WordPress.org build runs as the free version, with optional
     * accounts handled through Freemius. The plugin remains usable without
     * opting in or connecting an account.
     *
     * @return Freemius
     */
    function sensst_fs() {
        global $sensst_fs;
        if ( !isset( $sensst_fs ) ) {
            $freemius_start = dirname( __FILE__ ) . '/vendor/freemius/start.php';
            if ( !file_exists( $freemius_start ) ) {
                return null;
            }
            require_once $freemius_start;
            $sensst_fs = fs_dynamic_init( array(
                'id'               => '28238',
                'slug'             => 'sensible-stock',
                'type'             => 'plugin',
                'public_key'       => 'pk_901bbebe1f86f3240c71b48a1e8ec',
                'is_premium'       => false,
                'has_addons'       => false,
                'has_paid_plans'   => true,
                'is_org_compliant' => true,
                'menu'             => array(
                    'slug'    => 'sensst-besteladvies',
                    'contact' => false,
                    'support' => false,
                ),
                'is_live'          => true,
            ) );
        }
        return $sensst_fs;
    }

    sensst_fs();
    do_action( 'sensst_fs_loaded' );
}
if ( function_exists( 'sensst_fs' ) ) {
    $sensst_freemius = sensst_fs();
    if ( is_object( $sensst_freemius ) && method_exists( $sensst_freemius, 'set_basename' ) ) {
        $sensst_freemius->set_basename( true, __FILE__ );
    }
}
if ( !function_exists( 'sensst_cleanup_after_uninstall' ) ) {
    /**
     * Clean up Sensible Stock data after Freemius has reported the uninstall event.
     *
     * Freemius deployments must not include an uninstall.php file. The cleanup is
     * therefore attached to the Freemius after_uninstall action.
     *
     * @return void
     */
    function sensst_cleanup_after_uninstall() {
        global $wpdb;
        $options = array(
            'sensst_settings',
            'sensst_version',
            'sensst_license_status',
            'sensst_free_limit_notice_dismissed',
            'sensst_besteladvies_brand_minima',
            'sensst_besteladvies_product_minima',
            'sensst_besteladvies_suppliers',
            'sensst_besteladvies_seasonal_settings',
            'sensst_besteladvies_supplier_leadtimes',
            'sensst_besteladvies_supplier_min_orders',
            'sensst_besteladvies_open_orders',
            'sensst_besteladvies_slow_mover_exceptions'
        );
        foreach ( $options as $option ) {
            delete_option( $option );
            delete_site_option( $option );
        }
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Cleaning plugin transients during uninstall.
        $wpdb->query( "DELETE FROM {$wpdb->options}\n            WHERE option_name LIKE '_transient_ss_%'\n            OR option_name LIKE '_transient_timeout_ss_%'" );
        wp_clear_scheduled_hook( 'sensst_daily_stock_check' );
        wp_clear_scheduled_hook( 'sensst_weekly_forecast_refresh' );
        if ( function_exists( 'wp_cache_flush' ) ) {
            wp_cache_flush();
        }
    }

}
if ( function_exists( 'sensst_fs' ) ) {
    $sensst_uninstall_freemius = sensst_fs();
    if ( is_object( $sensst_uninstall_freemius ) && method_exists( $sensst_uninstall_freemius, 'add_action' ) ) {
        $sensst_uninstall_freemius->add_action( 'after_uninstall', 'sensst_cleanup_after_uninstall' );
    }
}
if ( !class_exists( 'Sensst_Besteladvies_Plugin' ) ) {
    final class Sensst_Besteladvies_Plugin {
        const VERSION = '2.4.31';

        const TEXT_DOMAIN = 'sensible-stock';

        const CAPABILITY = 'manage_woocommerce';

        const SLUG = 'sensst-besteladvies';

        const MINIMA_SLUG = 'sensst-besteladvies-minima';

        const BULK_SLUG = 'sensst-besteladvies-bulk-leverancier';

        const SUPPLIER_OVERVIEW_SLUG = 'sensst-besteladvies-per-leverancier';

        const SUPPLIERS_SLUG = 'sensst-besteladvies-leveranciers';

        const SEASONAL_SLUG = 'sensst-besteladvies-seizoenscorrectie';

        const BRAND_OPTION_KEY = 'sensst_besteladvies_brand_minima';

        const PRODUCT_OPTION_KEY = 'sensst_besteladvies_product_minima';

        const SUPPLIERS_OPTION_KEY = 'sensst_besteladvies_suppliers';

        const SEASONAL_OPTION_KEY = 'sensst_besteladvies_seasonal_settings';

        const ORDER_BLOCK_SLUG = 'sensst-besteladvies-bestelblok';

        const SUPPLIER_LEADTIME_OPTION_KEY = 'sensst_besteladvies_supplier_leadtimes';

        const SUPPLIER_MINORDER_OPTION_KEY = 'sensst_besteladvies_supplier_min_orders';


        // Fallback account URL used when the Freemius SDK is not available.
        const UPGRADE_URL = 'https://sensiblestock.com';


        const SLOW_MOVERS_SLUG = 'sensst-besteladvies-slechte-lopers';



        const ORDER_CREATE_SLUG = 'sensst-besteladvies-bestelling';

        const ORDERS_OPTION_KEY = 'sensst_besteladvies_open_orders';

        // array: supplier_key => order data
        const SLOW_MOVERS_OPTION_KEY = 'sensst_besteladvies_slow_mover_exceptions';

        const SLOW_MOVER_META = '_sensst_slow_mover_exception';

        const SLOW_MOVER_NOTE_META = '_sensst_slow_mover_note';

        const EXCLUDE_META = '_sensst_exclude_from_advice';

        // uitsluitingsvinkje
        const BRAND_META = '_sensst_brand';

        const SUPPLIER_META = '_sensst_supplier';

        const COST_META = '_sensst_cost_price';

        const LEADTIME_META = '_sensst_lead_time';

        const SUPPLIER2_META = '_sensst_supplier_2';

        const COST2_META = '_sensst_cost_price_2';

        const LEADTIME2_META = '_sensst_lead_time_2';

        public function __construct() {
            add_action( 'plugins_loaded', array($this, 'load_textdomain') );
            add_action( 'admin_menu', array($this, 'add_menu') );
            add_action( 'admin_enqueue_scripts', array($this, 'enqueue_assets') );
            add_action( 'admin_post_sensst_besteladvies_save_minima', array($this, 'handle_save_minima') );
            add_action( 'admin_post_sensst_besteladvies_save_suppliers', array($this, 'handle_save_suppliers') );
            add_action( 'admin_post_sensst_besteladvies_save_supplier_leadtimes', array($this, 'handle_save_supplier_leadtimes') );
            add_action( 'admin_post_sensst_besteladvies_save_order', array($this, 'handle_save_order') );
            add_action( 'admin_post_sensst_besteladvies_cancel_order', array($this, 'handle_cancel_order') );
            add_action( 'add_meta_boxes', array($this, 'add_supplier_meta_box') );
            add_action( 'save_post_product', array($this, 'save_supplier_meta') );
        }

        // =========================================================================
        // INTERNATIONALISATION
        // =========================================================================
        public function load_textdomain() {
            load_plugin_textdomain(
                self::TEXT_DOMAIN,
                false,
                dirname( plugin_basename( __FILE__ ) ) . '/languages'
            );
        }

        // =========================================================================
        // ASSETS
        // =========================================================================
        public function enqueue_assets( $hook ) {
            $our_pages = array(
                'toplevel_page_' . self::SLUG,
                'sensible-stock_page_' . self::MINIMA_SLUG,
                'sensible-stock_page_' . self::BULK_SLUG,
                'sensible-stock_page_' . self::SUPPLIER_OVERVIEW_SLUG,
                'sensible-stock_page_' . self::SUPPLIERS_SLUG,
                'sensible-stock_page_' . self::SEASONAL_SLUG,
                'sensible-stock_page_ss-besteladvies-levertijden',
                'sensible-stock_page_' . self::ORDER_BLOCK_SLUG,
                'sensible-stock_page_' . self::SLOW_MOVERS_SLUG,
                'sensible-stock_page_' . self::ORDER_CREATE_SLUG,
                'sensible-stock_page_ss-besteladvies-license'
            );
            $request_post_type = filter_input( INPUT_GET, 'post_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
            $is_product_list = $hook === 'edit.php' && sanitize_key( (string) $request_post_type ) === 'product';
            if ( !in_array( $hook, $our_pages, true ) && !$is_product_list ) {
                return;
            }
            wp_enqueue_style(
                'sensst-besteladvies',
                plugin_dir_url( __FILE__ ) . 'assets/ss-besteladvies.css',
                array(),
                self::VERSION
            );
            wp_enqueue_script(
                'sensst-besteladvies-admin',
                plugin_dir_url( __FILE__ ) . 'assets/ss-besteladvies-admin.js',
                array(),
                self::VERSION,
                true
            );
            wp_localize_script( 'sensst-besteladvies-admin', 'sensiblestockAdmin', array(
                'day'        => __( 'day', 'sensible-stock' ),
                'days'       => __( 'days', 'sensible-stock' ),
                'buffer'     => __( 'buffer', 'sensible-stock' ),
                'noBuffer'   => __( 'no buffer', 'sensible-stock' ),
                'copied'     => __( 'Copied', 'sensible-stock' ),
                'copyPrompt' => __( 'Copy this order list:', 'sensible-stock' ),
            ) );
        }

        // =========================================================================
        // LICENTIE & TIER SYSTEEM


        /**
         * Geeft de juiste account-URL terug.
         *
         * @return string
         */
        private function get_account_url() {
            if ( function_exists( 'sensst_fs' ) ) {
                $fs = sensst_fs();
                if ( is_object( $fs ) && method_exists( $fs, 'get_account_url' ) ) {
                    return $fs->get_account_url();
                }
            }
            return self::UPGRADE_URL;
        }





        /**
         * Rendert een nette account-banner als een pagina niet beschikbaar is.
         *
         * @param string $required_tier  'standard' of 'pro'
         * @param string $feature_label  Leesbare naam van de feature
         */
        /**
         * Rendert een kleine inline account-hint (voor in toolbars of naast knoppen).
         *
         * @param string $required_tier
         * @param string $label
         */
        private function render_account_badge( $required_tier, $label = '' ) {
            return;
        }

        /**
         * Toont het accountblokje onderaan elke pagina.
         */
        private function render_license_footer() {
            ?>
            <div class="ss-license-footer">
                <span><?php 
            esc_html_e( 'Sensible Stock is active.', 'sensible-stock' );
            ?></span>
            </div>
            <?php 
        }

        // =========================================================================
        // MENU
        // =========================================================================
        public function add_menu() {
            add_menu_page(
                __( 'Sensible Stock', 'sensible-stock' ),
                __( 'Sensible Stock', 'sensible-stock' ),
                self::CAPABILITY,
                self::SLUG,
                array($this, 'render_page'),
                'dashicons-list-view',
                56
            );
            add_submenu_page(
                self::SLUG,
                __( 'Purchase advice', 'sensible-stock' ),
                __( 'Purchase advice', 'sensible-stock' ),
                self::CAPABILITY,
                self::SLUG,
                array($this, 'render_page')
            );
            add_submenu_page(
                self::SLUG,
                __( 'Manage minimums', 'sensible-stock' ),
                __( 'Manage minimums', 'sensible-stock' ),
                self::CAPABILITY,
                self::MINIMA_SLUG,
                array($this, 'render_minima_page')
            );
            add_submenu_page(
                self::SLUG,
                __( 'Suppliers', 'sensible-stock' ),
                __( 'Suppliers', 'sensible-stock' ),
                self::CAPABILITY,
                self::SUPPLIERS_SLUG,
                array($this, 'render_suppliers_page')
            );
            add_submenu_page(
                self::SLUG,
                __( 'Lead times', 'sensible-stock' ),
                __( 'Lead times', 'sensible-stock' ),
                self::CAPABILITY,
                'sensst-besteladvies-levertijden',
                array($this, 'render_supplier_leadtimes_page')
            );
            add_submenu_page(
                self::SLUG,
                __( 'Create order', 'sensible-stock' ),
                __( 'Create order', 'sensible-stock' ),
                self::CAPABILITY,
                self::ORDER_CREATE_SLUG,
                array($this, 'render_order_create_page')
            );


        }

        // =========================================================================
        // HELPERS — normalisatie
        // =========================================================================
        private function normalize_text( $value ) {
            $value = (string) $value;
            $value = wp_strip_all_tags( $value );
            $value = remove_accents( $value );
            $value = strtolower( $value );
            $value = str_replace( array('&amp;', '&'), ' and ', $value );
            $value = preg_replace( '/[^a-z0-9]+/u', ' ', $value );
            $value = preg_replace( '/\\b(co|company)\\b/u', '', $value );
            $value = preg_replace( '/\\s+/u', ' ', $value );
            return trim( $value );
        }

        private function normalize_brand( $v ) {
            return $this->normalize_text( $v );
        }

        private function normalize_product_key( $v ) {
            return $this->normalize_text( $v );
        }

        private function normalize_supplier_label( $v ) {
            return $this->normalize_text( $v );
        }

        // =========================================================================
        // HELPERS — leveranciers
        // =========================================================================
        private function unique_supplier_labels( $labels ) {
            $unique = array();
            foreach ( (array) $labels as $label ) {
                $label = trim( sanitize_text_field( (string) $label ) );
                if ( $label === '' ) {
                    continue;
                }
                $key = $this->normalize_supplier_label( $label );
                if ( $key === '' ) {
                    continue;
                }
                if ( !isset( $unique[$key] ) ) {
                    $unique[$key] = $label;
                }
            }
            natcasesort( $unique );
            return array_values( $unique );
        }

        private function get_saved_suppliers() {
            $saved = get_option( self::SUPPLIERS_OPTION_KEY, array() );
            return $this->unique_supplier_labels( ( is_array( $saved ) ? $saved : array() ) );
        }

        private function get_existing_suppliers_from_products() {
            if ( !class_exists( 'WooCommerce' ) ) {
                return array();
            }
            $ids = wc_get_products( array(
                'status' => array('publish', 'private'),
                'limit'  => -1,
                'return' => 'ids',
                'type'   => array('simple', 'variable', 'variation'),
            ) );
            $labels = array();
            foreach ( $ids as $id ) {
                $labels[] = (string) get_post_meta( $id, self::SUPPLIER_META, true );
                $labels[] = (string) get_post_meta( $id, self::SUPPLIER2_META, true );
            }
            return $this->unique_supplier_labels( $labels );
        }

        private function get_all_supplier_labels() {
            return $this->unique_supplier_labels( array_merge( $this->get_saved_suppliers(), $this->get_existing_suppliers_from_products() ) );
        }

        private function get_supplier_options_map() {
            $map = array();
            foreach ( $this->get_all_supplier_labels() as $label ) {
                $key = $this->normalize_supplier_label( $label );
                if ( $key !== '' && !isset( $map[$key] ) ) {
                    $map[$key] = array(
                        'label' => $label,
                        'count' => 0,
                    );
                }
            }
            return $map;
        }

        private function render_supplier_dropdown( $field_id, $field_name, $selected_value ) {
            $options = $this->get_all_supplier_labels();
            ?>
            <select id="<?php 
            echo esc_attr( $field_id );
            ?>" name="<?php 
            echo esc_attr( $field_name );
            ?>" style="width:100%;">
                <option value=""><?php 
            esc_html_e( '— Select supplier —', 'sensible-stock' );
            ?></option>
                <?php 
            foreach ( $options as $opt ) {
                ?>
                    <option value="<?php 
                echo esc_attr( $opt );
                ?>" <?php 
                selected( $selected_value, $opt );
                ?>><?php 
                echo esc_html( $opt );
                ?></option>
                <?php 
            }
            ?>
            </select>
            <?php 
        }

        // =========================================================================
        // HELPERS — seizoenscorrectie
        // =========================================================================
        private function get_default_seasonal_settings() {
            return array(
                'enabled'              => 0,
                'christmas_percent'    => 20,
                'sinterklaas_percent'  => 12,
                'fathers_day_percent'  => 10,
                'mothers_day_percent'  => 8,
                'black_friday_percent' => 15,
            );
        }

        private function get_seasonal_settings() {
            $saved = get_option( self::SEASONAL_OPTION_KEY, array() );
            if ( !is_array( $saved ) ) {
                $saved = array();
            }
            $s = array_merge( $this->get_default_seasonal_settings(), $saved );
            $s['enabled'] = ( !empty( $s['enabled'] ) ? 1 : 0 );
            foreach ( array(
                'christmas_percent',
                'sinterklaas_percent',
                'fathers_day_percent',
                'mothers_day_percent',
                'black_friday_percent'
            ) as $k ) {
                $s[$k] = ( is_numeric( $s[$k] ) ? (float) $s[$k] : 0.0 );
            }
            return $s;
        }

        private function get_nth_weekday_of_month(
            $year,
            $month,
            $weekday,
            $nth
        ) {
            $date = new DateTime(sprintf( '%04d-%02d-01', $year, $month ));
            $first = (int) $date->format( 'w' );
            $offset = ($weekday - $first + 7) % 7;
            $day = 1 + $offset + ($nth - 1) * 7;
            return new DateTime(sprintf(
                '%04d-%02d-%02d',
                $year,
                $month,
                $day
            ));
        }

        private function get_black_friday_date( $year ) {
            $thanksgiving = $this->get_nth_weekday_of_month(
                $year,
                11,
                4,
                4
            );
            $bf = clone $thanksgiving;
            $bf->modify( '+1 day' );
            return $bf;
        }

        private function get_holiday_dates_for_range( $start_date, $end_date ) {
            $events = array();
            for ($y = (int) $start_date->format( 'Y' ); $y <= (int) $end_date->format( 'Y' ); $y++) {
                $events[] = array(
                    'key'   => 'christmas_percent',
                    'label' => __( 'Christmas', 'sensible-stock' ),
                    'date'  => new DateTime($y . '-12-25'),
                );
                $events[] = array(
                    'key'   => 'sinterklaas_percent',
                    'label' => 'Sinterklaas',
                    'date'  => new DateTime($y . '-12-05'),
                );
                $events[] = array(
                    'key'   => 'black_friday_percent',
                    'label' => 'Black Friday',
                    'date'  => $this->get_black_friday_date( $y ),
                );
                $events[] = array(
                    'key'   => 'mothers_day_percent',
                    'label' => __( "Mother's Day", 'sensible-stock' ),
                    'date'  => $this->get_nth_weekday_of_month(
                        $y,
                        5,
                        0,
                        2
                    ),
                );
                $events[] = array(
                    'key'   => 'fathers_day_percent',
                    'label' => __( "Father's Day", 'sensible-stock' ),
                    'date'  => $this->get_nth_weekday_of_month(
                        $y,
                        6,
                        0,
                        3
                    ),
                );
            }
            return $events;
        }

        private function calculate_seasonal_adjustment( $months_coverage ) {
            $settings = $this->get_seasonal_settings();
            if ( empty( $settings['enabled'] ) ) {
                return array(
                    'enabled' => false,
                    'percent' => 0.0,
                    'factor'  => 1.0,
                    'reasons' => array(),
                    'label'   => __( 'off', 'sensible-stock' ),
                );
            }
            $start = new DateTime(current_time( 'Y-m-d' ));
            $end = new DateTime(current_time( 'Y-m-d' ));
            $end->modify( '+' . max( 1, (int) $months_coverage ) . ' months' );
            $percent = 0.0;
            $reasons = array();
            foreach ( $this->get_holiday_dates_for_range( $start, $end ) as $event ) {
                if ( $event['date'] < $start || $event['date'] > $end ) {
                    continue;
                }
                $ep = ( isset( $settings[$event['key']] ) ? (float) $settings[$event['key']] : 0.0 );
                if ( $ep <= 0 ) {
                    continue;
                }
                $percent += $ep;
                $reasons[] = $event['label'] . ' +' . rtrim( rtrim( number_format(
                    $ep,
                    2,
                    ',',
                    ''
                ), '0' ), ',' ) . '%';
            }
            return array(
                'enabled' => true,
                'percent' => $percent,
                'factor'  => 1 + $percent / 100,
                'reasons' => $reasons,
                'label'   => ( empty( $reasons ) ? __( 'no active peak', 'sensible-stock' ) : implode( ', ', $reasons ) ),
            );
        }

        // =========================================================================
        // HELPERS — overige
        // =========================================================================
        /**
         * Controleert of een product uitgesloten moet worden van alle rapporten.
         * Voeg hier extra uitsluitingen toe als dat nodig is.
         *
         * @param  WC_Product $product
         * @return bool  true = uitsluiten
         */
        private function is_excluded_product( $product ) {
            $name = strtolower( $product->get_name() );
            // Handmatig uitgesloten via vinkje in productpagina
            if ( get_post_meta( $product->get_id(), self::EXCLUDE_META, true ) ) {
                return true;
            }
            // Interne WooCommerce POS producten
            if ( strpos( $name, 'woocommerce pos' ) !== false ) {
                return true;
            }
            // Producten van het type 'external' of 'grouped' nooit tonen
            if ( $product->is_type( 'external' ) || $product->is_type( 'grouped' ) ) {
                return true;
            }
            return false;
        }

        private function user_can_access() {
            return current_user_can( self::CAPABILITY );
        }

        private function get_query_arg_value( $key, $default = '' ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin filters and redirect notices are sanitized before use.
            if ( !isset( $_GET[$key] ) ) {
                return $default;
            }
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin filters and redirect notices are sanitized before use.
            return sanitize_text_field( wp_unslash( $_GET[$key] ) );
        }

        private function has_query_arg_value( $key ) {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin filters and redirect notices are sanitized before use.
            return isset( $_GET[$key] );
        }

        private function get_months_coverage() {
            $user_id = get_current_user_id();
            $dekking = $this->get_query_arg_value( 'dekking', '' );
            if ( $dekking !== '' ) {
                $m = max( 1, min( 12, absint( $dekking ) ) );
                if ( $user_id && isset( $_GET['sensst_filter_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['sensst_filter_nonce'] ) ), 'sensst_filter_update' ) ) {
                    update_user_meta( $user_id, 'sensst_months_coverage', $m );
                }
                return $m;
            }
            if ( $user_id ) {
                $saved = get_user_meta( $user_id, 'sensst_months_coverage', true );
                if ( $saved !== '' ) {
                    return max( 1, min( 12, (int) $saved ) );
                }
            }
            return 2;
        }

        private function get_brand_filter() {
            return $this->get_query_arg_value( 'merk', '' );
        }

        private function get_supplier_filter() {
            return $this->get_query_arg_value( 'leverancier', '' );
        }

        private function get_statuses() {
            return array('wc-completed');
        }

        private function format_money( $amount ) {
            return ( function_exists( 'wc_price' ) ? wc_price( $amount ) : '€' . number_format(
                (float) $amount,
                2,
                ',',
                '.'
            ) );
        }

        private function fmt_pct( $pct ) {
            return '+' . rtrim( rtrim( number_format(
                (float) $pct,
                2,
                ',',
                ''
            ), '0' ), ',' ) . '%';
        }

        // =========================================================================
        // PAGINA: LEVERANCIERS BEHEREN
        // =========================================================================
        public function render_suppliers_page() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }
            $saved = $this->get_saved_suppliers();

            ?>
            <div class="wrap">
                <h1><?php 
            esc_html_e( 'Manage suppliers', 'sensible-stock' );
            ?></h1>
                <?php 
            if ( $this->has_query_arg_value( 'updated' ) ) {
                ?><div class="notice notice-success is-dismissible"><p><?php 
                esc_html_e( 'Suppliers saved.', 'sensible-stock' );
                ?></p></div><?php 
            }
            ?>
                <p class="sensst-page-intro"><?php 
            esc_html_e( 'Add your regular suppliers here. They are used in all dropdowns and order workflows.', 'sensible-stock' );
            ?></p>
            ?>

                <div class="sensst-settings-box">
                    <h2><?php 
            esc_html_e( 'Supplier list', 'sensible-stock' );
            ?></h2>
                    <form method="post" action="<?php 
            echo esc_url( admin_url( 'admin-post.php' ) );
            ?>">
                        <?php 
            wp_nonce_field( 'sensst_besteladvies_save_suppliers' );
            ?>
                        <input type="hidden" name="action" value="sensst_besteladvies_save_suppliers" />
                        <table class="widefat striped ss-suppliers-table">
                            <thead><tr><th><?php 
            esc_html_e( 'Supplier name', 'sensible-stock' );
            ?></th></tr></thead>
                            <tbody>
                                <?php 
            foreach ( $saved as $label ) {
                ?>
                                    <tr><td><input type="text" name="suppliers[]" value="<?php 
                echo esc_attr( $label );
                ?>" class="regular-text"></td></tr>
                                <?php 
            }
            ?>
                                <?php 
            for ($i = 0; $i < 10; $i++) {
                ?>
                                    <tr><td><input type="text" name="suppliers[]" value="" class="regular-text" placeholder="<?php 
                esc_attr_e( 'New supplier', 'sensible-stock' );
                ?>"></td></tr>
                                <?php 
            }
            ?>
                            </tbody>
                        </table>
                        <p style="margin-top:16px;"><button type="submit" class="button button-primary"><?php 
            esc_html_e( 'Save', 'sensible-stock' );
            ?></button></p>
                    </form>
                </div>

                <div class="ss-info-box">
                    <p><strong><?php 
            esc_html_e( 'Automatically found suppliers from products:', 'sensible-stock' );
            ?></strong> <?php 
            echo esc_html( count( $this->get_existing_suppliers_from_products() ) );
            ?></p>
                    <p class="ss-small"><?php 
            esc_html_e( 'Suppliers already set on products are automatically added to the dropdown, even if not entered here.', 'sensible-stock' );
            ?></p>
                </div>
</div>
            <?php 
        }

        public function handle_save_suppliers() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }
            check_admin_referer( 'sensst_besteladvies_save_suppliers' );
            $suppliers = ( isset( $_POST['suppliers'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['suppliers'] ) ) : array() );
            $suppliers = $this->unique_supplier_labels( $suppliers );
            update_option( self::SUPPLIERS_OPTION_KEY, $suppliers );
            wp_safe_redirect( admin_url( 'admin.php?page=' . self::SUPPLIERS_SLUG . '&updated=1' ) );
            exit;
        }

        // =========================================================================
        // META BOX: LEVERANCIER PER PRODUCT
        // =========================================================================
        public function add_supplier_meta_box() {
            add_meta_box(
                'sensst_besteladvies_supplier_box',
                __( 'Brand, Suppliers & Purchase', 'sensible-stock' ),
                array($this, 'render_supplier_meta_box'),
                'product',
                'side',
                'default'
            );
        }

        public function render_supplier_meta_box( $post ) {
            wp_nonce_field( 'sensst_besteladvies_save_supplier_meta', 'sensst_besteladvies_supplier_nonce' );
            $brand = get_post_meta( $post->ID, self::BRAND_META, true );
            $s1 = get_post_meta( $post->ID, self::SUPPLIER_META, true );
            $c1 = get_post_meta( $post->ID, self::COST_META, true );
            $l1 = get_post_meta( $post->ID, self::LEADTIME_META, true );
            $s2 = get_post_meta( $post->ID, self::SUPPLIER2_META, true );
            $c2 = get_post_meta( $post->ID, self::COST2_META, true );
            $l2 = get_post_meta( $post->ID, self::LEADTIME2_META, true );
            // Haal huidige merknaam op uit bekende taxonomieën als het eigen veld leeg is
            if ( empty( $brand ) ) {
                foreach ( array(
                    'pa_merk',
                    'product_brand',
                    'pwb-brand',
                    'yith_product_brand'
                ) as $tax ) {
                    if ( taxonomy_exists( $tax ) ) {
                        $terms = get_the_terms( $post->ID, $tax );
                        if ( !empty( $terms ) && !is_wp_error( $terms ) ) {
                            $brand = trim( $terms[0]->name );
                            break;
                        }
                    }
                }
            }
            ?>
            <p><strong><?php 
            esc_html_e( 'Brand', 'sensible-stock' );
            ?></strong></p>
            <p><label for="sensst_brand"><?php 
            esc_html_e( 'Brand name', 'sensible-stock' );
            ?><br>
                <input type="text" id="sensst_brand" name="sensst_brand"
                       value="<?php 
            echo esc_attr( $brand );
            ?>"
                       style="width:100%;" placeholder="<?php 
            esc_attr_e( 'e.g. Truefitt &amp; Hill', 'sensible-stock' );
            ?>">
            </label></p>
            <p class="description" style="font-size:11px;margin-top:-6px;"><?php 
            esc_html_e( 'This field overrides the brand name from other brand plugins.', 'sensible-stock' );
            ?></p>
            <hr>
            <p><strong><?php 
            esc_html_e( 'Supplier 1', 'sensible-stock' );
            ?></strong></p>
            <p><label><?php 
            esc_html_e( 'Name', 'sensible-stock' );
            ?><br><?php 
            $this->render_supplier_dropdown( 'sensst_supplier', 'sensst_supplier', $s1 );
            ?></label></p>
            <p><label for="sensst_cost"><?php 
            esc_html_e( 'Purchase price (€)', 'sensible-stock' );
            ?><br><input type="number" step="0.01" min="0" id="sensst_cost" name="sensst_cost" value="<?php 
            echo esc_attr( $c1 );
            ?>" style="width:100%;"></label></p>
            <p><label for="sensst_lead"><?php 
            esc_html_e( 'Lead time (days)', 'sensible-stock' );
            ?><br><input type="number" min="0" step="1" id="sensst_lead" name="sensst_lead" value="<?php 
            echo esc_attr( $l1 );
            ?>" style="width:100%;"></label></p>
            <hr>
            <p><strong><?php 
            esc_html_e( 'Supplier 2', 'sensible-stock' );
            ?></strong></p>
            <p><label><?php 
            esc_html_e( 'Name', 'sensible-stock' );
            ?><br><?php 
            $this->render_supplier_dropdown( 'sensst_supplier_2', 'sensst_supplier_2', $s2 );
            ?></label></p>
            <p><label for="sensst_cost_2"><?php 
            esc_html_e( 'Purchase price (€)', 'sensible-stock' );
            ?><br><input type="number" step="0.01" min="0" id="sensst_cost_2" name="sensst_cost_2" value="<?php 
            echo esc_attr( $c2 );
            ?>" style="width:100%;"></label></p>
            <p><label for="sensst_lead_2"><?php 
            esc_html_e( 'Lead time (days)', 'sensible-stock' );
            ?><br><input type="number" min="0" step="1" id="sensst_lead_2" name="sensst_lead_2" value="<?php 
            echo esc_attr( $l2 );
            ?>" style="width:100%;"></label></p>
            <hr>
            <?php 
            $excluded = get_post_meta( $post->ID, self::EXCLUDE_META, true );
            ?>
            <p><label style="display:flex;align-items:center;gap:6px;">
                <input type="checkbox" name="sensst_exclude_from_advice" value="1" <?php 
            checked( !empty( $excluded ) );
            ?>>
                <span style="font-size:12px;font-weight:500;"><?php 
            esc_html_e( 'Exclude from purchase advice', 'sensible-stock' );
            ?></span>
            </label></p>
            <p class="description" style="font-size:11px;margin-top:-4px;"><?php 
            esc_html_e( 'Check if you no longer want to order this product (e.g. discontinued).', 'sensible-stock' );
            ?></p>
            <?php 
        }

        public function save_supplier_meta( $post_id ) {
            if ( !current_user_can( 'edit_post', $post_id ) ) {
                return;
            }
            if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
                return;
            }
            if ( !isset( $_POST['sensst_besteladvies_supplier_nonce'] ) ) {
                return;
            }
            $nonce = sanitize_text_field( wp_unslash( $_POST['sensst_besteladvies_supplier_nonce'] ) );
            if ( !wp_verify_nonce( $nonce, 'sensst_besteladvies_save_supplier_meta' ) ) {
                return;
            }
            // Merk opslaan
            $brand_val = ( isset( $_POST['sensst_brand'] ) ? sanitize_text_field( wp_unslash( $_POST['sensst_brand'] ) ) : '' );
            if ( $brand_val === '' ) {
                delete_post_meta( $post_id, self::BRAND_META );
            } else {
                update_post_meta( $post_id, self::BRAND_META, $brand_val );
            }
            $this->save_supplier_slot(
                $post_id,
                ( isset( $_POST['sensst_supplier'] ) ? sanitize_text_field( wp_unslash( $_POST['sensst_supplier'] ) ) : '' ),
                ( isset( $_POST['sensst_cost'] ) ? wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['sensst_cost'] ) ) ) : '' ),
                ( isset( $_POST['sensst_lead'] ) ? absint( wp_unslash( $_POST['sensst_lead'] ) ) : 0 ),
                self::SUPPLIER_META,
                self::COST_META,
                self::LEADTIME_META
            );
            $this->save_supplier_slot(
                $post_id,
                ( isset( $_POST['sensst_supplier_2'] ) ? sanitize_text_field( wp_unslash( $_POST['sensst_supplier_2'] ) ) : '' ),
                ( isset( $_POST['sensst_cost_2'] ) ? wc_format_decimal( sanitize_text_field( wp_unslash( $_POST['sensst_cost_2'] ) ) ) : '' ),
                ( isset( $_POST['sensst_lead_2'] ) ? absint( wp_unslash( $_POST['sensst_lead_2'] ) ) : 0 ),
                self::SUPPLIER2_META,
                self::COST2_META,
                self::LEADTIME2_META
            );
            // Uitsluitingsvinkje opslaan
            if ( !empty( $_POST['sensst_exclude_from_advice'] ) ) {
                update_post_meta( $post_id, self::EXCLUDE_META, 1 );
            } else {
                delete_post_meta( $post_id, self::EXCLUDE_META );
            }
        }

        private function save_supplier_slot(
            $post_id,
            $supplier,
            $cost,
            $lead,
            $sm,
            $cm,
            $lm
        ) {
            if ( $supplier === '' ) {
                delete_post_meta( $post_id, $sm );
            } else {
                update_post_meta( $post_id, $sm, $supplier );
            }
            if ( $cost === '' || $cost === false ) {
                delete_post_meta( $post_id, $cm );
            } else {
                update_post_meta( $post_id, $cm, $cost );
            }
            if ( $lead <= 0 ) {
                delete_post_meta( $post_id, $lm );
            } else {
                update_post_meta( $post_id, $lm, $lead );
            }
        }

        // =========================================================================
        // HELPERS — minima
        // =========================================================================
        private function get_default_brand_minimums() {
            return array(
                'van de zotte'      => 10,
                'dr k'              => 6,
                'dr k soap company' => 6,
                'mr bear'           => 6,
                'mr bear family'    => 6,
                'apothecary 78'     => 6,
                'captain fawcet'    => 6,
                'captain fawcett'   => 6,
            );
        }

        private function get_brand_minimums() {
            $saved = get_option( self::BRAND_OPTION_KEY, array() );
            if ( !is_array( $saved ) ) {
                $saved = array();
            }
            $normalized = array();
            foreach ( array_merge( $this->get_default_brand_minimums(), $saved ) as $brand => $min ) {
                $k = $this->normalize_brand( $brand );
                $min = absint( $min );
                if ( $k !== '' && $min > 0 ) {
                    $normalized[$k] = $min;
                }
            }
            return $normalized;
        }

        private function get_product_minimums() {
            $saved = get_option( self::PRODUCT_OPTION_KEY, array() );
            if ( !is_array( $saved ) ) {
                $saved = array();
            }
            $normalized = array();
            foreach ( $saved as $key => $min ) {
                $k = $this->normalize_product_key( $key );
                $min = absint( $min );
                if ( $k !== '' && $min > 0 ) {
                    $normalized[$k] = $min;
                }
            }
            return $normalized;
        }

        private function find_minimum_for_brand( $brand_key ) {
            $mins = $this->get_brand_minimums();
            if ( isset( $mins[$brand_key] ) ) {
                return (int) $mins[$brand_key];
            }
            foreach ( $mins as $mk => $mv ) {
                if ( strpos( $brand_key, $mk ) !== false || strpos( $mk, $brand_key ) !== false ) {
                    return (int) $mv;
                }
            }
            return 0;
        }

        private function find_minimum_for_product( $name, $sku = '' ) {
            $mins = $this->get_product_minimums();
            $pk = $this->normalize_product_key( $name );
            $sk = $this->normalize_product_key( $sku );
            if ( $sk !== '' && isset( $mins[$sk] ) ) {
                return (int) $mins[$sk];
            }
            if ( $pk !== '' && isset( $mins[$pk] ) ) {
                return (int) $mins[$pk];
            }
            foreach ( $mins as $k => $v ) {
                if ( $sk !== '' && (strpos( $sk, $k ) !== false || strpos( $k, $sk ) !== false) ) {
                    return (int) $v;
                }
                if ( $pk !== '' && (strpos( $pk, $k ) !== false || strpos( $k, $pk ) !== false) ) {
                    return (int) $v;
                }
            }
            return 0;
        }

        // =========================================================================
        // PAGINA: MINIMA BEHEREN
        // =========================================================================
        public function render_minima_page() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }
            $saved_brands = get_option( self::BRAND_OPTION_KEY, array() );
            if ( !is_array( $saved_brands ) || empty( $saved_brands ) ) {
                $saved_brands = $this->get_default_brand_minimums();
            }
            $saved_products = get_option( self::PRODUCT_OPTION_KEY, array() );
            if ( !is_array( $saved_products ) ) {
                $saved_products = array();
            }
            ksort( $saved_brands );
            ksort( $saved_products );
            ?>
            <div class="wrap">
                <h1><?php 
            esc_html_e( 'Manage minimums', 'sensible-stock' );
            ?></h1>
                <?php 
            if ( $this->has_query_arg_value( 'updated' ) ) {
                ?><div class="notice notice-success is-dismissible"><p><?php 
                esc_html_e( 'Minimums saved.', 'sensible-stock' );
                ?></p></div><?php 
            }
            ?>
                <p class="sensst-page-intro"><?php 
            esc_html_e( 'Set minimum order quantities per brand or product/SKU. The purchase advice will be adjusted upward if the calculation comes out lower.', 'sensible-stock' );
            ?></p>

                <form method="post" action="<?php 
            echo esc_url( admin_url( 'admin-post.php' ) );
            ?>">
                    <?php 
            wp_nonce_field( 'sensst_besteladvies_save_minima' );
            ?>
                    <input type="hidden" name="action" value="sensst_besteladvies_save_minima" />

                    <div class="sensst-settings-box">
                        <h2><?php 
            esc_html_e( 'Minimums per brand', 'sensible-stock' );
            ?></h2>
                        <table class="widefat striped" style="max-width:600px;">
                            <thead><tr><th><?php 
            esc_html_e( 'Brand', 'sensible-stock' );
            ?></th><th style="width:120px;"><?php 
            esc_html_e( 'Minimum units', 'sensible-stock' );
            ?></th></tr></thead>
                            <tbody>
                                <?php 
            foreach ( $saved_brands as $brand => $min ) {
                ?>
                                    <tr>
                                        <td><input type="text" name="brand[]" value="<?php 
                echo esc_attr( $brand );
                ?>" class="regular-text"></td>
                                        <td><input type="number" name="brand_minimum[]" value="<?php 
                echo esc_attr( $min );
                ?>" min="1" step="1" style="width:80px;"></td>
                                    </tr>
                                <?php 
            }
            ?>
                                <?php 
            for ($i = 0; $i < 5; $i++) {
                ?>
                                    <tr>
                                        <td><input type="text" name="brand[]" value="" class="regular-text" placeholder="<?php 
                esc_attr_e( 'New brand', 'sensible-stock' );
                ?>"></td>
                                        <td><input type="number" name="brand_minimum[]" value="" min="1" step="1" style="width:80px;" placeholder="0"></td>
                                    </tr>
                                <?php 
            }
            ?>
                            </tbody>
                        </table>
                    </div>

                    <div class="sensst-settings-box">
                        <h2><?php 
            esc_html_e( 'Minimums per product or SKU', 'sensible-stock' );
            ?></h2>
                        <table class="widefat striped" style="max-width:600px;">
                            <thead><tr><th><?php 
            esc_html_e( 'Product name or SKU', 'sensible-stock' );
            ?></th><th style="width:120px;"><?php 
            esc_html_e( 'Minimum units', 'sensible-stock' );
            ?></th></tr></thead>
                            <tbody>
                                <?php 
            foreach ( $saved_products as $pk => $min ) {
                ?>
                                    <tr>
                                        <td><input type="text" name="product_key[]" value="<?php 
                echo esc_attr( $pk );
                ?>" class="regular-text"></td>
                                        <td><input type="number" name="product_minimum[]" value="<?php 
                echo esc_attr( $min );
                ?>" min="1" step="1" style="width:80px;"></td>
                                    </tr>
                                <?php 
            }
            ?>
                                <?php 
            for ($i = 0; $i < 8; $i++) {
                ?>
                                    <tr>
                                        <td><input type="text" name="product_key[]" value="" class="regular-text" placeholder="<?php 
                esc_attr_e( 'e.g. alum stone or SKU', 'sensible-stock' );
                ?>"></td>
                                        <td><input type="number" name="product_minimum[]" value="" min="1" step="1" style="width:80px;" placeholder="0"></td>
                                    </tr>
                                <?php 
            }
            ?>
                            </tbody>
                        </table>
                    </div>

                    <p><button type="submit" class="button button-primary"><?php 
            esc_html_e( 'Save', 'sensible-stock' );
            ?></button></p>
                </form>
            </div>
            <?php 
        }

        public function handle_save_minima() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }
            check_admin_referer( 'sensst_besteladvies_save_minima' );
            $brands = ( isset( $_POST['brand'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['brand'] ) ) : array() );
            $brand_mins = ( isset( $_POST['brand_minimum'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['brand_minimum'] ) ) : array() );
            $prods = ( isset( $_POST['product_key'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['product_key'] ) ) : array() );
            $prod_mins = ( isset( $_POST['product_minimum'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['product_minimum'] ) ) : array() );
            $cb = array();
            foreach ( $brands as $i => $b ) {
                $b = sanitize_text_field( $b );
                $m = ( isset( $brand_mins[$i] ) ? absint( $brand_mins[$i] ) : 0 );
                if ( $b !== '' && $m > 0 ) {
                    $cb[$b] = $m;
                }
            }
            $cp = array();
            foreach ( $prods as $i => $pk ) {
                $pk = sanitize_text_field( $pk );
                $m = ( isset( $prod_mins[$i] ) ? absint( $prod_mins[$i] ) : 0 );
                if ( $pk !== '' && $m > 0 ) {
                    $cp[$pk] = $m;
                }
            }
            update_option( self::BRAND_OPTION_KEY, $cb );
            update_option( self::PRODUCT_OPTION_KEY, $cp );
            wp_safe_redirect( admin_url( 'admin.php?page=' . self::MINIMA_SLUG . '&updated=1' ) );
            exit;
        }

        // =========================================================================
        // RAPPORT: DATA
        // =========================================================================
        private function get_brand_for_product( $product_id ) {
            // 1. Eigen merkveld heeft hoogste prioriteit
            $own = get_post_meta( $product_id, self::BRAND_META, true );
            if ( !empty( $own ) && is_string( $own ) ) {
                return trim( $own );
            }
            // 2. Bekende merkplugin taxonomieën
            foreach ( array(
                'pa_merk',
                'product_brand',
                'pwb-brand',
                'yith_product_brand'
            ) as $tax ) {
                if ( taxonomy_exists( $tax ) ) {
                    $terms = get_the_terms( $product_id, $tax );
                    if ( !empty( $terms ) && !is_wp_error( $terms ) ) {
                        return trim( $terms[0]->name );
                    }
                }
            }
            // 3. Generieke meta velden
            foreach ( array('_brand', 'brand', 'merk') as $key ) {
                $v = get_post_meta( $product_id, $key, true );
                if ( !empty( $v ) && is_string( $v ) ) {
                    return trim( $v );
                }
            }
            return __( 'Unknown brand', 'sensible-stock' );
        }

        private function get_supplier_options_from_catalog() {
            $map = $this->get_supplier_options_map();
            foreach ( $this->get_existing_suppliers_from_products() as $supplier ) {
                $n = $this->normalize_supplier_label( $supplier );
                if ( $n === '' ) {
                    continue;
                }
                if ( !isset( $map[$n] ) ) {
                    $map[$n] = array(
                        'label' => $supplier,
                        'count' => 0,
                    );
                }
                $map[$n]['count']++;
            }
            uasort( $map, function ( $a, $b ) {
                return strnatcasecmp( $a['label'], $b['label'] );
            } );
            return $map;
        }

        private function product_matches_supplier_filter( $product, $norm ) {
            if ( $norm === '' ) {
                return true;
            }
            return $this->normalize_text( $product['supplier_1'] ?? '' ) === $norm || $this->normalize_text( $product['supplier_2'] ?? '' ) === $norm || $this->normalize_text( $product['best_supplier'] ?? '' ) === $norm;
        }

        private function get_brand_options_from_catalog() {
            if ( !class_exists( 'WooCommerce' ) ) {
                return array();
            }
            $ids = wc_get_products( array(
                'status' => array('publish'),
                'limit'  => -1,
                'return' => 'ids',
                'type'   => array('simple', 'variable', 'variation'),
            ) );
            $map = array();
            foreach ( $ids as $id ) {
                $p_tmp = wc_get_product( $id );
                if ( $p_tmp && $this->is_excluded_product( $p_tmp ) ) {
                    continue;
                }
                $brand = $this->get_brand_for_product( $id );
                $norm = $this->normalize_brand( $brand );
                if ( $norm === '' ) {
                    continue;
                }
                if ( !isset( $map[$norm] ) ) {
                    $map[$norm] = array(
                        'label' => $brand,
                        'count' => 0,
                    );
                }
                $map[$norm]['count']++;
                $len_new = ( function_exists( 'mb_strlen' ) ? mb_strlen( $brand ) : strlen( $brand ) );
                $len_cur = ( function_exists( 'mb_strlen' ) ? mb_strlen( $map[$norm]['label'] ) : strlen( $map[$norm]['label'] ) );
                if ( $len_new < $len_cur ) {
                    $map[$norm]['label'] = $brand;
                }
            }
            uasort( $map, function ( $a, $b ) {
                return strnatcasecmp( $a['label'], $b['label'] );
            } );
            return $map;
        }

        private function get_sales_stats() {
            $stats = $this->get_sales_stats_from_lookup_tables();
            if ( !empty( $stats ) ) {
                return $stats;
            }
            return $this->get_sales_stats_from_legacy_posts();
        }

        /**
         * Reads sales data from WooCommerce lookup tables.
         *
         * This works with both modern HPOS stores and classic stores when WooCommerce
         * analytics lookup tables are available. It prevents an empty report on shops
         * where orders no longer live primarily in wp_posts.
         *
         * @return array
         */
        private function get_sales_stats_from_lookup_tables() {
            global $wpdb;
            $order_product_lookup_raw = $wpdb->prefix . 'wc_order_product_lookup';
            $order_stats_raw = $wpdb->prefix . 'wc_order_stats';
            $order_product_lookup = esc_sql( $order_product_lookup_raw );
            $order_stats = esc_sql( $order_stats_raw );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check for WooCommerce compatibility.
            $has_product_lookup = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $order_product_lookup_raw ) );
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table existence check for WooCommerce compatibility.
            $has_order_stats = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $order_stats_raw ) );
            if ( $has_product_lookup !== $order_product_lookup_raw || $has_order_stats !== $order_stats_raw ) {
                return array();
            }
            $statuses = $this->get_statuses();
            $placeholders = implode( ',', array_fill( 0, count( $statuses ), '%s' ) );
            $sql = "\n                SELECT lookup.product_id AS product_id,\n                       DATE_FORMAT(stats.date_created, '%%Y-%%m') AS ym,\n                       SUM(CAST(lookup.product_qty AS DECIMAL(18,4))) AS qty_month,\n                       SUM(CASE WHEN stats.date_created >= DATE_SUB(NOW(), INTERVAL 90 DAY)  THEN CAST(lookup.product_qty AS DECIMAL(18,4)) ELSE 0 END) AS qty_90,\n                       SUM(CASE WHEN stats.date_created >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN CAST(lookup.product_qty AS DECIMAL(18,4)) ELSE 0 END) AS qty_12m,\n                       SUM(CASE WHEN stats.date_created >= DATE_SUB(NOW(), INTERVAL 36 MONTH) THEN CAST(lookup.product_qty AS DECIMAL(18,4)) ELSE 0 END) AS qty_36m\n                FROM {$order_product_lookup} AS lookup\n                INNER JOIN {$order_stats} AS stats ON stats.order_id = lookup.order_id\n                WHERE stats.status IN ({$placeholders})\n                  AND stats.date_created >= DATE_SUB(NOW(), INTERVAL 36 MONTH)\n                  AND lookup.product_id > 0\n                GROUP BY lookup.product_id, DATE_FORMAT(stats.date_created, '%%Y-%%m')\n            ";
            $cache_key = 'sensst_sales_lookup_' . md5( wp_json_encode( $statuses ) );
            $rows = wp_cache_get( $cache_key, 'sensible-stock' );
            if ( false === $rows ) {
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery -- Dynamic placeholder list is prepared above; sales report is calculated on demand.
                $rows = $wpdb->get_results( $wpdb->prepare( $sql, $statuses ), ARRAY_A );
                wp_cache_set(
                    $cache_key,
                    $rows,
                    'sensible-stock',
                    5 * MINUTE_IN_SECONDS
                );
            }
            return $this->build_stats_array_from_monthly_rows( $rows );
        }

        /**
         * Fallback for older WooCommerce installs where orders are stored in wp_posts.
         *
         * @return array
         */
        private function get_sales_stats_from_legacy_posts() {
            global $wpdb;
            $statuses = $this->get_statuses();
            $placeholders = implode( ',', array_fill( 0, count( $statuses ), '%s' ) );
            $sql = "\n                SELECT product_meta.meta_value AS product_id,\n                       DATE_FORMAT(posts.post_date, '%%Y-%%m') AS ym,\n                       SUM(CAST(qty_meta.meta_value AS DECIMAL(18,4))) AS qty_month,\n                       SUM(CASE WHEN posts.post_date >= DATE_SUB(NOW(), INTERVAL 90 DAY)  THEN CAST(qty_meta.meta_value AS DECIMAL(18,4)) ELSE 0 END) AS qty_90,\n                       SUM(CASE WHEN posts.post_date >= DATE_SUB(NOW(), INTERVAL 12 MONTH) THEN CAST(qty_meta.meta_value AS DECIMAL(18,4)) ELSE 0 END) AS qty_12m,\n                       SUM(CASE WHEN posts.post_date >= DATE_SUB(NOW(), INTERVAL 36 MONTH) THEN CAST(qty_meta.meta_value AS DECIMAL(18,4)) ELSE 0 END) AS qty_36m\n                FROM {$wpdb->prefix}woocommerce_order_items AS oi\n                INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS product_meta ON oi.order_item_id = product_meta.order_item_id AND product_meta.meta_key = '_product_id'\n                INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS qty_meta    ON oi.order_item_id = qty_meta.order_item_id    AND qty_meta.meta_key = '_qty'\n                INNER JOIN {$wpdb->posts} AS posts ON posts.ID = oi.order_id\n                WHERE oi.order_item_type = 'line_item'\n                  AND posts.post_type IN ('shop_order','shop_order_refund')\n                  AND posts.post_status IN ({$placeholders})\n                  AND posts.post_date >= DATE_SUB(NOW(), INTERVAL 36 MONTH)\n                GROUP BY product_meta.meta_value, DATE_FORMAT(posts.post_date, '%%Y-%%m')\n            ";
            $cache_key = 'sensst_sales_legacy_' . md5( wp_json_encode( $statuses ) );
            $rows = wp_cache_get( $cache_key, 'sensible-stock' );
            if ( false === $rows ) {
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery -- Dynamic placeholder list is prepared above; sales report is calculated on demand.
                $rows = $wpdb->get_results( $wpdb->prepare( $sql, $statuses ), ARRAY_A );
                wp_cache_set(
                    $cache_key,
                    $rows,
                    'sensible-stock',
                    5 * MINUTE_IN_SECONDS
                );
            }
            return $this->build_stats_array_from_monthly_rows( $rows );
        }

        /**
         * Converts monthly SQL rows to the structure used by the forecast.
         *
         * @param array $rows SQL result rows.
         * @return array
         */
        private function build_stats_array_from_monthly_rows( $rows ) {
            $stats = array();
            foreach ( (array) $rows as $row ) {
                $pid = absint( $row['product_id'] );
                if ( $pid <= 0 ) {
                    continue;
                }
                if ( !isset( $stats[$pid] ) ) {
                    $stats[$pid] = array(
                        'sold_12m'          => 0.0,
                        'sold_36m'          => 0.0,
                        'months_with_sales' => 0,
                        'qty_90'            => 0.0,
                    );
                }
                $qty_month = ( isset( $row['qty_month'] ) ? (float) $row['qty_month'] : 0.0 );
                $qty_90 = ( isset( $row['qty_90'] ) ? (float) $row['qty_90'] : 0.0 );
                $qty_12m = ( isset( $row['qty_12m'] ) ? (float) $row['qty_12m'] : 0.0 );
                $stats[$pid]['sold_12m'] += max( 0.0, $qty_12m );
                $stats[$pid]['sold_36m'] += max( 0.0, $qty_month );
                $stats[$pid]['qty_90'] += max( 0.0, $qty_90 );
                if ( $qty_month > 0 ) {
                    $stats[$pid]['months_with_sales']++;
                }
            }
            return $stats;
        }

        /**
         * Calculate how many months a product has been published.
         *
         * Newer products should not be treated as if they had 36 months of
         * sales history. This prevents fresh products with a recent sale from
         * disappearing from the reorder advice too early.
         *
         * @param WC_Product $product Product object.
         * @return int Number of months since publication, minimum 1.
         */
        private function get_product_age_months( $product ) {
            if ( !$product || !is_a( $product, 'WC_Product' ) ) {
                return 12;
            }
            $created = $product->get_date_created();
            if ( !$created ) {
                return 12;
            }
            $created_ts = $created->getTimestamp();
            if ( $created_ts <= 0 ) {
                return 12;
            }
            $days = max( 1, (int) floor( (time() - $created_ts) / DAY_IN_SECONDS ) );
            return max( 1, (int) ceil( $days / 30 ) );
        }

        /**
         * Berekent de maandelijkse vraag op basis van verkoophistorie.
         *
         * De berekening werkt in lagen op basis van het 36-maands gemiddelde:
         *
         * SNEL  (>= 2/mnd)   — 12m basis, 90d als trend, geen rem
         * NORMAAL (0.5-2/mnd) — 12m basis, begrensd op 1.5x het 36m gemiddelde
         * TRAAG (0.15-0.5/mnd) — altijd 36m gemiddelde, geen kortetermijn invloed
         * HEEL TRAAG (< 0.15/mnd = < 2/jaar) — 36m gemiddelde, harde max van 2 per bestelling
         *
         * @return array  ['per_month' => float, 'method' => string, 'max_per_order' => int|null]
         */
        private function calculate_monthly_demand(
            $sold_12m,
            $qty_90,
            $months_with_sales,
            $stock_qty,
            $sold_36m = null,
            $product_age_months = 12
        ) {
            $product_age_months = max( 1, (int) $product_age_months );
            $history_months = min( 12, $product_age_months );
            $avg_12m = $sold_12m / 12;
            $avg_90 = $qty_90 / 3;
            $avg_active = ( $months_with_sales > 0 ? $sold_12m / $months_with_sales : 0 );
            $avg_age = ( $sold_12m > 0 ? $sold_12m / $history_months : 0 );
            $avg_36m = ( $sold_36m !== null ? (float) $sold_36m / 36 : null );
            $max_per_order = null;
            // geen harde limiet tenzij heel traag
            // New products have less history by definition. Do not divide their
            // demand by 36 months, otherwise a product published a few months ago
            // and sold yesterday can disappear from the reorder advice.
            if ( $product_age_months < 12 && $sold_12m > 0 ) {
                $chosen = max( $avg_12m, $avg_age, $avg_active );
                $method = __( 'product age average', 'sensible-stock' );
                if ( $qty_90 > 0 && $avg_90 >= $chosen * 0.75 ) {
                    $chosen = max( $chosen, $avg_90 );
                    $method = __( 'new product 90d trend', 'sensible-stock' );
                }
            } elseif ( $avg_36m === null || $avg_36m >= 2.0 ) {
                // SNEL — normale berekening, kortetermijn trend mag doorwerken
                $chosen = $avg_12m;
                $method = __( '12m average', 'sensible-stock' );
                if ( $stock_qty <= 0 ) {
                    if ( $avg_active > $chosen ) {
                        $chosen = $avg_active;
                        $method = __( 'active months', 'sensible-stock' );
                    }
                    if ( $avg_90 > $chosen ) {
                        $chosen = $avg_90;
                        $method = __( 'last 90 days', 'sensible-stock' );
                    }
                } else {
                    if ( $avg_90 > $avg_12m * 1.35 && $avg_90 > $chosen ) {
                        $chosen = $avg_90;
                        $method = __( '90d trend', 'sensible-stock' );
                    }
                }
            } elseif ( $avg_36m >= 0.5 ) {
                // NORMAAL — 12m basis maar begrensd door langetermijn
                $chosen = $avg_12m;
                $method = __( '12m average', 'sensible-stock' );
                if ( $chosen > $avg_36m * 1.5 ) {
                    $chosen = $avg_36m * 1.5;
                    $method = __( '36m cap', 'sensible-stock' );
                }
            } elseif ( $avg_36m >= 0.15 ) {
                // TRAAG — altijd het 36m gemiddelde, geen kortetermijn invloed
                $chosen = $avg_36m;
                $method = __( '36m average', 'sensible-stock' );
                $max_per_order = max( 2, (int) ceil( $avg_36m * 6 ) );
                // max = halfjaar behoefte
            } else {
                // HEEL TRAAG — minder dan 2 per jaar
                // Gebruik 36m gemiddelde, hard maximum van 2 stuks per bestelling
                $chosen = ( $avg_36m > 0 ? $avg_36m : 0.0 );
                $method = __( '36m (slow seller)', 'sensible-stock' );
                $max_per_order = 2;
            }
            return array(
                'per_month'     => max( 0.0, $chosen ),
                'method'        => $method,
                'max_per_order' => $max_per_order,
            );
        }

        private function get_best_supplier_for_product( $product_id ) {
            $options = array();
            foreach ( array(array(self::SUPPLIER_META, self::COST_META, self::LEADTIME_META), array(self::SUPPLIER2_META, self::COST2_META, self::LEADTIME2_META)) as $slot => $metas ) {
                $name = (string) get_post_meta( $product_id, $metas[0], true );
                if ( $name === '' ) {
                    continue;
                }
                $cost = get_post_meta( $product_id, $metas[1], true );
                $lead = get_post_meta( $product_id, $metas[2], true );
                $options[] = array(
                    'name'      => $name,
                    'cost'      => ( $cost !== '' ? (float) $cost : null ),
                    'lead_time' => ( $lead !== '' ? (int) $lead : null ),
                );
            }
            if ( empty( $options ) ) {
                return array(
                    'label'      => '',
                    'cost'       => null,
                    'lead_time'  => null,
                    'all_labels' => '',
                );
            }
            $best = null;
            foreach ( $options as $o ) {
                if ( $best === null ) {
                    $best = $o;
                    continue;
                }
                if ( $o['cost'] !== null && $best['cost'] === null ) {
                    $best = $o;
                    continue;
                }
                if ( $o['cost'] !== null && $best['cost'] !== null && $o['cost'] < $best['cost'] ) {
                    $best = $o;
                    continue;
                }
            }
            $labels = array();
            foreach ( $options as $o ) {
                $piece = $o['name'];
                if ( $o['cost'] !== null ) {
                    $piece .= ' (€' . number_format(
                        $o['cost'],
                        2,
                        ',',
                        '.'
                    ) . ')';
                }
                $labels[] = $piece;
            }
            return array(
                'label'      => (string) $best['name'],
                'cost'       => $best['cost'],
                'lead_time'  => $best['lead_time'],
                'all_labels' => implode( ' | ', $labels ),
            );
        }

        private function apply_product_minimums( &$brands ) {
            foreach ( $brands as &$bd ) {
                foreach ( $bd['products'] as &$p ) {
                    $pm = $this->find_minimum_for_product( $p['name'], $p['sku'] );
                    $p['product_minimum_qty'] = $pm;
                    $p['product_minimum_extra'] = 0;
                    if ( $pm > 0 && $p['to_order'] < $pm ) {
                        $extra = $pm - $p['to_order'];
                        $p['product_minimum_extra'] = $extra;
                        $p['to_order'] += $extra;
                        $bd['total_to_order'] += $extra;
                        $bd['total_value'] += (float) $p['price'] * $extra;
                    }
                }
                unset($p);
            }
            unset($bd);
        }

        private function apply_brand_minimums( &$brands ) {
            // Drempels voor slimme ophoging:
            // — producten met inkoopprijs >= deze waarde worden overgeslagen
            // — producten die minder dan deze waarde per maand verkopen worden overgeslagen
            $expensive_threshold = 50.0;
            // inkoopprijs in euro
            $slow_threshold = 2.0;
            // stuks per maand
            foreach ( $brands as $bk => &$bd ) {
                $bm = $this->find_minimum_for_brand( $bk );
                $bd['minimum_order_qty'] = $bm;
                $bd['minimum_adjustment'] = 0;
                if ( $bm <= 0 || $bd['total_to_order'] <= 0 || $bd['total_to_order'] >= $bm ) {
                    continue;
                }
                $extra = $bm - $bd['total_to_order'];
                $bd['minimum_adjustment'] = $extra;
                // Sorteer alle producten op verkoopsnelheid (snelste eerst)
                usort( $bd['products'], function ( $a, $b ) {
                    return ( $a['per_month'] !== $b['per_month'] ? $b['per_month'] <=> $a['per_month'] : $a['stock'] <=> $b['stock'] );
                } );
                // Stel kandidaten samen: goedkoop EN snel genoeg
                $candidate_indices = array();
                foreach ( $bd['products'] as $idx => $p ) {
                    $cost = ( isset( $p['cost_price_1'] ) && $p['cost_price_1'] !== '' ? (float) $p['cost_price_1'] : (float) $p['price'] );
                    $per_month = (float) $p['per_month'];
                    if ( $cost < $expensive_threshold && $per_month >= $slow_threshold ) {
                        $candidate_indices[] = $idx;
                    }
                }
                // Geen geschikte kandidaten? Gebruik dan de snelst verkopende producten als fallback
                if ( empty( $candidate_indices ) ) {
                    foreach ( $bd['products'] as $idx => $p ) {
                        if ( (float) $p['per_month'] > 0 ) {
                            $candidate_indices[] = $idx;
                        }
                    }
                }
                // Laatste fallback: alle producten
                if ( empty( $candidate_indices ) ) {
                    $candidate_indices = array_keys( $bd['products'] );
                }
                // Verdeel het tekort over de kandidaten
                $ci = 0;
                $cnt = count( $candidate_indices );
                while ( $extra > 0 && $cnt > 0 ) {
                    $real_idx = $candidate_indices[$ci % $cnt];
                    $bd['products'][$real_idx]['to_order'] += 1;
                    $bd['products'][$real_idx]['minimum_extra'] += 1;
                    $bd['total_to_order'] += 1;
                    $bd['total_value'] += (float) $bd['products'][$real_idx]['price'];
                    $extra--;
                    $ci++;
                }
            }
            unset($bd);
        }

        // =========================================================================
        // HELPERS — levertijden per leverancier (veiligheidsvoorraad)
        // =========================================================================
        /**
         * Haal de opgeslagen levertijden op als array: normalized_label => dagen.
         * Bevat alleen leveranciers waarvoor expliciet een waarde is ingesteld.
         *
         * @return array  [ 'abc bv' => 14, 'snelle post' => 2, ... ]
         */
        private function get_supplier_min_orders() {
            $saved = get_option( self::SUPPLIER_MINORDER_OPTION_KEY, array() );
            if ( !is_array( $saved ) ) {
                return array();
            }
            $clean = array();
            foreach ( $saved as $label => $amount ) {
                $key = $this->normalize_supplier_label( (string) $label );
                $amount = (float) $amount;
                if ( $key !== '' && $amount > 0 ) {
                    $clean[$key] = $amount;
                }
            }
            return $clean;
        }

        private function get_min_order_for_supplier( $supplier_label ) {
            if ( $supplier_label === '' ) {
                return null;
            }
            $mins = $this->get_supplier_min_orders();
            $key = $this->normalize_supplier_label( $supplier_label );
            return ( isset( $mins[$key] ) ? (float) $mins[$key] : null );
        }

        private function get_supplier_leadtimes() {
            $saved = get_option( self::SUPPLIER_LEADTIME_OPTION_KEY, array() );
            if ( !is_array( $saved ) ) {
                return array();
            }
            $clean = array();
            foreach ( $saved as $label => $days ) {
                $key = $this->normalize_supplier_label( (string) $label );
                $days = absint( $days );
                if ( $key !== '' ) {
                    $clean[$key] = $days;
                }
            }
            return $clean;
        }

        /**
         * Geeft de bufferdagen voor een specifieke leveranciersnaam.
         * Geeft null terug als er geen instelling is (= geen veiligheidsvoorraad).
         *
         * @param  string $supplier_label
         * @return int|null
         */
        private function get_leadtime_days_for_supplier( $supplier_label ) {
            if ( $supplier_label === '' ) {
                return null;
            }
            $times = $this->get_supplier_leadtimes();
            $key = $this->normalize_supplier_label( $supplier_label );
            return ( isset( $times[$key] ) ? (int) $times[$key] : null );
        }

        /**
         * Berekent de veiligheidsvoorraad in stuks op basis van levertijd.
         * Formule: dagelijkse vraag × buffer dagen
         *
         * @param  float    $per_month  Maandvraag (na seizoenscorrectie)
         * @param  int|null $days       Bufferdagen; null of 0 = geen buffer
         * @return float
         */
        private function calculate_safety_stock( $per_month, $days ) {
            if ( $days === null || $days <= 0 || $per_month <= 0 ) {
                return 0.0;
            }
            return $per_month / 30.0 * $days;
        }

        /**
         * Calculates how many days the current stock is expected to last.
         *
         * Formula: current stock / average daily demand.
         *
         * @param float $stock_qty Current stock quantity.
         * @param float $per_month Adjusted monthly demand.
         * @return int|null Number of days remaining, or null when demand is unknown.
         */
        private function calculate_days_remaining( $stock_qty, $per_month ) {
            $stock_qty = (float) $stock_qty;
            $per_month = (float) $per_month;
            if ( $per_month <= 0 ) {
                return null;
            }
            if ( $stock_qty <= 0 ) {
                return 0;
            }
            return (int) ceil( $stock_qty / ($per_month / 30.0) );
        }

        /**
         * Formats days remaining for display.
         *
         * @param int|null $days_remaining Number of days remaining.
         * @return string
         */
        private function format_days_remaining( $days_remaining ) {
            if ( $days_remaining === null ) {
                return __( 'Unknown', 'sensible-stock' );
            }
            $days_remaining = max( 0, (int) $days_remaining );
            if ( 1 === $days_remaining ) {
                return __( '1 day', 'sensible-stock' );
            }
            /* translators: %d: number of days remaining before current stock is expected to run out. */
            return sprintf( __( '%d days', 'sensible-stock' ), $days_remaining );
        }

        private function build_report_data( $brand_filter = '', $supplier_filter = '' ) {
            if ( !class_exists( 'WooCommerce' ) ) {
                return array(
                    'error' => __( 'WooCommerce is not active.', 'sensible-stock' ),
                );
            }
            $stats = $this->get_sales_stats();
            $months_coverage = $this->get_months_coverage();
            $norm_brand = $this->normalize_brand( $brand_filter );
            $norm_supplier = $this->normalize_text( $supplier_filter );
            $seasonal = $this->calculate_seasonal_adjustment( $months_coverage );
            $supplier_leadtimes = $this->get_supplier_leadtimes();

            if ( empty( $stats ) ) {
                return array(
                    'brands'            => array(),
                    'summary'           => array(
                        'brands_count'   => 0,
                        'total_to_order' => 0,
                        'total_value'    => 0,
                    ),
                    'seasonal'          => $seasonal,

                );
            }
            $brands = array();
            foreach ( array_keys( $stats ) as $product_id ) {
                $product = wc_get_product( $product_id );
                if ( !$product || !$product->exists() ) {
                    continue;
                }
                if ( $product->is_type( 'variation' ) ) {
                    $parent_id = $product->get_parent_id();
                    if ( $parent_id ) {
                        $product_id = $parent_id;
                        $product = wc_get_product( $product_id );
                        if ( !$product || !$product->exists() ) {
                            continue;
                        }
                    }
                }
                // Alleen gepubliceerde producten — geen concept/draft/prullenbak
                if ( $product->get_status() !== 'publish' ) {
                    continue;
                }
                if ( $this->is_excluded_product( $product ) ) {
                    continue;
                }
                $brand = $this->get_brand_for_product( $product_id );
                $brand_key = $this->normalize_brand( $brand );
                if ( $norm_brand !== '' && $brand_key !== $norm_brand ) {
                    continue;
                }
                $stock_qty = ( $product->managing_stock() ? max( 0.0, (float) $product->get_stock_quantity() ) : 0.0 );
                $s = $stats[$product_id] ?? array(
                    'sold_12m'          => 0.0,
                    'months_with_sales' => 0,
                    'qty_90'            => 0.0,
                );
                $product_age_months = $this->get_product_age_months( $product );
                $demand = $this->calculate_monthly_demand(
                    (float) $s['sold_12m'],
                    (float) $s['qty_90'],
                    (int) $s['months_with_sales'],
                    $stock_qty,
                    ( isset( $s['sold_36m'] ) ? (float) $s['sold_36m'] : null ),
                    $product_age_months
                );
                $sea_pm = $demand['per_month'] * (float) $seasonal['factor'];
                // Veiligheidsvoorraad bepalen op basis van de beste leverancier
                // We bepalen dit vóór we get_best_supplier_for_product aanroepen om
                // een extra query te vermijden — we lezen de meta direct.
                $sup1_raw = (string) get_post_meta( $product_id, self::SUPPLIER_META, true );
                $sup2_raw = (string) get_post_meta( $product_id, self::SUPPLIER2_META, true );
                $safety_days = $this->get_leadtime_days_for_supplier( $sup1_raw );
                if ( $safety_days === null ) {
                    $safety_days = $this->get_leadtime_days_for_supplier( $sup2_raw );
                }
                $safety_stock = $this->calculate_safety_stock( $sea_pm, $safety_days );
                $to_order = max( 0, (int) ceil( $sea_pm * $months_coverage + $safety_stock - $stock_qty ) );
                // Harde maximum per bestelling voor trage/heel trage producten
                if ( !empty( $demand['max_per_order'] ) && $to_order > $demand['max_per_order'] ) {
                    $to_order = (int) $demand['max_per_order'];
                }
                // Bereken hoeveel maanden de huidige voorraad nog meegaat
                $months_remaining = ( $sea_pm > 0 && $stock_qty > 0 ? round( $stock_qty / $sea_pm, 1 ) : null );
                $days_remaining = $this->calculate_days_remaining( $stock_qty, $sea_pm );
                // "Binnenkort nodig": voorraad raakt binnen 3 maanden op maar to_order is 0
                $coming_soon = $to_order <= 0 && $months_remaining !== null && $months_remaining < 3.0;
                if ( $to_order <= 0 && !$coming_soon ) {
                    continue;
                }
                if ( !isset( $brands[$brand_key] ) ) {
                    $brands[$brand_key] = array(
                        'brand_label'        => $brand,
                        'products'           => array(),
                        'total_to_order'     => 0,
                        'total_value'        => 0,
                        'critical'           => 0,
                        'minimum_order_qty'  => 0,
                        'minimum_adjustment' => 0,
                    );
                }
                $price = (float) $product->get_price();
                $best_supplier = $this->get_best_supplier_for_product( $product_id );
                $product_row = array(
                    'id'                      => $product_id,
                    'name'                    => $product->get_name(),
                    'sku'                     => $product->get_sku(),
                    'stock'                   => $stock_qty,
                    'sold_12m'                => (float) $s['sold_12m'],
                    'sold_36m'                => ( isset( $s['sold_36m'] ) ? (float) $s['sold_36m'] : 0.0 ),
                    'per_month'               => $demand['per_month'],
                    'seasonal_per_month'      => $sea_pm,
                    'seasonal_percent'        => (float) $seasonal['percent'],
                    'seasonal_label'          => $seasonal['label'],
                    'seasonal_delta'          => $sea_pm - $demand['per_month'],
                    'safety_days'             => $safety_days,
                    'safety_stock'            => $safety_stock,
                    'calc_method'             => $demand['method'],
                    'max_per_order'           => $demand['max_per_order'],
                    'to_order'                => $to_order,
                    'coming_soon'             => $coming_soon,
                    'months_remaining'        => $months_remaining,
                    'days_remaining'          => $days_remaining,
                    'minimum_extra'           => 0,
                    'product_minimum_extra'   => 0,
                    'product_minimum_qty'     => 0,
                    'price'                   => $price,
                    'supplier_1'              => (string) get_post_meta( $product_id, self::SUPPLIER_META, true ),
                    'cost_price_1'            => (string) get_post_meta( $product_id, self::COST_META, true ),
                    'supplier_2'              => (string) get_post_meta( $product_id, self::SUPPLIER2_META, true ),
                    'cost_price_2'            => (string) get_post_meta( $product_id, self::COST2_META, true ),
                    'best_supplier'           => $best_supplier['label'],
                    'best_supplier_cost'      => $best_supplier['cost'],
                    'best_supplier_lead_time' => $best_supplier['lead_time'],
                    'all_supplier_labels'     => $best_supplier['all_labels'],
                    'edit_url'                => get_edit_post_link( $product_id ),
                );
                if ( !$this->product_matches_supplier_filter( $product_row, $norm_supplier ) ) {
                    continue;
                }
                $brands[$brand_key]['products'][] = $product_row;
                if ( !$coming_soon ) {
                    // Alleen echte bestellingen tellen mee in totalen
                    $brands[$brand_key]['total_to_order'] += $to_order;
                    $brands[$brand_key]['total_value'] += $to_order * $price;
                    if ( $stock_qty <= 0 ) {
                        $brands[$brand_key]['critical']++;
                    }
                }
            }
            foreach ( $brands as $bk => $bd ) {
                if ( empty( $bd['products'] ) ) {
                    unset($brands[$bk]);
                }
            }
            $this->apply_product_minimums( $brands );
            $this->apply_brand_minimums( $brands );
            uasort( $brands, function ( $a, $b ) {
                if ( strtolower( $a['brand_label'] ) === strtolower( __( 'Unknown brand', 'sensible-stock' ) ) ) {
                    return 1;
                }
                if ( strtolower( $b['brand_label'] ) === strtolower( __( 'Unknown brand', 'sensible-stock' ) ) ) {
                    return -1;
                }
                if ( $a['total_to_order'] !== $b['total_to_order'] ) {
                    return $b['total_to_order'] <=> $a['total_to_order'];
                }
                if ( $a['critical'] !== $b['critical'] ) {
                    return $b['critical'] <=> $a['critical'];
                }
                return $b['total_value'] <=> $a['total_value'];
            } );
            foreach ( $brands as &$bd ) {
                usort( $bd['products'], function ( $a, $b ) {
                    // Echte bestellingen altijd vóór "binnenkort nodig"
                    $a_soon = ( !empty( $a['coming_soon'] ) ? 1 : 0 );
                    $b_soon = ( !empty( $b['coming_soon'] ) ? 1 : 0 );
                    if ( $a_soon !== $b_soon ) {
                        return $a_soon - $b_soon;
                    }
                    // Hoogste bestelaantal bovenaan
                    if ( $a['to_order'] !== $b['to_order'] ) {
                        return $b['to_order'] <=> $a['to_order'];
                    }
                    // Gelijke aantallen: minste voorraad eerst (meest urgent)
                    if ( $a['stock'] !== $b['stock'] ) {
                        return $a['stock'] <=> $b['stock'];
                    }
                    // Gelijke voorraad: meest verkochte 36m als tiebreaker
                    $a_36m = ( isset( $a['sold_36m'] ) ? (float) $a['sold_36m'] : (float) $a['sold_12m'] );
                    $b_36m = ( isset( $b['sold_36m'] ) ? (float) $b['sold_36m'] : (float) $b['sold_12m'] );
                    return $b_36m <=> $a_36m;
                } );
            }
            unset($bd);
            return array(
                'brands'             => $brands,
                'summary'            => array(
                    'brands_count'   => count( $brands ),
                    'total_to_order' => array_sum( array_column( $brands, 'total_to_order' ) ),
                    'total_value'    => array_sum( array_column( $brands, 'total_value' ) ),
                ),
                'seasonal'           => $seasonal,
                'supplier_leadtimes' => $supplier_leadtimes,
            );
        }

        // =========================================================================
        // PAGINA: LEVERTIJDEN PER LEVERANCIER
        // =========================================================================
        public function render_supplier_leadtimes_page() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }
            $saved_times = get_option( self::SUPPLIER_LEADTIME_OPTION_KEY, array() );
            if ( !is_array( $saved_times ) ) {
                $saved_times = array();
            }
            // Alle bekende leveranciers ophalen zodat elke leverancier een rij krijgt
            $all_labels = $this->get_all_supplier_labels();
            ?>
            <div class="wrap">
                <h1><?php 
            esc_html_e( 'Lead times per supplier', 'sensible-stock' );
            ?></h1>
                <?php 
            if ( $this->has_query_arg_value( 'updated' ) ) {
                ?>
                    <div class="notice notice-success is-dismissible"><p><?php 
                esc_html_e( 'Lead times saved.', 'sensible-stock' );
                ?></p></div>
                <?php 
            }
            ?>

                <p class="sensst-page-intro">
                    <?php 
            esc_html_e( 'Set the number of days per supplier for order delivery. The purchase advice will automatically add a safety stock: daily demand &times; lead time. Suppliers with lead time 0 or empty get no buffer.', 'sensible-stock' );
            ?>
                </p>

                <div class="sensst-settings-box" style="max-width:860px;">
                    <h2><?php 
            esc_html_e( 'Set lead times', 'sensible-stock' );
            ?></h2>

                    <form method="post" action="<?php 
            echo esc_url( admin_url( 'admin-post.php' ) );
            ?>">
                        <?php 
            wp_nonce_field( 'sensst_besteladvies_save_supplier_leadtimes' );
            ?>
                        <input type="hidden" name="action" value="sensst_besteladvies_save_supplier_leadtimes" />

                        <?php 
            $saved_min_orders = get_option( self::SUPPLIER_MINORDER_OPTION_KEY, array() );
            if ( !is_array( $saved_min_orders ) ) {
                $saved_min_orders = array();
            }
            ?>
                        <table class="widefat striped ss-leadtime-table">
                            <thead>
                                <tr>
                                    <th><?php 
            esc_html_e( 'Supplier', 'sensible-stock' );
            ?></th>
                                    <th style="width:180px;"><?php 
            esc_html_e( 'Lead time (days)', 'sensible-stock' );
            ?></th>
                                    <th style="width:220px;"><?php 
            esc_html_e( 'Safety stock', 'sensible-stock' );
            ?></th>
                                    <th style="width:200px;"><?php 
            esc_html_e( 'Minimum order value (€)', 'sensible-stock' );
            ?></th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php 
            foreach ( $all_labels as $label ) {
                $norm = $this->normalize_supplier_label( $label );
                $days = ( isset( $saved_times[$norm] ) ? (int) $saved_times[$norm] : '' );
                $min_order = ( isset( $saved_min_orders[$norm] ) ? (float) $saved_min_orders[$norm] : '' );
                $hint = ( $days !== '' && $days > 0 ? $days . ' ' . (( $days !== 1 ? esc_html__( 'days', 'sensible-stock' ) : esc_html__( 'day', 'sensible-stock' ) )) . ' ' . esc_html__( 'buffer', 'sensible-stock' ) : esc_html__( 'no buffer', 'sensible-stock' ) );
                ?>
                                    <tr>
                                        <td>
                                            <strong><?php 
                echo esc_html( $label );
                ?></strong>
                                            <input type="hidden" name="supplier_label[]" value="<?php 
                echo esc_attr( $label );
                ?>">
                                        </td>
                                        <td>
                                            <div class="ss-leadtime-input">
                                                <input type="number" min="0" max="365" step="1"
                                                       name="supplier_days[]"
                                                       value="<?php 
                echo esc_attr( (string) $days );
                ?>"
                                                       placeholder="0"
                                                       style="width:80px;">
                                                <span class="ss-percent-label"><?php 
                esc_html_e( 'days', 'sensible-stock' );
                ?></span>
                                            </div>
                                        </td>
                                        <td class="ss-muted ss-small ss-leadtime-hint" data-days="<?php 
                echo esc_attr( (string) $days );
                ?>">
                                            <?php 
                echo esc_html( $hint );
                ?>
                                        </td>
                                        <td>
                                            <div style="display:flex;align-items:center;gap:6px;">
                                                <span style="color:#646970;">€</span>
                                                <input type="number" min="0" step="0.01"
                                                       name="supplier_min_order[]"
                                                       value="<?php 
                echo esc_attr( (string) $min_order );
                ?>"
                                                       placeholder="0"
                                                       style="width:100px;">
                                            </div>
                                            <?php 
                if ( $min_order > 0 ) {
                    ?>
                                                <div class="ss-small ss-muted" style="margin-top:3px;"><?php 
                    /* translators: %s: minimum order amount. */
                    printf( esc_html__( 'Order from €%s', 'sensible-stock' ), esc_html( number_format(
                        $min_order,
                        2,
                        ',',
                        '.'
                    ) ) );
                    ?></div>
                                            <?php 
                }
                ?>
                                        </td>
                                    </tr>
                                <?php 
            }
            ?>

                                <?php 
            if ( empty( $all_labels ) ) {
                ?>
                                    <tr>
                                        <td colspan="3">
                                            <p class="ss-muted"><?php 
                /* translators: %s: link to supplier settings. */
                echo wp_kses_post( sprintf( __( 'No suppliers found yet. Add suppliers via %s or set them on products.', 'sensible-stock' ), '<a href="' . esc_url( admin_url( 'admin.php?page=' . self::SUPPLIERS_SLUG ) ) . '">' . esc_html__( 'Manage suppliers', 'sensible-stock' ) . '</a>' ) );
                ?></p>
                                        </td>
                                    </tr>
                                <?php 
            }
            ?>
                            </tbody>
                        </table>

                        <?php 
            if ( !empty( $all_labels ) ) {
                ?>
                            <p style="margin-top:16px;">
                                <button type="submit" class="button button-primary"><?php 
                esc_html_e( 'Save lead times', 'sensible-stock' );
                ?></button>
                            </p>
                        <?php 
            }
            ?>
                    </form>
                </div>

                <div class="ss-info-box" style="margin-top:16px;">
                    <p><strong><?php 
            esc_html_e( 'How does the calculation work?', 'sensible-stock' );
            ?></strong></p>
                    <p><?php 
            esc_html_e( 'For a product that sells an average of 30 units per month (1 per day) with a supplier lead time of 14 days, the purchase advice adds 14 units as a buffer. This prevents stock running out while waiting for delivery.', 'sensible-stock' );
            ?></p>
                    <p><?php 
            esc_html_e( 'The supplier that the purchase advice identifies as the "best supplier" determines the buffer. If supplier 1 is cheaper but has a longer lead time, that lead time is used.', 'sensible-stock' );
            ?></p>
                </div>
            </div>
            <?php 
        }

        public function handle_save_supplier_leadtimes() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }
            check_admin_referer( 'sensst_besteladvies_save_supplier_leadtimes' );
            $labels = ( isset( $_POST['supplier_label'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['supplier_label'] ) ) : array() );
            $days = ( isset( $_POST['supplier_days'] ) ? array_map( 'absint', (array) wp_unslash( $_POST['supplier_days'] ) ) : array() );
            $clean = array();
            foreach ( $labels as $i => $label ) {
                $label = sanitize_text_field( $label );
                $d = ( isset( $days[$i] ) && $days[$i] !== '' ? absint( $days[$i] ) : null );
                $key = $this->normalize_supplier_label( $label );
                if ( $key !== '' && $d !== null ) {
                    // Sla de genormaliseerde key op zodat opzoeken altijd matcht
                    $clean[$key] = $d;
                }
                // Als $d === null (leeg veld) bewaren we de entry niet → geen buffer
            }
            update_option( self::SUPPLIER_LEADTIME_OPTION_KEY, $clean );
            // Sla minimum orderwaarden op
            $min_orders_raw = ( isset( $_POST['supplier_min_order'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['supplier_min_order'] ) ) : array() );
            $clean_min = array();
            foreach ( $labels as $i => $label ) {
                $label = sanitize_text_field( $label );
                $amount = ( isset( $min_orders_raw[$i] ) && $min_orders_raw[$i] !== '' ? (float) $min_orders_raw[$i] : 0.0 );
                $key = $this->normalize_supplier_label( $label );
                if ( $key !== '' && $amount > 0 ) {
                    $clean_min[$key] = $amount;
                }
            }
            update_option( self::SUPPLIER_MINORDER_OPTION_KEY, $clean_min );
            wp_safe_redirect( admin_url( 'admin.php?page=sensst-besteladvies-levertijden&updated=1' ) );
            exit;
        }

        // =========================================================================
        // PAGINA: LICENTIE BEHEREN
        // =========================================================================
        public function render_license_page() {
            ?>
            <div class="wrap ss-besteladvies">
                <h1><?php 
            esc_html_e( 'Sensible Stock account', 'sensible-stock' );
            ?></h1>
                <p><?php 
            esc_html_e( 'Licensing, account management and accounts are handled by Freemius.', 'sensible-stock' );
            ?></p>
                <?php 
            if ( function_exists( 'sensst_fs' ) ) {
                ?>
                    <p>
                        <a class="button button-primary" href="<?php 
                echo esc_url( $this->get_account_url() );
                ?>">
                            <?php 
                esc_html_e( 'View account options', 'sensible-stock' );
                ?>
                        </a>
                    </p>
                <?php 
            } else {
                ?>
                    <p><?php 
                esc_html_e( 'Freemius is not available in this build.', 'sensible-stock' );
                ?></p>
                <?php 
            }
            ?>
            </div>
            <?php 
        }

        // =========================================================================
        // RENDER HELPERS
        // =========================================================================
        private function render_filter_form(
            $page_slug,
            $months_coverage,
            $brand_filter,
            $supplier_filter,
            $extra_buttons = ''
        ) {
            ?>
            <div class="ss-toolbar">
                <div class="ss-toolbar-filters">
                    <input type="hidden" name="page" value="<?php 
            echo esc_attr( $page_slug );
            ?>" />
                    <div class="ss-filter-group">
                        <label for="dekking"><?php 
            esc_html_e( 'Months coverage', 'sensible-stock' );
            ?></label>
                        <input name="dekking" id="dekking" type="number" min="1" max="12" step="1" value="<?php 
            echo esc_attr( $months_coverage );
            ?>" />
                    </div>
                    <div class="ss-filter-group">
                        <label for="merk"><?php 
            esc_html_e( 'Brand', 'sensible-stock' );
            ?></label>
                        <select name="merk" id="merk">
                            <option value=""><?php 
            esc_html_e( 'All brands', 'sensible-stock' );
            ?></option>
                            <?php 
            foreach ( $this->get_brand_options_from_catalog() as $bk => $bd ) {
                ?>
                                <option value="<?php 
                echo esc_attr( $bk );
                ?>" <?php 
                selected( $this->normalize_brand( $brand_filter ), $bk );
                ?>><?php 
                echo esc_html( $bd['label'] );
                ?></option>
                            <?php 
            }
            ?>
                        </select>
                    </div>
                    <div class="ss-filter-group">
                        <label for="leverancier"><?php 
            esc_html_e( 'Supplier', 'sensible-stock' );
            ?></label>
                        <select name="leverancier" id="leverancier">
                            <option value=""><?php 
            esc_html_e( 'All suppliers', 'sensible-stock' );
            ?></option>
                            <?php 
            foreach ( $this->get_supplier_options_from_catalog() as $sk => $sd ) {
                ?>
                                <option value="<?php 
                echo esc_attr( $sk );
                ?>" <?php 
                selected( $this->normalize_text( $supplier_filter ), $sk );
                ?>><?php 
                echo esc_html( $sd['label'] );
                ?></option>
                            <?php 
            }
            ?>
                        </select>
                    </div>
                </div>
                <div class="ss-toolbar-actions">
                    <button class="button button-primary" type="submit"><?php 
            esc_html_e( 'Update', 'sensible-stock' );
            ?></button>
                    <?php 
            echo wp_kses( $extra_buttons, array(
                'a'      => array(
                    'href'   => array(),
                    'class'  => array(),
                    'target' => array(),
                    'rel'    => array(),
                ),
                'button' => array(
                    'type'  => array(),
                    'class' => array(),
                ),
            ) );
            ?>
                </div>
            </div>
            <?php 
        }

        private function render_seasonal_banner( $seasonal ) {
            if ( empty( $seasonal['enabled'] ) ) {
                return;
            }
            ?>
            <div class="ss-seasonal-banner">
                <span class="ss-seasonal-banner-icon">&#9788;</span>
                <span><strong><?php 
            esc_html_e( 'Seasonal correction active:', 'sensible-stock' );
            ?></strong> <?php 
            echo esc_html( $seasonal['label'] );
            ?>
                    <?php 
            if ( (float) $seasonal['percent'] > 0 ) {
                ?>
                        &nbsp;&mdash;&nbsp;<?php 
                esc_html_e( 'total', 'sensible-stock' );
                ?> <?php 
                echo esc_html( $this->fmt_pct( $seasonal['percent'] ) );
                ?>
                    <?php 
            }
            ?>
                </span>
                <a href="<?php 
            echo esc_url( admin_url( 'admin.php?page=' . self::SEASONAL_SLUG ) );
            ?>" class="button button-small" style="margin-left:auto;"><?php 
            esc_html_e( 'Settings', 'sensible-stock' );
            ?></a>
            </div>
            <?php 
        }

        private function get_status_badge( $product ) {
            if ( !empty( $product['coming_soon'] ) ) {
                return '<span class="ss-badge ss-badge-orange">' . esc_html__( 'soon', 'sensible-stock' ) . '</span>';
            }
            if ( $product['stock'] <= 0 ) {
                return '<span class="ss-badge ss-badge-red">' . esc_html__( 'out of stock', 'sensible-stock' ) . '</span>';
            }
            if ( $product['product_minimum_extra'] > 0 ) {
                return '<span class="ss-badge ss-badge-blue">' . esc_html__( 'min. product', 'sensible-stock' ) . '</span>';
            }
            if ( $product['minimum_extra'] > 0 ) {
                return '<span class="ss-badge ss-badge-yellow">' . esc_html__( 'min. brand', 'sensible-stock' ) . '</span>';
            }
            if ( (float) $product['seasonal_percent'] > 0 ) {
                return '<span class="ss-badge ss-badge-purple">' . esc_html__( 'seasonal', 'sensible-stock' ) . '</span>';
            }
            if ( !empty( $product['safety_stock'] ) && (float) $product['safety_stock'] > 0 ) {
                return '<span class="ss-badge ss-badge-teal">+ ' . esc_html__( 'buffer', 'sensible-stock' ) . '</span>';
            }
            return '<span class="ss-badge ss-badge-green">' . esc_html__( 'normal', 'sensible-stock' ) . '</span>';
        }

        private function get_priority_products( $report, $limit = 8 ) {
            $rows = array();
            if ( empty( $report['brands'] ) || !is_array( $report['brands'] ) ) {
                return $rows;
            }
            foreach ( $report['brands'] as $brand_data ) {
                if ( empty( $brand_data['products'] ) || !is_array( $brand_data['products'] ) ) {
                    continue;
                }
                foreach ( $brand_data['products'] as $product ) {
                    $rows[] = $product;
                }
            }
            usort( $rows, function ( $a, $b ) {
                $a_stock = ( isset( $a['stock'] ) ? (float) $a['stock'] : 0 );
                $b_stock = ( isset( $b['stock'] ) ? (float) $b['stock'] : 0 );
                $a_order = ( isset( $a['to_order'] ) ? (int) $a['to_order'] : 0 );
                $b_order = ( isset( $b['to_order'] ) ? (int) $b['to_order'] : 0 );
                if ( $a_stock <= 0 !== $b_stock <= 0 ) {
                    return ( $a_stock <= 0 ? -1 : 1 );
                }
                if ( $a_order !== $b_order ) {
                    return $b_order <=> $a_order;
                }
                return strnatcasecmp( (string) ($a['name'] ?? ''), (string) ($b['name'] ?? '') );
            } );
            return array_slice( $rows, 0, max( 1, (int) $limit ) );
        }

        private function get_supplier_notice_label( $product ) {
            $supplier = ( isset( $product['best_supplier'] ) ? trim( (string) $product['best_supplier'] ) : '' );
            return ( $supplier !== '' ? $supplier : __( 'No supplier set', 'sensible-stock' ) );
        }

        private function render_sales_action_bar( $report ) {
            if ( empty( $report['brands'] ) ) {
                return;
            }
            // Bereken gemiste omzet: producten uitverkocht of bijna op
            $missed_revenue = 0.0;
            foreach ( $report['brands'] as $brand_data ) {
                foreach ( $brand_data['products'] as $p ) {
                    if ( $p['stock'] <= 0 || !empty( $p['coming_soon'] ) ) {
                        $missed_revenue += (float) $p['to_order'] * (float) $p['price'];
                    }
                }
            }
            // Alleen tonen als er iets te melden is
            if ( $missed_revenue <= 0 ) {
                return;
            }
            ?>
            <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;margin-bottom:20px;">
                <?php 
            if ( $missed_revenue > 0 ) {
                ?>
                <div style="background:#fdf0ee;border:1.5px solid #e8a898;border-radius:8px;padding:16px 20px;display:flex;align-items:center;gap:16px;">
                    <div style="font-size:28px;line-height:1;">⚠️</div>
                    <div>
                        <div style="font-size:11px;font-weight:500;text-transform:uppercase;letter-spacing:0.07em;color:#a32d2d;margin-bottom:4px;">
                            <?php 
                esc_html_e( 'Potential missed revenue', 'sensible-stock' );
                ?>
                        </div>
                        <div style="font-size:22px;font-weight:600;color:#a32d2d;line-height:1.1;">
                            <?php 
                echo wp_kses_post( $this->format_money( $missed_revenue ) );
                ?>
                        </div>

                    </div>
                </div>
                <?php 
            }
            ?>

            </div>
            <?php 
        }

        public function render_page() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }
            $months_coverage = $this->get_months_coverage();
            $brand_filter = $this->get_brand_filter();
            $supplier_filter = $this->get_supplier_filter();
            $report = $this->build_report_data( $brand_filter, $supplier_filter );
            $norm_brand = $this->normalize_brand( $brand_filter );
            $norm_supplier = $this->normalize_text( $supplier_filter );
            $export_url = admin_url( 'admin-post.php?action=sensst_besteladvies_export_csv&dekking=' . $months_coverage . '&merk=' . rawurlencode( $norm_brand ) . '&leverancier=' . rawurlencode( $norm_supplier ) );
            ?>
            <div class="wrap">
                <h1><?php 
            esc_html_e( 'Purchase advice', 'sensible-stock' );
            ?></h1>
                <p class="sensst-page-intro"><?php 
            esc_html_e( 'Calculated based on 36 months of sales history, recent trends and active sales months, combined with current WooCommerce stock.', 'sensible-stock' );
            ?></p>

                <?php 
            if ( !empty( $report['error'] ) ) {
                ?>
                    <div class="notice notice-error"><p><?php 
                echo esc_html( $report['error'] );
                ?></p></div>
                    <?php 
                return;
                ?>
                <?php 
            }
            ?>

                <form method="get">
                    <input type="hidden" name="sensst_filter_nonce" value="<?php echo esc_attr( wp_create_nonce( 'sensst_filter_update' ) ); ?>">
                    <?php 
            $free_actions = array(
                '<a class="button" href="' . esc_url( admin_url( 'admin.php?page=' . self::MINIMA_SLUG ) ) . '">' . esc_html__( 'Minimums', 'sensible-stock' ) . '</a>',
                '<a class="button" href="' . esc_url( admin_url( 'admin.php?page=' . self::SUPPLIERS_SLUG ) ) . '">' . esc_html__( 'Suppliers', 'sensible-stock' ) . '</a>',
                '<a class="button" href="' . esc_url( admin_url( 'admin.php?page=sensst-besteladvies-levertijden' ) ) . '">' . esc_html__( 'Lead times', 'sensible-stock' ) . '</a>',
                '<a class="button" href="' . esc_url( admin_url( 'admin.php?page=' . self::ORDER_CREATE_SLUG ) ) . '">' . esc_html__( 'Create order', 'sensible-stock' ) . '</a>'
            );
            $extra = implode( '', $free_actions );
            $this->render_filter_form(
                self::SLUG,
                $months_coverage,
                $brand_filter,
                $supplier_filter,
                $extra
            );
            ?>
                </form>

                <?php 
            ?>

                <?php 
            $this->render_sales_action_bar( $report );
            ?>

                <div class="ss-summary-grid">
                    <div class="ss-card">
                        <div class="ss-card-label"><?php 
            esc_html_e( 'Brands', 'sensible-stock' );
            ?></div>
                        <div class="ss-card-value"><?php 
            echo esc_html( $report['summary']['brands_count'] );
            ?></div>
                    </div>
                    <div class="ss-card">
                        <div class="ss-card-label"><?php 
            esc_html_e( 'To order', 'sensible-stock' );
            ?></div>
                        <div class="ss-card-value"><?php 
            echo esc_html( $report['summary']['total_to_order'] );
            ?></div>
                    </div>
                    <div class="ss-card">
                        <div class="ss-card-label"><?php 
            esc_html_e( 'Value (sales price)', 'sensible-stock' );
            ?></div>
                        <div class="ss-card-value"><?php 
            echo wp_kses_post( $this->format_money( $report['summary']['total_value'] ) );
            ?></div>
                    </div>
                    <?php 
            if ( !empty( $report['seasonal']['enabled'] ) && (float) $report['seasonal']['percent'] > 0 ) {
                ?>
                        <div class="ss-card ss-card--seasonal">
                            <div class="ss-card-label"><?php 
                esc_html_e( 'Seasonal increase', 'sensible-stock' );
                ?></div>
                            <div class="ss-card-value"><?php 
                echo esc_html( $this->fmt_pct( $report['seasonal']['percent'] ) );
                ?></div>
                            <div class="ss-card-sub"><?php 
                echo esc_html( $report['seasonal']['label'] );
                ?></div>
                        </div>
                    <?php 
            }
            ?>
                </div>




                <?php 
            if ( empty( $report['brands'] ) ) {
                ?>
                    <div class="notice notice-info"><p><?php 
                esc_html_e( 'No products to order for the selected filters.', 'sensible-stock' );
                ?></p></div>
                    <?php 
                return;
                ?>
                <?php 
            }
            ?>

                <?php 
            foreach ( $report['brands'] as $brand_data ) {
                ?>
                    <div class="ss-block">
                        <div class="ss-block-head">
                            <div>
                                <p class="ss-block-title"><?php 
                echo esc_html( $brand_data['brand_label'] );
                ?></p>
                                <?php 
                if ( $brand_data['minimum_order_qty'] > 0 ) {
                    ?>
                                    <p class="ss-block-note">
                                        <?php 
                    esc_html_e( 'Min.', 'sensible-stock' );
                    ?> <?php 
                    echo esc_html( $brand_data['minimum_order_qty'] );
                    ?> <?php 
                    esc_html_e( 'units', 'sensible-stock' );
                    ?>
                                        <?php 
                    if ( $brand_data['minimum_adjustment'] > 0 ) {
                        ?>
                                            &mdash; <?php 
                        /* translators: %d: number of extra units added due to minimum order quantity. */
                        printf( esc_html__( 'increased by %d units', 'sensible-stock' ), absint( $brand_data['minimum_adjustment'] ) );
                        ?>
                                        <?php 
                    }
                    ?>
                                    </p>
                                <?php 
                }
                ?>
                            </div>
                            <div class="ss-block-meta">
                                <span><?php 
                echo esc_html( count( $brand_data['products'] ) );
                ?> <?php 
                esc_html_e( 'products', 'sensible-stock' );
                ?></span>
                                <span class="ss-strong"><?php 
                echo esc_html( $brand_data['total_to_order'] );
                ?> <?php 
                esc_html_e( 'units', 'sensible-stock' );
                ?></span>
                                <span><?php 
                echo wp_kses_post( $this->format_money( $brand_data['total_value'] ) );
                ?></span>
                            </div>
                        </div>
                        <table class="widefat striped ss-decision-table">
                            <thead>
                                <tr>
                                    <th class="ss-col-name"><?php 
                esc_html_e( 'Product', 'sensible-stock' );
                ?></th>
                                    <th class="ss-col-supplier"><?php 
                esc_html_e( 'Supplier', 'sensible-stock' );
                ?></th>
                                    <th class="ss-col-num"><?php 
                esc_html_e( 'Stock', 'sensible-stock' );
                ?></th>
                                    <th class="ss-col-days"><?php 
                esc_html_e( 'Days remaining', 'sensible-stock' );
                ?></th>
                                    <th class="ss-col-order"><?php 
                esc_html_e( 'To order', 'sensible-stock' );
                ?></th>
                                    <th class="ss-col-badge"><?php 
                esc_html_e( 'Status', 'sensible-stock' );
                ?></th>
                                    <th class="ss-col-toggle"></th>
                                </tr>
                            </thead>
                            <tbody>
                                <?php 
                $prev_product = null;
                ?>
                                <?php 
                foreach ( $brand_data['products'] as $product ) {
                    $base = $product['to_order'] - $product['minimum_extra'] - $product['product_minimum_extra'];
                    $sea_extra = ( (float) $product['seasonal_percent'] > 0 ? round( $product['seasonal_delta'], 1 ) : 0 );
                    $detail_id = 'ss-detail-' . absint( $product['id'] );
                    ?>
                                    <?php 
                    if ( !empty( $product['coming_soon'] ) && (empty( $prev_product ) || empty( $prev_product['coming_soon'] )) ) {
                        ?>
                                        <tr>
                                            <td colspan="7" style="padding:6px 16px;background:#fffbf0;border-top:2px solid #ffc107;border-bottom:1px solid #ffe58a;">
                                                <span style="font-size:12px;font-weight:500;color:#664d03;">&#9660; <?php 
                        esc_html_e( 'Coming soon — stock runs out within 3 months', 'sensible-stock' );
                        ?></span>
                                            </td>
                                        </tr>
                                    <?php 
                    }
                    $prev_product = $product;
                    ?>
                                    <tr class="ss-main-row<?php 
                    echo ( !empty( $product['coming_soon'] ) ? ' ss-row-coming-soon' : '' );
                    ?>">
                                        <td class="ss-col-name">
                                            <a href="<?php 
                    echo esc_url( $product['edit_url'] );
                    ?>"><?php 
                    echo esc_html( $product['name'] );
                    ?></a>
                                            <?php 
                    if ( !empty( $product['sku'] ) ) {
                        ?>
                                                <div class="ss-small ss-muted">SKU: <?php 
                        echo esc_html( $product['sku'] );
                        ?></div>
                                            <?php 
                    }
                    ?>
                                            <?php 
                    if ( !empty( $product['calc_method'] ) ) {
                        ?>
                                                <div class="ss-small ss-calc-method"><?php 
                        esc_html_e( 'Calculation:', 'sensible-stock' );
                        ?> <?php 
                        echo esc_html( $product['calc_method'] );
                        ?></div>
                                            <?php 
                    }
                    ?>
                                        </td>
                                        <td class="ss-col-supplier">
                                            <?php 
                    if ( !empty( $product['best_supplier'] ) ) {
                        ?>
                                                <span><?php 
                        echo esc_html( $product['best_supplier'] );
                        ?></span>
                                                <?php 
                        if ( $product['best_supplier_cost'] !== null ) {
                            ?>
                                                    <div class="ss-small">€<?php 
                            echo esc_html( number_format(
                                (float) $product['best_supplier_cost'],
                                2,
                                ',',
                                '.'
                            ) );
                            ?> p/st</div>
                                                <?php 
                        }
                        ?>
                                            <?php 
                    } else {
                        ?>
                                                <span class="ss-missing-supplier"><?php 
                        esc_html_e( 'No supplier set', 'sensible-stock' );
                        ?></span>
                                            <?php 
                    }
                    ?>
                                        </td>
                                        <td class="ss-col-num"><?php 
                    echo esc_html( (string) $product['stock'] );
                    ?></td>
                                        <td class="ss-col-days">
                                            <?php 
                    $days_remaining = ( array_key_exists( 'days_remaining', $product ) ? $product['days_remaining'] : null );
                    $days_class = 'ss-days-ok';
                    if ( $days_remaining !== null && (int) $days_remaining <= 7 ) {
                        $days_class = 'ss-days-critical';
                    } elseif ( $days_remaining !== null && (int) $days_remaining <= 21 ) {
                        $days_class = 'ss-days-warning';
                    }
                    ?>
                                            <span class="ss-days-remaining <?php 
                    echo esc_attr( $days_class );
                    ?>"><?php 
                    echo esc_html( $this->format_days_remaining( $days_remaining ) );
                    ?></span>
                                        </td>
                                        <td class="ss-col-order">
                                            <?php 
                    if ( !empty( $product['coming_soon'] ) ) {
                        ?>
                                                <span class="ss-order-qty" style="color:#646970;font-weight:400;">—</span>
                                                <div class="ss-small" style="color:#854F0B;">
                                                    ~<?php 
                        echo esc_html( number_format(
                            (float) $product['months_remaining'],
                            1,
                            ',',
                            '.'
                        ) );
                        ?> <?php 
                        esc_html_e( 'mths left', 'sensible-stock' );
                        ?>
                                                </div>
                                            <?php 
                    } else {
                        ?>
                                                <strong class="ss-order-qty"><?php 
                        echo esc_html( (string) $product['to_order'] );
                        ?></strong>
                                                <?php 
                        if ( $sea_extra > 0 ) {
                            ?>
                                                    <span class="ss-order-extra">(+<?php 
                            echo esc_html( (string) round( $sea_extra, 1 ) );
                            ?> <?php 
                            esc_html_e( 'seasonal', 'sensible-stock' );
                            ?>)</span>
                                                <?php 
                        }
                        ?>
                                                <?php 
                        if ( !empty( $product['safety_stock'] ) && (float) $product['safety_stock'] > 0 ) {
                            ?>
                                                    <span class="ss-order-buffer">(+<?php 
                            echo esc_html( (string) round( $product['safety_stock'], 1 ) );
                            ?> <?php 
                            esc_html_e( 'buffer', 'sensible-stock' );
                            ?>)</span>
                                                <?php 
                        }
                        ?>
                                            <?php 
                    }
                    ?>
                                        </td>
                                        <td class="ss-col-badge"><?php 
                    echo wp_kses_post( $this->get_status_badge( $product ) );
                    ?></td>
                                        <td class="ss-col-toggle">
                                            <details class="ss-inline-details">
                                                <summary>Details</summary>
                                                <div class="ss-inline-details-box">
                                                    <div class="ss-detail-grid">
                                                        <div><strong><?php 
                    esc_html_e( 'Sold 12 months', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo esc_html( (string) round( $product['sold_12m'], 1 ) );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Average per month', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo esc_html( (string) round( $product['per_month'], 1 ) );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Days remaining', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo esc_html( $this->format_days_remaining( ( array_key_exists( 'days_remaining', $product ) ? $product['days_remaining'] : null ) ) );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Base order', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo esc_html( (string) $base );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Extra product minimum', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo ( $product['product_minimum_extra'] > 0 ? esc_html( '+' . $product['product_minimum_extra'] ) : esc_html( '—' ) );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Extra brand minimum', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo ( $product['minimum_extra'] > 0 ? esc_html( '+' . $product['minimum_extra'] ) : esc_html( '—' ) );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Seasonal impact', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo ( $sea_extra > 0 ? esc_html( '+' . round( $sea_extra, 1 ) . ' (' . $this->fmt_pct( $product['seasonal_percent'] ) . ')' ) : esc_html( '—' ) );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Calculation', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo esc_html( $product['calc_method'] );
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'Safety stock', 'sensible-stock' );
                    ?></strong><br><?php 
                    if ( !empty( $product['safety_stock'] ) && (float) $product['safety_stock'] > 0 ) {
                        echo esc_html( '+' . round( $product['safety_stock'], 1 ) . ' ' . __( 'units', 'sensible-stock' ) . ' (' . $product['safety_days'] . ' ' . __( 'days lead time', 'sensible-stock' ) . ')' );
                    } else {
                        echo '—';
                    }
                    ?></div>
                                                        <div><strong><?php 
                    esc_html_e( 'All suppliers', 'sensible-stock' );
                    ?></strong><br><?php 
                    echo esc_html( ( $product['all_supplier_labels'] ?: '—' ) );
                    ?></div>
                                                    </div>
                                                </div>
                                            </details>
                                        </td>
                                    </tr>
                                <?php 
                }
                ?>
                            </tbody>
                        </table>
                    </div>
                <?php 
            }
            ?>
            </div>
            <?php 
        }

        // =========================================================================
        // PAGINA: BESTELLEN PER LEVERANCIER
        // =========================================================================
        private function get_flat_products_from_report( $report ) {
            $products = array();
            if ( empty( $report['brands'] ) ) {
                return $products;
            }
            foreach ( $report['brands'] as $brand_data ) {
                foreach ( $brand_data['products'] as $p ) {
                    $p['brand_label'] = $brand_data['brand_label'] ?? '';
                    $products[] = $p;
                }
            }
            return $products;
        }

        private function group_products_by_best_supplier( $products ) {
            $groups = array();
            foreach ( $products as $p ) {
                $supplier = ( $p['best_supplier'] !== '' ? $p['best_supplier'] : __( 'No best supplier', 'sensible-stock' ) );
                $sk = ( $this->normalize_text( $supplier ) ?: 'no-best-supplier' );
                if ( !isset( $groups[$sk] ) ) {
                    $groups[$sk] = array(
                        'supplier_label'       => $supplier,
                        'products'             => array(),
                        'total_to_order'       => 0,
                        'total_purchase_value' => 0.0,
                        'total_sales_value'    => 0.0,
                    );
                }
                $pc = ( $p['best_supplier_cost'] !== null ? (float) $p['best_supplier_cost'] : 0.0 );
                $groups[$sk]['products'][] = $p;
                $groups[$sk]['total_to_order'] += (int) $p['to_order'];
                $groups[$sk]['total_purchase_value'] += $pc * (int) $p['to_order'];
                $groups[$sk]['total_sales_value'] += (float) $p['price'] * (int) $p['to_order'];
            }
            uasort( $groups, function ( $a, $b ) {
                return ( $a['total_to_order'] !== $b['total_to_order'] ? $b['total_to_order'] <=> $a['total_to_order'] : strnatcasecmp( $a['supplier_label'], $b['supplier_label'] ) );
            } );
            foreach ( $groups as &$g ) {
                usort( $g['products'], function ( $a, $b ) {
                    return ( $a['to_order'] !== $b['to_order'] ? $b['to_order'] <=> $a['to_order'] : strnatcasecmp( $a['name'], $b['name'] ) );
                } );
            }
            unset($g);
            return $groups;
        }


        /**
         * Render a useful free preview of the order creation workflow.
         *
         * The WordPress.org/free version shows real supplier-level reorder insight,
         * while export and generated purchase order documents remain upgrade features.
         * This keeps the page useful and prevents an upsell-only screen.
         *
         * @return void
         */
        public function render_order_create_page() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }

            $months_coverage = $this->get_months_coverage();
            $brand_filter = $this->get_brand_filter();
            $supplier_filter = $this->get_supplier_filter();
            $report = $this->build_report_data( $brand_filter, $supplier_filter );
            $products = $this->get_flat_products_from_report( $report );
            $groups = $this->group_products_by_best_supplier( $products );
            $total_products = 0;
            $total_quantity = 0;
            $total_sales_value = 0.0;
            $total_purchase_value = 0.0;

            foreach ( $groups as $group ) {
                $total_products += count( $group['products'] );
                $total_quantity += (int) $group['total_to_order'];
                $total_sales_value += (float) $group['total_sales_value'];
                $total_purchase_value += (float) $group['total_purchase_value'];
            }
            ?>
            <div class="wrap">
                <h1><?php esc_html_e( 'Create order preview', 'sensible-stock' ); ?></h1>
                <p class="sensst-page-intro">
                    <?php esc_html_e( 'See which suppliers need attention before you build your next purchase order.', 'sensible-stock' ); ?>
                </p>

                <?php if ( !empty( $report['error'] ) ) : ?>
                    <div class="notice notice-error"><p><?php echo esc_html( $report['error'] ); ?></p></div>
                    <?php return; ?>
                <?php endif; ?>

                <form method="get">
                    <input type="hidden" name="sensst_filter_nonce" value="<?php echo esc_attr( wp_create_nonce( 'sensst_filter_update' ) ); ?>">
                    <?php
                    $this->render_filter_form(
                        self::ORDER_CREATE_SLUG,
                        $months_coverage,
                        $brand_filter,
                        $supplier_filter,
                        '<a class="button" href="' . esc_url( admin_url( 'admin.php?page=' . self::SLUG ) ) . '">' . esc_html__( 'Back to purchase advice', 'sensible-stock' ) . '</a>'
                    );
                    ?>
                </form>

                <div class="ss-summary-grid">
                    <div class="ss-card">
                        <div class="ss-card-label"><?php esc_html_e( 'Suppliers', 'sensible-stock' ); ?></div>
                        <div class="ss-card-value"><?php echo esc_html( count( $groups ) ); ?></div>
                    </div>
                    <div class="ss-card">
                        <div class="ss-card-label"><?php esc_html_e( 'Products', 'sensible-stock' ); ?></div>
                        <div class="ss-card-value"><?php echo esc_html( $total_products ); ?></div>
                    </div>
                    <div class="ss-card">
                        <div class="ss-card-label"><?php esc_html_e( 'Quantity to order', 'sensible-stock' ); ?></div>
                        <div class="ss-card-value"><?php echo esc_html( $total_quantity ); ?></div>
                    </div>
                    <div class="ss-card">
                        <div class="ss-card-label"><?php esc_html_e( 'Value', 'sensible-stock' ); ?></div>
                        <div class="ss-card-value"><?php echo wp_kses_post( $this->format_money( $total_sales_value ) ); ?></div>
                    </div>
                </div>

                <?php if ( empty( $groups ) ) : ?>
                    <div class="notice notice-info"><p><?php esc_html_e( 'No products currently need to be ordered with the selected filters.', 'sensible-stock' ); ?></p></div>
                <?php else : ?>
                    <table class="widefat striped">
                        <thead>
                            <tr>
                                <th><?php esc_html_e( 'Supplier', 'sensible-stock' ); ?></th>
                                <th><?php esc_html_e( 'Products', 'sensible-stock' ); ?></th>
                                <th><?php esc_html_e( 'Quantity', 'sensible-stock' ); ?></th>
                                <th><?php esc_html_e( 'Estimated purchase value', 'sensible-stock' ); ?></th>
                                <th><?php esc_html_e( 'Estimated sales value', 'sensible-stock' ); ?></th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ( $groups as $group ) : ?>
                                <tr>
                                    <td><strong><?php echo esc_html( $group['supplier_label'] ); ?></strong></td>
                                    <td><?php echo esc_html( count( $group['products'] ) ); ?></td>
                                    <td><?php echo esc_html( (int) $group['total_to_order'] ); ?></td>
                                    <td>
                                        <?php
                                        if ( (float) $group['total_purchase_value'] > 0 ) {
                                            echo wp_kses_post( $this->format_money( $group['total_purchase_value'] ) );
                                        } else {
                                            echo esc_html__( 'Add cost prices for this calculation', 'sensible-stock' );
                                        }
                                        ?>
                                    </td>
                                    <td><?php echo wp_kses_post( $this->format_money( $group['total_sales_value'] ) ); ?></td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                <?php endif; ?>

                <div class="notice notice-info" style="margin-top:20px;">
                    <p>
                        <strong><?php esc_html_e( 'Available in paid plans:', 'sensible-stock' ); ?></strong>
                        <?php esc_html_e( 'generate supplier purchase orders, export order lists and build supplier-specific order sheets.', 'sensible-stock' ); ?>
                    </p>
                    <p>
                        <a class="button button-primary" href="<?php echo esc_url( self::UPGRADE_URL ); ?>" target="_blank" rel="noopener noreferrer">
                            <?php esc_html_e( 'View upgrade options', 'sensible-stock' ); ?>
                        </a>
                    </p>
                </div>
            </div>
            <?php
        }

        /**
         * Free build fallback for legacy order form actions.
         *
         * @return void
         */
        public function handle_save_order() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }

            wp_safe_redirect( admin_url( 'admin.php?page=' . self::ORDER_CREATE_SLUG ) );
            exit;
        }

        /**
         * Free build fallback for legacy order cancel actions.
         *
         * @return void
         */
        public function handle_cancel_order() {
            if ( !$this->user_can_access() ) {
                wp_die( esc_html__( 'No access.', 'sensible-stock' ) );
            }

            wp_safe_redirect( admin_url( 'admin.php?page=' . self::ORDER_CREATE_SLUG ) );
            exit;
        }

    }

    new Sensst_Besteladvies_Plugin();
}
