CountyCollective Reference

CSV Tools Reference

Original CSV tools reference copy, including removed map stat-gathering logic.

Back to CountyCollective Reference

wordpress-plugin/includes/csv-tools/class-cgop-csv-tools.php

<?php
/**
 * County GOP Core — CSV export, import, and wipe tools.
 *
 * Extracted from theme/cgop-theme/inc/leadership-csv-tools.php in Pass 09A.
 * Moved from Representatives (Leadership CPT) admin menu to GOP Setup → CSV Tools.
 *
 * Page slug, admin-post action names, nonce strings, helper function names, and
 * stored identifiers are all frozen — do not rename without explicit owner approval.
 *
 * @package CountyGOPCore
 */

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

// ---------------------------------------------------------------------------
// Admin menu
// ---------------------------------------------------------------------------

function CGOP_csv_tools_add_menu() {
	add_submenu_page(
		CGOP_SETUP_MENU_SLUG,
		__( 'CSV Tools', 'county-gop-core' ),
		__( 'CSV Tools', 'county-gop-core' ),
		CGOP_POSITION_KEYS_CAP,
		'cgop-csv-tools',
		'CGOP_csv_tools_screen'
	);
}
add_action( 'admin_menu', 'CGOP_csv_tools_add_menu' );

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function CGOP_csv_tools_admin_url( array $args = array() ) {
	return add_query_arg( $args, admin_url( 'admin.php?page=cgop-csv-tools' ) );
}

/**
 * Redirect the legacy Representatives → CSV Tools URL to the new GOP Setup location.
 *
 * Old URL: edit.php?post_type=representative&page=cgop-csv-tools
 * New URL: admin.php?page=cgop-csv-tools
 */
function CGOP_csv_tools_redirect_legacy() {
	global $pagenow;
	if ( 'edit.php' !== $pagenow ) {
		return;
	}
	$post_type = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : '';
	$page      = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
	if ( 'representative' === $post_type && 'cgop-csv-tools' === $page ) {
		wp_safe_redirect( admin_url( 'admin.php?page=cgop-csv-tools' ), 301 );
		exit;
	}
}
add_action( 'admin_init', 'CGOP_csv_tools_redirect_legacy' );

/**
 * Return counts for the status panel.
 *
 * @return array { position_keys, rep_records, rep_only, multi_section }
 */
function CGOP_csv_tools_get_counts() {
	global $wpdb;
	$table = CGOP_position_keys_table();
	// County-scoped count when column exists.
	if ( function_exists( 'cgop_get_table_county_scope' ) ) {
		$_pk_scope = cgop_get_table_county_scope( $table );
		if ( null === $_pk_scope ) {
			$position_keys = 0;
		} elseif ( false !== $_pk_scope ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$position_keys = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `{$table}` WHERE county_id = %s", $_pk_scope ) );
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$position_keys = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
		}
	} else {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$position_keys = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
	}

	$rep_query = new WP_Query( array(
		'post_type'              => 'representative',
		'post_status'            => array( 'publish', 'draft', 'private' ),
		'posts_per_page'         => -1,
		'fields'                 => 'ids',
		'no_found_rows'          => true,
		'update_post_meta_cache' => true,
		'update_post_term_cache' => false,
		'meta_query'             => array(
			array(
				'key'     => 'leadership_sections',
				'value'   => '"representatives"',
				'compare' => 'LIKE',
			),
		),
	) );

	$rep_records   = 0;
	$rep_only      = 0;
	$multi_section = 0;

	foreach ( $rep_query->posts as $post_id ) {
		$sections = CGOP_csv_tools_get_sections( $post_id );
		$rep_records++;
		if ( count( $sections ) <= 1 ) {
			$rep_only++;
		} else {
			$multi_section++;
		}
	}

	$overlay_count = 0;
	if ( function_exists( 'CGOP_overlay_table' ) ) {
		$ov_table = CGOP_overlay_table();
		if ( function_exists( 'cgop_get_table_county_scope' ) ) {
			$_ov_scope = cgop_get_table_county_scope( $ov_table );
			if ( null === $_ov_scope ) {
				$overlay_count = 0;
			} elseif ( false !== $_ov_scope ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$overlay_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `{$ov_table}` WHERE county_id = %s", $_ov_scope ) );
			} else {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$overlay_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$ov_table}`" );
			}
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$overlay_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$ov_table}`" );
		}
	}

	$gis_beacon_layers = count( CGOP_gis_get_custom_layer_registry() );
	$gis_generated_files = 0;
	if ( defined( 'CGOP_GIS_IMPORT_STATUS_OPTION' ) ) {
		$gis_status = get_option( CGOP_GIS_IMPORT_STATUS_OPTION, array() );
		if ( ! empty( $gis_status['layers'] ) && is_array( $gis_status['layers'] ) ) {
			foreach ( $gis_status['layers'] as $glayer ) {
				$gis_generated_files += count( $glayer['files'] ?? array() );
			}
		}
	}

	return compact( 'position_keys', 'rep_records', 'rep_only', 'multi_section', 'overlay_count', 'gis_beacon_layers', 'gis_generated_files' );
}

/**
 * Render a county selector <tr> for CSV import/export form-tables.
 *
 * Returns an empty string when cgop is not active, the county_id column does
 * not yet exist (pre-wizard), or the current user cannot manage any county.
 *
 * @param string $field_name  POST field name, e.g. 'import_county_id'.
 * @param string $selected_id Pre-selected county_id (defaults to current context).
 * @param bool   $for_export  When true, label and description are export-flavoured.
 * @return string HTML <tr>…</tr> or empty string.
 */
function CGOP_csv_county_selector_row( $field_name, $selected_id = '', $for_export = false ) {
	if ( ! function_exists( 'cgop_get_table_county_scope' ) || ! function_exists( 'cgop_get_all_county_ids' ) ) {
		return '';
	}
	$pk_table = function_exists( 'CGOP_position_keys_table' ) ? CGOP_position_keys_table() : '';
	if ( ! $pk_table ) {
		return '';
	}
	$scope = cgop_get_table_county_scope( $pk_table );
	if ( false === $scope ) {
		return ''; // Column not yet added — pre-wizard.
	}
	$all_ids = cgop_get_all_county_ids( false );
	if ( empty( $all_ids ) ) {
		return '';
	}
	$manageable = array_values( array_filter( $all_ids, function( $cid ) {
		return function_exists( 'cgop_current_user_can_manage_county' ) && cgop_current_user_can_manage_county( $cid );
	} ) );
	if ( empty( $manageable ) ) {
		return '';
	}
	if ( '' === $selected_id && function_exists( 'cgop_get_current_county_id' ) ) {
		$selected_id = (string) cgop_get_current_county_id();
	}
	$label = $for_export
		? __( 'County', 'county-gop-core' )
		: __( 'Import County', 'county-gop-core' );
	$desc = $for_export
		? __( 'Export only rows belonging to this county.', 'county-gop-core' )
		: __( 'Assign imported rows to this county. Only counties you manage are listed.', 'county-gop-core' );

	ob_start();
	?>
	<tr>
		<th scope="row"><label for="<?php echo esc_attr( $field_name ); ?>"><?php echo esc_html( $label ); ?> <span style="color:#a00;" aria-hidden="true">*</span></label></th>
		<td>
			<select id="<?php echo esc_attr( $field_name ); ?>" name="<?php echo esc_attr( $field_name ); ?>" required>
				<option value=""><?php esc_html_e( '— Select county —', 'county-gop-core' ); ?></option>
				<?php foreach ( $manageable as $cid ) :
					$profile = function_exists( 'cgop_get_county_profile' ) ? cgop_get_county_profile( $cid ) : null;
					$clabel  = $profile
						? esc_html( $profile->county_name . ' (' . $profile->county_slug . ')' )
						: esc_html( $cid );
					?>
					<option value="<?php echo esc_attr( $cid ); ?>"<?php selected( $selected_id, $cid ); ?>><?php echo $clabel; // phpcs:ignore WordPress.Security.EscapeOutput ?></option>
				<?php endforeach; ?>
			</select>
			<p class="description"><?php echo esc_html( $desc ); ?></p>
		</td>
	</tr>
	<?php
	return ob_get_clean();
}

/**
 * Return the leadership_sections value for a post as a normalized flat array.
 */
function CGOP_csv_tools_get_sections( $post_id ) {
	$sections = get_post_meta( $post_id, 'leadership_sections', true );
	if ( empty( $sections ) ) {
		return array();
	}
	if ( ! is_array( $sections ) ) {
		$sections = maybe_unserialize( $sections );
	}
	if ( ! is_array( $sections ) ) {
		$sections = array( $sections );
	}
	return array_values( array_filter( $sections ) );
}

/**
 * Build a deterministic representative_import_key for a post.
 */
function CGOP_csv_tools_build_import_key( $post_id, $post_title ) {
	$parts = array_filter( array(
		sanitize_title( $post_title ),
		sanitize_title( (string) get_post_meta( $post_id, 'leadership_role_title', true ) ),
		sanitize_title( (string) get_post_meta( $post_id, 'leadership_district', true ) ),
		sanitize_title( (string) get_post_meta( $post_id, 'leadership_service_start_year', true ) ),
		sanitize_title( (string) get_post_meta( $post_id, 'leadership_service_end_year', true ) ),
	) );
	return implode( '-', $parts );
}

/**
 * Parse an uploaded CSV file.
 *
 * @param string $file_key $_FILES key.
 * @return array { headers: string[], rows: array[], error: string }
 */
function CGOP_csv_tools_parse_upload( $file_key ) {
	$empty = array( 'headers' => array(), 'rows' => array(), 'error' => '' );

	if ( empty( $_FILES[ $file_key ] ) || UPLOAD_ERR_OK !== (int) $_FILES[ $file_key ]['error'] ) {
		return array_merge( $empty, array( 'error' => 'No file uploaded or upload error.' ) );
	}

	$tmp = $_FILES[ $file_key ]['tmp_name'];
	if ( ! is_uploaded_file( $tmp ) ) {
		return array_merge( $empty, array( 'error' => 'Invalid file upload.' ) );
	}

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
	$fh = fopen( $tmp, 'r' );
	if ( ! $fh ) {
		return array_merge( $empty, array( 'error' => 'Could not open uploaded file.' ) );
	}

	$raw_headers = fgetcsv( $fh );
	if ( ! $raw_headers ) {
		fclose( $fh );
		return array_merge( $empty, array( 'error' => 'CSV file is empty or missing headers.' ) );
	}

	// Strip UTF-8 BOM from first header.
	$raw_headers[0] = ltrim( $raw_headers[0], "\xEF\xBB\xBF" );
	$headers        = array_map( 'trim', $raw_headers );

	$rows = array();
	while ( ( $line = fgetcsv( $fh ) ) !== false ) {
		if ( count( $line ) === count( $headers ) ) {
			$rows[] = array_combine( $headers, $line );
		}
	}
	fclose( $fh );

	return array( 'headers' => $headers, 'rows' => $rows, 'error' => '' );
}

/**
 * ACF field key map for leadership representative meta fields.
 */
function CGOP_csv_tools_acf_field_keys() {
	return array(
		'leadership_sections'                       => 'field_CGOP_leadership_sections',
		'leadership_service_status'                 => 'field_CGOP_leadership_service_status',
		'leadership_service_start_year'             => 'field_CGOP_leadership_service_start_year',
		'leadership_service_end_year'               => 'field_CGOP_leadership_service_end_year',
		'leadership_service_note'                   => 'field_CGOP_leadership_service_note',
		'leadership_bio'                            => 'field_CGOP_leadership_bio',
		'leadership_government_level'               => 'field_CGOP_leadership_government_level',
		'leadership_role_title'                     => 'field_CGOP_leadership_role_title',
		'leadership_district'                       => 'field_CGOP_leadership_district',
		'leadership_jurisdiction'                   => 'field_CGOP_leadership_jurisdiction',
		'leadership_party_affiliation'              => 'field_CGOP_leadership_party_affiliation',
		'leadership_committee_assignments'          => 'field_CGOP_leadership_committee_assignments',
		'leadership_official_url'                   => 'field_CGOP_leadership_official_url',
		// CSV column 'leadership_official_profile_embed_url' maps to this true/false ACF field.
		'leadership_embed_official_profile'         => 'field_CGOP_leadership_embed_official_profile',
		'leadership_representative_history_key'     => 'field_CGOP_leadership_representative_history_key',
		'leadership_representative_position_status' => 'field_CGOP_leadership_representative_position_status',
		'leadership_email'                          => 'field_CGOP_leadership_email',
		'leadership_phone'                          => 'field_CGOP_leadership_phone',
		'leadership_facebook_url'                   => 'field_CGOP_leadership_facebook_url',
		'leadership_wikipedia_url'                  => 'field_CGOP_leadership_wikipedia_url',
	);
}

// ---------------------------------------------------------------------------
// Export: Position Keys
// ---------------------------------------------------------------------------

function CGOP_csv_export_pkeys_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_export_pkeys' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	global $wpdb;
	$table = CGOP_position_keys_table();

	// County-scoped export: require county context when the column exists.
	$selected_county = isset( $_POST['export_county_id'] ) ? sanitize_text_field( wp_unslash( $_POST['export_county_id'] ) ) : '';
	$scope           = function_exists( 'cgop_get_table_county_scope' ) ? cgop_get_table_county_scope( $table ) : false;

	if ( false !== $scope ) {
		if ( '' === $selected_county ) {
			wp_die( esc_html__( 'Select a county before exporting.', 'county-gop-core' ) );
		}
		if ( ! function_exists( 'cgop_county_exists' ) || ! cgop_county_exists( $selected_county ) ) {
			wp_die( esc_html__( 'Selected county was not found.', 'county-gop-core' ) );
		}
		if ( ! function_exists( 'cgop_current_user_can_manage_county' ) || ! cgop_current_user_can_manage_county( $selected_county ) ) {
			wp_die( esc_html__( 'Not authorized for the selected county.', 'county-gop-core' ) );
		}
		$export_county = $selected_county;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT * FROM `{$table}` WHERE county_id = %s ORDER BY sort_order ASC, id ASC",
			$export_county
		), ARRAY_A );
	} else {
		// Pre-wizard: unscoped.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results( "SELECT * FROM `{$table}` ORDER BY sort_order ASC, id ASC", ARRAY_A );
	}

	$filename = 'cgop-position-keys-' . gmdate( 'Y-m-d' ) . '.csv';
	header( 'Content-Type: text/csv; charset=utf-8' );
	header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
	header( 'Pragma: no-cache' );
	header( 'Expires: 0' );

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
	$fh = fopen( 'php://output', 'w' );

	// Include county_id + county_slug when the county-gop-core plugin provides them.
	$include_county = function_exists( 'cgop_get_county_slug' );

	$header = array(
		'position_key', 'leader_type', 'page_location', 'label', 'government_level',
		'municipality_type', 'role_title', 'elected_how', 'district', 'jurisdiction',
		'position_status', 'sort_order', 'description', 'live_in_map', 'voting_area_map',
	);
	if ( $include_county ) {
		$header[] = 'county_id';
		$header[] = 'county_slug';
	}
	fputcsv( $fh, $header );

	foreach ( $rows as $row ) {
		$values = array(
			$row['position_key'],
			$row['leader_type'],
			$row['page_location'],
			$row['label'],
			$row['government_level'],
			$row['municipality_type'],
			$row['role_title'],
			$row['elected_how'],
			$row['district'],
			$row['jurisdiction'],
			$row['position_status'],
			$row['sort_order'],
			$row['description'],
			$row['live_in_map'] ?? '',
			$row['voting_area_map'] ?? '',
		);
		if ( $include_county ) {
			$county_id   = $row['county_id'] ?? '';
			$values[]    = $county_id;
			$values[]    = $county_id ? cgop_get_county_slug( $county_id ) : '';
		}
		fputcsv( $fh, $values );
	}

	fclose( $fh );
	exit;
}
add_action( 'admin_post_CGOP_csv_export_pkeys', 'CGOP_csv_export_pkeys_handler' );

