<?php
/**
 * @package QuickStockForWooCommerce
 * @license GPL-2.0-or-later (https://www.gnu.org/licenses/gpl-2.0.html)
 */

if (!defined('ABSPATH')) exit;

/**
 * Globale "Ansicht-Presets" (vom Admin verwaltet, gelten für alle Benutzer).
 *
 * Ein Preset speichert die komplette Tabellenansicht:
 *   - cols          geordnete Liste der sichtbaren optionalen Spalten
 *   - sort          Tabellen-Sortierung
 *   - per_page      Produkte pro Seite
 *   - status_filter Statusfilter
 *
 * Speicherung:
 *   Option   qsfw_view_presets    = array( id => array(name, cols, sort, per_page, status_filter) )
 *   Option   qsfw_preset_defaults = array( 'all' => id, 'reorder' => id )
 *   UserMeta qsfw_active_preset_<mode> = aktuell angewendete Preset-ID des Users
 */
class QSFW_Presets {

    const OPTION   = 'qsfw_view_presets';
    const DEFAULTS = 'qsfw_preset_defaults';

    /** Alle Presets (id => data). */
    public static function all() {
        $saved = get_option(self::OPTION, array());
        return is_array($saved) ? $saved : array();
    }

    public static function get($id) {
        $all = self::all();
        return isset($all[$id]) ? $all[$id] : null;
    }

    /** Liefert die ID eines Presets mit gleichem Namen (case-insensitive), sonst ''. */
    public static function find_by_name($name) {
        $needle = mb_strtolower(trim($name));
        foreach (self::all() as $id => $p) {
            if (mb_strtolower(trim($p['name'])) === $needle) return $id;
        }
        return '';
    }

    public static function get_defaults() {
        $d = get_option(self::DEFAULTS, array());
        if (!is_array($d)) $d = array();
        return array(
            'all'     => isset($d['all'])     ? $d['all']     : '',
            'reorder' => isset($d['reorder']) ? $d['reorder'] : '',
        );
    }

    public static function get_default_for($mode) {
        $mode = ($mode === 'all') ? 'all' : 'reorder';
        $d = self::get_defaults();
        $id = $d[$mode];
        return ($id && self::get($id)) ? $id : '';
    }

    public static function get_active_id($mode) {
        $mode = ($mode === 'all') ? 'all' : 'reorder';
        $id = get_user_meta(get_current_user_id(), 'qsfw_active_preset_' . $mode, true);
        return is_string($id) ? $id : '';
    }

    /* ===================================================================
     * Schreiben / Verwalten (Settings-Tab)
     * =================================================================== */

    /**
     * Speichert ein Preset. $id leer = neu anlegen.
     * $data wird aus der bereits validierten Settings-Form gebaut.
     */
    public static function save($id, $data) {
        $all = self::all();
        if (empty($id)) {
            $id = 'p' . substr(md5(uniqid('', true)), 0, 8);
        }
        $all[$id] = array(
            'name'          => $data['name'],
            'cols'          => $data['cols'],
            'sort'          => $data['sort'],
            'per_page'      => $data['per_page'],
            'status_filter' => $data['status_filter'],
        );
        update_option(self::OPTION, $all);
        return $id;
    }

    public static function delete($id) {
        $all = self::all();
        if (isset($all[$id])) {
            unset($all[$id]);
            update_option(self::OPTION, $all);
        }
        // Aus den Tab-Defaults entfernen
        $d = self::get_defaults();
        $changed = false;
        foreach (array('all', 'reorder') as $m) {
            if ($d[$m] === $id) { $d[$m] = ''; $changed = true; }
        }
        if ($changed) update_option(self::DEFAULTS, $d);
    }

    public static function set_default($mode, $id) {
        $mode = ($mode === 'all') ? 'all' : 'reorder';
        $d = self::get_defaults();
        $d[$mode] = ($id && self::get($id)) ? $id : '';
        update_option(self::DEFAULTS, $d);
    }

