<?php

/**
 * @package QuickStockForWooCommerce
 * @license GPL-2.0-or-later (https://www.gnu.org/licenses/gpl-2.0.html)
 */
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
class QSFW_Columns {
    /**
     * Master-Liste ALLER verfügbaren Spalten.
     * `group` = Gruppierung im Settings-Tab
     * `fixed` = wird immer angezeigt (nicht abwählbar)
     * `default_visible` = im QuickStock-Dropdown standardmässig aktiviert
     * `default_enabled` = im Settings-Tab standardmässig in der Bibliothek
     */
    private static $cache = null;

    const OVERRIDES_OPTION = 'qsfw_col_overrides';

    public static function invalidate_cache() : void {
        self::$cache = null;
    }

    /**
     * Einen neuen Eintrag hinter einem bestehenden Key in ein
     * assoziatives Array einfügen. Wenn der Anker-Key fehlt, wird
     * der Eintrag ans Ende angehängt.
     */
    private static function insert_after(
        array $arr,
        string $after_key,
        string $new_key,
        array $value
    ) : array {
        $out = array();
        $inserted = false;
        foreach ( $arr as $k => $v ) {
            $out[$k] = $v;
            if ( $k === $after_key ) {
                $out[$new_key] = $value;
                $inserted = true;
            }
        }
        if ( !$inserted ) {
            $out[$new_key] = $value;
        }
        return $out;
    }

    /**
     * Benutzer-Overrides für eingebaute Spalten:
     * [ key => ['label' => string, 'width' => string] ]
     */
    public static function get_overrides() : array {
        $o = get_option( self::OVERRIDES_OPTION, array() );
        return ( is_array( $o ) ? $o : array() );
    }

    public static function set_override( string $key, string $label, $width ) : void {
        $all = self::get_overrides();
        $entry = array();
        if ( $label !== '' ) {
            $entry['label'] = $label;
        }
        $w = (string) $width;
        if ( $w !== '' && ctype_digit( $w ) ) {
            $entry['width'] = $w;
        }
        if ( empty( $entry ) ) {
            unset($all[$key]);
        } else {
            $all[$key] = $entry;
        }
        update_option( self::OVERRIDES_OPTION, $all );
        self::invalidate_cache();
    }

    public static function reset_override( string $key ) : void {
        $all = self::get_overrides();
        if ( isset( $all[$key] ) ) {
            unset($all[$key]);
            update_option( self::OVERRIDES_OPTION, $all );
            self::invalidate_cache();
        }
    }