// ---------------------------------------------------------------------------
// Export: Representatives
// ---------------------------------------------------------------------------

function CGOP_csv_export_reps_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_export_reps' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$posts = get_posts( array(
		'post_type'              => 'representative',
		'post_status'            => array( 'publish', 'draft', 'private' ),
		'posts_per_page'         => -1,
		'orderby'                => 'menu_order',
		'order'                  => 'ASC',
		'update_post_meta_cache' => true,
		'update_post_term_cache' => false,
		'meta_query'             => array(
			array(
				'key'     => 'leadership_sections',
				'value'   => '"representatives"',
				'compare' => 'LIKE',
			),
		),
	) );

	$filename = 'cgop-representatives-' . gmdate( 'Y-m-d' ) . '.csv';
	header( 'Content-Type: text/csv; charset=utf-8' );
	header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
	header( 'Pragma: no-cache' );
	header( 'Expires: 0' );

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
	$fh = fopen( 'php://output', 'w' );

	fputcsv( $fh, array(
		'representative_import_key', 'legacy_post_id', 'post_title', 'post_name',
		'post_status', 'menu_order', 'card_summary', 'profile_content',
		'leadership_sections', 'portrait_attachment_id', 'portrait_url',
		'leadership_service_status', 'leadership_service_start_year',
		'leadership_service_end_year', 'leadership_service_note',
		'leadership_government_level', 'leadership_role_title', 'leadership_district',
		'leadership_jurisdiction', 'leadership_party_affiliation',
		'leadership_representative_history_key', 'leadership_representative_position_status',
		'leadership_bio', 'leadership_official_url', 'leadership_wikipedia_url',
		'leadership_facebook_url', 'leadership_email', 'leadership_phone',
		'leadership_committee_assignments', 'leadership_official_profile_embed_url',
		'_CGOP_leadership_research_source', '_CGOP_leadership_review_note',
	) );

	foreach ( $posts as $post ) {
		$pid      = $post->ID;
		$sections = CGOP_csv_tools_get_sections( $pid );

		$portrait_id  = (int) get_post_thumbnail_id( $pid );
		$portrait_url = $portrait_id ? (string) wp_get_attachment_url( $portrait_id ) : '';

		$stored_key = get_post_meta( $pid, '_CGOP_representative_import_key', true );
		$import_key = $stored_key ? $stored_key : CGOP_csv_tools_build_import_key( $pid, $post->post_title );

		fputcsv( $fh, array(
			$import_key,
			$pid,
			$post->post_title,
			$post->post_name,
			$post->post_status,
			$post->menu_order,
			$post->post_excerpt,
			$post->post_content,
			implode( '|', $sections ),
			$portrait_id ?: '',
			$portrait_url,
			get_post_meta( $pid, 'leadership_service_status', true ),
			get_post_meta( $pid, 'leadership_service_start_year', true ),
			get_post_meta( $pid, 'leadership_service_end_year', true ),
			get_post_meta( $pid, 'leadership_service_note', true ),
			get_post_meta( $pid, 'leadership_government_level', true ),
			get_post_meta( $pid, 'leadership_role_title', true ),
			get_post_meta( $pid, 'leadership_district', true ),
			get_post_meta( $pid, 'leadership_jurisdiction', true ),
			get_post_meta( $pid, 'leadership_party_affiliation', true ),
			get_post_meta( $pid, 'leadership_representative_history_key', true ),
			get_post_meta( $pid, 'leadership_representative_position_status', true ),
			get_post_meta( $pid, 'leadership_bio', true ),
			get_post_meta( $pid, 'leadership_official_url', true ),
			get_post_meta( $pid, 'leadership_wikipedia_url', true ),
			get_post_meta( $pid, 'leadership_facebook_url', true ),
			get_post_meta( $pid, 'leadership_email', true ),
			get_post_meta( $pid, 'leadership_phone', true ),
			get_post_meta( $pid, 'leadership_committee_assignments', true ),
			// 'leadership_official_profile_embed_url' column = leadership_embed_official_profile (true/false).
			get_post_meta( $pid, 'leadership_embed_official_profile', true ),
			get_post_meta( $pid, '_CGOP_leadership_research_source', true ),
			get_post_meta( $pid, '_CGOP_leadership_review_note', true ),
		) );
	}

	fclose( $fh );
	exit;
}
add_action( 'admin_post_CGOP_csv_export_reps', 'CGOP_csv_export_reps_handler' );

// ---------------------------------------------------------------------------
// Import: Position Keys
// ---------------------------------------------------------------------------