    /**
     * Hook: admin_init
     *
     * Preset-Editor der Einstellungen (POST): Preset anlegen/aktualisieren.
     * Die Tab-Standard-Häkchen (preset_default_all/_reorder) reiten auf
     * demselben Formular mit und werden von apply_default_flags() gelesen.
     *
     * Läuft via admin_init vor jeglichem Output, damit der PRG-Redirect greift.
     * Schreibt die globalen Optionen qsfw_view_presets und qsfw_preset_defaults
     * und ist deshalb nonce-pflichtig.
     */
    public static function maybe_handle_settings_post() {
        if (sanitize_text_field(wp_unslash($_SERVER['REQUEST_METHOD'] ?? '')) !== 'POST') return;
        if (!isset($_GET['page']) || $_GET['page'] !== 'quickstock-for-woocommerce') return;
        if (!current_user_can('manage_woocommerce')) return;

        $base_url = admin_url('admin.php?page=quickstock-for-woocommerce&mode=settings&settings_subtab=presets');

        if (isset($_POST['preset_submitted'])) {
            check_admin_referer('qsfw_preset_save');

            $name = isset($_POST['preset_name'])
                ? sanitize_text_field(wp_unslash($_POST['preset_name'])) : '';
            if ($name === '') $name = __('Untitled', 'quickstock-for-woocommerce');

            $id = isset($_POST['preset_id'])
                ? sanitize_text_field(wp_unslash($_POST['preset_id'])) : '';

            // Spalten kommen als kommaseparierte, bereits sortierte Liste (Hidden-Input).
            $enabled  = QSFW_Columns::get_enabled();
            $cols_raw = isset($_POST['preset_cols'])
                ? sanitize_text_field(wp_unslash($_POST['preset_cols'])) : '';
            $cols = array_filter(array_map('trim', explode(',', $cols_raw)));
            $cols = array_values(array_intersect($cols, $enabled));

            $sort = isset($_POST['preset_sort'])
                ? sanitize_text_field(wp_unslash($_POST['preset_sort'])) : '';

            $pp = isset($_POST['preset_per_page'])
                ? sanitize_text_field(wp_unslash($_POST['preset_per_page'])) : '50';
            if (!in_array($pp, array('25', '50', '100', '200', 'all'), true)) $pp = '50';

            $allowed_status = array('publish', 'draft', 'private', 'pending');
            $status = isset($_POST['preset_status'])
                ? array_map('sanitize_text_field', wp_unslash((array) $_POST['preset_status'])) : array();
            $status = array_values(array_intersect($status, $allowed_status));
            if (empty($status)) $status = $allowed_status;

            $id = self::save($id, array(
                'name'          => $name,
                'cols'          => $cols,
                'sort'          => $sort,
                'per_page'      => $pp,
                'status_filter' => $status,
            ));

            // Optional gleich als Tab-Standard markieren. Flags hier lesen (Nonce
            // oben geprueft) und durchreichen, damit apply_default_flags keinen
            // $_POST-Zugriff ohne sichtbaren Nonce hat.
            $flags = array(
                'all'     => !empty($_POST['preset_default_all']),
                'reorder' => !empty($_POST['preset_default_reorder']),
            );
            self::apply_default_flags($id, $flags);
            wp_safe_redirect($base_url);
            exit;
        }
    }

    /**
     * Hook: admin_init
     *
     * Preset-Link-Aktionen aus der Tab-Auswahl (Cockpit) und der Settings-Liste.
     * Alle schreiben persistenten State, tragen deshalb einen objektgebundenen
     * Nonce (Konvention wie qsfw_supplier_delete_<id>) und leiten anschliessend
     * per PRG auf eine saubere URL um. Der Redirect ersetzt den früheren
     * replaceState-Hack in admin.js.
     */
    public static function maybe_handle_tab_actions() {
        if (!isset($_GET['page']) || $_GET['page'] !== 'quickstock-for-woocommerce') return;
        if (!current_user_can('manage_woocommerce')) return;

        $raw_mode = isset($_REQUEST['mode']) ? sanitize_text_field(wp_unslash($_REQUEST['mode'])) : 'all';
        $mode     = ($raw_mode === 'reorder') ? 'reorder' : 'all';

        if (isset($_REQUEST['apply_preset'])) {
            $id = sanitize_text_field(wp_unslash($_REQUEST['apply_preset']));
            check_admin_referer('qsfw_apply_preset_' . $id);
            self::apply($id, $mode);
            update_user_meta(get_current_user_id(), 'qsfw_preset_last_mode', $mode);
            // apply() hat die Metas bereits geschrieben – process_apply() darf
            // nach dem Rückprall nicht erneut greifen.
            QSFW_Filters::mark_prg_bounce();
            self::redirect_to_tab($mode);
        }

        if (isset($_REQUEST['preset_delete'])) {
            $id = sanitize_text_field(wp_unslash($_REQUEST['preset_delete']));
            check_admin_referer('qsfw_preset_delete_' . $id);
            self::delete($id);
            // 'preset_delete' galt bisher als Interaktion (is_fresh_entry), die
            // Ansicht blieb nach dem Löschen stehen. Ohne Marker würde der
            // Rückprall als frischer Aufruf gelten und das Standard-Preset neu
            // anwenden.
            QSFW_Filters::mark_prg_bounce();
            // Löschen ist aus dem Cockpit UND aus den Einstellungen möglich –
            // jeweils zum Ursprung zurück.
            if ($raw_mode === 'settings') {
                wp_safe_redirect(admin_url('admin.php?page=quickstock-for-woocommerce&mode=settings&settings_subtab=presets'));
                exit;
            }
            self::redirect_to_tab($mode);
        }

        if (isset($_REQUEST['set_default_preset'])) {
            $id = sanitize_text_field(wp_unslash($_REQUEST['set_default_preset']));
            check_admin_referer('qsfw_preset_default_' . $mode . '_' . $id);
            self::set_default($mode, $id);
            // Bewusst KEIN mark_prg_bounce(): 'set_default_preset' stand noch nie
            // in is_fresh_entry(), der Aufruf galt also auch vor diesem Umbau
            // schon als frisch. process_apply() soll unverändert laufen.
            self::redirect_to_tab($mode);
        }

        if (isset($_REQUEST['clear_default_preset'])) {
            check_admin_referer('qsfw_preset_default_' . $mode . '_clear');
            self::set_default($mode, '');
            self::redirect_to_tab($mode);
        }
    }

