<?php
namespace GSW\Core;

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

/**
 * Import configuration:
 * - list screen with all configured imports
 * - dedicated editor screen for a single import source
 */
class Mapping_Settings
{
    private const SAVE_ACTION   = 'gsw_save_import_config';
    private const DELETE_ACTION = 'gsw_delete_import_config';
    private const SAVE_NONCE    = 'gsw_save_import_config';
    private const DELETE_NONCE  = 'gsw_delete_import_config';
    private const DRAFT_TTL     = 300;

    public static function boot(): void
    {
        add_action( 'admin_init', [ self::class, 'register_option' ] );
        add_filter( 'option_page_capability_gsw_mapping_group', [ self::class, 'settings_capability' ] );

        add_action( 'admin_enqueue_scripts', [ self::class, 'enqueue_assets' ] );
        add_action( 'admin_post_' . self::SAVE_ACTION, [ self::class, 'handle_save' ] );
        add_action( 'admin_post_' . self::DELETE_ACTION, [ self::class, 'handle_delete' ] );
        add_action( 'wp_ajax_gsw_source_image_preview', [ self::class, 'ajax_source_image_preview' ] );
    }

    public static function settings_capability(): string
    {
        return function_exists( 'ditsgsw_manage_capability' ) ? ditsgsw_manage_capability() : 'manage_options';
    }

    public static function register_option(): void
    {
        register_setting(
            'gsw_mapping_group',
            'gsw_import_configs',
            [
                'type'              => 'array',
                'sanitize_callback' => [ self::class, 'sanitize_rows' ],
                'default'           => [],
            ]
        );
    }

    public static function enqueue_assets( $hook ): void
    {
        if ( ! isset( $_GET['page'] ) ) {
            return;
        }

        $page = sanitize_key( (string) $_GET['page'] );
        if ( ! in_array( $page, [ 'gsw-importer', 'gsw-importer-edit' ], true ) ) {
            return;
        }

        wp_enqueue_style(
            'gsw-mapping-settings',
            Assets::url( 'gsw-core/admin/mapping-settings.css' ),
            [],
            Assets::ver( 'gsw-core/admin/mapping-settings.css' )
        );

        wp_enqueue_script(
            'gsw-mapping-settings',
            Assets::url( 'gsw-core/admin/mapping-settings.js' ),
            [ 'jquery' ],
            Assets::ver( 'gsw-core/admin/mapping-settings.js' ),
            true
        );
    }

    public static function render_page(): void
    {
        self::render_list_page();
    }