function CGOP_csv_import_pkeys_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_import_pkeys' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$mode             = isset( $_POST['pkeys_import_mode'] ) ? sanitize_key( $_POST['pkeys_import_mode'] ) : 'dry_run';
	$dry_run          = ( 'dry_run' === $mode );
	$allow_valid_only = ! empty( $_POST['pkeys_import_valid_only'] );
	$uid              = get_current_user_id();

	$result = array(
		'mode'           => $mode,
		'dry_run'        => $dry_run,
		'parsed'         => 0,
		'valid'          => 0,
		'skipped'        => 0,
		'inserted'       => 0,
		'updated'        => 0,
		'generated_keys' => array(),
		'errors'         => array(),
	);

	$parsed = CGOP_csv_tools_parse_upload( 'pkeys_csv_file' );
	if ( $parsed['error'] ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => $parsed['error'] );
		set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
		exit;
	}

	$headers = $parsed['headers'];
	$rows    = $parsed['rows'];

	if ( ! in_array( 'position_key', $headers, true ) || ! in_array( 'label', $headers, true ) ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'CSV must include position_key and label columns.' );
		set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
		exit;
	}

	global $wpdb;
	$table = CGOP_position_keys_table();

	// County-scoped import: when county_id column exists, scope the existing_set.
	$import_scope = function_exists( 'cgop_get_table_county_scope' ) ? cgop_get_table_county_scope( $table ) : false;
	if ( false !== $import_scope && ( ! in_array( 'county_id', $headers, true ) || ! in_array( 'county_slug', $headers, true ) ) ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'CSV must include county_id and county_slug columns after county adoption.' );
		set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
		exit;
	}
	// Require an explicit authorized county when the county_id column exists.
	$import_county = '';
	if ( false !== $import_scope ) {
		if ( ! isset( $_POST['import_county_id'] ) || '' === $_POST['import_county_id'] ) {
			$result['errors'][] = array( 'row' => 0, 'msg' => 'Import county is required. Select a county before importing.' );
			set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
			exit;
		}
		$import_county = sanitize_text_field( wp_unslash( $_POST['import_county_id'] ) );
		if ( ! function_exists( 'cgop_county_exists' ) || ! cgop_county_exists( $import_county ) ) {
			$result['errors'][] = array( 'row' => 0, 'msg' => 'Selected import county was not found.' );
			set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
			exit;
		}
		if ( ! function_exists( 'cgop_current_user_can_manage_county' ) || ! cgop_current_user_can_manage_county( $import_county ) ) {
			$result['errors'][] = array( 'row' => 0, 'msg' => 'Not authorized for the selected county.' );
			set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
			exit;
		}
		if ( function_exists( 'cgop_set_current_county_id' ) ) {
			cgop_set_current_county_id( $import_county );
		}
	}

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
	$existing_rows_query = false !== $import_scope && '' !== $import_county
		? $wpdb->get_results( $wpdb->prepare( "SELECT * FROM `{$table}` WHERE county_id = %s", $import_county ), ARRAY_A )
		: $wpdb->get_results( "SELECT * FROM `{$table}`", ARRAY_A );
	$existing_rows = (array) $existing_rows_query;
	$existing_set  = array();
	foreach ( $existing_rows as $existing_row ) {
		$existing_set[ $existing_row['position_key'] ] = $existing_row;
	}

	$valid_map_ids = array();
	foreach ( CGOP_get_map_boundary_rows() as $mrow ) {
		$valid_map_ids[ $mrow->map_id ] = true;
	}
	$generated_map_choices = CGOP_gis_get_generated_file_choices();

	$seen_keys  = array();
	$valid_rows = array();

	foreach ( $rows as $i => $row ) {
		$line = $i + 2;
		$result['parsed']++;

		$raw_position_key = trim( $row['position_key'] ?? '' );
		$position_key     = sanitize_title( $raw_position_key );
		$label        = function_exists( 'CGOP_sanitize_position_label_template' )
			? CGOP_sanitize_position_label_template( trim( $row['label'] ?? '' ) )
			: sanitize_text_field( trim( $row['label'] ?? '' ) );

		if ( '' === $position_key ) {
			do {
				$position_key = function_exists( 'CGOP_generate_leadership_position_key_id' )
					? CGOP_generate_leadership_position_key_id()
					: sanitize_title( uniqid( 'position-', true ) );
			} while ( isset( $seen_keys[ $position_key ] ) );

			$result['generated_keys'][] = array(
				'row' => $line,
				'key' => $position_key,
			);
		}
		if ( '' === $label ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: label is blank for key '{$position_key}'." );
			continue;
		}
		if ( isset( $seen_keys[ $position_key ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: duplicate position_key '{$position_key}' in CSV (first at row {$seen_keys[$position_key]})." );
			continue;
		}

		$status = sanitize_key( $row['position_status'] ?? 'active' );
		if ( ! in_array( $status, array( 'active', 'historical', 'inactive' ), true ) ) {
			$status = 'active';
		}

		$live_in_map     = sanitize_text_field( $row['live_in_map'] ?? '' );
		$voting_area_map = sanitize_text_field( $row['voting_area_map'] ?? '' );
		$live_in_generated_key = str_starts_with( $live_in_map, 'generated:' )
			? ltrim( substr( $live_in_map, strlen( 'generated:' ) ), '/' )
			: '';
		$voting_generated_key = str_starts_with( $voting_area_map, 'generated:' )
			? ltrim( substr( $voting_area_map, strlen( 'generated:' ) ), '/' )
			: '';

		if ( $live_in_generated_key && empty( $generated_map_choices[ $live_in_generated_key ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: live_in_map generated map '{$live_in_map}' was not found in generated GIS files." );
			continue;
		}
		if ( $voting_generated_key && empty( $generated_map_choices[ $voting_generated_key ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: voting_area_map generated map '{$voting_area_map}' was not found in generated GIS files." );
			continue;
		}
		if ( $live_in_map && ! $live_in_generated_key && ! isset( $valid_map_ids[ $live_in_map ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: live_in_map '{$live_in_map}' is not a valid map ID." );
			continue;
		}
		if ( $voting_area_map && ! $voting_generated_key && ! isset( $valid_map_ids[ $voting_area_map ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: voting_area_map '{$voting_area_map}' is not a valid map ID." );
			continue;
		}

		// Validate optional file ownership columns against the selected import county.
		$row_county_id = $import_county;
		if ( false !== $import_scope ) {
			$file_county_id   = sanitize_text_field( $row['county_id'] ?? '' );
			$file_county_slug = sanitize_title( $row['county_slug'] ?? '' );
			$import_slug      = function_exists( 'cgop_get_county_slug' ) ? sanitize_title( cgop_get_county_slug( $import_county ) ) : '';
			if ( ! $file_county_id || ! $file_county_slug ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: county_id and county_slug are required after county adoption." );
				continue;
			}
			if ( $file_county_id && $file_county_id !== $import_county ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: county_id '{$file_county_id}' does not match selected import county '{$import_county}'." );
				continue;
			}
			if ( $file_county_slug && $import_slug && $file_county_slug !== $import_slug ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: county_slug '{$file_county_slug}' does not match selected import county slug '{$import_slug}'." );
				continue;
			}
		}

		$seen_keys[ $position_key ] = $line;
		$result['valid']++;
		$valid_rows[] = array(
			'line'             => $line,
			'position_key'     => $position_key,
			'leader_type'      => sanitize_key( $row['leader_type'] ?? '' ),
			'page_location'    => sanitize_key( $row['page_location'] ?? '' ),
			'label'            => $label,
			'government_level' => sanitize_text_field( $row['government_level'] ?? '' ),
			'municipality_type' => sanitize_text_field( $row['municipality_type'] ?? '' ),
			'role_title'       => sanitize_text_field( $row['role_title'] ?? '' ),
			'elected_how'      => sanitize_text_field( $row['elected_how'] ?? '' ),
			'district'         => sanitize_text_field( $row['district'] ?? '' ),
			'jurisdiction'     => sanitize_text_field( $row['jurisdiction'] ?? '' ),
			'position_status'  => $status,
			'sort_order'       => isset( $row['sort_order'] ) ? (int) $row['sort_order'] : 0,
			'description'      => sanitize_textarea_field( $row['description'] ?? '' ),
			'live_in_map'      => $live_in_map,
			'voting_area_map'  => $voting_area_map,
			'county_id'        => $row_county_id,
			'live_in_generated_key' => $live_in_generated_key,
			'voting_generated_key'  => $voting_generated_key,
			'in_db'            => isset( $existing_set[ $position_key ] ),
		);
	}

	if ( ! $dry_run && ! empty( $result['errors'] ) && ( ! $allow_valid_only || 'replace_registry' === $mode ) ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'Import aborted: validation errors found. Replace Registry requires every row to be valid.' );
		set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
		exit;
	}

	if ( $dry_run ) {
		set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
		exit;
	}

	$now = current_time( 'mysql' );

	if ( 'replace_registry' === $mode ) {
		// County-scoped delete: remove only rows for the explicitly selected county.
		if ( function_exists( 'cgop_get_table_county_scope' ) ) {
			$scope = cgop_get_table_county_scope( $table );
			if ( null === $scope ) {
				$result['errors'][] = array( 'row' => 0, 'msg' => 'Replace Registry requires an active county context. No rows deleted.' );
				set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
				wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
				exit;
			}
			if ( false !== $scope ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
				$delete_result = $wpdb->delete( $table, array( 'county_id' => $import_county ), array( '%s' ) );
				if ( false === $delete_result ) {
					$result['errors'][] = array( 'row' => 0, 'msg' => 'Replace Registry delete failed: ' . ( $wpdb->last_error ?: 'unknown error' ) );
					set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
					wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
					exit;
				}
				$existing_set = array();
			} else {
				// Pre-wizard (no column) — TRUNCATE only when plugin is not active.
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
				$wpdb->query( "TRUNCATE TABLE {$table}" );
				$existing_set = array();
			}
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$wpdb->query( "TRUNCATE TABLE {$table}" );
			$existing_set = array();
		}
	}

	foreach ( $valid_rows as $vr ) {
		$in_db = $vr['in_db'] && 'replace_registry' !== $mode;

		if ( $in_db && 'insert_missing' === $mode ) {
			$result['skipped']++;
			continue;
		}

		$live_in_map     = $vr['live_in_map'];
		$voting_area_map = $vr['voting_area_map'];
		if ( ! empty( $vr['live_in_generated_key'] ) ) {
			$existing_live_in = $in_db && ! empty( $existing_set[ $vr['position_key'] ]['live_in_map'] )
				? $existing_set[ $vr['position_key'] ]['live_in_map']
				: '';
			$generated_result = function_exists( 'CGOP_position_key_save_generated_map' )
				? CGOP_position_key_save_generated_map( $vr['live_in_generated_key'], $existing_live_in, $vr['position_key'] )
				: new WP_Error( 'generated_map_helper_missing', __( 'Generated map assignment helper is unavailable.', 'county-gop-core' ) );
			if ( is_wp_error( $generated_result ) ) {
				$result['errors'][] = array( 'row' => $vr['line'], 'msg' => 'Row ' . $vr['line'] . ': live_in_map could not be assigned: ' . $generated_result->get_error_message() );
				continue;
			}
			$live_in_map = (string) $generated_result;
		}
		if ( ! empty( $vr['voting_generated_key'] ) ) {
			$existing_voting = $in_db && ! empty( $existing_set[ $vr['position_key'] ]['voting_area_map'] )
				? $existing_set[ $vr['position_key'] ]['voting_area_map']
				: '';
			$generated_result = function_exists( 'CGOP_position_key_save_generated_map' )
				? CGOP_position_key_save_generated_map( $vr['voting_generated_key'], $existing_voting, $vr['position_key'] )
				: new WP_Error( 'generated_map_helper_missing', __( 'Generated map assignment helper is unavailable.', 'county-gop-core' ) );
			if ( is_wp_error( $generated_result ) ) {
				$result['errors'][] = array( 'row' => $vr['line'], 'msg' => 'Row ' . $vr['line'] . ': voting_area_map could not be assigned: ' . $generated_result->get_error_message() );
				continue;
			}
			$voting_area_map = (string) $generated_result;
		}

		$data = array(
			'label'            => $vr['label'],
			'leader_type'      => $vr['leader_type'],
			'page_location'    => $vr['page_location'],
			'government_level' => $vr['government_level'],
			'municipality_type' => $vr['municipality_type'],
			'role_title'       => $vr['role_title'],
			'elected_how'      => $vr['elected_how'],
			'district'         => $vr['district'],
			'jurisdiction'     => $vr['jurisdiction'],
			'position_status'  => $vr['position_status'],
			'sort_order'       => $vr['sort_order'],
			'description'      => $vr['description'],
			'live_in_map'      => $live_in_map,
			'voting_area_map'  => $voting_area_map,
			'updated_at'       => $now,
		);
		$fmt = array( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d', '%s', '%s', '%s', '%s' );

		// Persist county_id on every write when the column exists.
		if ( false !== $import_scope && '' !== $import_county ) {
			$data['county_id'] = $import_county;
			$fmt[]             = '%s';
		}

		if ( $in_db ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$where        = array( 'position_key' => $vr['position_key'] );
			$where_format = array( '%s' );
			if ( false !== $import_scope ) {
				$where['county_id'] = $import_county;
				$where_format[]     = '%s';
			}
			$write_result = $wpdb->update( $table, $data, $where, $fmt, $where_format );
			if ( false === $write_result ) {
				$result['errors'][] = array( 'row' => $vr['line'], 'msg' => "Row {$vr['line']}: DB update failed for '{$vr['position_key']}': " . ( $wpdb->last_error ?: 'unknown error' ) );
				continue;
			}
			$result['updated']++;
		} else {
			$data['position_key'] = $vr['position_key'];
			$data['created_at']   = $now;
			$fmt[]                = '%s';
			$fmt[]                = '%s';
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$write_result = $wpdb->insert( $table, $data, $fmt );
			if ( false === $write_result ) {
				$result['errors'][] = array( 'row' => $vr['line'], 'msg' => "Row {$vr['line']}: DB insert failed for '{$vr['position_key']}': " . ( $wpdb->last_error ?: 'unknown error' ) );
				continue;
			}
			$result['inserted']++;
		}
	}

	CGOP_position_keys_clear_cache();
	set_transient( 'CGOP_csv_result_pkeys_' . $uid, $result, 120 );
	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'pkeys' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_import_pkeys', 'CGOP_csv_import_pkeys_handler' );

// ---------------------------------------------------------------------------
// Import: Representatives
// ---------------------------------------------------------------------------

function CGOP_csv_import_reps_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_import_reps' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$mode             = isset( $_POST['reps_import_mode'] ) ? sanitize_key( $_POST['reps_import_mode'] ) : 'dry_run';
	$dry_run          = ( 'dry_run' === $mode );
	$allow_valid_only = ! empty( $_POST['reps_import_valid_only'] );
	$uid              = get_current_user_id();

	$result = array(
		'mode'     => $mode,
		'dry_run'  => $dry_run,
		'parsed'   => 0,
		'valid'    => 0,
		'skipped'  => 0,
		'inserted' => 0,
		'updated'  => 0,
		'errors'   => array(),
	);

	$parsed = CGOP_csv_tools_parse_upload( 'reps_csv_file' );
	if ( $parsed['error'] ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => $parsed['error'] );
		set_transient( 'CGOP_csv_result_reps_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'reps' ) ) );
		exit;
	}

	$headers = $parsed['headers'];
	$rows    = $parsed['rows'];

	$required_cols = array(
		'representative_import_key',
		'post_title',
		'leadership_representative_history_key',
		'leadership_service_status',
		'leadership_government_level',
		'leadership_role_title',
	);
	foreach ( $required_cols as $col ) {
		if ( ! in_array( $col, $headers, true ) ) {
			$result['errors'][] = array( 'row' => 0, 'msg' => "CSV missing required column: {$col}" );
		}
	}
	if ( ! empty( $result['errors'] ) ) {
		set_transient( 'CGOP_csv_result_reps_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'reps' ) ) );
		exit;
	}

	// Load registry keys for validation.
	$registry_set = array();
	foreach ( CGOP_get_leadership_position_key_registry_rows() as $rrow ) {
		$registry_set[ $rrow->position_key ] = true;
	}

	// Load existing representative import keys → post IDs.
	$existing_import_keys = array();
	$keyed_posts = get_posts( array(
		'post_type'              => 'representative',
		'post_status'            => 'any',
		'posts_per_page'         => -1,
		'fields'                 => 'ids',
		'no_found_rows'          => true,
		'update_post_term_cache' => false,
		'meta_query'             => array(
			array( 'key' => '_CGOP_representative_import_key', 'compare' => 'EXISTS' ),
		),
	) );
	foreach ( $keyed_posts as $pid ) {
		$k = get_post_meta( $pid, '_CGOP_representative_import_key', true );
		if ( $k ) {
			$existing_import_keys[ $k ] = $pid;
		}
	}

	$seen_keys  = array();
	$valid_rows = array();

	foreach ( $rows as $i => $row ) {
		$line = $i + 2;
		$result['parsed']++;

		$import_key = sanitize_text_field( trim( $row['representative_import_key'] ?? '' ) );
		$post_title = sanitize_text_field( trim( $row['post_title'] ?? '' ) );

		if ( '' === $import_key ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: representative_import_key is blank." );
			continue;
		}
		if ( '' === $post_title ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: post_title is blank." );
			continue;
		}
		if ( isset( $seen_keys[ $import_key ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: duplicate representative_import_key '{$import_key}' (first at row {$seen_keys[$import_key]})." );
			continue;
		}

		$history_key = sanitize_title( trim( $row['leadership_representative_history_key'] ?? '' ) );
		if ( $history_key && ! isset( $registry_set[ $history_key ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: position key '{$history_key}' not in registry." );
			$seen_keys[ $import_key ] = $line;
			continue;
		}

		$service_status = sanitize_key( $row['leadership_service_status'] ?? 'current' );
		if ( ! in_array( $service_status, array( 'current', 'past' ), true ) ) {
			$service_status = 'current';
		}

		$position_status = sanitize_key( $row['leadership_representative_position_status'] ?? 'active' );
		if ( ! in_array( $position_status, array( 'active', 'historical' ), true ) ) {
			$position_status = 'active';
		}

		$start_year = trim( $row['leadership_service_start_year'] ?? '' );
		$end_year   = trim( $row['leadership_service_end_year'] ?? '' );

		if ( $start_year && ( ! ctype_digit( $start_year ) || 4 !== strlen( $start_year ) ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: leadership_service_start_year must be 4-digit year or blank." );
			$seen_keys[ $import_key ] = $line;
			continue;
		}
		if ( $end_year && ( ! ctype_digit( $end_year ) || 4 !== strlen( $end_year ) ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: leadership_service_end_year must be 4-digit year or blank." );
			$seen_keys[ $import_key ] = $line;
			continue;
		}

		$post_status = sanitize_key( $row['post_status'] ?? 'publish' );
		if ( ! in_array( $post_status, array( 'publish', 'draft', 'private' ), true ) ) {
			$post_status = 'publish';
		}

		$sections_raw = trim( $row['leadership_sections'] ?? 'representatives' );
		$sections     = array_values( array_filter( array_map( 'sanitize_key', explode( '|', $sections_raw ) ) ) );
		if ( ! in_array( 'representatives', $sections, true ) ) {
			$sections[] = 'representatives';
		}

		// CSV column 'leadership_official_profile_embed_url' = meta key 'leadership_embed_official_profile' (true/false).
		$embed_raw = $row['leadership_official_profile_embed_url'] ?? '1';
		$embed_val = ( '' !== $embed_raw && '0' !== $embed_raw ) ? 1 : 0;

		$seen_keys[ $import_key ] = $line;
		$result['valid']++;
		$valid_rows[] = array(
			'line'                                   => $line,
			'import_key'                             => $import_key,
			'existing_post_id'                       => $existing_import_keys[ $import_key ] ?? 0,
			'post_title'                             => $post_title,
			'post_name'                              => sanitize_title( $row['post_name'] ?? '' ),
			'post_status'                            => $post_status,
			'menu_order'                             => isset( $row['menu_order'] ) ? (int) $row['menu_order'] : 0,
			'post_excerpt'                           => sanitize_textarea_field( $row['card_summary'] ?? '' ),
			'post_content'                           => wp_kses_post( $row['profile_content'] ?? '' ),
			'sections'                               => $sections,
			'portrait_attachment_id'                 => isset( $row['portrait_attachment_id'] ) ? (int) $row['portrait_attachment_id'] : 0,
			'portrait_url'                           => esc_url_raw( $row['portrait_url'] ?? '' ),
			'leadership_service_status'              => $service_status,
			'leadership_service_start_year'          => $start_year,
			'leadership_service_end_year'            => $end_year,
			'leadership_service_note'                => sanitize_text_field( $row['leadership_service_note'] ?? '' ),
			'leadership_government_level'            => sanitize_key( $row['leadership_government_level'] ?? '' ),
			'leadership_role_title'                  => sanitize_text_field( $row['leadership_role_title'] ?? '' ),
			'leadership_district'                    => sanitize_text_field( $row['leadership_district'] ?? '' ),
			'leadership_jurisdiction'                => sanitize_text_field( $row['leadership_jurisdiction'] ?? '' ),
			'leadership_party_affiliation'           => sanitize_text_field( $row['leadership_party_affiliation'] ?? '' ),
			'leadership_representative_history_key'  => $history_key,
			'leadership_representative_position_status' => $position_status,
			'leadership_bio'                         => wp_kses_post( $row['leadership_bio'] ?? '' ),
			'leadership_official_url'                => esc_url_raw( $row['leadership_official_url'] ?? '' ),
			'leadership_wikipedia_url'               => esc_url_raw( $row['leadership_wikipedia_url'] ?? '' ),
			'leadership_facebook_url'                => esc_url_raw( $row['leadership_facebook_url'] ?? '' ),
			'leadership_email'                       => sanitize_email( $row['leadership_email'] ?? '' ),
			'leadership_phone'                       => sanitize_text_field( $row['leadership_phone'] ?? '' ),
			'leadership_committee_assignments'       => sanitize_textarea_field( $row['leadership_committee_assignments'] ?? '' ),
			'leadership_embed_official_profile'      => $embed_val,
			'_CGOP_leadership_research_source'  => esc_url_raw( $row['_CGOP_leadership_research_source'] ?? '' ),
			'_CGOP_leadership_review_note'      => sanitize_textarea_field( $row['_CGOP_leadership_review_note'] ?? '' ),
		);
	}

	if ( ! $dry_run && ! empty( $result['errors'] ) && ! $allow_valid_only ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'Import aborted: validation errors found. Check "import valid rows only" to skip invalid rows.' );
		set_transient( 'CGOP_csv_result_reps_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'reps' ) ) );
		exit;
	}

	if ( $dry_run ) {
		set_transient( 'CGOP_csv_result_reps_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'reps' ) ) );
		exit;
	}

	// Replace mode: wipe active representative records before importing.
	// Validation passed above, so the wipe is safe. existing_post_id values
	// captured before wipe still reference the (now-trashed) posts; wp_update_post
	// on a trashed post restores it with the new data.
	if ( 'replace_representatives' === $mode ) {
		$acf_keys_wipe = CGOP_csv_tools_acf_field_keys();
		$wipe_posts    = get_posts( array(
			'post_type'              => 'representative',
			'post_status'            => array( 'publish', 'draft', 'private' ),
			'posts_per_page'         => -1,
			'fields'                 => 'ids',
			'no_found_rows'          => true,
			'update_post_term_cache' => false,
			'meta_query'             => array(
				array(
					'key'     => 'leadership_sections',
					'value'   => '"representatives"',
					'compare' => 'LIKE',
				),
			),
		) );
		foreach ( $wipe_posts as $wipe_id ) {
			$wipe_sections = CGOP_csv_tools_get_sections( $wipe_id );
			if ( count( $wipe_sections ) <= 1 ) {
				wp_trash_post( $wipe_id );
			} else {
				$new_sec = array_values( array_filter( $wipe_sections, function( $s ) {
					return 'representatives' !== $s;
				} ) );
				update_post_meta( $wipe_id, 'leadership_sections', $new_sec );
				update_post_meta( $wipe_id, '_leadership_sections', $acf_keys_wipe['leadership_sections'] );
			}
		}
	}

	$acf_keys = CGOP_csv_tools_acf_field_keys();

	foreach ( $valid_rows as $vr ) {
		$post_data = array(
			'post_type'    => 'representative',
			'post_title'   => $vr['post_title'],
			'post_status'  => $vr['post_status'],
			'menu_order'   => $vr['menu_order'],
			'post_excerpt' => $vr['post_excerpt'],
			'post_content' => $vr['post_content'],
		);
		if ( $vr['post_name'] ) {
			$post_data['post_name'] = $vr['post_name'];
		}

		$existing_id = $vr['existing_post_id'];

		if ( $existing_id ) {
			$post_data['ID'] = $existing_id;
			$post_id         = wp_update_post( $post_data, true );
			if ( is_wp_error( $post_id ) ) {
				$result['errors'][] = array( 'row' => $vr['line'], 'msg' => "Row {$vr['line']}: update failed for '{$vr['import_key']}': " . $post_id->get_error_message() );
				continue;
			}
			$result['updated']++;
		} else {
			$post_id = wp_insert_post( $post_data, true );
			if ( is_wp_error( $post_id ) ) {
				$result['errors'][] = array( 'row' => $vr['line'], 'msg' => "Row {$vr['line']}: insert failed for '{$vr['import_key']}': " . $post_id->get_error_message() );
				continue;
			}
			$result['inserted']++;
		}

		update_post_meta( $post_id, '_CGOP_representative_import_key', $vr['import_key'] );

		$acf_meta_fields = array(
			'leadership_sections', 'leadership_service_status', 'leadership_service_start_year',
			'leadership_service_end_year', 'leadership_service_note', 'leadership_government_level',
			'leadership_role_title', 'leadership_district', 'leadership_jurisdiction',
			'leadership_party_affiliation', 'leadership_committee_assignments', 'leadership_official_url',
			'leadership_embed_official_profile', 'leadership_representative_history_key',
			'leadership_representative_position_status', 'leadership_bio', 'leadership_email',
			'leadership_phone', 'leadership_facebook_url', 'leadership_wikipedia_url',
		);

		foreach ( $acf_meta_fields as $field ) {
			$val = ( 'leadership_sections' === $field ) ? $vr['sections'] : ( $vr[ $field ] ?? '' );
			update_post_meta( $post_id, $field, $val );
			if ( isset( $acf_keys[ $field ] ) ) {
				update_post_meta( $post_id, '_' . $field, $acf_keys[ $field ] );
			}
		}

		update_post_meta( $post_id, '_CGOP_leadership_research_source', $vr['_CGOP_leadership_research_source'] );
		update_post_meta( $post_id, '_CGOP_leadership_review_note', $vr['_CGOP_leadership_review_note'] );

		// Set featured image.
		$portrait_id = $vr['portrait_attachment_id'];
		if ( $portrait_id && 'attachment' === get_post_type( $portrait_id ) ) {
			set_post_thumbnail( $post_id, $portrait_id );
		} elseif ( $vr['portrait_url'] ) {
			$resolved = attachment_url_to_postid( $vr['portrait_url'] );
			if ( $resolved ) {
				set_post_thumbnail( $post_id, $resolved );
			}
		}
	}

	set_transient( 'CGOP_csv_result_reps_' . $uid, $result, 120 );
	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'reps' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_import_reps', 'CGOP_csv_import_reps_handler' );

// ---------------------------------------------------------------------------
// Wipe: Position Keys
// ---------------------------------------------------------------------------

function CGOP_csv_wipe_pkeys_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_wipe_pkeys' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$typed = isset( $_POST['wipe_confirm_pkeys'] ) ? wp_unslash( $_POST['wipe_confirm_pkeys'] ) : '';
	if ( 'WIPE POSITION KEYS' !== $typed ) {
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_error' => 'pkeys_confirm' ) ) );
		exit;
	}

	global $wpdb;
	$table = CGOP_position_keys_table();

	// County-scoped delete: never TRUNCATE when county-gop-core is active.
	if ( function_exists( 'cgop_get_table_county_scope' ) ) {
		$scope = cgop_get_table_county_scope( $table );
		if ( null === $scope ) {
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_error' => 'pkeys_no_county_context' ) ) );
			exit;
		}
		if ( false !== $scope ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$wpdb->delete( $table, array( 'county_id' => $scope ), array( '%s' ) );
			CGOP_position_keys_clear_cache();
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_done' => 'pkeys' ) ) );
			exit;
		}
	}

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
	$wpdb->query( "TRUNCATE TABLE {$table}" );
	CGOP_position_keys_clear_cache();

	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_done' => 'pkeys' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_wipe_pkeys', 'CGOP_csv_wipe_pkeys_handler' );

// ---------------------------------------------------------------------------
// Wipe: Representatives
// ---------------------------------------------------------------------------

function CGOP_csv_wipe_reps_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_wipe_reps' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$typed = isset( $_POST['wipe_confirm_reps'] ) ? wp_unslash( $_POST['wipe_confirm_reps'] ) : '';
	if ( 'WIPE REPRESENTATIVES' !== $typed ) {
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_error' => 'reps_confirm' ) ) );
		exit;
	}

	$permanent = ! empty( $_POST['wipe_reps_permanent'] );
	$acf_keys  = CGOP_csv_tools_acf_field_keys();

	$posts = get_posts( array(
		'post_type'              => 'representative',
		'post_status'            => array( 'publish', 'draft', 'private' ),
		'posts_per_page'         => -1,
		'fields'                 => 'ids',
		'no_found_rows'          => true,
		'update_post_term_cache' => false,
		'meta_query'             => array(
			array(
				'key'     => 'leadership_sections',
				'value'   => '"representatives"',
				'compare' => 'LIKE',
			),
		),
	) );

	$trashed  = 0;
	$deleted  = 0;
	$stripped = 0;

	foreach ( $posts as $post_id ) {
		$sections = CGOP_csv_tools_get_sections( $post_id );
		if ( count( $sections ) <= 1 ) {
			if ( $permanent ) {
				wp_delete_post( $post_id, true );
				$deleted++;
			} else {
				wp_trash_post( $post_id );
				$trashed++;
			}
		} else {
			$new_sec = array_values( array_filter( $sections, function( $s ) {
				return 'representatives' !== $s;
			} ) );
			update_post_meta( $post_id, 'leadership_sections', $new_sec );
			update_post_meta( $post_id, '_leadership_sections', $acf_keys['leadership_sections'] );
			$stripped++;
		}
	}

	wp_safe_redirect( CGOP_csv_tools_admin_url( array(
		'wipe_done'     => 'reps',
		'wipe_trashed'  => $trashed,
		'wipe_deleted'  => $deleted,
		'wipe_stripped' => $stripped,
	) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_wipe_reps', 'CGOP_csv_wipe_reps_handler' );

// ---------------------------------------------------------------------------
// Wipe: Representative Overlays
// ---------------------------------------------------------------------------

function CGOP_csv_wipe_overlays_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_wipe_overlays' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}
	$confirm = isset( $_POST['wipe_confirm_overlays'] ) ? trim( (string) wp_unslash( $_POST['wipe_confirm_overlays'] ) ) : '';
	if ( 'WIPE REPRESENTATIVE OVERLAYS' !== $confirm ) {
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_error' => 'overlays_confirm' ) ) );
		exit;
	}

	global $wpdb;
	$table = function_exists( 'CGOP_overlay_table' ) ? CGOP_overlay_table() : $wpdb->prefix . 'cgop_representative_overlays';

	// County-scoped delete: never TRUNCATE when county-gop-core is active.
	if ( function_exists( 'cgop_get_table_county_scope' ) ) {
		$scope = cgop_get_table_county_scope( $table );
		if ( null === $scope ) {
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_error' => 'overlays_no_county_context' ) ) );
			exit;
		}
		if ( false !== $scope ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$wpdb->delete( $table, array( 'county_id' => $scope ), array( '%s' ) );
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_done' => 'overlays' ) ) );
			exit;
		}
	}

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
	$wpdb->query( "TRUNCATE TABLE `{$table}`" );

	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_done' => 'overlays' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_wipe_overlays', 'CGOP_csv_wipe_overlays_handler' );

// ---------------------------------------------------------------------------
// Export: Representative Overlays
// ---------------------------------------------------------------------------

function CGOP_csv_export_overlays_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_export_overlays' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	global $wpdb;
	$table = function_exists( 'CGOP_overlay_table' ) ? CGOP_overlay_table() : $wpdb->prefix . 'cgop_representative_overlays';
	$overlay_order = function_exists( 'CGOP_get_overlay_history_order_sql' )
		? CGOP_get_overlay_history_order_sql()
		: "CASE WHEN LOWER(TRIM(`leader_status`)) = 'current' THEN 0 ELSE 1 END ASC, COALESCE(`end_year`, `start_year`, 0) DESC, COALESCE(`start_year`, 0) DESC, `name` ASC, `leader_key` ASC";

	// County-scoped overlay export.
	$ov_selected_county = isset( $_POST['export_county_id'] ) ? sanitize_text_field( wp_unslash( $_POST['export_county_id'] ) ) : '';
	$ov_scope           = function_exists( 'cgop_get_table_county_scope' ) ? cgop_get_table_county_scope( $table ) : false;

	if ( false !== $ov_scope ) {
		if ( '' === $ov_selected_county ) {
			wp_die( esc_html__( 'Select a county before exporting.', 'county-gop-core' ) );
		}
		if ( ! function_exists( 'cgop_county_exists' ) || ! cgop_county_exists( $ov_selected_county ) ) {
			wp_die( esc_html__( 'Selected county was not found.', 'county-gop-core' ) );
		}
		if ( ! function_exists( 'cgop_current_user_can_manage_county' ) || ! cgop_current_user_can_manage_county( $ov_selected_county ) ) {
			wp_die( esc_html__( 'Not authorized for the selected county.', 'county-gop-core' ) );
		}
		$ov_export_county = $ov_selected_county;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT * FROM `{$table}` WHERE county_id = %s ORDER BY `position_key` ASC, {$overlay_order}",
			$ov_export_county
		), ARRAY_A );
	} else {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_results(
			"SELECT * FROM `{$table}` ORDER BY `position_key` ASC, {$overlay_order}",
			ARRAY_A
		);
	}

	$filename = 'cgop-representative-overlays-' . gmdate( 'Y-m-d' ) . '.csv';
	header( 'Content-Type: text/csv; charset=utf-8' );
	header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
	header( 'Pragma: no-cache' );
	header( 'Expires: 0' );

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
	$fh = fopen( 'php://output', 'w' );

	$include_county_ov = function_exists( 'cgop_get_county_slug' );

	$ov_header = array(
		'leader_key', 'position_key', 'leader_status', 'leader_post', 'name',
		'start_year', 'end_year', 'term_start', 'term_end', 'portrait_id',
		'party_affiliation', 'official_url', 'inline_switch',
		'wikipedia_url', 'facebook_url', 'email', 'phone', 'bio',
	);
	if ( $include_county_ov ) {
		$ov_header[] = 'county_id';
		$ov_header[] = 'county_slug';
	}
	fputcsv( $fh, $ov_header );

	foreach ( (array) $rows as $row ) {
		$ov_values = array(
			$row['leader_key']        ?? '',
			$row['position_key']      ?? '',
			$row['leader_status']     ?? '',
			$row['leader_post']       ?? '',
			$row['name']              ?? '',
			$row['start_year']        ?? '',
			$row['end_year']          ?? '',
			$row['term_start']        ?? '',
			$row['term_end']          ?? '',
			$row['portrait_id']       ?? '',
			$row['party_affiliation'] ?? '',
			$row['official_url']      ?? '',
			$row['inline_switch']     ?? '',
			$row['wikipedia_url']     ?? '',
			$row['facebook_url']      ?? '',
			$row['email']             ?? '',
			$row['phone']             ?? '',
			$row['bio']               ?? '',
		);
		if ( $include_county_ov ) {
			$ov_county_id = $row['county_id'] ?? '';
			$ov_values[]  = $ov_county_id;
			$ov_values[]  = $ov_county_id ? cgop_get_county_slug( $ov_county_id ) : '';
		}
		fputcsv( $fh, $ov_values );
	}

	fclose( $fh );
	exit;
}
add_action( 'admin_post_CGOP_csv_export_overlays', 'CGOP_csv_export_overlays_handler' );

// ---------------------------------------------------------------------------
// Import: Representative Overlays
// ---------------------------------------------------------------------------

function CGOP_csv_import_overlays_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_import_overlays' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$mode             = isset( $_POST['overlays_import_mode'] ) ? sanitize_key( $_POST['overlays_import_mode'] ) : 'dry_run';
	if ( 'upsert' === $mode ) {
		$mode = 'update_existing';
	}
	if ( ! in_array( $mode, array( 'dry_run', 'insert_missing', 'update_existing', 'replace_registry' ), true ) ) {
		$mode = 'dry_run';
	}
	$dry_run          = ( 'dry_run' === $mode );
	$allow_valid_only = ! empty( $_POST['overlays_import_valid_only'] );
	$uid              = get_current_user_id();

	$result = array(
		'mode'         => $mode,
		'dry_run'      => $dry_run,
		'parsed'       => 0,
		'valid'        => 0,
		'skipped'      => 0,
		'inserted'     => 0,
		'updated'      => 0,
		'errors'       => array(),
		'generated_keys' => array(),
	);

	// County scope — determine import county from explicit POST selector.
	global $wpdb;
	$ov_table     = function_exists( 'CGOP_overlay_table' ) ? CGOP_overlay_table() : $wpdb->prefix . 'cgop_representative_overlays';
	$import_scope = function_exists( 'cgop_get_table_county_scope' ) ? cgop_get_table_county_scope( $ov_table ) : false;

	// Require an explicit county selector when the column exists — never infer from hostname.
	$import_county = '';
	if ( false !== $import_scope ) {
		$raw_import_county = isset( $_POST['overlays_import_county_id'] ) ? sanitize_text_field( wp_unslash( $_POST['overlays_import_county_id'] ) ) : '';
		if ( '' === $raw_import_county ) {
			$result['errors'][] = array( 'row' => 0, 'msg' => 'Import county is required. Select a county before importing.' );
			set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
			exit;
		}
		if ( ! function_exists( 'cgop_county_exists' ) || ! cgop_county_exists( $raw_import_county ) ) {
			$result['errors'][] = array( 'row' => 0, 'msg' => 'Selected import county was not found.' );
			set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
			exit;
		}
		if ( ! function_exists( 'cgop_current_user_can_manage_county' ) || ! cgop_current_user_can_manage_county( $raw_import_county ) ) {
			$result['errors'][] = array( 'row' => 0, 'msg' => 'Not authorized for the selected import county.' );
			set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
			wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
			exit;
		}
		$import_county = $raw_import_county;
		if ( function_exists( 'cgop_set_current_county_id' ) ) {
			cgop_set_current_county_id( $import_county );
		}
	}

	$expected_headers = array(
		'leader_key', 'position_key', 'leader_status', 'leader_post', 'name',
		'start_year', 'end_year', 'term_start', 'term_end', 'portrait_id',
		'party_affiliation', 'official_url', 'inline_switch',
		'wikipedia_url', 'facebook_url', 'email', 'phone', 'bio',
	);

	$parsed = CGOP_csv_tools_parse_upload( 'overlays_csv_file' );
	if ( $parsed['error'] ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => $parsed['error'] );
		set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
		exit;
	}

	$missing = array_diff( $expected_headers, $parsed['headers'] );
	if ( $missing ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'Missing required headers: ' . implode( ', ', $missing ) );
		set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
		exit;
	}
	if ( false !== $import_scope && ( ! in_array( 'county_id', $parsed['headers'], true ) || ! in_array( 'county_slug', $parsed['headers'], true ) ) ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'CSV must include county_id and county_slug columns after county adoption.' );
		set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
		exit;
	}

	// Pre-load known position keys, scoped to the import county when active.
	$known_pkeys = array();
	if ( function_exists( 'CGOP_position_keys_table' ) ) {
		$pk_table = CGOP_position_keys_table();
		if ( false !== $import_scope && '' !== $import_county ) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$known_pkeys = array_flip( (array) $wpdb->get_col( $wpdb->prepare( "SELECT `position_key` FROM `{$pk_table}` WHERE county_id = %s", $import_county ) ) );
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$known_pkeys = array_flip( (array) $wpdb->get_col( "SELECT `position_key` FROM `{$pk_table}`" ) );
		}
	}

	// Pre-load existing overlay leader_keys, scoped to the import county when active.
	$existing_keys = array();
	if ( false !== $import_scope && '' !== $import_county ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$existing_keys = array_flip( (array) $wpdb->get_col( $wpdb->prepare( "SELECT `leader_key` FROM `{$ov_table}` WHERE county_id = %s", $import_county ) ) );
	} elseif ( false === $import_scope ) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
		$existing_keys = array_flip( (array) $wpdb->get_col( "SELECT `leader_key` FROM `{$ov_table}`" ) );
	}

	$valid_rows = array();

	foreach ( $parsed['rows'] as $row_idx => $row ) {
		$line = $row_idx + 2;
		$result['parsed']++;

		$raw = array();
		foreach ( $expected_headers as $h ) {
			$raw[ $h ] = isset( $row[ $h ] ) ? trim( $row[ $h ] ) : '';
		}
		if ( false !== $import_scope ) {
			$file_county_id   = sanitize_text_field( $row['county_id'] ?? '' );
			$file_county_slug = sanitize_title( $row['county_slug'] ?? '' );
			$import_slug      = function_exists( 'cgop_get_county_slug' ) ? sanitize_title( cgop_get_county_slug( $import_county ) ) : '';
			if ( ! $file_county_id || ! $file_county_slug ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: county_id and county_slug are required after county adoption." );
				$result['skipped']++;
				continue;
			}
			if ( $file_county_id && $file_county_id !== $import_county ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: county_id '{$file_county_id}' does not match selected import county '{$import_county}'." );
				$result['skipped']++;
				continue;
			}
			if ( $file_county_slug && $import_slug && $file_county_slug !== $import_slug ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: county_slug '{$file_county_slug}' does not match selected import county slug '{$import_slug}'." );
				$result['skipped']++;
				continue;
			}
		}

		// Validate position_key exists.
		$position_key = sanitize_key( $raw['position_key'] );
		if ( ! $position_key ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: position_key is required." );
			if ( ! $allow_valid_only ) {
				set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
				wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
				exit;
			}
			$result['skipped']++;
			continue;
		}

		if ( ! isset( $known_pkeys[ $position_key ] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: position_key '{$position_key}' not found in registry." );
			if ( ! $allow_valid_only ) {
				set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
				wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
				exit;
			}
			$result['skipped']++;
			continue;
		}

		// Validate name.
		if ( empty( $raw['name'] ) ) {
			$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: name is required." );
			if ( ! $allow_valid_only ) {
				set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
				wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
				exit;
			}
			$result['skipped']++;
			continue;
		}

		// Validate portrait_id if provided.
		$portrait_id_raw = $raw['portrait_id'];
		if ( '' !== $portrait_id_raw ) {
			if ( ! ctype_digit( $portrait_id_raw ) || (int) $portrait_id_raw <= 0 ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: portrait_id must be a positive integer attachment ID (not a URL)." );
				if ( ! $allow_valid_only ) {
					set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
					wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
					exit;
				}
				$result['skipped']++;
				continue;
			}
			$pid = (int) $portrait_id_raw;
			if ( ! wp_attachment_is( 'image', $pid ) ) {
				$result['errors'][] = array( 'row' => $line, 'msg' => "Row {$line}: portrait_id {$pid} does not resolve to an image attachment." );
				if ( ! $allow_valid_only ) {
					set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
					wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
					exit;
				}
				$result['skipped']++;
				continue;
			}
		}

		// Generate leader_key if blank.
		$leader_key = sanitize_text_field( $raw['leader_key'] );
		if ( empty( $leader_key ) ) {
			$leader_key = function_exists( 'CGOP_generate_leader_key' ) ? CGOP_generate_leader_key() : wp_generate_uuid4();
			$result['generated_keys'][] = array( 'row' => $line, 'key' => $leader_key, 'name' => $raw['name'] );
		}

		$result['valid']++;
		$valid_rows[] = array(
			'leader_key'       => $leader_key,
			'in_db'            => isset( $existing_keys[ $leader_key ] ),
			'position_key'     => $position_key,
			'leader_status'    => in_array( sanitize_key( $raw['leader_status'] ), array( 'current', 'past' ), true ) ? sanitize_key( $raw['leader_status'] ) : 'current',
			'leader_post'      => function_exists( 'CGOP_normalize_overlay_leader_post' )
				? CGOP_normalize_overlay_leader_post( $raw['leader_post'] )
				: ( in_array( sanitize_key( $raw['leader_post'] ), array( 'published', 'unpublished' ), true ) ? sanitize_key( $raw['leader_post'] ) : 'unpublished' ),
			'name'             => sanitize_text_field( $raw['name'] ),
			'start_year'       => ( '' !== $raw['start_year'] && ctype_digit( $raw['start_year'] ) ) ? (int) $raw['start_year'] : null,
			'end_year'         => ( '' !== $raw['end_year'] && ctype_digit( $raw['end_year'] ) ) ? (int) $raw['end_year'] : null,
			'term_start'       => sanitize_text_field( $raw['term_start'] ),
			'term_end'         => sanitize_text_field( $raw['term_end'] ),
			'portrait_id'      => ( '' !== $raw['portrait_id'] && ctype_digit( $raw['portrait_id'] ) ) ? (int) $raw['portrait_id'] : null,
			'party_affiliation' => sanitize_text_field( $raw['party_affiliation'] ),
			'official_url'     => esc_url_raw( sanitize_text_field( $raw['official_url'] ) ),
			'inline_switch'    => ! empty( $raw['inline_switch'] ) && '0' !== $raw['inline_switch'] ? 1 : 0,
			'wikipedia_url'    => esc_url_raw( sanitize_text_field( $raw['wikipedia_url'] ) ),
			'facebook_url'     => esc_url_raw( sanitize_text_field( $raw['facebook_url'] ) ),
			'email'            => sanitize_email( $raw['email'] ),
			'phone'            => sanitize_text_field( $raw['phone'] ),
			'bio'              => sanitize_textarea_field( $raw['bio'] ),
		);
	}

	if ( ! $dry_run && ! empty( $result['errors'] ) && ( ! $allow_valid_only || 'replace_registry' === $mode ) ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'Import aborted: validation errors found. Replace Registry requires every row to be valid.' );
		set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
		exit;
	}

	if ( $dry_run ) {
		set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
		exit;
	}

	if ( ! function_exists( 'CGOP_overlay_insert' ) || ! function_exists( 'CGOP_overlay_update' ) ) {
		$result['errors'][] = array( 'row' => 0, 'msg' => 'Representative overlay functions not available.' );
		set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
		exit;
	}

	if ( 'replace_registry' === $mode ) {
		global $wpdb;
		$table = CGOP_overlay_table();

		// County-scoped delete: use the explicitly selected import county.
		if ( function_exists( 'cgop_get_table_county_scope' ) ) {
			$scope = cgop_get_table_county_scope( $table );
			if ( null === $scope ) {
				$result['errors'][] = array( 'row' => 0, 'msg' => 'Replace Registry requires an active county context. No rows deleted.' );
				set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
				wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
				exit;
			}
			if ( false !== $scope ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
				$delete_result = $wpdb->delete( $table, array( 'county_id' => $import_county ), array( '%s' ) );
				if ( false === $delete_result ) {
					$result['errors'][] = array( 'row' => 0, 'msg' => 'Replace Registry delete failed: ' . ( $wpdb->last_error ?: 'unknown error' ) );
					set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
					wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
					exit;
				}
			} else {
				// Pre-wizard fallback — TRUNCATE only when plugin not yet active.
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
				$wpdb->query( "TRUNCATE TABLE `{$table}`" );
			}
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$wpdb->query( "TRUNCATE TABLE `{$table}`" );
		}

		$existing_keys = array();
		foreach ( $valid_rows as $idx => $vr ) {
			$valid_rows[ $idx ]['in_db'] = false;
		}
	}

	foreach ( $valid_rows as $vr ) {
		$data = $vr;
		unset( $data['in_db'] );
		$leader_key = $data['leader_key'];
		unset( $data['leader_key'] );

		if ( $vr['in_db'] && 'insert_missing' === $mode ) {
			$result['skipped']++;
			continue;
		}

		if ( $vr['in_db'] ) {
			$update_result = CGOP_overlay_update( $leader_key, $data );
			if ( is_wp_error( $update_result ) ) {
				$result['errors'][] = array( 'row' => 0, 'msg' => "leader_key '{$leader_key}': update failed — " . $update_result->get_error_message() );
				continue;
			}
			if ( false === $update_result ) {
				$result['errors'][] = array( 'row' => 0, 'msg' => "leader_key '{$leader_key}': DB update failed." );
				continue;
			}
			$result['updated']++;
		} else {
			// Direct INSERT preserving the supplied/generated leader_key.
			$ins_table = CGOP_overlay_table();
			$cols = array(
				'leader_key', 'position_key', 'leader_status', 'leader_post', 'name',
				'term_start', 'term_end', 'party_affiliation', 'official_url', 'inline_switch',
				'wikipedia_url', 'facebook_url', 'email', 'phone', 'bio',
			);
			$vals = array(
				$leader_key,
				$data['position_key'],
				$data['leader_status'],
				$data['leader_post'],
				$data['name'],
				$data['term_start'],
				$data['term_end'],
				$data['party_affiliation'],
				$data['official_url'],
				(int) $data['inline_switch'],
				$data['wikipedia_url'],
				$data['facebook_url'],
				$data['email'],
				$data['phone'],
				$data['bio'],
			);
			$fmts = array(
				'%s', '%s', '%s', '%s', '%s',
				'%s', '%s', '%s', '%s', '%d',
				'%s', '%s', '%s', '%s', '%s',
			);

			foreach ( array( 'start_year' => '%d', 'end_year' => '%d', 'portrait_id' => '%d' ) as $col => $fmt ) {
				if ( null !== $data[ $col ] ) {
					$cols[] = $col;
					$vals[] = (int) $data[ $col ];
					$fmts[] = $fmt;
				}
			}

			// Persist county_id when the column exists.
			if ( false !== $import_scope && '' !== $import_county ) {
				$cols[] = 'county_id';
				$vals[] = $import_county;
				$fmts[] = '%s';
			}

			$col_str    = '`' . implode( '`, `', $cols ) . '`';
			$ph_str     = implode( ', ', $fmts );
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
			$ins_result = $wpdb->query( $wpdb->prepare( "INSERT INTO `{$ins_table}` ({$col_str}) VALUES ({$ph_str})", $vals ) );
			if ( false === $ins_result ) {
				$result['errors'][] = array( 'row' => 0, 'msg' => "leader_key '{$leader_key}': DB insert failed — " . ( $wpdb->last_error ?: 'unknown error' ) );
				continue;
			}
			$result['inserted']++;
		}
	}

	set_transient( 'CGOP_csv_result_overlays_' . $uid, $result, 120 );
	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'overlays' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_import_overlays', 'CGOP_csv_import_overlays_handler' );

// ---------------------------------------------------------------------------
// Export: GIS Beacon Layers
// ---------------------------------------------------------------------------

function CGOP_csv_export_gis_layers_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_export_gis_layers' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$layers = CGOP_gis_get_custom_layer_registry();

	usort( $layers, function( $a, $b ) {
		$cmp = strnatcasecmp( $a['label'] ?? '', $b['label'] ?? '' );
		if ( 0 !== $cmp ) {
			return $cmp;
		}
		return strnatcasecmp( $a['id'] ?? '', $b['id'] ?? '' );
	} );

	$filename = 'cgop-gis-beacon-layers-' . gmdate( 'Y-m-d' ) . '.csv';
	header( 'Content-Type: text/csv; charset=utf-8' );
	header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
	header( 'Pragma: no-cache' );
	header( 'Expires: 0' );

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
	$fh = fopen( 'php://output', 'w' );
	fputcsv( $fh, array( 'id', 'label', 'beacon_layer_id', 'position_type', 'level', 'output_dir', 'preferred_fields', 'position_name_template', 'position_id_template' ) );

	foreach ( $layers as $layer ) {
		$pf = $layer['preferred_fields'] ?? array();
		if ( is_array( $pf ) ) {
			$pf = implode( ', ', $pf );
		}
		fputcsv( $fh, array(
			$layer['id']                    ?? '',
			$layer['label']                 ?? '',
			$layer['beacon_layer_id']       ?? '',
			$layer['position_type']         ?? '',
			$layer['level']                 ?? '',
			$layer['output_dir']            ?? '',
			$pf,
			$layer['position_name_template'] ?? '',
			$layer['position_id_template']   ?? '',
		) );
	}

	fclose( $fh );
	exit;
}
add_action( 'admin_post_CGOP_csv_export_gis_layers', 'CGOP_csv_export_gis_layers_handler' );

// ---------------------------------------------------------------------------
// Export: Generated GIS Maps
// ---------------------------------------------------------------------------

function CGOP_csv_export_gis_maps_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_export_gis_maps' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$status = array();
	if ( defined( 'CGOP_GIS_IMPORT_STATUS_OPTION' ) ) {
		$status = get_option( CGOP_GIS_IMPORT_STATUS_OPTION, array() );
	}

	$filename = 'cgop-gis-generated-maps-' . gmdate( 'Y-m-d' ) . '.csv';
	header( 'Content-Type: text/csv; charset=utf-8' );
	header( 'Content-Disposition: attachment; filename="' . $filename . '"' );
	header( 'Pragma: no-cache' );
	header( 'Expires: 0' );

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
	$fh = fopen( 'php://output', 'w' );
	fputcsv( $fh, array( 'generated_map_id', 'layer_id', 'layer_label', 'beacon_layer_id', 'generated_at', 'position_id', 'label', 'source_key', 'file', 'url', 'existing_assignment_map_ids', 'existing_assignment_position_keys' ) );

	$map_rows_by_url = array();
	foreach ( CGOP_get_map_boundary_rows() as $map_row ) {
		$resolved_url = CGOP_resolve_map_boundary_json_url( $map_row->json_file ?? '' );
		if ( '' === $resolved_url ) {
			continue;
		}
		if ( ! isset( $map_rows_by_url[ $resolved_url ] ) ) {
			$map_rows_by_url[ $resolved_url ] = array();
		}
		$map_rows_by_url[ $resolved_url ][] = $map_row;
	}

	if ( ! empty( $status['layers'] ) && is_array( $status['layers'] ) ) {
		foreach ( $status['layers'] as $layer ) {
			$layer_id        = $layer['layer_id']        ?? '';
			$layer_label     = $layer['label']           ?? '';
			$beacon_layer_id = $layer['beacon_layer_id'] ?? '';
			$generated_at    = $layer['generated_at']    ?? '';
			$files           = $layer['files']           ?? array();
			foreach ( (array) $files as $frow ) {
				$rel_file = $frow['file'] ?? '';
				$url      = '' !== $rel_file ? CGOP_gis_get_generated_file_url( $rel_file ) : '';
				$assigned_map_ids       = array();
				$assigned_position_keys = array();
				if ( $url && ! empty( $map_rows_by_url[ $url ] ) ) {
					foreach ( $map_rows_by_url[ $url ] as $map_row ) {
						$assigned_map_ids[]       = $map_row->map_id ?? '';
						$assigned_position_keys[] = $map_row->position_key ?? '';
					}
				}
				fputcsv( $fh, array(
					'generated:' . ltrim( (string) $rel_file, '/' ),
					$layer_id,
					$layer_label,
					$beacon_layer_id,
					$generated_at,
					$frow['position_id'] ?? '',
					$frow['label']       ?? '',
					$frow['source_key']  ?? '',
					$rel_file,
					$url,
					implode( ', ', array_filter( $assigned_map_ids ) ),
					implode( ', ', array_filter( $assigned_position_keys ) ),
				) );
			}
		}
	}

	fclose( $fh );
	exit;
}
add_action( 'admin_post_CGOP_csv_export_gis_maps', 'CGOP_csv_export_gis_maps_handler' );

// ---------------------------------------------------------------------------
// Import: GIS Beacon Layers
// ---------------------------------------------------------------------------

function CGOP_csv_import_gis_layers_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_import_gis_layers' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$mode = isset( $_POST['gis_layers_import_mode'] ) ? sanitize_key( wp_unslash( $_POST['gis_layers_import_mode'] ) ) : 'dry_run';
	if ( ! in_array( $mode, array( 'dry_run', 'insert_missing', 'update_existing', 'replace_registry' ), true ) ) {
		$mode = 'dry_run';
	}

	$result = array(
		'parsed'   => 0,
		'valid'    => 0,
		'skipped'  => 0,
		'inserted' => 0,
		'updated'  => 0,
		'errors'   => array(),
	);

	$parsed_rows = CGOP_csv_tools_parse_upload( 'gis_layers_csv_file' );
	if ( is_wp_error( $parsed_rows ) ) {
		$result['errors'][] = $parsed_rows->get_error_message();
		$uid = get_current_user_id();
		set_transient( 'CGOP_csv_result_gis_layers_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'gis_layers' ) ) );
		exit;
	}

	$required_cols = array( 'label', 'beacon_layer_id', 'position_id_template' );
	$seen_ids      = array();
	$valid_rows    = array();

	foreach ( $parsed_rows as $row_num => $row ) {
		$result['parsed']++;
		$row_label = 'Row ' . ( $row_num + 2 );

		$missing = array();
		foreach ( $required_cols as $col ) {
			if ( ! isset( $row[ $col ] ) || '' === trim( $row[ $col ] ) ) {
				$missing[] = $col;
			}
		}
		if ( $missing ) {
			$result['errors'][] = $row_label . ': missing required column(s): ' . implode( ', ', $missing );
			continue;
		}

		$beacon_id = (int) trim( $row['beacon_layer_id'] );
		if ( $beacon_id <= 0 ) {
			$result['errors'][] = $row_label . ': beacon_layer_id must be a positive integer.';
			continue;
		}

		$raw_id = isset( $row['id'] ) ? trim( $row['id'] ) : '';
		if ( '' !== $raw_id && isset( $seen_ids[ $raw_id ] ) ) {
			$result['errors'][] = $row_label . ': duplicate id "' . $raw_id . '" in CSV (first seen at row ' . $seen_ids[ $raw_id ] . ').';
			continue;
		}
		if ( '' !== $raw_id ) {
			$seen_ids[ $raw_id ] = $row_num + 2;
		}

		$pf_raw = isset( $row['preferred_fields'] ) ? trim( $row['preferred_fields'] ) : '';
		$pf_raw = str_replace( '|', ',', $pf_raw );

		$raw_config = array(
			'id'                     => $raw_id,
			'label'                  => trim( $row['label'] ),
			'beacon_layer_id'        => $beacon_id,
			'position_type'          => isset( $row['position_type'] )        ? trim( $row['position_type'] )        : '',
			'level'                  => isset( $row['level'] )                 ? trim( $row['level'] )                 : '',
			'output_dir'             => isset( $row['output_dir'] )            ? trim( $row['output_dir'] )            : '',
			'preferred_fields'       => $pf_raw,
			'position_name_template' => isset( $row['position_name_template'] ) ? trim( $row['position_name_template'] ) : '',
			'position_id_template'   => trim( $row['position_id_template'] ),
		);

		$normalized = CGOP_gis_normalize_layer_config( $raw_config );

		$result['valid']++;
		$valid_rows[] = $normalized;
	}

	if ( 'dry_run' === $mode ) {
		$uid = get_current_user_id();
		set_transient( 'CGOP_csv_result_gis_layers_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'gis_layers' ) ) );
		exit;
	}

	$option_key = defined( 'CGOP_GIS_IMPORT_CUSTOM_LAYERS_OPTION' )
		? CGOP_GIS_IMPORT_CUSTOM_LAYERS_OPTION
		: 'CGOP_gis_boundary_import_custom_layers';

	$existing_registry = CGOP_gis_get_custom_layer_registry();

	$existing_by_id = array();
	foreach ( $existing_registry as $el ) {
		$existing_by_id[ $el['id'] ] = $el;
	}

	if ( 'replace_registry' === $mode ) {
		$new_registry = array();
		foreach ( $valid_rows as $vrow ) {
			$new_registry[ $vrow['id'] ] = $vrow;
			$result['inserted']++;
		}
		update_option( $option_key, array_values( $new_registry ) );
	} else {
		$merged = $existing_by_id;
		foreach ( $valid_rows as $vrow ) {
			$vid = $vrow['id'];
			if ( 'insert_missing' === $mode ) {
				if ( isset( $merged[ $vid ] ) ) {
					$result['skipped']++;
				} else {
					$merged[ $vid ] = $vrow;
					$result['inserted']++;
				}
			} elseif ( 'update_existing' === $mode ) {
				if ( isset( $merged[ $vid ] ) ) {
					$merged[ $vid ] = $vrow;
					$result['updated']++;
				} else {
					$merged[ $vid ] = $vrow;
					$result['inserted']++;
				}
			}
		}
		update_option( $option_key, array_values( $merged ) );
	}

	$uid = get_current_user_id();
	set_transient( 'CGOP_csv_result_gis_layers_' . $uid, $result, 120 );
	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'gis_layers' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_import_gis_layers', 'CGOP_csv_import_gis_layers_handler' );

// ---------------------------------------------------------------------------
// Import: Generated GIS Maps
// ---------------------------------------------------------------------------

function CGOP_csv_import_gis_maps_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_import_gis_maps' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}

	$mode = isset( $_POST['gis_maps_import_mode'] ) ? sanitize_key( wp_unslash( $_POST['gis_maps_import_mode'] ) ) : 'dry_run';
	if ( ! in_array( $mode, array( 'dry_run', 'upsert_status', 'replace_status' ), true ) ) {
		$mode = 'dry_run';
	}

	$result = array(
		'parsed'   => 0,
		'valid'    => 0,
		'skipped'  => 0,
		'inserted' => 0,
		'updated'  => 0,
		'errors'   => array(),
	);

	$parsed_rows = CGOP_csv_tools_parse_upload( 'gis_maps_csv_file' );
	if ( is_wp_error( $parsed_rows ) ) {
		$result['errors'][] = $parsed_rows->get_error_message();
		$uid = get_current_user_id();
		set_transient( 'CGOP_csv_result_gis_maps_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'gis_maps' ) ) );
		exit;
	}

	$required_cols     = array( 'layer_id', 'layer_label', 'beacon_layer_id', 'position_id', 'label', 'file' );
	$generated_dir     = CGOP_gis_get_generated_dir();
	$is_dry_run        = ( 'dry_run' === $mode );
	$seen_layer_file   = array();
	$valid_rows        = array();

	foreach ( $parsed_rows as $row_num => $row ) {
		$result['parsed']++;
		$row_label = 'Row ' . ( $row_num + 2 );

		$missing = array();
		foreach ( $required_cols as $col ) {
			if ( ! isset( $row[ $col ] ) || '' === trim( $row[ $col ] ) ) {
				$missing[] = $col;
			}
		}
		if ( $missing ) {
			$result['errors'][] = $row_label . ': missing required column(s): ' . implode( ', ', $missing );
			continue;
		}

		$beacon_id = (int) trim( $row['beacon_layer_id'] );
		if ( $beacon_id <= 0 ) {
			$result['errors'][] = $row_label . ': beacon_layer_id must be a positive integer.';
			continue;
		}

		$rel_file = trim( $row['file'] );
		if ( false !== strpos( $rel_file, '..' ) ) {
			$result['errors'][] = $row_label . ': file path must not contain "..".';
			continue;
		}
		$ext = strtolower( pathinfo( $rel_file, PATHINFO_EXTENSION ) );
		if ( ! in_array( $ext, array( 'geojson', 'json' ), true ) ) {
			$result['errors'][] = $row_label . ': file must end in .geojson or .json.';
			continue;
		}
		if ( '' !== $generated_dir ) {
			$abs_file = rtrim( $generated_dir, '/\\' ) . DIRECTORY_SEPARATOR . ltrim( $rel_file, '/\\' );
			$real_gen = realpath( $generated_dir );
			$real_abs = realpath( dirname( $abs_file ) );
			if ( false !== $real_gen && false !== $real_abs && 0 !== strpos( $real_abs . DIRECTORY_SEPARATOR, $real_gen . DIRECTORY_SEPARATOR ) ) {
				$result['errors'][] = $row_label . ': file path resolves outside the generated directory.';
				continue;
			}
			if ( ! $is_dry_run && ! file_exists( $abs_file ) ) {
				$result['errors'][] = $row_label . ': generated file does not exist on disk: ' . $rel_file;
				continue;
			}
		}

		$layer_id   = sanitize_key( trim( $row['layer_id'] ) );
		$combo_key  = $layer_id . '|||' . $rel_file;
		if ( isset( $seen_layer_file[ $combo_key ] ) ) {
			$result['errors'][] = $row_label . ': duplicate layer_id + file combination (first seen at row ' . $seen_layer_file[ $combo_key ] . ').';
			continue;
		}
		$seen_layer_file[ $combo_key ] = $row_num + 2;

		$result['valid']++;
		$valid_rows[] = array(
			'layer_id'        => $layer_id,
			'layer_label'     => sanitize_text_field( trim( $row['layer_label'] ) ),
			'beacon_layer_id' => $beacon_id,
			'generated_at'    => isset( $row['generated_at'] ) ? sanitize_text_field( trim( $row['generated_at'] ) ) : '',
			'position_id'     => sanitize_text_field( trim( $row['position_id'] ) ),
			'label'           => sanitize_text_field( trim( $row['label'] ) ),
			'source_key'      => isset( $row['source_key'] ) ? sanitize_text_field( trim( $row['source_key'] ) ) : '',
			'file'            => $rel_file,
		);
	}

	if ( $is_dry_run ) {
		$uid = get_current_user_id();
		set_transient( 'CGOP_csv_result_gis_maps_' . $uid, $result, 120 );
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'gis_maps' ) ) );
		exit;
	}

	$status_option = defined( 'CGOP_GIS_IMPORT_STATUS_OPTION' )
		? CGOP_GIS_IMPORT_STATUS_OPTION
		: 'CGOP_gis_boundary_import_status';

	$existing_status = get_option( $status_option, array() );
	$existing_layers = ( ! empty( $existing_status['layers'] ) && is_array( $existing_status['layers'] ) )
		? $existing_status['layers']
		: array();

	if ( 'replace_status' === $mode ) {
		$new_layers = array();
		foreach ( $valid_rows as $vrow ) {
			$lid = $vrow['layer_id'];
			if ( ! isset( $new_layers[ $lid ] ) ) {
				$new_layers[ $lid ] = array(
					'layer_id'        => $lid,
					'label'           => $vrow['layer_label'],
					'beacon_layer_id' => $vrow['beacon_layer_id'],
					'generated_at'    => '' !== $vrow['generated_at'] ? $vrow['generated_at'] : gmdate( 'Y-m-d\TH:i:s\Z' ),
					'feature_count'   => 0,
					'files'           => array(),
				);
			}
			if ( '' !== $vrow['generated_at'] ) {
				$new_layers[ $lid ]['generated_at'] = $vrow['generated_at'];
			}
			$new_layers[ $lid ]['files'][] = array(
				'position_id' => $vrow['position_id'],
				'label'       => $vrow['label'],
				'source_key'  => $vrow['source_key'],
				'file'        => $vrow['file'],
			);
			$result['inserted']++;
		}
		foreach ( $new_layers as &$nl ) {
			$nl['feature_count'] = count( $nl['files'] );
		}
		unset( $nl );
		$new_status = array( 'layers' => $new_layers );
	} else {
		// upsert_status
		$merged_layers = $existing_layers;
		foreach ( $valid_rows as $vrow ) {
			$lid = $vrow['layer_id'];
			if ( ! isset( $merged_layers[ $lid ] ) ) {
				$merged_layers[ $lid ] = array(
					'layer_id'        => $lid,
					'label'           => $vrow['layer_label'],
					'beacon_layer_id' => $vrow['beacon_layer_id'],
					'generated_at'    => '' !== $vrow['generated_at'] ? $vrow['generated_at'] : gmdate( 'Y-m-d\TH:i:s\Z' ),
					'feature_count'   => 0,
					'files'           => array(),
				);
			}
			if ( '' !== $vrow['generated_at'] ) {
				$merged_layers[ $lid ]['generated_at'] = $vrow['generated_at'];
			}
			$new_file_entry = array(
				'position_id' => $vrow['position_id'],
				'label'       => $vrow['label'],
				'source_key'  => $vrow['source_key'],
				'file'        => $vrow['file'],
			);
			$found = false;
			foreach ( $merged_layers[ $lid ]['files'] as $fi => $existing_file ) {
				if ( ( $existing_file['file'] ?? '' ) === $vrow['file'] ) {
					$merged_layers[ $lid ]['files'][ $fi ] = $new_file_entry;
					$result['updated']++;
					$found = true;
					break;
				}
			}
			if ( ! $found ) {
				$merged_layers[ $lid ]['files'][] = $new_file_entry;
				$result['inserted']++;
			}
		}
		foreach ( $merged_layers as &$ml ) {
			$ml['feature_count'] = count( $ml['files'] );
		}
		unset( $ml );
		$new_status = array( 'layers' => $merged_layers );
	}

	update_option( $status_option, $new_status );
	CGOP_gis_write_manifest( $new_status );

	$uid = get_current_user_id();
	set_transient( 'CGOP_csv_result_gis_maps_' . $uid, $result, 120 );
	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'csv_result' => 'gis_maps' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_import_gis_maps', 'CGOP_csv_import_gis_maps_handler' );

// ---------------------------------------------------------------------------
// Wipe: GIS Beacon Layers
// ---------------------------------------------------------------------------

function CGOP_csv_wipe_gis_layers_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_wipe_gis_layers' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}
	$typed = isset( $_POST['wipe_confirm_gis_layers'] ) ? trim( (string) wp_unslash( $_POST['wipe_confirm_gis_layers'] ) ) : '';
	if ( 'WIPE GIS BEACON LAYERS' !== $typed ) {
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_error' => 'gis_layers_confirm' ) ) );
		exit;
	}

	$option_key = defined( 'CGOP_GIS_IMPORT_CUSTOM_LAYERS_OPTION' )
		? CGOP_GIS_IMPORT_CUSTOM_LAYERS_OPTION
		: 'CGOP_gis_boundary_import_custom_layers';
	update_option( $option_key, array() );

	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_done' => 'gis_layers' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_wipe_gis_layers', 'CGOP_csv_wipe_gis_layers_handler' );

// ---------------------------------------------------------------------------
// Wipe: Generated GIS Map Status
// ---------------------------------------------------------------------------

function CGOP_csv_wipe_gis_maps_handler() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission.', 'county-gop-core' ) );
	}
	$nonce = isset( $_POST['_wpnonce'] ) ? wp_unslash( $_POST['_wpnonce'] ) : '';
	if ( ! wp_verify_nonce( $nonce, 'CGOP_csv_wipe_gis_maps' ) ) {
		wp_die( esc_html__( 'Security check failed.', 'county-gop-core' ) );
	}
	$typed = isset( $_POST['wipe_confirm_gis_maps'] ) ? trim( (string) wp_unslash( $_POST['wipe_confirm_gis_maps'] ) ) : '';
	if ( 'WIPE GIS GENERATED MAP STATUS' !== $typed ) {
		wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_error' => 'gis_maps_confirm' ) ) );
		exit;
	}

	$status_option = defined( 'CGOP_GIS_IMPORT_STATUS_OPTION' )
		? CGOP_GIS_IMPORT_STATUS_OPTION
		: 'CGOP_gis_boundary_import_status';
	$empty_status = array( 'layers' => array() );
	update_option( $status_option, $empty_status );
	CGOP_gis_write_manifest( $empty_status );

	wp_safe_redirect( CGOP_csv_tools_admin_url( array( 'wipe_done' => 'gis_maps' ) ) );
	exit;
}
add_action( 'admin_post_CGOP_csv_wipe_gis_maps', 'CGOP_csv_wipe_gis_maps_handler' );

// ---------------------------------------------------------------------------
// Admin screen
// ---------------------------------------------------------------------------

function CGOP_csv_tools_screen() {
	if ( ! current_user_can( CGOP_POSITION_KEYS_CAP ) ) {
		wp_die( esc_html__( 'You do not have permission to view this page.', 'county-gop-core' ) );
	}

	$uid              = get_current_user_id();
	$pkeys_result     = get_transient( 'CGOP_csv_result_pkeys_' . $uid );
	$reps_result      = get_transient( 'CGOP_csv_result_reps_' . $uid );
	$overlays_result  = get_transient( 'CGOP_csv_result_overlays_' . $uid );
	$gis_layers_result = get_transient( 'CGOP_csv_result_gis_layers_' . $uid );
	$gis_maps_result   = get_transient( 'CGOP_csv_result_gis_maps_' . $uid );
	if ( $pkeys_result ) {
		delete_transient( 'CGOP_csv_result_pkeys_' . $uid );
	}
	if ( $reps_result ) {
		delete_transient( 'CGOP_csv_result_reps_' . $uid );
	}
	if ( $overlays_result ) {
		delete_transient( 'CGOP_csv_result_overlays_' . $uid );
	}
	if ( $gis_layers_result ) {
		delete_transient( 'CGOP_csv_result_gis_layers_' . $uid );
	}
	if ( $gis_maps_result ) {
		delete_transient( 'CGOP_csv_result_gis_maps_' . $uid );
	}

	$counts = CGOP_csv_tools_get_counts();
	?>
	<div class="wrap cgop-csv-tools">
		<h1><?php esc_html_e( 'Representative Data CSV Tools', 'county-gop-core' ); ?></h1>

		<style>
			.cgop-csv-tools {
				max-width: 1220px;
			}

			.cgop-csv-tools .notice.inline {
				margin-left: 0;
			}

			.cgop-csv-counts {
				display: grid;
				grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
				gap: 12px;
				margin: 18px 0;
				max-width: 920px;
			}

			.cgop-csv-count {
				background: #fff;
				border: 1px solid #dcdcde;
				border-left: 4px solid #7b2428;
				border-radius: 3px;
				padding: 14px 16px;
			}

			.cgop-csv-count strong {
				display: block;
				color: #1d2327;
				font-size: 26px;
				line-height: 1.1;
				margin-bottom: 4px;
			}

			.cgop-csv-count span {
				color: #50575e;
				font-size: 13px;
			}

			.cgop-csv-section {
				background: #fff;
				border: 1px solid #dcdcde;
				border-radius: 4px;
				box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
				margin: 20px 0;
				max-width: 980px;
				padding: 0;
			}

			.cgop-csv-section__header {
				background: #f6f7f7;
				border-bottom: 1px solid #dcdcde;
				padding: 14px 18px;
			}

			.cgop-csv-section__header h2 {
				margin: 0;
			}

			.cgop-csv-section__header p {
				margin: 6px 0 0;
				color: #50575e;
			}

			.cgop-csv-section__body {
				padding: 18px;
			}

			.cgop-csv-action-grid {
				display: grid;
				grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
				gap: 18px;
			}

			.cgop-csv-action {
				border: 1px solid #dcdcde;
				border-radius: 4px;
				padding: 16px;
			}

			.cgop-csv-action h3 {
				font-size: 15px;
				margin: 0 0 8px;
			}

			.cgop-csv-action .form-table {
				margin-top: 8px;
			}

			.cgop-csv-action .form-table th {
				width: 145px;
			}

			.cgop-csv-radio-list label {
				display: block;
				margin: 0 0 7px;
			}

			.cgop-csv-workflow {
				column-count: 2;
				column-gap: 34px;
				margin-bottom: 0;
				max-width: 760px;
			}

			.cgop-csv-workflow li {
				break-inside: avoid;
				margin-bottom: 8px;
			}

			.cgop-csv-tools > h2 {
				background: #fff;
				border: 1px solid #dcdcde;
				border-left: 4px solid #7b2428;
				border-radius: 4px 4px 0 0;
				margin: 22px 0 0;
				max-width: 940px;
				padding: 14px 18px;
			}

			.cgop-csv-tools > h3 {
				background: #f6f7f7;
				border: 1px solid #dcdcde;
				border-bottom: 0;
				margin: 16px 0 0;
				max-width: 900px;
				padding: 12px 18px;
			}

			.cgop-csv-tools > h2 + p,
			.cgop-csv-tools > h3 + p,
			.cgop-csv-tools > h2 + .description,
			.cgop-csv-tools > h3 + .description,
			.cgop-csv-tools > h2 + p + form,
			.cgop-csv-tools > h3 + p + form,
			.cgop-csv-tools > form {
				background: #fff;
				border-left: 1px solid #dcdcde;
				border-right: 1px solid #dcdcde;
				margin: 0;
				max-width: 940px;
				padding: 12px 18px 16px;
			}

			.cgop-csv-tools > form {
				border-bottom: 1px solid #dcdcde;
				border-radius: 0 0 4px 4px;
				margin-bottom: 16px;
			}

			.cgop-csv-tools > .description + h3,
			.cgop-csv-tools > form + h3 {
				margin-top: 18px;
			}

			.cgop-csv-tools > hr {
				border: 0;
				border-top: 1px solid #dcdcde;
				margin: 24px 0;
				max-width: 980px;
			}

			.cgop-csv-tools .form-table {
				margin-top: 0;
			}

			.cgop-csv-tools .form-table th {
				padding-left: 0;
				width: 170px;
			}

			.cgop-csv-tools .form-table td label {
				display: block;
				margin-bottom: 7px;
			}

			.cgop-csv-danger {
				border-color: #d63638;
			}

			.cgop-csv-danger .cgop-csv-section__header {
				background: #fcf0f1;
				border-bottom-color: #f1c4c6;
			}

			.cgop-csv-danger-grid {
				display: grid;
				grid-template-columns: repeat(auto-fit, minmax(360px, 1fr));
				gap: 18px;
			}

			.cgop-csv-danger-card {
				background: #fff8f8;
				border: 1px solid #d63638;
				border-radius: 4px;
				padding: 16px;
			}

			.cgop-csv-danger-card h3 {
				margin-top: 0;
			}
		</style>

		<?php
		$_wipe_done  = isset( $_GET['wipe_done'] )  ? sanitize_key( $_GET['wipe_done'] )  : '';
		$_wipe_error = isset( $_GET['wipe_error'] ) ? sanitize_key( $_GET['wipe_error'] ) : '';
		if ( 'pkeys' === $_wipe_done ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Position key registry wiped.', 'county-gop-core' ); ?></p></div>
		<?php elseif ( 'overlays' === $_wipe_done ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Representative overlays wiped.', 'county-gop-core' ); ?></p></div>
		<?php elseif ( 'gis_layers' === $_wipe_done ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'GIS Beacon Layer registry wiped.', 'county-gop-core' ); ?></p></div>
		<?php elseif ( 'gis_maps' === $_wipe_done ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'GIS Generated Map Status wiped and manifest updated.', 'county-gop-core' ); ?></p></div>
		<?php elseif ( 'pkeys_confirm' === $_wipe_error ) : ?>
			<div class="notice notice-error is-dismissible"><p><?php esc_html_e( 'Wipe cancelled: type exactly WIPE POSITION KEYS to confirm.', 'county-gop-core' ); ?></p></div>
		<?php elseif ( 'overlays_confirm' === $_wipe_error ) : ?>
			<div class="notice notice-error is-dismissible"><p><?php esc_html_e( 'Wipe cancelled: type exactly WIPE REPRESENTATIVE OVERLAYS to confirm.', 'county-gop-core' ); ?></p></div>
		<?php elseif ( 'gis_layers_confirm' === $_wipe_error ) : ?>
			<div class="notice notice-error is-dismissible"><p><?php esc_html_e( 'Wipe cancelled: type exactly WIPE GIS BEACON LAYERS to confirm.', 'county-gop-core' ); ?></p></div>
		<?php elseif ( 'gis_maps_confirm' === $_wipe_error ) : ?>
			<div class="notice notice-error is-dismissible"><p><?php esc_html_e( 'Wipe cancelled: type exactly WIPE GIS GENERATED MAP STATUS to confirm.', 'county-gop-core' ); ?></p></div>
		<?php endif; ?>

		<div class="cgop-csv-counts" aria-label="<?php esc_attr_e( 'CSV data counts', 'county-gop-core' ); ?>">
			<div class="cgop-csv-count">
				<strong><?php echo (int) $counts['position_keys']; ?></strong>
				<span><?php esc_html_e( 'Position keys', 'county-gop-core' ); ?></span>
			</div>
			<div class="cgop-csv-count">
				<strong><?php echo (int) ( $counts['overlay_count'] ?? 0 ); ?></strong>
				<span><?php esc_html_e( 'Representative overlays', 'county-gop-core' ); ?></span>
			</div>
			<div class="cgop-csv-count">
				<strong><?php echo (int) ( $counts['gis_beacon_layers'] ?? 0 ); ?></strong>
				<span><?php esc_html_e( 'GIS Beacon layers', 'county-gop-core' ); ?></span>
			</div>
			<div class="cgop-csv-count">
				<strong><?php echo (int) ( $counts['gis_generated_files'] ?? 0 ); ?></strong>
				<span><?php esc_html_e( 'Generated map files', 'county-gop-core' ); ?></span>
			</div>
		</div>

		<div class="notice notice-warning inline" style="max-width:700px;">
			<p><strong><?php esc_html_e( 'Current workflow only.', 'county-gop-core' ); ?></strong>
			<?php esc_html_e( 'Position Keys create the cards. Representative Overlays put people on those cards. Legacy CSV tools are intentionally hidden from this page.', 'county-gop-core' ); ?></p>
		</div>

		<div class="cgop-csv-section">
			<div class="cgop-csv-section__header">
				<h2><?php esc_html_e( 'Clean Reload Workflow', 'county-gop-core' ); ?></h2>
				<p><?php esc_html_e( 'Use this order when exporting, editing, and loading cleaned representative data.', 'county-gop-core' ); ?></p>
			</div>
			<div class="cgop-csv-section__body">
				<ol class="cgop-csv-workflow">
					<li><?php esc_html_e( 'Export Position Keys CSV.', 'county-gop-core' ); ?></li>
					<li><?php esc_html_e( 'Export Representative Overlays CSV.', 'county-gop-core' ); ?></li>
					<li><?php esc_html_e( 'Edit CSV files locally.', 'county-gop-core' ); ?></li>
					<li><?php esc_html_e( 'Dry-run Position Keys import.', 'county-gop-core' ); ?></li>
					<li><?php esc_html_e( 'Dry-run Representative Overlays import.', 'county-gop-core' ); ?></li>
					<li><?php esc_html_e( 'Import cleaned Position Keys CSV.', 'county-gop-core' ); ?></li>
					<li><?php esc_html_e( 'Import cleaned Representative Overlays CSV.', 'county-gop-core' ); ?></li>
					<li><?php esc_html_e( 'Visit the Representatives page and verify.', 'county-gop-core' ); ?></li>
				</ol>
			</div>
		</div>

		<h2><?php esc_html_e( 'Export Position Keys', 'county-gop-core' ); ?></h2>
		<p><?php printf( esc_html__( '%d position key(s) will be exported.', 'county-gop-core' ), (int) $counts['position_keys'] ); ?></p>
		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="CGOP_csv_export_pkeys">
			<?php wp_nonce_field( 'CGOP_csv_export_pkeys' ); ?>
			<?php $pkeys_export_selector = CGOP_csv_county_selector_row( 'export_county_id', '', true );
			if ( $pkeys_export_selector ) : ?>
			<table class="form-table" style="max-width:700px;"><?php echo $pkeys_export_selector; // phpcs:ignore WordPress.Security.EscapeOutput ?></table>
			<?php endif; ?>
			<?php submit_button( __( 'Download Position Keys CSV', 'county-gop-core' ), 'secondary', 'submit', false ); ?>
		</form>

		<hr>

		<h2><?php esc_html_e( 'Import Position Keys', 'county-gop-core' ); ?></h2>

		<?php if ( $pkeys_result ) {
			CGOP_csv_tools_render_result( $pkeys_result, __( 'Position Keys', 'county-gop-core' ) );
			if ( ! empty( $pkeys_result['generated_keys'] ) ) :
				?>
				<div class="notice notice-info inline" style="max-width:700px;margin-bottom:1em;">
					<p><strong><?php esc_html_e( 'Generated position IDs for blank rows:', 'county-gop-core' ); ?></strong></p>
					<ul style="margin:.25em 0 .5em 1.5em;font-family:monospace;font-size:12px;">
						<?php foreach ( $pkeys_result['generated_keys'] as $gk ) : ?>
							<li><?php printf( esc_html__( 'Row %1$d: %2$s', 'county-gop-core' ), (int) $gk['row'], esc_html( $gk['key'] ) ); ?></li>
						<?php endforeach; ?>
					</ul>
				</div>
				<?php
			endif;
		} ?>

		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data">
			<input type="hidden" name="action" value="CGOP_csv_import_pkeys">
			<?php wp_nonce_field( 'CGOP_csv_import_pkeys' ); ?>
			<p class="description" style="max-width:700px;">
				<?php esc_html_e( 'The position_key column may be blank for new rows. Blank values are assigned UUID-style position IDs during dry run and import. The live_in_map and voting_area_map columns accept existing Map Boundary IDs, or generated map IDs from the Generated GIS Maps export in the form generated:path/to/file.geojson.', 'county-gop-core' ); ?>
			</p>
			<table class="form-table" style="max-width:700px;">
				<tr>
					<th scope="row"><label for="pkeys_csv_file"><?php esc_html_e( 'Position Keys CSV', 'county-gop-core' ); ?></label></th>
					<td><input type="file" id="pkeys_csv_file" name="pkeys_csv_file" accept=".csv,text/csv" required></td>
				</tr>
				<?php echo CGOP_csv_county_selector_row( 'import_county_id', '', false ); // phpcs:ignore WordPress.Security.EscapeOutput ?>
				<tr>
					<th scope="row"><?php esc_html_e( 'Import Mode', 'county-gop-core' ); ?></th>
					<td>
						<label><input type="radio" name="pkeys_import_mode" value="dry_run" checked> <?php esc_html_e( 'Dry Run - parse and report counts/errors, no writes', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="pkeys_import_mode" value="insert_missing"> <?php esc_html_e( 'Insert Missing - add rows not already in registry', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="pkeys_import_mode" value="update_existing"> <?php esc_html_e( 'Update Existing - upsert all rows', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="pkeys_import_mode" value="replace_registry"> <?php esc_html_e( 'Replace Registry - truncate position key table then import', 'county-gop-core' ); ?></label>
					</td>
				</tr>
				<tr>
					<th scope="row"></th>
					<td><label><input type="checkbox" name="pkeys_import_valid_only" value="1"> <?php esc_html_e( 'Import valid rows only - skip invalid rows instead of aborting', 'county-gop-core' ); ?></label></td>
				</tr>
			</table>
			<?php submit_button( __( 'Import Position Keys', 'county-gop-core' ) ); ?>
		</form>

		<hr>

		<h2><?php esc_html_e( 'Export Representative Overlays', 'county-gop-core' ); ?></h2>
		<p><?php printf( esc_html__( '%d overlay row(s) will be exported.', 'county-gop-core' ), (int) ( $counts['overlay_count'] ?? 0 ) ); ?></p>
		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="CGOP_csv_export_overlays">
			<?php wp_nonce_field( 'CGOP_csv_export_overlays' ); ?>
			<?php $ov_export_selector = CGOP_csv_county_selector_row( 'export_county_id', '', true );
			if ( $ov_export_selector ) : ?>
			<table class="form-table" style="max-width:700px;"><?php echo $ov_export_selector; // phpcs:ignore WordPress.Security.EscapeOutput ?></table>
			<?php endif; ?>
			<?php submit_button( __( 'Download Representative Overlays CSV', 'county-gop-core' ), 'secondary', 'submit', false ); ?>
		</form>

		<hr>

		<h2><?php esc_html_e( 'Import Representative Overlays', 'county-gop-core' ); ?></h2>

		<?php if ( $overlays_result ) {
			CGOP_csv_tools_render_result( $overlays_result, __( 'Representative Overlays', 'county-gop-core' ) );
			if ( ! empty( $overlays_result['generated_keys'] ) ) :
				?>
				<div class="notice notice-info inline" style="max-width:700px;margin-bottom:1em;">
					<p><strong><?php esc_html_e( 'Generated leader keys for blank rows:', 'county-gop-core' ); ?></strong></p>
					<ul style="margin:.25em 0 .5em 1.5em;font-family:monospace;font-size:12px;">
						<?php foreach ( $overlays_result['generated_keys'] as $gk ) : ?>
							<li><?php printf( esc_html__( 'Row %1$d - %2$s: %3$s', 'county-gop-core' ), (int) $gk['row'], esc_html( $gk['name'] ), esc_html( $gk['key'] ) ); ?></li>
						<?php endforeach; ?>
					</ul>
				</div>
				<?php
			endif;
		} ?>

		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data">
			<input type="hidden" name="action" value="CGOP_csv_import_overlays">
			<?php wp_nonce_field( 'CGOP_csv_import_overlays' ); ?>
			<p class="description" style="max-width:700px;">
				<?php esc_html_e( 'The party_affiliation column accepts the standard party keys or any custom party label. Custom values are imported as written after normal text sanitizing.', 'county-gop-core' ); ?>
			</p>
			<table class="form-table" style="max-width:700px;">
				<tr>
					<th scope="row"><label for="overlays_csv_file"><?php esc_html_e( 'Representative Overlays CSV', 'county-gop-core' ); ?></label></th>
					<td><input type="file" id="overlays_csv_file" name="overlays_csv_file" accept=".csv,text/csv" required></td>
				</tr>
				<?php echo CGOP_csv_county_selector_row( 'overlays_import_county_id', '', false ); // phpcs:ignore WordPress.Security.EscapeOutput ?>
				<tr>
					<th scope="row"><?php esc_html_e( 'Import Mode', 'county-gop-core' ); ?></th>
					<td>
						<label><input type="radio" name="overlays_import_mode" value="dry_run" checked> <?php esc_html_e( 'Dry Run - parse and validate only, no writes', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="overlays_import_mode" value="insert_missing"> <?php esc_html_e( 'Insert Missing - add overlay rows not already in the table', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="overlays_import_mode" value="update_existing"> <?php esc_html_e( 'Update Existing - update matching rows and insert missing rows', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="overlays_import_mode" value="replace_registry"> <?php esc_html_e( 'Replace Registry - wipe representative overlay rows, then import this CSV', 'county-gop-core' ); ?></label>
					</td>
				</tr>
				<tr>
					<th scope="row"></th>
					<td><label><input type="checkbox" name="overlays_import_valid_only" value="1"> <?php esc_html_e( 'Import valid rows only - skip invalid rows instead of aborting', 'county-gop-core' ); ?></label></td>
				</tr>
			</table>
			<?php submit_button( __( 'Import Representative Overlays', 'county-gop-core' ) ); ?>
		</form>

		<hr>

		<h2><?php esc_html_e( 'GIS Beacon Layers', 'county-gop-core' ); ?></h2>
		<p class="description" style="max-width:700px;">
			<?php esc_html_e( 'Beacon Layer definitions control which Beacon API layers are imported to generate GeoJSON map files. Export to back up your configuration; import to restore or modify it. Importing does not regenerate map files or call Beacon.', 'county-gop-core' ); ?>
		</p>

		<h3><?php esc_html_e( 'Export GIS Beacon Layers', 'county-gop-core' ); ?></h3>
		<p><?php printf( esc_html__( '%d beacon layer definition(s) will be exported.', 'county-gop-core' ), (int) ( $counts['gis_beacon_layers'] ?? 0 ) ); ?></p>
		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="CGOP_csv_export_gis_layers">
			<?php wp_nonce_field( 'CGOP_csv_export_gis_layers' ); ?>
			<?php submit_button( __( 'Download GIS Beacon Layers CSV', 'county-gop-core' ), 'secondary', 'submit', false ); ?>
		</form>

		<h3><?php esc_html_e( 'Import GIS Beacon Layers', 'county-gop-core' ); ?></h3>

		<?php if ( $gis_layers_result ) {
			CGOP_csv_tools_render_result( $gis_layers_result, __( 'GIS Beacon Layers', 'county-gop-core' ) );
		} ?>

		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data">
			<input type="hidden" name="action" value="CGOP_csv_import_gis_layers">
			<?php wp_nonce_field( 'CGOP_csv_import_gis_layers' ); ?>
			<p class="description" style="max-width:700px;">
				<?php esc_html_e( 'Required columns: label, beacon_layer_id, position_id_template. The id and output_dir columns are optional; blank IDs are generated automatically. The preferred_fields column accepts comma-separated or newline-separated values.', 'county-gop-core' ); ?>
			</p>
			<table class="form-table" style="max-width:700px;">
				<tr>
					<th scope="row"><label for="gis_layers_csv_file"><?php esc_html_e( 'GIS Beacon Layers CSV', 'county-gop-core' ); ?></label></th>
					<td><input type="file" id="gis_layers_csv_file" name="gis_layers_csv_file" accept=".csv,text/csv" required></td>
				</tr>
				<tr>
					<th scope="row"><?php esc_html_e( 'Import Mode', 'county-gop-core' ); ?></th>
					<td>
						<label><input type="radio" name="gis_layers_import_mode" value="dry_run" checked> <?php esc_html_e( 'Dry Run - validate only, no writes', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="gis_layers_import_mode" value="insert_missing"> <?php esc_html_e( 'Insert Missing - add layers not already in registry (skip existing ids)', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="gis_layers_import_mode" value="update_existing"> <?php esc_html_e( 'Update Existing - update matching ids and insert missing ones', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="gis_layers_import_mode" value="replace_registry"> <?php esc_html_e( 'Replace Registry - replace all layer definitions with valid rows from this CSV', 'county-gop-core' ); ?></label>
					</td>
				</tr>
			</table>
			<?php submit_button( __( 'Import GIS Beacon Layers', 'county-gop-core' ) ); ?>
		</form>

		<hr>

		<h2><?php esc_html_e( 'GIS Generated Maps', 'county-gop-core' ); ?></h2>
			<p class="description" style="max-width:700px;">
				<?php esc_html_e( 'Generated map status tracks which GeoJSON files have been produced for each layer. Export for audit or backup; import to restore status records. The generated_map_id column is the value you can paste into live_in_map or voting_area_map in a Position Keys CSV. Importing Generated GIS Maps does not create or modify physical GeoJSON files and does not call Beacon. Physical files are managed from GOP Setup → GIS Boundary Import.', 'county-gop-core' ); ?>
			</p>

		<h3><?php esc_html_e( 'Export Generated GIS Maps', 'county-gop-core' ); ?></h3>
		<p><?php printf( esc_html__( '%d generated map file record(s) will be exported.', 'county-gop-core' ), (int) ( $counts['gis_generated_files'] ?? 0 ) ); ?></p>
		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
			<input type="hidden" name="action" value="CGOP_csv_export_gis_maps">
			<?php wp_nonce_field( 'CGOP_csv_export_gis_maps' ); ?>
			<?php submit_button( __( 'Download Generated GIS Maps CSV', 'county-gop-core' ), 'secondary', 'submit', false ); ?>
		</form>

		<h3><?php esc_html_e( 'Import Generated GIS Maps', 'county-gop-core' ); ?></h3>

		<?php if ( $gis_maps_result ) {
			CGOP_csv_tools_render_result( $gis_maps_result, __( 'GIS Generated Maps', 'county-gop-core' ) );
		} ?>

		<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" enctype="multipart/form-data">
			<input type="hidden" name="action" value="CGOP_csv_import_gis_maps">
			<?php wp_nonce_field( 'CGOP_csv_import_gis_maps' ); ?>
			<p class="description" style="max-width:700px;">
				<?php esc_html_e( 'Required columns: layer_id, layer_label, beacon_layer_id, position_id, label, file. The generated_map_id, existing_assignment_map_ids, and existing_assignment_position_keys export columns are informational and ignored on Generated GIS Maps import. The file column must be a relative path ending in .geojson or .json. On non-dry-run import, each file must exist on disk in the generated directory.', 'county-gop-core' ); ?>
			</p>
			<table class="form-table" style="max-width:700px;">
				<tr>
					<th scope="row"><label for="gis_maps_csv_file"><?php esc_html_e( 'Generated GIS Maps CSV', 'county-gop-core' ); ?></label></th>
					<td><input type="file" id="gis_maps_csv_file" name="gis_maps_csv_file" accept=".csv,text/csv" required></td>
				</tr>
				<tr>
					<th scope="row"><?php esc_html_e( 'Import Mode', 'county-gop-core' ); ?></th>
					<td>
						<label><input type="radio" name="gis_maps_import_mode" value="dry_run" checked> <?php esc_html_e( 'Dry Run - validate only, no writes', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="gis_maps_import_mode" value="upsert_status"> <?php esc_html_e( 'Upsert Status - merge rows into current status (update matching layer+file, insert new)', 'county-gop-core' ); ?></label><br>
						<label><input type="radio" name="gis_maps_import_mode" value="replace_status"> <?php esc_html_e( 'Replace Status - replace all status records with valid rows from this CSV', 'county-gop-core' ); ?></label>
					</td>
				</tr>
			</table>
			<?php submit_button( __( 'Import Generated GIS Maps', 'county-gop-core' ) ); ?>
		</form>

		<hr>

		<h2 style="color:#a00;"><?php esc_html_e( 'Danger Zone', 'county-gop-core' ); ?></h2>
		<div style="border:2px solid #a00;padding:1.5em;max-width:700px;margin-bottom:2em;background:#fff8f8;">
			<h3 style="margin-top:0;"><?php esc_html_e( 'Wipe Position Key Registry', 'county-gop-core' ); ?></h3>
			<p><?php printf(
				/* translators: %d: row count. */
				esc_html__( 'Deletes all %d position key(s) from the registry. Overlay rows are not deleted, but will not display until matching position keys are re-imported.', 'county-gop-core' ),
				(int) $counts['position_keys']
			); ?></p>
			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
				<input type="hidden" name="action" value="CGOP_csv_wipe_pkeys">
				<?php wp_nonce_field( 'CGOP_csv_wipe_pkeys' ); ?>
				<p>
					<label for="wipe_confirm_pkeys"><?php esc_html_e( 'Type exactly to confirm:', 'county-gop-core' ); ?> <strong>WIPE POSITION KEYS</strong></label><br>
					<input type="text" id="wipe_confirm_pkeys" name="wipe_confirm_pkeys" class="regular-text" autocomplete="off" style="margin-top:.4em;">
				</p>
				<?php submit_button( __( 'Wipe Position Key Registry', 'county-gop-core' ), 'delete', 'submit', false ); ?>
			</form>
		</div>

		<div style="border:2px solid #a00;padding:1.5em;max-width:700px;margin-bottom:2em;background:#fff8f8;">
			<h3 style="margin-top:0;"><?php esc_html_e( 'Wipe Representative Overlays', 'county-gop-core' ); ?></h3>
			<p><?php printf(
				/* translators: %d: row count. */
				esc_html__( 'Deletes all %d representative overlay row(s). Position keys are not deleted.', 'county-gop-core' ),
				(int) ( $counts['overlay_count'] ?? 0 )
			); ?></p>
			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
				<input type="hidden" name="action" value="CGOP_csv_wipe_overlays">
				<?php wp_nonce_field( 'CGOP_csv_wipe_overlays' ); ?>
				<p>
					<label for="wipe_confirm_overlays"><?php esc_html_e( 'Type exactly to confirm:', 'county-gop-core' ); ?> <strong>WIPE REPRESENTATIVE OVERLAYS</strong></label><br>
					<input type="text" id="wipe_confirm_overlays" name="wipe_confirm_overlays" class="regular-text" autocomplete="off" style="margin-top:.4em;">
				</p>
				<?php submit_button( __( 'Wipe Representative Overlays', 'county-gop-core' ), 'delete', 'submit', false ); ?>
			</form>
		</div>

		<div style="border:2px solid #a00;padding:1.5em;max-width:700px;margin-bottom:2em;background:#fff8f8;">
			<h3 style="margin-top:0;"><?php esc_html_e( 'Wipe GIS Beacon Layers', 'county-gop-core' ); ?></h3>
			<p><?php printf(
				/* translators: %d: layer count. */
				esc_html__( 'Clears all %d GIS Beacon Layer definition(s) from the registry. Generated map files and status records are not affected.', 'county-gop-core' ),
				(int) ( $counts['gis_beacon_layers'] ?? 0 )
			); ?></p>
			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
				<input type="hidden" name="action" value="CGOP_csv_wipe_gis_layers">
				<?php wp_nonce_field( 'CGOP_csv_wipe_gis_layers' ); ?>
				<p>
					<label for="wipe_confirm_gis_layers"><?php esc_html_e( 'Type exactly to confirm:', 'county-gop-core' ); ?> <strong>WIPE GIS BEACON LAYERS</strong></label><br>
					<input type="text" id="wipe_confirm_gis_layers" name="wipe_confirm_gis_layers" class="regular-text" autocomplete="off" style="margin-top:.4em;">
				</p>
				<?php submit_button( __( 'Wipe GIS Beacon Layers', 'county-gop-core' ), 'delete', 'submit', false ); ?>
			</form>
		</div>

		<div style="border:2px solid #a00;padding:1.5em;max-width:700px;background:#fff8f8;">
			<h3 style="margin-top:0;"><?php esc_html_e( 'Wipe Generated GIS Map Status', 'county-gop-core' ); ?></h3>
			<p><?php printf(
				/* translators: %d: file count. */
				esc_html__( 'Clears all %d generated map file status record(s) and rewrites the manifest. Physical GeoJSON files on disk are not deleted. Beacon Layer definitions are not affected.', 'county-gop-core' ),
				(int) ( $counts['gis_generated_files'] ?? 0 )
			); ?></p>
			<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
				<input type="hidden" name="action" value="CGOP_csv_wipe_gis_maps">
				<?php wp_nonce_field( 'CGOP_csv_wipe_gis_maps' ); ?>
				<p>
					<label for="wipe_confirm_gis_maps"><?php esc_html_e( 'Type exactly to confirm:', 'county-gop-core' ); ?> <strong>WIPE GIS GENERATED MAP STATUS</strong></label><br>
					<input type="text" id="wipe_confirm_gis_maps" name="wipe_confirm_gis_maps" class="regular-text" autocomplete="off" style="margin-top:.4em;">
				</p>
				<?php submit_button( __( 'Wipe Generated GIS Map Status', 'county-gop-core' ), 'delete', 'submit', false ); ?>
			</form>
		</div>
	</div>
	<?php
}

/**
 * Render an import result summary block.
 *
 * @param array  $result Result array from handler.
 * @param string $label  Human label (Position Keys / Representatives).
 */
function CGOP_csv_tools_render_result( $result, $label ) {
	$type = empty( $result['errors'] ) ? 'success' : 'warning';
	$dry  = ! empty( $result['dry_run'] );
	?>
	<div class="notice notice-<?php echo esc_attr( $type ); ?> inline" style="max-width:700px;margin-bottom:1.25em;">
		<p><strong><?php
			if ( $dry ) {
				/* translators: %s: data type label. */
				printf( esc_html__( '%s — Dry Run Results', 'county-gop-core' ), esc_html( $label ) );
			} else {
				/* translators: %s: data type label. */
				printf( esc_html__( '%s — Import Results', 'county-gop-core' ), esc_html( $label ) );
			}
		?></strong></p>
		<ul style="margin:.25em 0 .5em 1.5em;">
			<li><?php printf( esc_html__( 'Rows parsed: %d', 'county-gop-core' ), (int) $result['parsed'] ); ?></li>
			<li><?php printf( esc_html__( 'Rows valid: %d', 'county-gop-core' ), (int) $result['valid'] ); ?></li>
			<li><?php printf( esc_html__( 'Rows skipped: %d', 'county-gop-core' ), (int) $result['skipped'] ); ?></li>
			<?php if ( ! $dry ) : ?>
				<li><?php printf( esc_html__( 'Rows inserted: %d', 'county-gop-core' ), (int) $result['inserted'] ); ?></li>
				<li><?php printf( esc_html__( 'Rows updated: %d', 'county-gop-core' ), (int) $result['updated'] ); ?></li>
			<?php endif; ?>
		</ul>
		<?php if ( ! empty( $result['errors'] ) ) : ?>
			<p><strong><?php esc_html_e( 'Errors:', 'county-gop-core' ); ?></strong></p>
			<ul style="margin:.25em 0 .5em 1.5em;color:#a00;">
				<?php foreach ( $result['errors'] as $e ) : ?>
					<li><?php echo esc_html( $e['msg'] ); ?></li>
				<?php endforeach; ?>
			</ul>
		<?php endif; ?>
	</div>
	<?php
}