    /** Saubere Tab-URL nach einer Preset-Aktion (PRG-Ziel). */
    private static function redirect_to_tab($mode) {
        wp_safe_redirect(add_query_arg(array(
            'page' => 'quickstock-for-woocommerce',
            'mode' => $mode,
        ), admin_url('admin.php')));
        exit;
    }

    /**
     * Liest preset_default_all / preset_default_reorder für ein Preset.
     * Nur aus maybe_handle_settings_post() aufgerufen – der Nonce des Aufrufers
     * deckt diese POST-Werte mit ab.
     */
    private static function apply_default_flags($id, array $flags) {
        if (!$id || !self::get($id)) return;
        foreach (array('all', 'reorder') as $m) {
            $checked = !empty($flags[$m]);
            $cur = self::get_defaults();
            if ($checked) {
                self::set_default($m, $id);
            } elseif ($cur[$m] === $id) {
                // Häkchen entfernt → Standard für diesen Tab löschen
                self::set_default($m, '');
            }
        }
    }

    /* ===================================================================
     * Anwenden (Tabs)
     * =================================================================== */

    /**
     * Speichert die aktuelle Ansicht des Users direkt als neues Preset.
     * Wird aus render_table() aufgerufen, nachdem die Getter die Metas
     * bereits mit den gerade eingereichten Formular-Werten aktualisiert haben.
     */
    public static function save_from_current_metas($name, $mode) {
        $mode    = ($mode === 'all') ? 'all' : 'reorder';
        $user_id = get_current_user_id();

        $cols   = get_user_meta($user_id, 'qsfw_visible_cols', true);
        $order  = get_user_meta($user_id, 'qsfw_col_order',   true);
        $status = get_user_meta($user_id, 'qsfw_status_filter', true);
        $pp     = get_user_meta($user_id, 'qsfw_per_page',     true);
        $sort   = get_user_meta($user_id, 'qsfw_sort_' . $mode, true);

        // Sichtbare Spalten in ihrer Reihenfolge zusammenführen (wie get_active)
        $cols   = is_array($cols)  ? $cols  : array();
        $order  = is_array($order) ? $order : array();
        $ordered = array();
        foreach ($order as $k) {
            if (in_array($k, $cols, true)) $ordered[] = $k;
        }
        foreach ($cols as $k) {
            if (!in_array($k, $ordered, true)) $ordered[] = $k;
        }

        if (!is_string($sort) || $sort === '') $sort = self::_default_sort($mode);
        if (!in_array($pp, array('25', '50', '100', '200', 'all'), true)) $pp = '50';
        if (!is_array($status) || empty($status)) $status = array('publish', 'draft', 'private', 'pending');

        // Upsert: existiert bereits ein Preset mit gleichem Namen, wird es
        // aktualisiert statt ein Duplikat anzulegen (verhindert doppelte
        // Presets bei versehentlichem Doppel-Submit/Reload).
        $existing_id = self::find_by_name($name);

        $id = self::save($existing_id, array(
            'name'          => $name,
            'cols'          => $ordered,
            'sort'          => $sort,
            'per_page'      => $pp,
            'status_filter' => $status,
        ));

        update_user_meta($user_id, 'qsfw_active_preset_' . $mode, (string) $id);
        return $id;
    }