    public static function render_list_page(): void
    {
        $rows         = self::get_rows();
        $row_count    = count( $rows );
        $can_add_more = self::can_add_more_rows( $row_count );
        $extra_actions = (string) apply_filters( 'gsw_mapping_footer_action_html', '', $row_count, $can_add_more );

        echo '<p class="description">';
        esc_html_e(
            'Manage import sources from a single overview, then open each import on its own settings page for focused configuration.',
            DITSGSW_TEXT_DOMAIN
        );
        echo '</p>';

        echo '<div class="gsw-page-actions">';
        if ( $can_add_more ) {
            echo '<a class="button button-primary" href="' . esc_url( self::editor_url() ) . '">'
                . esc_html__( 'Add import', DITSGSW_TEXT_DOMAIN )
                . '</a>';
        }

        if ( '' !== $extra_actions ) {
            echo $extra_actions; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        }
        echo '</div>';

        if ( empty( $rows ) ) {
            echo '<div class="gsw-empty-state">';
            echo '<h2>' . esc_html__( 'No import sources yet', DITSGSW_TEXT_DOMAIN ) . '</h2>';
            echo '<p>' . esc_html__(
                'Create your first import source and configure it on a separate page, without editing a full batch of rows.',
                DITSGSW_TEXT_DOMAIN
            ) . '</p>';
            if ( $can_add_more ) {
                echo '<p><a class="button button-primary" href="' . esc_url( self::editor_url() ) . '">'
                    . esc_html__( 'Create first import', DITSGSW_TEXT_DOMAIN )
                    . '</a></p>';
            }
            echo '</div>';
            return;
        }

        echo '<table class="widefat striped gsw-import-list"><thead><tr>';
        echo '<th>' . esc_html__( 'Import', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th>' . esc_html__( 'Source', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th class="gsw-col-start">' . esc_html__( 'Start row', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th>' . esc_html__( 'Options', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th class="gsw-col-actions">' . esc_html__( 'Actions', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '</tr></thead><tbody>';

        foreach ( $rows as $row ) {
            if ( ! is_array( $row ) || empty( $row['id'] ) ) {
                continue;
            }

            $id      = sanitize_text_field( (string) $row['id'] );
            $cat     = self::category_label( (int) ( $row['category'] ?? 0 ) );
            $url     = (string) ( $row['url'] ?? '' );
            $short   = self::shorten_url( $url, 64 );
            $actions = self::render_row_actions( $id );

            echo '<tr>';
            echo '<td>';
            echo '<strong>' . esc_html( $cat ) . '</strong>';
            echo '<div class="gsw-list-meta">' . esc_html__( 'Import ID:', DITSGSW_TEXT_DOMAIN ) . ' <code>' . esc_html( $id ) . '</code></div>';
            echo '</td>';
            echo '<td>';
            if ( '' !== $url ) {
                echo '<a class="gsw-source-link" href="' . esc_url( $url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html( $short ) . '</a>';
            } else {
                echo '—';
            }
            echo '</td>';
            echo '<td>' . esc_html( (string) ( (int) ( $row['start_row'] ?? 1 ) ) ) . '</td>';
            echo '<td>' . self::render_option_badges( $row ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
            echo '</td>';
            echo '<td>' . $actions . '</td>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
            echo '</tr>';
        }

        echo '</tbody></table>';
    }

    public static function editor_page_title(): string
    {
        $row_id = isset( $_GET['row_id'] ) ? sanitize_text_field( (string) $_GET['row_id'] ) : '';

        if ( '' === $row_id ) {
            return __( 'New import', DITSGSW_TEXT_DOMAIN );
        }

        return __( 'Import settings', DITSGSW_TEXT_DOMAIN );
    }

    public static function render_editor_page(): void
    {
        $row_id       = isset( $_GET['row_id'] ) ? sanitize_text_field( (string) $_GET['row_id'] ) : '';
        $rows         = self::get_rows();
        $row_count    = count( $rows );
        $can_add_more = self::can_add_more_rows( $row_count );
        $preview_page = self::current_preview_page();
        $row          = null;
        $is_new       = '' === $row_id;
        $context      = $is_new ? 'new' : $row_id;
        $draft        = self::consume_draft( $context );

        if ( $is_new && ! $can_add_more ) {
            echo '<div class="notice notice-warning inline"><p>'
                . esc_html__( 'Your current plan allows only the existing import sources. Edit an existing import or unlock more slots in PRO.', DITSGSW_TEXT_DOMAIN )
                . '</p></div>';
            echo '<p><a class="button" href="' . esc_url( self::list_url() ) . '">' . esc_html__( 'Back to import list', DITSGSW_TEXT_DOMAIN ) . '</a></p>';
            return;
        }

        if ( ! $is_new ) {
            $row = self::get_row_by_id( $row_id, $rows );
            if ( null === $row ) {
                echo '<div class="notice notice-error inline"><p>'
                    . esc_html__( 'Import configuration not found.', DITSGSW_TEXT_DOMAIN )
                    . '</p></div>';
                echo '<p><a class="button" href="' . esc_url( self::list_url() ) . '">' . esc_html__( 'Back to import list', DITSGSW_TEXT_DOMAIN ) . '</a></p>';
                return;
            }
        }

        if ( is_array( $draft ) ) {
            $row = $draft;
        } elseif ( null === $row ) {
            $row = self::default_row();
        } else {
            $row = self::merge_row_with_defaults( $row );
        }

        $can_images    = self::can_use_images();
        $can_ai        = self::can_use_ai();
        $images_reason = $can_images ? '' : self::images_disabled_reason();
        $ai_reason     = $can_ai ? '' : self::ai_disabled_reason();
        $category_name = isset( $row['category'] ) ? self::category_label( (int) $row['category'] ) : '';

        echo '<div class="gsw-editor-shell">';
        echo '<p><a class="button" href="' . esc_url( self::list_url() ) . '">' . esc_html__( 'Back to import list', DITSGSW_TEXT_DOMAIN ) . '</a></p>';

        echo '<div class="gsw-editor-toolbar">';
        if ( ! $is_new ) {
            echo '<span class="gsw-editor-chip"><strong>' . esc_html__( 'Import ID:', DITSGSW_TEXT_DOMAIN ) . '</strong> <code>' . esc_html( (string) $row['id'] ) . '</code></span>';
            if ( '' !== $category_name ) {
                echo '<span class="gsw-editor-chip"><strong>' . esc_html__( 'Category:', DITSGSW_TEXT_DOMAIN ) . '</strong> ' . esc_html( $category_name ) . '</span>';
            }
            echo '<a class="button button-secondary" href="' . esc_url( admin_url( 'admin.php?page=gsw-logs&row_id=' . rawurlencode( (string) $row['id'] ) ) ) . '">'
                . esc_html__( 'View log', DITSGSW_TEXT_DOMAIN ) . '</a>';
            echo '<a class="button button-secondary" href="' . esc_url( admin_url( 'admin.php?page=gsw-cron&row_id=' . rawurlencode( (string) $row['id'] ) ) ) . '">'
                . esc_html__( 'Schedule', DITSGSW_TEXT_DOMAIN ) . '</a>';
        }
        echo '</div>';

        if ( ! $can_images ) {
            echo '<div class="notice notice-warning inline"><p>' . esc_html( $images_reason ) . '</p></div>';
        }

        if ( ! $can_ai ) {
            echo '<div class="notice notice-warning inline"><p>' . esc_html( $ai_reason ) . '</p></div>';
        }

        echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="gsw-config-form" id="gsw-config-form">';
        wp_nonce_field( self::SAVE_NONCE );
        echo '<input type="hidden" name="action" value="' . esc_attr( self::SAVE_ACTION ) . '" />';
        echo '<input type="hidden" name="original_id" value="' . esc_attr( $is_new ? '' : (string) $row['id'] ) . '" />';
        echo '<input type="hidden" name="preview_page" value="' . esc_attr( (string) $preview_page ) . '" />';

        echo '<div class="gsw-row gsw-editor-grid" data-index="0">';

        echo '<section class="gsw-editor-card">';
        echo '<h2>' . esc_html__( 'Source', DITSGSW_TEXT_DOMAIN ) . '</h2>';
        echo '<p class="description">' . esc_html__( 'Set the base source, category, and row offset for this import.', DITSGSW_TEXT_DOMAIN ) . '</p>';
        echo '<div class="gsw-field">';
        echo '<label for="gsw-category">' . esc_html__( 'Category', DITSGSW_TEXT_DOMAIN ) . '</label>';
        echo self::dropdown_category( 'gsw_import_configs[0][category]', (int) ( $row['category'] ?? 0 ), [ 'id' => 'gsw-category' ] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
        echo '<span class="gsw-help">' . esc_html__( 'WooCommerce product category for imported items.', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</div>';
        echo '<div class="gsw-field">';
        echo '<label for="gsw-url">' . esc_html__( 'Google Sheet URL', DITSGSW_TEXT_DOMAIN ) . '</label>';
        echo '<input id="gsw-url" type="text" name="gsw_import_configs[0][url]" value="' . esc_attr( (string) ( $row['url'] ?? '' ) ) . '" placeholder="https://... (Google Sheet or CSV)" required />';
        echo '<span class="gsw-help">' . esc_html__( 'Link to Google Sheet or CSV file.', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</div>';
        echo '<div class="gsw-field">';
        echo '<label for="gsw-start-row">' . esc_html__( 'Start row', DITSGSW_TEXT_DOMAIN ) . '</label>';
        echo '<input id="gsw-start-row" type="number" min="1" name="gsw_import_configs[0][start_row]" value="' . esc_attr( (string) ( $row['start_row'] ?? 1 ) ) . '" />';
        echo '<span class="gsw-help">' . esc_html__( 'Row number to start from (1-based).', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</div>';
        echo '</section>';

        echo '<section class="gsw-editor-card">';
        echo '<h2>' . esc_html__( 'Columns', DITSGSW_TEXT_DOMAIN ) . '</h2>';
        echo '<p class="description">' . esc_html__( 'Map spreadsheet columns to the required WooCommerce product fields.', DITSGSW_TEXT_DOMAIN ) . '</p>';
        echo '<div class="gsw-inline-fields">';
        self::render_number_field( 'gsw-status-col', __( 'Status col', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][col_status]', $row['col_status'] ?? '', __( 'Status col: number of the column containing textual product status (e.g., "In stock", "Sale").', DITSGSW_TEXT_DOMAIN ) );
        self::render_number_field( 'gsw-sku-col', __( 'SKU col', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][col_sku]', $row['col_sku'] ?? '', '' );
        self::render_number_field( 'gsw-title-col', __( 'Title col', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][col_title]', $row['col_title'] ?? '', '' );
        self::render_number_field( 'gsw-description-col', __( 'Description col', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][col_description]', $row['col_description'] ?? '', '' );
        self::render_number_field( 'gsw-price-col', __( 'Price col', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][col_price]', $row['col_price'] ?? '', '' );
        echo '</div>';
        echo '</section>';

        $size_cols = isset( $row['size_cols'] ) && is_array( $row['size_cols'] ) ? array_values( $row['size_cols'] ) : [];
        $sizes_cnt = isset( $row['sizes_count'] ) ? max( 0, (int) $row['sizes_count'] ) : 0;
        echo '<section class="gsw-editor-card">';
        echo '<h2>' . esc_html__( 'Sizes and variations', DITSGSW_TEXT_DOMAIN ) . '</h2>';
        echo '<label class="gsw-toggle-row">';
        echo '<input type="checkbox" class="js-use-sizes" name="gsw_import_configs[0][use_sizes]" ' . checked( ! empty( $row['use_sizes'] ), true, false ) . ' />';
        echo '<span class="gsw-badge">' . esc_html__( 'Sizes', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</label>';
        echo '<span class="gsw-help">' . esc_html__( 'Provide columns with size names. If multiple, product becomes variable.', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '<input type="hidden" class="js-size-cols-saved" value="' . esc_attr( wp_json_encode( $size_cols ) ) . '" />';
        echo '<div class="gsw-conditional js-sizes-wrap gsw-my-6">';
        echo '<div class="gsw-field">';
        echo '<label for="gsw-sizes-count">' . esc_html__( 'Count', DITSGSW_TEXT_DOMAIN ) . '</label>';
        echo '<input id="gsw-sizes-count" type="number" min="0" max="20" class="js-sizes-count" name="gsw_import_configs[0][sizes_count]" value="' . esc_attr( (string) $sizes_cnt ) . '" />';
        echo '</div>';
        echo '<div class="js-size-cols gsw-inline-fields">';
        for ( $i = 0; $i < $sizes_cnt; $i++ ) {
            echo '<div class="gsw-field">';
            echo '<label>' . esc_html( sprintf( '#%d', $i + 1 ) ) . '</label>';
            echo '<input type="number" min="1" class="js-size-col" name="gsw_import_configs[0][size_cols][' . esc_attr( (string) $i ) . ']" value="' . esc_attr( (string) ( $size_cols[ $i ] ?? '' ) ) . '" />';
            echo '</div>';
        }
        echo '</div>';
        echo '<span class="gsw-help">' . esc_html__( 'Only the column with size name is required (status may be embedded in size text).', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</div>';
        echo '</section>';

        echo '<section class="gsw-editor-card">';
        echo '<h2>' . esc_html__( 'Pricing', DITSGSW_TEXT_DOMAIN ) . '</h2>';
        echo '<label class="gsw-toggle-row">';
        echo '<input type="checkbox" class="js-use-markup" name="gsw_import_configs[0][use_markup]" ' . checked( ! empty( $row['use_markup'] ), true, false ) . ' />';
        echo '<span class="gsw-badge">' . esc_html__( 'Markup', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</label>';
        echo '<span class="gsw-help">' . esc_html__( 'Markup: multiplier > 1.0. Example: 1.15 adds 15% to the base price before RRP constraints.', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '<div class="gsw-conditional js-markup-wrap gsw-my-6">';
        echo '<div class="gsw-inline-fields">';
        self::render_text_field( 'gsw-markup-value', __( 'Markup value', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][markup_value]', $row['markup_value'] ?? '', __( 'Markup: multiplier > 1.0. Example: 1.15 adds 15% to the base price before RRP constraints.', DITSGSW_TEXT_DOMAIN ) );
        self::render_number_field( 'gsw-rrp-col', __( 'RRP col', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][col_rrp]', $row['col_rrp'] ?? '', __( 'RRP col: may contain a single value "199.99" or a range "180-220".', DITSGSW_TEXT_DOMAIN ), 0 );
        echo '</div>';
        echo '</div>';
        echo '</section>';

        echo '<section class="gsw-editor-card">';
        echo '<h2>' . esc_html__( 'Optional modules', DITSGSW_TEXT_DOMAIN ) . '</h2>';

        echo '<label class="gsw-toggle-row">';
        echo '<input type="checkbox" class="js-use-images" name="gsw_import_configs[0][use_images]" ' . checked( ! empty( $row['use_images'] ), true, false ) . disabled( $can_images, false, false ) . ' />';
        echo '<span class="gsw-badge">' . esc_html__( 'Images', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</label>';
        echo '<span class="gsw-help">' . esc_html( $can_images ? __( 'Images: Google Drive folder must be shared publicly or with the Service Account email.', DITSGSW_TEXT_DOMAIN ) : $images_reason ) . '</span>';
        echo '<div class="gsw-conditional js-images-wrap gsw-my-6">';
        self::render_number_field( 'gsw-images-col', __( 'Folder col', DITSGSW_TEXT_DOMAIN ), 'gsw_import_configs[0][col_images]', $row['col_images'] ?? '', '' );
        echo '</div>';

        echo '<label class="gsw-toggle-row">';
        echo '<input type="checkbox" name="gsw_import_configs[0][use_ai]" ' . checked( ! empty( $row['use_ai'] ), true, false ) . disabled( $can_ai, false, false ) . ' />';
        echo '<span class="gsw-badge">' . esc_html__( 'AI integration', DITSGSW_TEXT_DOMAIN ) . '</span>';
        echo '</label>';
        echo '<span class="gsw-help">' . esc_html( $can_ai ? __( 'Generates Title / Short / Full from prompts during updates according to OpenAI settings.', DITSGSW_TEXT_DOMAIN ) : $ai_reason ) . '</span>';

        echo '<input type="hidden" name="gsw_import_configs[0][id]" value="' . esc_attr( (string) ( $row['id'] ?? '' ) ) . '" />';
        echo '</section>';

        echo '</div>';

        if ( ! $is_new ) {
            if ( self::can_use_product_routing() ) {
                do_action( 'gsw_render_product_routing_section', $row, $preview_page );
            } else {
                self::render_product_routing_promo_section( $row );
            }
        }

        self::render_editor_actions( (string) ( $row['id'] ?? '' ), $is_new, true );

        echo '</form>';
        echo '</div>';
    }

    public static function dropdown_category( string $name, int $selected = 0, array $extra = [] ): string
    {
        if ( ! taxonomy_exists( 'product_cat' ) ) {
            return '<em>' . esc_html__( 'WooCommerce not active', DITSGSW_TEXT_DOMAIN ) . '</em>';
        }

        $show_option_none  = $extra['show_option_none'] ?? __( '— Select —', DITSGSW_TEXT_DOMAIN );
        $option_none_value = $extra['option_none_value'] ?? '';

        $args = [
            'taxonomy'          => 'product_cat',
            'hide_empty'        => false,
            'name'              => $name,
            'id'                => $extra['id'] ?? '',
            'orderby'           => 'name',
            'hierarchical'      => true,
            'show_option_none'  => $show_option_none,
            'option_none_value' => $option_none_value,
            'echo'              => 0,
            'selected'          => $selected,
            'required'          => isset( $extra['required'] ) ? (bool) $extra['required'] : true,
        ];

        return wp_dropdown_categories( $args );
    }

    public static function sanitize_rows( $in ): array
    {
        [ $rows, $errors ] = self::validate_rows( $in );

        if ( ! empty( $errors ) ) {
            foreach ( $errors as $error ) {
                add_settings_error( 'gsw_import_configs', 'gsw_import_configs_error', $error, 'error' );
            }
            return self::get_rows();
        }

        return $rows;
    }

    public static function handle_save(): void
    {
        if ( ! ditsgsw_current_user_can_manage() ) {
            wp_die( esc_html__( 'Unauthorized', DITSGSW_TEXT_DOMAIN ) );
        }

        check_admin_referer( self::SAVE_NONCE );

        $rows        = self::get_rows();
        $row_count   = count( $rows );
        $original_id = isset( $_POST['original_id'] ) ? sanitize_text_field( (string) $_POST['original_id'] ) : '';
        $preview_page = self::current_preview_page();
        $is_new      = '' === $original_id;

        if ( $is_new && ! self::can_add_more_rows( $row_count ) ) {
            self::flash_notice( 'error', __( 'Your current plan does not allow additional import sources.', DITSGSW_TEXT_DOMAIN ) );
            wp_safe_redirect( self::list_url() );
            exit;
        }

        if ( ! $is_new && null === self::find_row_index( $rows, $original_id ) ) {
            self::flash_notice( 'error', __( 'Import configuration not found.', DITSGSW_TEXT_DOMAIN ) );
            wp_safe_redirect( self::list_url() );
            exit;
        }

        $raw_row = self::posted_row();
        if ( ! $is_new ) {
            $raw_row['id'] = $original_id;
        } else {
            unset( $raw_row['id'] );
        }

        [ $validated, $errors ] = self::validate_rows( [ $raw_row ] );
        if ( ! empty( $errors ) || empty( $validated ) ) {
            self::store_draft( $is_new ? 'new' : $original_id, $raw_row );
            self::flash_notice( 'error', implode( ' ', $errors ?: [ __( 'Please review the import settings and try again.', DITSGSW_TEXT_DOMAIN ) ] ) );
            wp_safe_redirect( self::editor_url( $original_id, true, [ 'preview_page' => $preview_page ] ) );
            exit;
        }

        $row = $validated[0];
        if ( ! $is_new ) {
            $row['id'] = $original_id;
            $row_index = self::find_row_index( $rows, $original_id );
            if ( null === $row_index ) {
                self::flash_notice( 'error', __( 'Import configuration not found.', DITSGSW_TEXT_DOMAIN ) );
                wp_safe_redirect( self::list_url() );
                exit;
            }
            $rows[ $row_index ] = $row;
        } else {
            $rows[] = $row;
        }

        update_option( 'gsw_import_configs', array_values( $rows ), false );
        self::clear_draft( $is_new ? 'new' : $original_id );

        $saved_id = (string) $row['id'];
        if ( ! $is_new && self::can_use_product_routing() ) {
            do_action( 'gsw_save_product_routing', $saved_id, $_POST['gsw_category_overrides'] ?? [] );
        }
        self::flash_notice( 'success', __( 'Import configuration saved.', DITSGSW_TEXT_DOMAIN ) );
        wp_safe_redirect( self::editor_url( $saved_id, false, [ 'preview_page' => $preview_page ] ) );
        exit;
    }

    public static function handle_delete(): void
    {
        if ( ! ditsgsw_current_user_can_manage() ) {
            wp_die( esc_html__( 'Unauthorized', DITSGSW_TEXT_DOMAIN ) );
        }

        $id = isset( $_GET['id'] ) ? sanitize_text_field( (string) $_GET['id'] ) : '';
        if ( '' === $id || ! check_admin_referer( self::DELETE_NONCE . '_' . $id ) ) {
            wp_die( esc_html__( 'Bad request', DITSGSW_TEXT_DOMAIN ) );
        }

        $rows      = self::get_rows();
        $row_index = self::find_row_index( $rows, $id );
        if ( null === $row_index ) {
            self::flash_notice( 'error', __( 'Import configuration not found.', DITSGSW_TEXT_DOMAIN ) );
            wp_safe_redirect( self::list_url() );
            exit;
        }

        unset( $rows[ $row_index ] );
        update_option( 'gsw_import_configs', array_values( $rows ), false );

        self::cleanup_deleted_import( $id );
        self::clear_draft( $id );
        self::flash_notice( 'success', __( 'Import configuration deleted.', DITSGSW_TEXT_DOMAIN ) );
        wp_safe_redirect( self::list_url() );
        exit;
    }

    public static function ajax_source_image_preview(): void
    {
        if ( ! ditsgsw_current_user_can_manage() ) {
            wp_die( '', '', [ 'response' => 403 ] );
        }

        check_ajax_referer( 'gsw_source_image_preview', 'nonce' );

        $row_id = isset( $_GET['row_id'] ) ? sanitize_text_field( (string) $_GET['row_id'] ) : '';
        $folder = isset( $_GET['folder'] ) ? trim( wp_unslash( (string) $_GET['folder'] ) ) : '';

        if ( '' === $folder ) {
            wp_die( '', '', [ 'response' => 400 ] );
        }

        if ( Image_Import::output_preview_image( $folder, $row_id ) ) {
            exit;
        }

        wp_die( '', '', [ 'response' => 404 ] );
    }

    protected static function validate_rows( $in ): array
    {
        $out = [];

        if ( ! is_array( $in ) ) {
            return [ [], [ __( 'Invalid import data received.', DITSGSW_TEXT_DOMAIN ) ] ];
        }

        $can_images = self::can_use_images();
        $can_ai     = self::can_use_ai();
        $errors     = [];

        foreach ( $in as $idx => $row ) {
            if ( ! is_array( $row ) ) {
                continue;
            }

            $label = count( $in ) > 1
                ? sprintf( __( 'Row %d', DITSGSW_TEXT_DOMAIN ), (int) $idx + 1 )
                : __( 'Import', DITSGSW_TEXT_DOMAIN );

            $category = isset( $row['category'] ) ? (int) $row['category'] : 0;
            if ( $category <= 0 ) {
                $errors[] = sprintf( __( '%s: Category is required.', DITSGSW_TEXT_DOMAIN ), $label );
            } elseif ( ! term_exists( $category, 'product_cat' ) ) {
                $errors[] = sprintf( __( '%s: Selected category does not exist.', DITSGSW_TEXT_DOMAIN ), $label );
            }

            $url = isset( $row['url'] ) ? esc_url_raw( trim( (string) $row['url'] ) ) : '';
            if ( '' === $url ) {
                $errors[] = sprintf( __( '%s: Google Sheet URL is required.', DITSGSW_TEXT_DOMAIN ), $label );
            }

            $start_row = isset( $row['start_row'] ) ? max( 1, (int) $row['start_row'] ) : 1;

            $col_status      = self::sanitize_positive_int( $row['col_status'] ?? 0 );
            $col_sku         = self::sanitize_positive_int( $row['col_sku'] ?? 0 );
            $col_title       = self::sanitize_positive_int( $row['col_title'] ?? 0 );
            $col_description = self::sanitize_positive_int( $row['col_description'] ?? 0 );
            $col_price       = self::sanitize_positive_int( $row['col_price'] ?? 0 );

            $required_columns = [
                'Status col'      => $col_status,
                'SKU col'         => $col_sku,
                'Title col'       => $col_title,
                'Description col' => $col_description,
                'Price col'       => $col_price,
            ];
            foreach ( $required_columns as $column_label => $column_value ) {
                if ( $column_value <= 0 ) {
                    $errors[] = sprintf(
                        __( '%1$s: %2$s must be a positive column index.', DITSGSW_TEXT_DOMAIN ),
                        $label,
                        $column_label
                    );
                }
            }

            $use_markup   = ! empty( $row['use_markup'] );
            $markup_value = isset( $row['markup_value'] ) ? (float) str_replace( ',', '.', (string) $row['markup_value'] ) : 0.0;
            if ( $use_markup && $markup_value <= 1.0 ) {
                $errors[] = sprintf( __( '%s: Markup value must be greater than 1.0.', DITSGSW_TEXT_DOMAIN ), $label );
            }
            if ( ! $use_markup ) {
                $markup_value = 0.0;
            }

            $col_rrp = isset( $row['col_rrp'] ) ? max( 0, (int) $row['col_rrp'] ) : 0;

            $use_sizes    = ! empty( $row['use_sizes'] );
            $sizes_count  = $use_sizes ? max( 0, min( 20, (int) ( $row['sizes_count'] ?? 0 ) ) ) : 0;
            $raw_size_cols = isset( $row['size_cols'] ) && is_array( $row['size_cols'] ) ? $row['size_cols'] : [];
            $size_cols     = [];

            if ( $use_sizes ) {
                if ( $sizes_count <= 0 ) {
                    $errors[] = sprintf( __( '%s: Sizes count must be at least 1 when sizes are enabled.', DITSGSW_TEXT_DOMAIN ), $label );
                }

                for ( $i = 0; $i < $sizes_count; $i++ ) {
                    $col = self::sanitize_positive_int( $raw_size_cols[ $i ] ?? 0 );
                    if ( $col <= 0 ) {
                        $errors[] = sprintf( __( '%1$s: Size col #%2$d is invalid.', DITSGSW_TEXT_DOMAIN ), $label, $i + 1 );
                        continue;
                    }
                    $size_cols[] = $col;
                }

                $sizes_count = count( $size_cols );
            }

            $use_images = $can_images ? ! empty( $row['use_images'] ) : false;
            $col_images = $use_images ? self::sanitize_positive_int( $row['col_images'] ?? 0 ) : 0;
            if ( $use_images && $col_images <= 0 ) {
                $errors[] = sprintf( __( '%s: Folder col must be a positive column index when images are enabled.', DITSGSW_TEXT_DOMAIN ), $label );
            }

            $use_ai = $can_ai ? ! empty( $row['use_ai'] ) : false;

            $id = isset( $row['id'] ) && is_string( $row['id'] ) && '' !== $row['id']
                ? sanitize_text_field( $row['id'] )
                : wp_unique_id( 'gsw_' );

            $out[] = [
                'id'              => $id,
                'category'        => max( 0, $category ),
                'url'             => $url,
                'start_row'       => $start_row,
                'col_status'      => $col_status,
                'col_sku'         => $col_sku,
                'col_title'       => $col_title,
                'col_description' => $col_description,
                'col_price'       => $col_price,
                'use_markup'      => $use_markup,
                'markup_value'    => $markup_value,
                'col_rrp'         => $col_rrp,
                'use_sizes'       => $use_sizes,
                'sizes_count'     => $sizes_count,
                'size_cols'       => $size_cols,
                'use_images'      => $use_images,
                'col_images'      => $col_images,
                'use_ai'          => $use_ai,
            ];
        }

        return [ $out, $errors ];
    }

    protected static function can_use_images(): bool
    {
        $g = (array) get_option( 'gsw_google', [] );
        $has_consent = ! empty( $g['consent'] );
        $has_json    = trim( (string) ( $g['service_json'] ?? '' ) ) !== '';
        return $has_consent && $has_json;
    }

    protected static function can_use_ai(): bool
    {
        $o = (array) get_option( 'gsw_openai', [] );
        $has_consent = ! empty( $o['consent'] );
        $has_key     = trim( (string) ( $o['api_key'] ?? '' ) ) !== '';
        $has_model   = trim( (string) ( $o['model'] ?? '' ) ) !== '';
        return $has_consent && $has_key && $has_model;
    }

    protected static function can_use_product_routing(): bool
    {
        return (bool) apply_filters( 'gsw_can_use_product_routing', false );
    }

    protected static function images_disabled_reason(): string
    {
        $g = (array) get_option( 'gsw_google', [] );
        if ( empty( $g['consent'] ) ) {
            return __( 'Disabled: please accept Google Drive consent in Settings → Google API.', DITSGSW_TEXT_DOMAIN );
        }
        if ( trim( (string) ( $g['service_json'] ?? '' ) ) === '' ) {
            return __( 'Disabled: please provide Service Account JSON in Settings → Google API.', DITSGSW_TEXT_DOMAIN );
        }
        return '';
    }

    protected static function ai_disabled_reason(): string
    {
        $o = (array) get_option( 'gsw_openai', [] );
        if ( empty( $o['consent'] ) ) {
            return __( 'Disabled: please accept OpenAI consent in Settings → OpenAI.', DITSGSW_TEXT_DOMAIN );
        }
        if ( trim( (string) ( $o['api_key'] ?? '' ) ) === '' ) {
            return __( 'Disabled: please provide OpenAI API key in Settings → OpenAI.', DITSGSW_TEXT_DOMAIN );
        }
        if ( trim( (string) ( $o['model'] ?? '' ) ) === '' ) {
            return __( 'Disabled: please select OpenAI model in Settings → OpenAI.', DITSGSW_TEXT_DOMAIN );
        }
        return '';
    }

    protected static function list_url(): string
    {
        return admin_url( 'admin.php?page=gsw-importer' );
    }

    protected static function current_preview_page(): int
    {
        $raw_page = $_POST['preview_page'] ?? $_GET['preview_page'] ?? 1;
        return max( 1, (int) $raw_page );
    }

    protected static function editor_url( string $row_id = '', bool $with_draft = false, array $extra_args = [] ): string
    {
        $args = [ 'page' => 'gsw-importer-edit' ];
        if ( '' !== $row_id ) {
            $args['row_id'] = $row_id;
        }
        if ( $with_draft ) {
            $args['draft'] = 1;
        }

        foreach ( $extra_args as $key => $value ) {
            if ( null === $value || '' === $value ) {
                continue;
            }

            $args[ $key ] = $value;
        }

        return add_query_arg( $args, admin_url( 'admin.php' ) );
    }

    protected static function render_option_badges( array $row ): string
    {
        $badges = [];

        if ( ! empty( $row['use_markup'] ) ) {
            $badges[] = __( 'Markup', DITSGSW_TEXT_DOMAIN );
        }
        if ( ! empty( $row['use_sizes'] ) ) {
            $badges[] = __( 'Sizes', DITSGSW_TEXT_DOMAIN );
        }
        if ( ! empty( $row['use_images'] ) ) {
            $badges[] = __( 'Images', DITSGSW_TEXT_DOMAIN );
        }
        if ( ! empty( $row['use_ai'] ) ) {
            $badges[] = __( 'AI integration', DITSGSW_TEXT_DOMAIN );
        }

        if ( empty( $badges ) ) {
            $badges[] = __( 'Basic import', DITSGSW_TEXT_DOMAIN );
        }

        $html = [];
        foreach ( $badges as $badge ) {
            $html[] = '<span class="gsw-pill">' . esc_html( $badge ) . '</span>';
        }

        return implode( ' ', $html );
    }

    protected static function render_product_routing_promo_section( array $row ): void
    {
        $row_id = isset( $row['id'] ) ? sanitize_text_field( (string) $row['id'] ) : '';
        if ( '' === $row_id ) {
            return;
        }

        $preview = Source_Preview::preview_rows( $row, $row_id, 12, 1 );
        $pro_url = ditsgsw_get_pro_url();

        echo '<section class="gsw-editor-card gsw-editor-card-full">';
        echo '<h2>' . esc_html__( 'Product routing preview', DITSGSW_TEXT_DOMAIN ) . '</h2>';
        echo '<p class="description">' . esc_html__(
            'Free version shows a read-only preview of available source items. Upgrade to PRO to assign categories per SKU, attach multiple categories to the same product, and exclude broken items from future imports.',
            DITSGSW_TEXT_DOMAIN
        ) . '</p>';
        echo '<p class="gsw-help">';
        printf(
            /* translators: %s: default category path */
            esc_html__( 'Default category for items without an override: %s', DITSGSW_TEXT_DOMAIN ),
            esc_html( (string) ( $preview['default_cat'] ?? __( '(no category)', DITSGSW_TEXT_DOMAIN ) ) )
        );
        echo '</p>';
        echo '<p><a class="button button-secondary" href="' . esc_url( $pro_url ) . '" target="_blank" rel="noopener noreferrer">' . esc_html__( 'Learn more about PRO', DITSGSW_TEXT_DOMAIN ) . '</a></p>';

        if ( ! empty( $preview['error'] ) ) {
            echo '<div class="notice notice-warning inline"><p>';
            printf(
                /* translators: %s: preview fetch error */
                esc_html__( 'Preview is unavailable right now: %s', DITSGSW_TEXT_DOMAIN ),
                esc_html( (string) $preview['error'] )
            );
            echo '</p></div>';
            echo '</section>';
            return;
        }

        if ( empty( $preview['items'] ) ) {
            echo '<div class="gsw-empty-state gsw-empty-state-inline">';
            echo '<h3>' . esc_html__( 'No available products found', DITSGSW_TEXT_DOMAIN ) . '</h3>';
            echo '<p>' . esc_html__(
                'Sold or unknown-status rows are hidden from this preview. Save the import settings and check that your status column matches the feed values if you expected products here.',
                DITSGSW_TEXT_DOMAIN
            ) . '</p>';
            echo '</div>';
            echo '</section>';
            return;
        }

        echo '<div class="gsw-preview-toolbar">';
        echo '<div class="gsw-preview-toolbar-meta">';
        printf(
            /* translators: 1: shown preview rows, 2: total matched rows */
            esc_html__( 'Showing %1$d of %2$d available products from the source.', DITSGSW_TEXT_DOMAIN ),
            (int) ( $preview['shown'] ?? 0 ),
            (int) ( $preview['matched'] ?? 0 )
        );
        echo '<span class="gsw-help gsw-help-inline">';
        echo esc_html__( 'Read-only preview in free. Upgrade to PRO to add manual routing, multiple categories, and import exclusions.', DITSGSW_TEXT_DOMAIN );
        echo '</span>';
        echo '</div>';
        echo '</div>';

        echo '<div class="gsw-preview-table-wrap">';
        echo '<table class="widefat striped gsw-preview-table"><thead><tr>';
        echo '<th class="gsw-preview-thumb-col">' . esc_html__( 'Image', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th class="gsw-preview-sku-col">' . esc_html__( 'SKU', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th>' . esc_html__( 'Title', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th class="gsw-preview-status-col">' . esc_html__( 'Status', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '<th>' . esc_html__( 'Import target category', DITSGSW_TEXT_DOMAIN ) . '</th>';
        echo '</tr></thead><tbody>';

        foreach ( $preview['items'] as $item ) {
            if ( ! is_array( $item ) ) {
                continue;
            }

            $thumb_url          = (string) ( $item['thumbnail_url'] ?? '' );
            $lightbox_url       = (string) ( $item['lightbox_url'] ?? $thumb_url );
            $has_thumbnail      = ! empty( $item['has_thumbnail'] );
            $has_source_preview = ! empty( $item['has_source_preview'] );
            $can_open_lightbox  = ! empty( $item['can_open_lightbox'] );
            $default_display    = sprintf(
                /* translators: %s: default category path */
                __( 'Default: %s', DITSGSW_TEXT_DOMAIN ),
                (string) ( $item['default_term_label'] ?? __( '(no category)', DITSGSW_TEXT_DOMAIN ) )
            );
            $actual_categories = (string) ( $item['product_categories_label'] ?? '' );
            $status_class      = 'is-' . sanitize_html_class( (string) ( $item['status'] ?? 'unknown' ) );
            $lightbox_title    = trim( (string) ( $item['sku'] ?? '' ) . ' ' . (string) ( $item['title'] ?? '' ) );

            echo '<tr class="gsw-preview-row" data-sku="' . esc_attr( (string) ( $item['sku'] ?? '' ) ) . '">';
            echo '<td class="gsw-preview-thumb-col">';
            if ( $can_open_lightbox && '' !== $thumb_url ) {
                echo '<a class="gsw-preview-thumb-link js-gsw-preview-lightbox" href="' . esc_url( '' !== $lightbox_url ? $lightbox_url : $thumb_url ) . '" data-lightbox-title="' . esc_attr( $lightbox_title ) . '">';
                echo '<img src="' . esc_url( $thumb_url ) . '" alt="' . esc_attr( (string) ( $item['title'] ?? '' ) ) . '" loading="lazy" />';
                echo '</a>';
            } elseif ( '' !== $thumb_url ) {
                echo '<img src="' . esc_url( $thumb_url ) . '" alt="" loading="lazy" />';
            } else {
                echo '<span class="gsw-preview-thumb-placeholder">-</span>';
            }
            echo '</td>';
            echo '<td class="gsw-preview-sku-col"><strong>' . esc_html( (string) ( $item['sku'] ?? '' ) ) . '</strong></td>';
            echo '<td>';
            echo '<strong>' . esc_html( (string) ( $item['title'] ?? '' ) ) . '</strong>';
            echo '<div class="gsw-list-meta">';
            printf(
                /* translators: %d: source spreadsheet row number */
                esc_html__( 'Source row: %d', DITSGSW_TEXT_DOMAIN ),
                (int) ( $item['row_index'] ?? 0 )
            );
            echo '</div>';
            echo '</td>';
            echo '<td class="gsw-preview-status-col"><span class="gsw-preview-status ' . esc_attr( $status_class ) . '">' . esc_html( (string) ( $item['status_label'] ?? '' ) ) . '</span></td>';
            echo '<td>';
            echo '<strong>' . esc_html( $default_display ) . '</strong>';
            if ( '' !== $actual_categories ) {
                echo '<div class="gsw-help">';
                printf(
                    /* translators: %s: current WooCommerce categories */
                    esc_html__( 'Current Woo categories: %s', DITSGSW_TEXT_DOMAIN ),
                    esc_html( $actual_categories )
                );
                echo '</div>';
            } else {
                echo '<div class="gsw-help">' . esc_html__( 'Product has not been imported into WooCommerce yet.', DITSGSW_TEXT_DOMAIN ) . '</div>';
            }
            if ( ! $has_thumbnail && $has_source_preview ) {
                echo '<div class="gsw-help">' . esc_html__( 'Preview image is loaded directly from the source folder before import.', DITSGSW_TEXT_DOMAIN ) . '</div>';
            }
            echo '</td>';
            echo '</tr>';
        }

        echo '</tbody></table>';
        echo '</div>';
        echo '<div id="gsw-preview-lightbox" class="gsw-preview-lightbox" hidden>';
        echo '<div class="gsw-preview-lightbox-backdrop js-gsw-preview-lightbox-close"></div>';
        echo '<div class="gsw-preview-lightbox-dialog" role="dialog" aria-modal="true" aria-label="' . esc_attr__( 'Product image preview', DITSGSW_TEXT_DOMAIN ) . '">';
        echo '<button type="button" class="button-link gsw-preview-lightbox-close js-gsw-preview-lightbox-close" aria-label="' . esc_attr__( 'Close image preview', DITSGSW_TEXT_DOMAIN ) . '">&times;</button>';
        echo '<img class="gsw-preview-lightbox-image" src="" alt="" />';
        echo '<p class="gsw-preview-lightbox-caption"></p>';
        echo '</div>';
        echo '</div>';
        echo '</section>';
    }

    protected static function render_editor_actions( string $row_id, bool $is_new, bool $sticky = false ): void
    {
        $classes = [ 'gsw-editor-actions' ];
        if ( $sticky ) {
            $classes[] = 'gsw-editor-actions--sticky';
        }

        echo '<div class="' . esc_attr( implode( ' ', $classes ) ) . '">';
        submit_button( __( 'Save', DITSGSW_TEXT_DOMAIN ), 'primary', 'submit', false );
        echo '<a class="button button-secondary" href="' . esc_url( self::list_url() ) . '">' . esc_html__( 'Back to import list', DITSGSW_TEXT_DOMAIN ) . '</a>';
        if ( ! $is_new && '' !== $row_id ) {
            $delete_url = wp_nonce_url(
                admin_url( 'admin-post.php?action=' . self::DELETE_ACTION . '&id=' . rawurlencode( $row_id ) ),
                self::DELETE_NONCE . '_' . $row_id
            );
            echo '<a class="button button-link-delete" href="' . esc_url( $delete_url ) . '" onclick="return confirm(\'' . esc_js( __( 'Delete this import configuration?', DITSGSW_TEXT_DOMAIN ) ) . '\');">'
                . esc_html__( 'Delete import', DITSGSW_TEXT_DOMAIN )
                . '</a>';
        }
        echo '</div>';
    }

    protected static function render_row_actions( string $id ): string
    {
        $edit_url   = self::editor_url( $id );
        $log_url    = admin_url( 'admin.php?page=gsw-logs&row_id=' . rawurlencode( $id ) );
        $cron_url   = admin_url( 'admin.php?page=gsw-cron&row_id=' . rawurlencode( $id ) );
        $delete_url = wp_nonce_url(
            admin_url( 'admin-post.php?action=' . self::DELETE_ACTION . '&id=' . rawurlencode( $id ) ),
            self::DELETE_NONCE . '_' . $id
        );

        return '<div class="gsw-actions-inline">'
            . '<a class="button button-primary" href="' . esc_url( $edit_url ) . '">' . esc_html__( 'Edit', DITSGSW_TEXT_DOMAIN ) . '</a>'
            . '<a class="button button-secondary" href="' . esc_url( $log_url ) . '">' . esc_html__( 'Log', DITSGSW_TEXT_DOMAIN ) . '</a>'
            . '<a class="button button-secondary" href="' . esc_url( $cron_url ) . '">' . esc_html__( 'Schedule', DITSGSW_TEXT_DOMAIN ) . '</a>'
            . '<a class="button button-link-delete" href="' . esc_url( $delete_url ) . '" onclick="return confirm(\'' . esc_js( __( 'Delete this import configuration?', DITSGSW_TEXT_DOMAIN ) ) . '\');">' . esc_html__( 'Delete', DITSGSW_TEXT_DOMAIN ) . '</a>'
            . '</div>';
    }

    protected static function render_number_field( string $id, string $label, string $name, $value, string $help, int $min = 1 ): void
    {
        echo '<div class="gsw-field">';
        echo '<label for="' . esc_attr( $id ) . '">' . esc_html( $label ) . '</label>';
        echo '<input id="' . esc_attr( $id ) . '" type="number" min="' . esc_attr( (string) $min ) . '" class="gsw-data" name="' . esc_attr( $name ) . '" value="' . esc_attr( (string) $value ) . '" />';
        if ( '' !== $help ) {
            echo '<span class="gsw-help">' . esc_html( $help ) . '</span>';
        }
        echo '</div>';
    }

    protected static function render_text_field( string $id, string $label, string $name, $value, string $help ): void
    {
        echo '<div class="gsw-field">';
        echo '<label for="' . esc_attr( $id ) . '">' . esc_html( $label ) . '</label>';
        echo '<input id="' . esc_attr( $id ) . '" type="text" class="gsw-data" name="' . esc_attr( $name ) . '" value="' . esc_attr( (string) $value ) . '" />';
        if ( '' !== $help ) {
            echo '<span class="gsw-help">' . esc_html( $help ) . '</span>';
        }
        echo '</div>';
    }

    protected static function sanitize_positive_int( $value ): int
    {
        $value = (int) $value;
        return $value > 0 ? $value : 0;
    }

    protected static function get_rows(): array
    {
        $rows = get_option( 'gsw_import_configs', [] );
        return is_array( $rows ) ? array_values( $rows ) : [];
    }

    protected static function get_row_by_id( string $row_id, ?array $rows = null ): ?array
    {
        $rows = is_array( $rows ) ? $rows : self::get_rows();
        foreach ( $rows as $row ) {
            if ( is_array( $row ) && ! empty( $row['id'] ) && (string) $row['id'] === $row_id ) {
                return self::merge_row_with_defaults( $row );
            }
        }

        return null;
    }

    protected static function find_row_index( array $rows, string $row_id ): ?int
    {
        foreach ( $rows as $index => $row ) {
            if ( is_array( $row ) && ! empty( $row['id'] ) && (string) $row['id'] === $row_id ) {
                return (int) $index;
            }
        }

        return null;
    }

    protected static function default_row(): array
    {
        return [
            'id'              => '',
            'category'        => 0,
            'url'             => '',
            'start_row'       => 1,
            'col_status'      => '',
            'col_sku'         => '',
            'col_title'       => '',
            'col_description' => '',
            'col_price'       => '',
            'use_markup'      => 0,
            'markup_value'    => '',
            'col_rrp'         => '',
            'use_sizes'       => 0,
            'sizes_count'     => 0,
            'size_cols'       => [],
            'use_images'      => 0,
            'col_images'      => '',
            'use_ai'          => 0,
        ];
    }

    protected static function merge_row_with_defaults( array $row ): array
    {
        $row['size_cols'] = isset( $row['size_cols'] ) && is_array( $row['size_cols'] ) ? array_values( $row['size_cols'] ) : [];
        return array_merge( self::default_row(), $row );
    }

    protected static function category_label( int $category_id ): string
    {
        if ( $category_id <= 0 || ! taxonomy_exists( 'product_cat' ) ) {
            return __( '(no category)', DITSGSW_TEXT_DOMAIN );
        }

        $term = get_term( $category_id, 'product_cat' );
        if ( $term && ! is_wp_error( $term ) ) {
            return (string) $term->name;
        }

        return '#' . $category_id;
    }

    protected static function shorten_url( string $url, int $max = 80 ): string
    {
        if ( mb_strlen( $url ) <= $max ) {
            return $url;
        }

        return mb_substr( $url, 0, $max - 1 ) . '…';
    }

    protected static function can_add_more_rows( int $row_count ): bool
    {
        $default = $row_count < 1;
        return (bool) apply_filters( 'gsw_mapping_can_add_more_rows', $default, $row_count );
    }

    protected static function posted_row(): array
    {
        $posted = $_POST['gsw_import_configs'] ?? [];
        if ( ! is_array( $posted ) ) {
            return self::default_row();
        }

        $row = $posted[0] ?? [];
        return is_array( $row ) ? $row : self::default_row();
    }

    protected static function draft_key( string $context ): string
    {
        $safe_context = sanitize_key( $context ?: 'new' );
        return 'gsw_import_draft_' . get_current_user_id() . '_' . $safe_context;
    }

    protected static function store_draft( string $context, array $row ): void
    {
        set_transient( self::draft_key( $context ), $row, self::DRAFT_TTL );
    }

    protected static function consume_draft( string $context ): ?array
    {
        $use_draft = isset( $_GET['draft'] ) && '1' === (string) $_GET['draft'];
        if ( ! $use_draft ) {
            return null;
        }

        $key   = self::draft_key( $context );
        $draft = get_transient( $key );
        delete_transient( $key );

        return is_array( $draft ) ? self::merge_row_with_defaults( $draft ) : null;
    }

    protected static function clear_draft( string $context ): void
    {
        delete_transient( self::draft_key( $context ) );
    }

    protected static function flash_notice( string $type, string $message ): void
    {
        set_transient( 'gsw_notice_' . get_current_user_id(), $type . '|' . $message, 60 );
    }

    protected static function cleanup_deleted_import( string $row_id ): void
    {
        while ( $ts = wp_next_scheduled( Imports::HOOK_RUN, [ $row_id ] ) ) {
            wp_unschedule_event( $ts, Imports::HOOK_RUN, [ $row_id ] );
        }
        while ( $ts = wp_next_scheduled( Imports::HOOK_RUN_NOW, [ $row_id ] ) ) {
            wp_unschedule_event( $ts, Imports::HOOK_RUN_NOW, [ $row_id ] );
        }

        $state = (array) get_option( 'gsw_import_state', [] );
        if ( isset( $state[ $row_id ] ) ) {
            unset( $state[ $row_id ] );
            update_option( 'gsw_import_state', $state, false );
        }

        $rules = (array) get_option( 'gsw_cron_rules', [] );
        if ( isset( $rules[ $row_id ] ) ) {
            unset( $rules[ $row_id ] );
            update_option( 'gsw_cron_rules', $rules, false );
        }

        do_action( 'gsw_delete_product_routing', $row_id );
    }
}