    public static function get_all() {
        if ( self::$cache !== null ) {
            return self::$cache;
        }
        $static = array(
            'bulk_select'    => array(
                'label'     => __( 'Select', 'quickstock-for-woocommerce' ),
                'short'     => '',
                'group'     => 'core',
                'fixed'     => true,
                'width'     => '32',
                'bulk_only' => true,
            ),
            'check'          => array(
                'label' => __( 'Done', 'quickstock-for-woocommerce' ),
                'short' => '',
                'group' => 'core',
                'fixed' => true,
                'width' => '35',
            ),
            'sku'            => array(
                'label' => __( 'SKU', 'quickstock-for-woocommerce' ),
                'short' => __( 'SKU', 'quickstock-for-woocommerce' ),
                'group' => 'core',
                'fixed' => true,
                'width' => '104',
            ),
            'image'          => array(
                'label'           => __( 'Image', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Image', 'quickstock-for-woocommerce' ),
                'group'           => 'core',
                'width'           => '44',
                'default_visible' => true,
                'default_enabled' => true,
            ),
            'title'          => array(
                'label'           => __( 'Product', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Product', 'quickstock-for-woocommerce' ),
                'group'           => 'core',
                'width'           => '',
                'default_visible' => true,
                'default_enabled' => true,
            ),
            'price_reg'      => array(
                'label'           => __( 'Regular price', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Regular<br>price', 'quickstock-for-woocommerce' ),
                'group'           => 'price',
                'width'           => '85',
                'default_visible' => true,
                'default_enabled' => true,
            ),
            'price_sale'     => array(
                'label'           => __( 'Sale price', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Sale<br>price', 'quickstock-for-woocommerce' ),
                'group'           => 'price',
                'width'           => '85',
                'default_visible' => true,
                'default_enabled' => true,
            ),
            'sale_from'      => array(
                'label'           => __( 'Sale from', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Sale<br>from', 'quickstock-for-woocommerce' ),
                'group'           => 'price',
                'width'           => '120',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'sale_to'        => array(
                'label'           => __( 'Sale to', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Sale<br>to', 'quickstock-for-woocommerce' ),
                'group'           => 'price',
                'width'           => '120',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'manage_stock'   => array(
                'label'           => __( 'Stock management', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Stock<br>mgmt.', 'quickstock-for-woocommerce' ),
                'group'           => 'stock',
                'width'           => '70',
                'default_visible' => true,
                'default_enabled' => true,
            ),
            'limit'          => array(
                'label'           => __( 'Threshold', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Threshold', 'quickstock-for-woocommerce' ),
                'group'           => 'stock',
                'width'           => '70',
                'default_visible' => true,
                'default_enabled' => true,
            ),
            'stock'          => array(
                'label'           => __( 'Stock', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Stock', 'quickstock-for-woocommerce' ),
                'group'           => 'stock',
                'width'           => '70',
                'default_visible' => true,
                'default_enabled' => true,
            ),
            'restock_target' => array(
                'label'           => __( 'Restock to', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Restock<br>to', 'quickstock-for-woocommerce' ),
                'group'           => 'stock',
                'width'           => '70',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'order_qty'      => array(
                'label'           => __( 'Order qty', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Order<br>qty', 'quickstock-for-woocommerce' ),
                'group'           => 'stock',
                'width'           => '70',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'stock_status'   => array(
                'label'           => __( 'Stock status', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Stock<br>status', 'quickstock-for-woocommerce' ),
                'group'           => 'stock',
                'width'           => '120',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'backorders'     => array(
                'label'           => __( 'Backorders', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Back-<br>orders', 'quickstock-for-woocommerce' ),
                'group'           => 'stock',
                'width'           => '110',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'shipping_class' => array(
                'label'           => __( 'Shipping class', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Shipping<br>class', 'quickstock-for-woocommerce' ),
                'group'           => 'shipping',
                'width'           => '120',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'product_cat'    => array(
                'label'           => __( 'Categories', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Categories', 'quickstock-for-woocommerce' ),
                'group'           => 'taxonomy',
                'width'           => '180',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'permalink'      => array(
                'label'           => __( 'Permalink', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Permalink', 'quickstock-for-woocommerce' ),
                'group'           => 'meta',
                'width'           => '180',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'post_status'    => array(
                'label'           => __( 'Status', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Status', 'quickstock-for-woocommerce' ),
                'group'           => 'meta',
                'width'           => '110',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'excerpt'        => array(
                'label'           => __( 'Short description', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Short desc.', 'quickstock-for-woocommerce' ),
                'group'           => 'content',
                'width'           => '220',
                'default_visible' => false,
                'default_enabled' => false,
            ),
            'content'        => array(
                'label'           => __( 'Description', 'quickstock-for-woocommerce' ),
                'short'           => __( 'Description', 'quickstock-for-woocommerce' ),
                'group'           => 'content',
                'width'           => '260',
                'default_visible' => false,
                'default_enabled' => false,
            ),
        );
        // === KONDITIONAL: NATIVE WC-FEATURES ===
        // Spalten, die zu einem nativen WooCommerce-Feature gehören und
        // daher nur in passenden Shop-Versionen sinnvoll sind. Shop-spezifische
        // Attribute (z.B. pa_verfuegbarkeit) oder Plugin-Features (Germanized,
        // Lieferzeit) gehören NICHT hierher – die fügt der Kunde bei Bedarf
        // selbst über die Feld-Bibliothek aus seinen WC-Attributen hinzu.
        // WooCommerce 9.1+ natives GTIN / Global Unique ID
        if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '9.1', '>=' ) ) {
            $static = self::insert_after(
                $static,
                'post_status',
                'global_unique_id',
                array(
                    'label'           => __( 'GTIN / Unique ID', 'quickstock-for-woocommerce' ),
                    'short'           => __( 'GTIN /<br>Unique ID', 'quickstock-for-woocommerce' ),
                    'group'           => 'meta',
                    'width'           => '130',
                    'default_visible' => false,
                    'default_enabled' => false,
                )
            );
        }
        // Benutzer-Overrides auf eingebaute Spalten anwenden (Label/Breite)
        $overrides = self::get_overrides();
        foreach ( $overrides as $k => $ov ) {
            if ( !isset( $static[$k] ) ) {
                continue;
            }
            if ( isset( $ov['label'] ) && $ov['label'] !== '' ) {
                $static[$k]['label'] = $ov['label'];
                $static[$k]['short'] = $ov['label'];
            }
            if ( isset( $ov['width'] ) && $ov['width'] !== '' ) {
                $static[$k]['width'] = (string) $ov['width'];
            }
        }
        self::$cache = $static;
        return self::$cache;
    }

    /**
     * Zentrale Zuordnung Bulk-Op → Spalten-Key → Taxonomie-Slug.
     * Einzige Quelle für die Taxonomie-Slugs der Massenbearbeitung.
     */
    public static function taxonomy_bulk_map() : array {
        return array(
            'shipping_class' => array(
                'col' => 'shipping_class',
                'tax' => 'product_shipping_class',
            ),
            'product_cat'    => array(
                'col' => 'product_cat',
                'tax' => 'product_cat',
            ),
        );
    }

    /**
     * Im aktuellen Shop tatsächlich verfügbare Bulk-Taxonomien:
     * Spalte aktiviert UND Taxonomie registriert.
     */
    public static function available_bulk_taxonomies() : array {
        $enabled = self::get_enabled();
        $out = array();
        foreach ( self::taxonomy_bulk_map() as $op => $m ) {
            if ( in_array( $m['col'], $enabled, true ) && taxonomy_exists( $m['tax'] ) ) {
                $out[$op] = $m;
            }
        }
        return $out;
    }

    public static function get_groups() {
        return array(
            'core'     => '⭐ ' . __( 'Basics', 'quickstock-for-woocommerce' ),
            'price'    => '🏷️ ' . __( 'Price & margin', 'quickstock-for-woocommerce' ),
            'stock'    => '📦 ' . __( 'Stock & status', 'quickstock-for-woocommerce' ),
            'shipping' => '🚚 ' . __( 'Shipping & delivery', 'quickstock-for-woocommerce' ),
            'taxonomy' => '🏷️ ' . __( 'Attributes & categories', 'quickstock-for-woocommerce' ),
            'meta'     => '🔗 ' . __( 'Meta & SEO', 'quickstock-for-woocommerce' ),
            'content'  => '📝 ' . __( 'Content', 'quickstock-for-woocommerce' ),
            'custom'   => '🔧 ' . __( 'Custom fields', 'quickstock-for-woocommerce' ),
        );
    }

    /* ===================================================================
     * COCKPIT-FORMULAR (Ansichts-State)
     *
     * Das Cockpit sendet Spaltenauswahl, Reihenfolge, Sortierung, Seitengrösse,
     * Status-Filter und erweiterte Filter in EINEM POST. Der Handler läuft via
     * admin_init vor jeglichem Output, prüft den Nonce, speichert in user_meta
     * und leitet auf eine saubere GET-URL um (Post/Redirect/Get).
     *
     * Die Getter bleiben dadurch reine Leser: Vor diesem Umbau schrieben sie
     * ihren Zustand direkt beim Lesen aus $_REQUEST -- per GET und ohne Nonce.
     * =================================================================== */
    /**
     * Hook: admin_init
     */
    public static function maybe_handle_view_post() {
        if ( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) !== 'POST' ) {
            return;
        }
        if ( !isset( $_POST['qsfw_view_submitted'] ) ) {
            return;
        }
        if ( !isset( $_GET['page'] ) || $_GET['page'] !== 'quickstock-for-woocommerce' ) {
            return;
        }
        if ( !current_user_can( 'manage_woocommerce' ) ) {
            return;
        }
        check_admin_referer( 'qsfw_view_state' );
        $raw_mode = ( isset( $_POST['mode'] ) ? sanitize_text_field( wp_unslash( $_POST['mode'] ) ) : 'all' );
        $mode = ( $raw_mode === 'reorder' ? 'reorder' : 'all' );
        $visible_cols = ( isset( $_POST['visible_cols'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['visible_cols'] ) ) : array() );
        $col_order = ( isset( $_POST['col_order'] ) ? sanitize_text_field( wp_unslash( $_POST['col_order'] ) ) : null );
        $per_page = ( isset( $_POST['per_page'] ) ? sanitize_text_field( wp_unslash( $_POST['per_page'] ) ) : null );
        $stock_sort = ( isset( $_POST['stock_sort'] ) ? sanitize_text_field( wp_unslash( $_POST['stock_sort'] ) ) : null );
        $status_filter = ( isset( $_POST['status_filter'] ) ? array_map( 'sanitize_text_field', (array) wp_unslash( $_POST['status_filter'] ) ) : array() );
        $adv = ( isset( $_POST['adv'] ) && is_array( $_POST['adv'] ) ? map_deep( wp_unslash( $_POST['adv'] ), 'sanitize_textarea_field' ) : array() );
        self::save_visible( $visible_cols );
        self::save_order( $col_order );
        self::save_per_page( $per_page );
        self::save_sort( $stock_sort, $mode );
        self::save_status_filter( $status_filter );
        QSFW_Filters::save_adv( $adv, $mode );
        // „Aktuelle Ansicht als Preset speichern" reitet auf demselben Formular
        // mit, schreibt aber die globale Option qsfw_view_presets -> eigener,
        // strengerer Nonce.
        if ( !empty( $_POST['save_preset_name'] ) ) {
            check_admin_referer( 'qsfw_preset_save', '_qsfw_preset_nonce' );
            $name = sanitize_text_field( wp_unslash( $_POST['save_preset_name'] ) );
            if ( $name !== '' ) {
                QSFW_Presets::save_from_current_metas( $name, $mode );
            }
        }
        // Redirect-Parameter hier lesen (Nonce ist oben schon geprueft), damit
        // view_redirect_url keinen $_POST-Zugriff ohne sichtbaren Nonce hat.
        $extra = array();
        foreach ( array('cat_filter', 'stock_start_date', 'hb_q') as $k ) {
            if ( isset( $_POST[$k] ) && $_POST[$k] !== '' ) {
                $extra[$k] = sanitize_text_field( wp_unslash( $_POST[$k] ) );
            }
        }
        QSFW_Filters::mark_prg_bounce();
        wp_safe_redirect( self::view_redirect_url( $mode, $extra ) );
        exit;
    }

    /**
     * PRG-Ziel nach einem Cockpit-Submit.
     *
     * Enthält nur noch die reinen Anzeige-Parameter, die NICHT persistiert
     * werden. stock_sort/per_page stehen danach in user_meta und wären in der
     * URL inert -- sie würden Zustand suggerieren, den sie nicht mehr setzen.
     * 'paged' fehlt bewusst: jede Cockpit-Änderung springt auf Seite 1 (wie
     * bisher, da das Formular kein paged-Feld hat).
     */
    private static function view_redirect_url( $mode, array $extra = array() ) {
        $args = array(
            'page' => 'quickstock-for-woocommerce',
            'mode' => $mode,
        );
        foreach ( array('cat_filter', 'stock_start_date', 'hb_q') as $k ) {
            if ( isset( $extra[$k] ) && $extra[$k] !== '' ) {
                $args[$k] = $extra[$k];
            }
        }
        return add_query_arg( $args, admin_url( 'admin.php' ) );
    }

    /**
     * Speichert die Spaltenauswahl eines Formular-Submits.
     *
     * Guard ist bewusst NUR der Formular-Marker des Aufrufers und nicht
     * isset($src['visible_cols']): Sind alle Häkchen abgewählt, fehlt
     * 'visible_cols' im Body komplett. Das muss „keine Spalte sichtbar"
     * bedeuten -- nicht „Defaults wiederherstellen".
     */
    public static function save_visible( array $visible_cols ) {
        $enabled = self::get_enabled();
        $val = $visible_cols;
        $val = array_values( array_intersect( array_map( 'sanitize_text_field', $val ), $enabled ) );
        update_user_meta( get_current_user_id(), 'qsfw_visible_cols', $val );
        return $val;
    }

    /** Speichert die Spaltenreihenfolge (kommaseparierter Hidden-Input). */
    public static function save_order( $col_order ) {
        if ( !is_scalar( $col_order ) ) {
            return null;
        }
        $all = self::get_all();
        $raw = sanitize_text_field( $col_order );
        $arr = array_filter( array_map( 'trim', explode( ',', $raw ) ) );
        $arr = array_values( array_intersect( $arr, array_keys( $all ) ) );
        update_user_meta( get_current_user_id(), 'qsfw_col_order', $arr );
        return $arr;
    }

    /** Speichert die Seitengrösse. Ungültige Werte werden ignoriert. */
    public static function save_per_page( $per_page ) {
        if ( !is_scalar( $per_page ) ) {
            return null;
        }
        $val = sanitize_text_field( $per_page );
        if ( !in_array( $val, array(
            '25',
            '50',
            '100',
            '200',
            'all'
        ), true ) ) {
            return null;
        }
        update_user_meta( get_current_user_id(), 'qsfw_per_page', $val );
        return $val;
    }

    /** Speichert die Sortierung des Tabs. Ungültige Werte werden ignoriert. */
    public static function save_sort( $stock_sort, $mode = 'reorder' ) {
        if ( !is_scalar( $stock_sort ) ) {
            return null;
        }
        $mode = ( $mode === 'all' ? 'all' : 'reorder' );
        $val = sanitize_text_field( $stock_sort );
        $legacy = self::legacy_sort_map();
        if ( isset( $legacy[$val] ) ) {
            $val = $legacy[$val];
        }
        if ( !self::is_valid_sort( $val, $mode ) ) {
            return null;
        }
        update_user_meta( get_current_user_id(), 'qsfw_sort_' . $mode, $val );
        return $val;
    }

    /** Speichert den Status-Filter. Leere Auswahl fällt auf 'publish' zurück. */
    public static function save_status_filter( array $status_filter ) {
        $allowed = array(
            'publish',
            'draft',
            'private',
            'pending'
        );
        $val = $status_filter;
        $val = array_values( array_intersect( array_map( 'sanitize_text_field', $val ), $allowed ) );
        if ( empty( $val ) ) {
            $val = array('publish');
        }
        update_user_meta( get_current_user_id(), 'qsfw_status_filter', $val );
        return $val;
    }

    /**
     * Welche Spalten sind in der "Bibliothek" verfügbar?
     * (= im Spalten-Dropdown von QuickStock auswählbar)
     * Konfiguriert im Settings-Tab.
     */
    public static function get_enabled() {
        $all = self::get_all();
        $optional = array();
        $defaults = array();
        foreach ( $all as $k => $v ) {
            if ( !empty( $v['fixed'] ) ) {
                continue;
            }
            $optional[] = $k;
            if ( !empty( $v['default_enabled'] ) ) {
                $defaults[] = $k;
            }
        }
        // Kein Schreibzweig hier: 'enabled_submitted' wird ausschliesslich von
        // QSFW_Settings_Page::process_columns_forms() verarbeitet (mit Nonce-Prüfung
        // und Redirect). Ein Write an dieser Stelle wäre per GET ohne Nonce
        // auslösbar und würde die globale Option site-weit leeren.
        $saved = get_option( 'qsfw_enabled_cols', null );
        if ( is_array( $saved ) ) {
            return array_values( array_intersect( $saved, $optional ) );
        }
        return $defaults;
    }

    /**
     * Welche Spalten hat der User aktuell sichtbar?
     * (Aus den enabled-Spalten ausgewählt)
     */
    public static function get_visible() {
        $enabled = self::get_enabled();
        $user_id = get_current_user_id();
        // Reiner Leser: Geschrieben wird ausschliesslich in
        // maybe_handle_view_post() (nonce-geprüft, mit PRG-Redirect).
        $saved = get_user_meta( $user_id, 'qsfw_visible_cols', true );
        if ( is_array( $saved ) ) {
            return array_values( array_intersect( $saved, $enabled ) );
        }
        // Default: alle aktivierten, die default_visible=true sind
        $all = self::get_all();
        $defaults = array();
        foreach ( $enabled as $k ) {
            if ( !empty( $all[$k]['default_visible'] ) ) {
                $defaults[] = $k;
            }
        }
        return $defaults;
    }

    /**
     * Liefert die User-spezifische Reihenfolge der Spalten.
     * Fixe Spalten kommen immer zuerst (in ihrer Original-Reihenfolge),
     * danach die optionalen Spalten in der vom User definierten Reihenfolge.
     */
    public static function get_order() {
        $all = self::get_all();
        $user_id = get_current_user_id();
        $saved = get_user_meta( $user_id, 'qsfw_col_order', true );
        if ( is_array( $saved ) && !empty( $saved ) ) {
            return array_values( array_intersect( $saved, array_keys( $all ) ) );
        }
        return array();
        // leer = Default-Reihenfolge aus get_all()
    }

    public static function get_active( $mode = null ) {
        $all = self::get_all();
        $visible = self::get_visible();
        $order = self::get_order();
        // Welche Spalten sollen überhaupt erscheinen?
        $candidate_keys = array();
        foreach ( $all as $key => $cfg ) {
            if ( !empty( $cfg['fixed'] ) || in_array( $key, $visible, true ) ) {
                $candidate_keys[] = $key;
            }
        }
        // Fixe Spalten in ihrer Original-Reihenfolge sammeln
        $fixed_ordered = array();
        foreach ( $all as $key => $cfg ) {
            if ( !empty( $cfg['fixed'] ) && in_array( $key, $candidate_keys, true ) ) {
                $fixed_ordered[] = $key;
            }
        }
        // Optionale Spalten in User-Reihenfolge (falls vorhanden), sonst Default
        $optional_ordered = array();
        if ( !empty( $order ) ) {
            // Erst die vom User sortierten optionalen, die auch in candidates sind
            foreach ( $order as $key ) {
                if ( in_array( $key, $candidate_keys, true ) && empty( $all[$key]['fixed'] ) ) {
                    $optional_ordered[] = $key;
                }
            }
            // Dann optionale, die der User noch nicht sortiert hat (neue Spalten),
            // in Default-Reihenfolge hinten anhängen
            foreach ( $all as $key => $cfg ) {
                if ( empty( $cfg['fixed'] ) && in_array( $key, $candidate_keys, true ) && !in_array( $key, $optional_ordered, true ) ) {
                    $optional_ordered[] = $key;
                }
            }
        } else {
            // Keine User-Reihenfolge → Default
            foreach ( $all as $key => $cfg ) {
                if ( empty( $cfg['fixed'] ) && in_array( $key, $candidate_keys, true ) ) {
                    $optional_ordered[] = $key;
                }
            }
        }
        $final_keys = array_merge( $fixed_ordered, $optional_ordered );
        $active = array();
        foreach ( $final_keys as $key ) {
            $active[$key] = $all[$key];
        }
        // "Erledigt"-Spalte gibt es nur im Nachbestellungs-Tab.
        // "Auswahl"-Checkbox gibt es in beiden Tabs (im Reorder-Tab für die
        // CSV-Produktauswahl; im „Alle Produkte"-Tab für Massenbearbeitung).
        if ( $mode === 'all' ) {
            unset($active['check']);
        }
        return $active;
    }

    public static function get_per_page() {
        $allowed = array(
            '25',
            '50',
            '100',
            '200',
            'all'
        );
        $user_id = get_current_user_id();
        $saved = get_user_meta( $user_id, 'qsfw_per_page', true );
        if ( in_array( $saved, $allowed, true ) ) {
            return $saved;
        }
        $default = QSFW_Settings_Page::get_general_setting( 'default_per_page', '50' );
        return ( in_array( $default, $allowed, true ) ? $default : '50' );
    }

    /**
     * Sortierung – pro Modus (reorder / all) separat gespeichert.
     * Default: reorder → "Offen zuerst", all → "Neueste zuerst".
     * Die erlaubten Werte entsprechen den Dropdown-Optionen des jeweiligen
     * Modus, damit ein über die URL mitgeschleppter Fremdwert sauber auf
     * den Default zurückfällt statt eine "leere" Auswahl zu zeigen.
     */
    /**
     * Sortier-„Spezialwerte" je Modus, die KEIN Spalten-Äquivalent haben
     * und daher im schlanken Dropdown landen. Alle anderen Sortierungen
     * laufen über die Spaltenköpfe (col__<key>__asc|desc).
     */
    public static function allowed_sorts( $mode = 'reorder' ) {
        return ( (( $mode === 'all' ? 'all' : 'reorder' )) === 'all' ? array('newest', 'oldest') : array('open_first') );
    }

    public static function default_sort( $mode = 'reorder' ) {
        if ( class_exists( 'QSFW_Settings_Page' ) ) {
            $key = ( $mode === 'all' ? 'default_sort_all' : 'default_sort_reorder' );
            $val = QSFW_Settings_Page::get_general_setting( $key );
            if ( self::is_valid_sort( $val, $mode ) ) {
                return $val;
            }
        }
        return ( $mode === 'all' ? 'newest' : 'open_first' );
    }

    /**
     * Spalten, die per Spaltenkopf sortierbar sind: key => 'num'|'text'|'date'.
     * Der Typ steuert die Beschriftung des Mini-Menüs (A-Z vs. Kleinste zuerst).
     * Eigene Felder werden anhand ihres Feldtyps ergänzt.
     */
    public static function sortable_columns() {
        $map = array(
            'sku'              => 'text',
            'title'            => 'text',
            'price_reg'        => 'num',
            'price_sale'       => 'num',
            'stock'            => 'num',
            'limit'            => 'num',
            'manage_stock'     => 'text',
            'stock_status'     => 'text',
            'backorders'       => 'text',
            'sale_from'        => 'date',
            'sale_to'          => 'date',
            'shipping_class'   => 'text',
            'product_cat'      => 'text',
            'permalink'        => 'text',
            'post_status'      => 'text',
            'global_unique_id' => 'text',
            'excerpt'          => 'text',
            'content'          => 'text',
        );
        return $map;
    }

    /** Legacy-Dropdownwerte auf die neue Spaltensyntax abbilden. */
    private static function legacy_sort_map() {
        return array(
            'stock_asc'  => 'col__stock__asc',
            'stock_desc' => 'col__stock__desc',
            'title_asc'  => 'col__title__asc',
            'sku_asc'    => 'col__sku__asc',
        );
    }

    /**
     * Zerlegt einen Spalten-Sortierwert "col__<key>__<asc|desc>".
     * @return array|null  ['key' => ..., 'dir' => 'asc'|'desc'] oder null.
     */
    public static function parse_column_sort( $value ) {
        if ( !is_string( $value ) || strpos( $value, 'col__' ) !== 0 ) {
            return null;
        }
        $rest = substr( $value, 5 );
        $pos = strrpos( $rest, '__' );
        if ( $pos === false ) {
            return null;
        }
        $key = substr( $rest, 0, $pos );
        $dir = substr( $rest, $pos + 2 );
        if ( $key === '' || $dir !== 'asc' && $dir !== 'desc' ) {
            return null;
        }
        $sortable = self::sortable_columns();
        if ( !isset( $sortable[$key] ) ) {
            return null;
        }
        return array(
            'key' => $key,
            'dir' => $dir,
        );
    }

    /** Ist der Wert für diesen Modus eine gültige Sortierung? */
    public static function is_valid_sort( $value, $mode = 'reorder' ) {
        if ( in_array( $value, self::allowed_sorts( $mode ), true ) ) {
            return true;
        }
        return self::parse_column_sort( $value ) !== null;
    }

    /** Validiert/normalisiert einen Sort-Wert; Fallback = Modus-Default. */
    public static function sanitize_sort( $value, $mode = 'reorder' ) {
        $mode = ( $mode === 'all' ? 'all' : 'reorder' );
        $legacy = self::legacy_sort_map();
        if ( isset( $legacy[$value] ) ) {
            $value = $legacy[$value];
        }
        return ( self::is_valid_sort( $value, $mode ) ? $value : self::default_sort( $mode ) );
    }

    public static function get_sort( $mode = 'reorder' ) {
        $mode = ( $mode === 'all' ? 'all' : 'reorder' );
        $default = self::default_sort( $mode );
        $user_id = get_current_user_id();
        $meta_key = 'qsfw_sort_' . $mode;
        $legacy = self::legacy_sort_map();
        $saved = get_user_meta( $user_id, $meta_key, true );
        if ( isset( $legacy[$saved] ) ) {
            $saved = $legacy[$saved];
        }
        if ( self::is_valid_sort( $saved, $mode ) ) {
            return $saved;
        }
        return $default;
    }

    public static function get_status_filter() {
        $allowed = array(
            'publish',
            'draft',
            'private',
            'pending'
        );
        $user_id = get_current_user_id();
        $saved = get_user_meta( $user_id, 'qsfw_status_filter', true );
        if ( is_array( $saved ) && !empty( $saved ) ) {
            $saved = array_values( array_intersect( $saved, $allowed ) );
            if ( !empty( $saved ) ) {
                return $saved;
            }
        }
        return $allowed;
    }

    /**
     * Merkt sich den zuletzt besuchten Tab (für „Standard-Tab = zuletzt besucht").
     *
     * BEWUSSTE AUSNAHME von der Nonce-Regel: Ein Nonce auf den Tab-Links würde
     * die URLs unbookmarkbar machen. Der Schaden ist auf „der nächste Aufruf
     * öffnet Reorder statt Alle Produkte" begrenzt, durch die Whitelist gedeckelt
     * und heilt beim nächsten echten Tab-Klick von selbst.
     *
     * settings/verlauf/po(-draft) werden nicht gemerkt – sonst landet man dort
     * immer wieder.
     */
    public static function remember_mode( $mode ) {
        if ( !in_array( $mode, array('reorder', 'all'), true ) ) {
            return;
        }
        update_user_meta( get_current_user_id(), 'qsfw_mode', $mode );
    }

    /**
     * Aktueller Tab (reiner Leser).
     *
     * Das Merken des Tabs steckte früher hier drin und feuerte bei jedem
     * beiläufigen get_mode()-Aufruf (Localize, Render, QSFW_Filters::norm_mode()).
     * Es liegt jetzt in remember_mode() – genau ein Aufruf pro echtem Render.
     */
    public static function get_mode() {
        $allowed = array(
            'reorder',
            'all',
            'settings',
            'verlauf',
            'po',
            'po-draft'
        );
        $user_id = get_current_user_id();
        // phpcs:disable WordPress.Security.NonceVerification.Recommended -- reiner Leser fuer die aktuelle Ansicht; keine Zustandsaenderung
        if ( isset( $_REQUEST['mode'] ) ) {
            $val = sanitize_text_field( wp_unslash( $_REQUEST['mode'] ) );
            if ( in_array( $val, $allowed, true ) ) {
                return $val;
            }
        }
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
        // Globaler Standard-Tab aus den Allgemein-Einstellungen
        $default_tab = ( class_exists( 'QSFW_Settings_Page' ) ? QSFW_Settings_Page::get_general_setting( 'default_tab', 'all' ) : 'all' );
        if ( $default_tab === 'last_visited' ) {
            $saved = get_user_meta( $user_id, 'qsfw_mode', true );
            if ( in_array( $saved, array('reorder', 'all'), true ) ) {
                return $saved;
            }
            return 'all';
        }
        return ( in_array( $default_tab, array('all', 'reorder'), true ) ? $default_tab : 'all' );
    }

}