    private static function _default_sort($mode) {
        return ($mode === 'all') ? 'newest' : 'open_first';
    }

    /**
     * Wird zu Beginn von render_table() aufgerufen.
     * Wendet ein per Klick gewähltes Preset an oder – falls der User für
     * diesen Tab noch keines gewählt hat – das Tab-Standard-Preset.
     */
    public static function process_apply($mode) {
        $mode    = ($mode === 'all') ? 'all' : 'reorder';
        $user_id = get_current_user_id();

        // 'apply_preset' wird nicht mehr hier behandelt, sondern nonce-geprüft in
        // maybe_handle_tab_actions() (admin_init) – inklusive PRG-Redirect.

        // PRG-Rückprall einer In-Page-Aktion (Spalten-Wertfilter, Lieferanten-
        // Sprung): Der Zustand wurde bereits in user_meta geschrieben und auf
        // eine saubere URL umgeleitet, die wie ein frischer Aufruf AUSSIEHT. Das
        // Standard-Preset darf hier NICHT neu greifen, sonst geht die gerade
        // angepasste Ansicht (z. B. eine hinzugefügte Spalte) verloren.
        if (get_transient(QSFW_Filters::SKIP_APPLY_PREFIX . $user_id)) {
            delete_transient(QSFW_Filters::SKIP_APPLY_PREFIX . $user_id);
            update_user_meta($user_id, 'qsfw_preset_last_mode', $mode);
            return;
        }

        // Beim frischen Seitenaufruf (WP-Menü/Tab-Klick, keine In-Page-Aktion)
        // sowie beim Tab-Wechsel gewinnt das Tab-Standard-Preset – auch über
        // ein klebriges zuletzt aktives Preset. Innerhalb desselben Tabs
        // (Blättern/Filtern/Spalten) bleiben manuelle Anpassungen erhalten.
        $last        = get_user_meta($user_id, 'qsfw_preset_last_mode', true);
        $tab_changed = ($last !== $mode);
        $is_fresh    = self::is_fresh_entry();

        // Eine bewusst modifizierte aktive Ansicht (etwa nach dem Filterreset)
        // bleibt auch bei einem Reload erhalten. Erst ein echter Tabwechsel oder
        // das bewusste Anwenden eines Presets schreibt dessen Werte wieder.
        $is_modified = self::is_modified($mode);
        if ($tab_changed || self::get_active_id($mode) === '' || ($is_fresh && !$is_modified)) {
            $desired = self::get_active_id($mode);
            if ($desired !== '' && self::get($desired)) {
                // Zuletzt benutztes Preset dieses Tabs wiederherstellen
                self::apply($desired, $mode);
            } else {
                // Kein aktives Preset gesetzt → Tab-Standard als Fallback
                $default = self::get_default_for($mode);
                if ($default) {
                    self::apply($default, $mode);
                }
            }
        }

        update_user_meta($user_id, 'qsfw_preset_last_mode', $mode);
    }

    /**
     * Echter, frischer Seitenaufruf (nur page/mode, keine In-Page-Interaktion)?
     * Filter-Submit, Pagination, Preset-/Spalten-Aktionen tragen jeweils einen
     * dieser Keys und gelten daher NICHT als frisch.
     */
    private static function is_fresh_entry() {
        // Nur noch die Parameter, die auf einer gerenderten GET-URL überhaupt
        // stehen können. Alles andere (Spalten, Sortierung, Seitengrösse,
        // Status, colf/adv, Preset-Aktionen) läuft seit dem PRG-Umbau per POST
        // bzw. per Redirect und erreicht das Rendering nie – der Rückprall wird
        // stattdessen über QSFW_Filters::mark_prg_bounce() erkannt.
        $interaction_keys = array(
            'paged', 'cat_filter', 'stock_start_date', 'edit_preset',
        );
        foreach ($interaction_keys as $k) {
            // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- reine Erkennung einer GET-Interaktion ohne Schreibzugriff
            if (isset($_REQUEST[$k])) return false;
        }
        return true;
    }

    /**
     * Schreibt die Preset-Werte in die bestehenden User-Metas, damit der
     * komplette Render-Pfad (QSFW_Columns::get_active() etc.) unverändert
     * bleibt. Setzt zudem die aktive Preset-ID für den Tab.
     */
    public static function apply($id, $mode) {
        $mode    = ($mode === 'all') ? 'all' : 'reorder';
        $preset  = self::get($id);
        if (!$preset) return false;

        $user_id = get_current_user_id();
        $enabled = QSFW_Columns::get_enabled();

        $cols = array_values(array_intersect((array) $preset['cols'], $enabled));
        update_user_meta($user_id, 'qsfw_visible_cols', $cols);
        update_user_meta($user_id, 'qsfw_col_order',   $cols);

        $sort = QSFW_Columns::sanitize_sort($preset['sort'], $mode);
        update_user_meta($user_id, 'qsfw_sort_' . $mode, $sort);

        $pp = in_array($preset['per_page'], array('25', '50', '100', '200', 'all'), true)
            ? $preset['per_page'] : '50';
        update_user_meta($user_id, 'qsfw_per_page', $pp);

        $status = array_values(array_intersect(
            (array) $preset['status_filter'],
            array('publish', 'draft', 'private', 'pending')
        ));
        if (empty($status)) $status = array('publish', 'draft', 'private', 'pending');
        update_user_meta($user_id, 'qsfw_status_filter', $status);

        update_user_meta($user_id, 'qsfw_active_preset_' . $mode, (string) $id);

        // Hier stand ein unset()-Hack auf $_REQUEST/$_GET/$_POST, damit die
        // nachfolgenden Getter die soeben gespeicherten Metas verwenden und
        // nicht mitgeschickte Formularwerte des Filter-Forms gewinnen. Seit die
        // Getter reine Leser sind (Schreiben nur in maybe_handle_view_post()),
        // kann kein Formularwert mehr gewinnen – der Hack ist ersatzlos weg.

        return true;
    }

    /**
     * Weicht die aktuell sichtbare Ansicht vom aktiven Preset ab?
     * (rein für die Anzeige "(angepasst)" – nichts wird persistiert)
     */
    public static function is_modified($mode) {
        $mode = ($mode === 'all') ? 'all' : 'reorder';
        $id   = self::get_active_id($mode);
        if ($id === '' || $id === null) return false;
        $preset = self::get($id);
        if (!$preset) return false;

        // Effektive sichtbare optionale Spalten in Reihenfolge
        $visible = QSFW_Columns::get_visible();
        $order   = QSFW_Columns::get_order();
        $all     = QSFW_Columns::get_all();
        $ordered = array();
        foreach ($order as $k) {
            if (in_array($k, $visible, true)) $ordered[] = $k;
        }
        foreach ($all as $k => $cfg) {
            if (in_array($k, $visible, true) && !in_array($k, $ordered, true)) {
                $ordered[] = $k;
            }
        }

        // WICHTIG: Gegen die NORMALISIERTEN Preset-Werte vergleichen – exakt so,
        // wie apply() sie beim Anwenden in die Metas schreibt. Sonst bliebe die
        // Ansicht direkt nach dem Anwenden dauerhaft "(geändert)", z. B. wenn das
        // Preset eine inzwischen deaktivierte Spalte enthält oder einen leeren
        // Status (den apply() zu "alle Status" auffüllt).
        $allowed_status = array('publish', 'draft', 'private', 'pending');
        $allowed_pp     = array('25', '50', '100', '200', 'all');

        $preset_cols = array_values(array_intersect(
            (array) $preset['cols'],
            QSFW_Columns::get_enabled()
        ));
        if ($ordered !== $preset_cols) return true;

        if (QSFW_Columns::get_sort($mode) !== QSFW_Columns::sanitize_sort($preset['sort'], $mode)) {
            return true;
        }

        $pre_pp = in_array($preset['per_page'], $allowed_pp, true) ? $preset['per_page'] : '50';
        if (QSFW_Columns::get_per_page() !== $pre_pp) return true;

        $cur_status = QSFW_Columns::get_status_filter();
        $pre_status = array_values(array_intersect((array) $preset['status_filter'], $allowed_status));
        if (empty($pre_status)) $pre_status = $allowed_status;
        sort($cur_status);
        sort($pre_status);
        if ($cur_status !== $pre_status) return true;

        return false;
    }

    /* ===================================================================
     * Export / Import (JSON)
     * =================================================================== */

    /**
     * Wird via admin_init aufgerufen, bevor irgendein Output stattfindet –
     * sicher für header()/exit beim Datei-Download und für Redirect nach Import.
     */
    public static function maybe_handle_export_import() {
        if (!is_admin()) return;
        if (!current_user_can('manage_woocommerce')) return;

        // === Export ===
        if (!empty($_REQUEST['preset_export'])) {
            check_admin_referer('qsfw_preset_export');

            $payload = array(
                'version'     => 1,
                'plugin'      => 'quickstock-for-woocommerce',
                'exported_at' => gmdate('c'),
                'presets'     => self::all(),
            );

            nocache_headers();
            header('Content-Type: application/json; charset=utf-8');
            header('Content-Disposition: attachment; filename="qsfw-presets-' . gmdate('Y-m-d') . '.json"');
            echo wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
            exit;
        }

        // === Import ===
        if (!empty($_POST['preset_import_submitted'])) {
            check_admin_referer('qsfw_preset_import');

            $back = admin_url('admin.php?page=quickstock-for-woocommerce&mode=settings&settings_subtab=presets');

            // $_FILES ist nicht slashed -> KEIN wp_unslash (wuerde Windows-Temppfade
            // zerstoeren). is_uploaded_file() bleibt der eigentliche Guard.
            $tmp = isset($_FILES['preset_import_file']['tmp_name'])
                ? sanitize_text_field($_FILES['preset_import_file']['tmp_name'])
                : '';
            if ($tmp === '' || !is_uploaded_file($tmp)) {
                set_transient('qsfw_preset_import_msg', array('type' => 'error', 'text' => __('No file uploaded.', 'quickstock-for-woocommerce')), 60);
                wp_safe_redirect($back); exit;
            }

            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Upload-Temp-Datei (oben per is_uploaded_file() geprüft); WP_Filesystem ist für den PHP-Upload-Temppfad nicht zuständig
            $raw  = file_get_contents($tmp);
            $data = json_decode($raw, true);
            if (!is_array($data) || empty($data['presets']) || !is_array($data['presets'])) {
                set_transient('qsfw_preset_import_msg', array('type' => 'error', 'text' => __('Invalid preset file.', 'quickstock-for-woocommerce')), 60);
                wp_safe_redirect($back); exit;
            }

            $enabled        = QSFW_Columns::get_enabled();
            $allowed_status = array('publish', 'draft', 'private', 'pending');
            $allowed_pp     = array('25', '50', '100', '200', 'all');
            $imported       = 0;
            $existing       = self::all();

            foreach ($data['presets'] as $p) {
                if (!is_array($p)) continue;
                $name = isset($p['name']) ? sanitize_text_field($p['name']) : '';
                if ($name === '') $name = __('Untitled', 'quickstock-for-woocommerce');

                // Bei Namens-Konflikt Suffix anhängen (Import, Import 2, …)
                $base = $name;
                $i    = 1;
                while (self::find_by_name($name) !== '') {
                    $i++;
                    $name = ($i === 2)
                        ? $base . ' ' . __('(Import)', 'quickstock-for-woocommerce')
                        : sprintf('%s (%s %d)', $base, __('Import', 'quickstock-for-woocommerce'), $i);
                }

                $cols = isset($p['cols']) && is_array($p['cols']) ? array_map('sanitize_text_field', $p['cols']) : array();
                $cols = array_values(array_intersect($cols, $enabled));

                $sort = isset($p['sort']) ? sanitize_text_field($p['sort']) : '';
                $pp   = isset($p['per_page']) ? sanitize_text_field($p['per_page']) : '50';
                if (!in_array($pp, $allowed_pp, true)) $pp = '50';

                $status = isset($p['status_filter']) && is_array($p['status_filter'])
                    ? array_map('sanitize_text_field', $p['status_filter'])
                    : array();
                $status = array_values(array_intersect($status, $allowed_status));
                if (empty($status)) $status = $allowed_status;

                // Frische ID erzwingen (keine Kollision mit bestehenden Presets).
                self::save('', array(
                    'name'          => $name,
                    'cols'          => $cols,
                    'sort'          => $sort,
                    'per_page'      => $pp,
                    'status_filter' => $status,
                ));
                $imported++;
                // self::all() neu laden, damit find_by_name beim nächsten Iteration
                // den gerade importierten Namen sieht.
                $existing = self::all();
            }

            set_transient('qsfw_preset_import_msg', array(
                'type' => 'success',
                /* translators: %d is the number of presets imported from a JSON file */
                'text' => sprintf(_n('%d preset imported.', '%d presets imported.', $imported, 'quickstock-for-woocommerce'), $imported),
            ), 60);
            wp_safe_redirect($back); exit;
        }
    }
}
