<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.NonceVerification.Recommended
// Justification: All state-mutating handlers verify a nonce via check_admin_referer( 'ccgal_save_*' ) immediately before reading the corresponding $_POST values; static analysis cannot link the conditional read to the nonce check on the surrounding line. Custom sanitizer paths (sanitize_folder_id / sanitize_color_value / CCGAL_Storage_Adapter::sanitize) are used in place of sanitize_text_field for fields that contain encoded URL fragments (e.g. Dropbox shared-link paths with %20) — sanitize_text_field would silently strip those encodings and break round-trip persistence. The text-override bulk array $_POST['ccgal_text_override'] is sanitized per-key inside the foreach loop. NonceVerification.Recommended on admin_notices() reads $_GET['ccgal_msg'] purely for read-only display.

class CCGAL_Admin {

	public static function init() {
		add_action( 'admin_menu', array( __CLASS__, 'register_menu' ) );
		add_action( 'admin_init', array( __CLASS__, 'register_settings' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_assets' ) );
		add_action( 'add_meta_boxes', array( __CLASS__, 'add_meta_boxes' ) );
		add_action( 'save_post_' . CCGAL_CPT::POST_TYPE, array( __CLASS__, 'save_gallery_meta' ), 10, 2 );
		add_filter( 'manage_' . CCGAL_CPT::POST_TYPE . '_posts_columns', array( __CLASS__, 'gallery_columns' ) );
		add_action( 'manage_' . CCGAL_CPT::POST_TYPE . '_posts_custom_column', array( __CLASS__, 'gallery_column_content' ), 10, 2 );
		add_action( 'admin_notices', array( __CLASS__, 'admin_notices' ) );
		add_action( 'wp_ajax_ccgal_validate_folder', array( __CLASS__, 'ajax_validate_folder' ) );
		add_filter( 'plugin_action_links_' . CCGAL_BASENAME, array( __CLASS__, 'plugin_action_links' ) );
	}

	public static function register_menu() {
		$parent = 'edit.php?post_type=' . CCGAL_CPT::POST_TYPE;

		add_submenu_page(
			$parent,
			__( 'Settings', 'customer-cloud-gallery' ),
			__( 'Settings', 'customer-cloud-gallery' ),
			'manage_options',
			'ccgal-settings',
			array( __CLASS__, 'render_settings_page' )
		);

		// In Pro werden die echten Submenüs (Statistics, Print Options, Orders)
		// von den jeweiligen Klassen (CCGAL_Dashboard, CCGAL_Print_Settings, CCGAL_Orders)
		// registriert. Im Free-Build existieren weder die Klassen noch die Submenüs —
		// wir verlinken auf Pro nur über statische Hinweise (Settings-Page + Plugin-Action-Link).
	}

	public static function register_settings() {
		register_setting(
			'ccgal_settings',
			'ccgal_oauth_client_id',
			array(
				'type'              => 'string',
				'sanitize_callback' => 'sanitize_text_field',
				'default'           => '',
			)
		);
		register_setting(
			'ccgal_settings',
			'ccgal_accent_color',
			array(
				'type'              => 'string',
				'sanitize_callback' => array( __CLASS__, 'sanitize_color_value' ),
				'default'           => '',
			)
		);
		register_setting(
			'ccgal_settings',
			'ccgal_hover_color',
			array(
				'type'              => 'string',
				'sanitize_callback' => array( __CLASS__, 'sanitize_color_value' ),
				'default'           => '',
			)
		);
		register_setting(
			'ccgal_settings',
			'ccgal_border_color',
			array(
				'type'              => 'string',
				'sanitize_callback' => array( __CLASS__, 'sanitize_color_value' ),
				'default'           => '',
			)
		);
	}

	public static function sanitize_color_value( $value ) {
		$value = trim( (string) $value );
		if ( '' === $value ) {
			return '';
		}
		$sanitized = sanitize_hex_color( $value );
		return $sanitized ? $sanitized : '';
	}

	public static function get_accent_color() {
		$color = sanitize_hex_color( (string) get_option( 'ccgal_accent_color', '' ) );
		return $color ? $color : '';
	}

	public static function get_hover_color() {
		$color = sanitize_hex_color( (string) get_option( 'ccgal_hover_color', '' ) );
		return $color ? $color : '';
	}

	public static function get_border_color() {
		$color = sanitize_hex_color( (string) get_option( 'ccgal_border_color', '' ) );
		return $color ? $color : '';
	}

	public static function enqueue_assets( $hook ) {
		$screen = get_current_screen();
		$is_plugin_screen = false;

		if ( $screen && $screen->post_type === CCGAL_CPT::POST_TYPE ) {
			$is_plugin_screen = true;
		}
		if ( false !== strpos( (string) $hook, 'ccgal-' ) ) {
			$is_plugin_screen = true;
		}

		if ( ! $is_plugin_screen ) {
			return;
		}

		wp_enqueue_style(
			'ccgal-admin',
			CCGAL_URL . 'assets/css/admin.css',
			array(),
			CCGAL_VERSION
		);

		if ( $screen && $screen->post_type === CCGAL_CPT::POST_TYPE && in_array( $hook, array( 'post.php', 'post-new.php' ), true ) ) {
			wp_enqueue_script(
				'ccgal-admin',
				CCGAL_URL . 'assets/js/admin.js',
				array( 'jquery' ),
				CCGAL_VERSION,
				true
			);
			wp_set_script_translations( 'ccgal-admin', 'customer-cloud-gallery', CCGAL_DIR . 'languages' );
			wp_localize_script(
				'ccgal-admin',
				'ccgalAdmin',
				array(
					'validateNonce'    => wp_create_nonce( 'ccgal_validate_folder' ),
					'driveConnected'   => CCGAL_Google_OAuth::is_connected(),
					'dropboxConnected' => CCGAL_Dropbox_OAuth::is_connected(),
					'i18n'             => array(
						'checking' => __( 'Checking folder…', 'customer-cloud-gallery' ),
						'error'    => __( 'Connection error.', 'customer-cloud-gallery' ),
						/* translators: %s: provider name (Google Drive or Dropbox). */
						'clearOther' => __( 'Clear the %s field first (✕), then enter here.', 'customer-cloud-gallery' ),
					),
				)
			);
		}

		if ( false !== strpos( (string) $hook, 'ccgal-settings' ) ) {
			wp_enqueue_style( 'wp-color-picker' );
			wp_enqueue_script( 'wp-color-picker' );
			wp_add_inline_script(
				'wp-color-picker',
				'jQuery(function($){ $(".ccgal-color-picker").wpColorPicker(); });'
			);

			wp_enqueue_script(
				'ccgal-admin-text-overrides',
				CCGAL_URL . 'assets/js/admin-text-overrides.js',
				array(),
				CCGAL_VERSION,
				true
			);
		}
	}

	public static function ajax_validate_folder() {
		check_ajax_referer( 'ccgal_validate_folder', 'nonce' );
		if ( ! current_user_can( 'edit_posts' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'customer-cloud-gallery' ) ) );
		}

		// Kein sanitize_text_field hier — es strippt %xx-Encoding (z. B. %20 → leer).
		// sanitize_folder_id() übernimmt die vollständige Validierung.
		$raw      = isset( $_POST['folder'] )   ? wp_unslash( $_POST['folder'] )             : '';
		$provider = isset( $_POST['provider'] ) ? sanitize_key( wp_unslash( $_POST['provider'] ) ) : 'google_drive';
		if ( ! array_key_exists( $provider, CCGAL_Storage::get_available_adapters() ) ) {
			$provider = 'google_drive';
		}
		$adapter  = CCGAL_Storage::adapter( $provider );
		$folder_id = $adapter->sanitize_folder_id( $raw );

		if ( '' === $folder_id ) {
			if ( 'dropbox' === $provider && false !== strpos( $raw, 'dropbox.com' ) ) {
				wp_send_json_error( array(
					'message' => __( 'Share links are not supported. Please use the navigation URL: dropbox.com/home/Folderpath', 'customer-cloud-gallery' ),
				) );
			}
			wp_send_json_error( array( 'message' => __( 'No valid folder URL or ID detected.', 'customer-cloud-gallery' ) ) );
		}

		if ( ! $adapter->is_connected() ) {
			wp_send_json_error( array( 'message' => __( 'Provider not connected.', 'customer-cloud-gallery' ) ) );
		}

		$name = $adapter->get_folder_name( $folder_id );
		if ( is_wp_error( $name ) ) {
			wp_send_json_error( array( 'message' => $name->get_error_message() ) );
		}

		wp_send_json_success( array(
			'folder_id' => $folder_id,
			'name'      => $name,
		) );
	}

	public static function add_meta_boxes() {
		add_meta_box(
			'ccgal_gallery_config',
			__( 'Gallery configuration', 'customer-cloud-gallery' ),
			array( __CLASS__, 'render_gallery_meta_box' ),
			CCGAL_CPT::POST_TYPE,
			'normal',
			'high'
		);

		add_meta_box(
			'ccgal_gallery_shortcode',
			__( 'Shortcode', 'customer-cloud-gallery' ),
			array( __CLASS__, 'render_shortcode_box' ),
			CCGAL_CPT::POST_TYPE,
			'side',
			'default'
		);
	}

	public static function render_gallery_meta_box( $post ) {
		wp_nonce_field( 'ccgal_gallery_meta', 'ccgal_gallery_meta_nonce' );

		// Show result of server-side folder validation from last save
		$notice_error = get_transient( 'ccgal_meta_notice_' . $post->ID );
		$notice_ok    = get_transient( 'ccgal_meta_notice_ok_' . $post->ID );
		if ( $notice_error ) {
			delete_transient( 'ccgal_meta_notice_' . $post->ID );
			echo '<div style="margin:8px 0;padding:8px 12px;background:#fce8e8;border-left:3px solid #d63638;font-size:.9em;">'
				. esc_html( $notice_error ) . '</div>';
		} elseif ( $notice_ok ) {
			delete_transient( 'ccgal_meta_notice_ok_' . $post->ID );
			echo '<div style="margin:8px 0;padding:8px 12px;background:#edfaef;border-left:3px solid #1a7e3a;font-size:.9em;">&#10003; '
				. esc_html__( 'Folder found:', 'customer-cloud-gallery' ) . ' <strong>' . esc_html( $notice_ok ) . '</strong></div>';
		}

		$couple_name       = get_post_meta( $post->ID, 'ccgal_couple_name', true );
		$drive_folder_id   = get_post_meta( $post->ID, 'ccgal_drive_folder_id', true )
		                   ?: get_post_meta( $post->ID, 'ccgal_storage_folder_id', true );
		$dropbox_folder_id = get_post_meta( $post->ID, 'ccgal_dropbox_folder_id', true );
		$drive_connected   = CCGAL_Google_OAuth::is_connected();
		$dropbox_connected = CCGAL_Dropbox_OAuth::is_connected();
		$per_page        = (int) ( get_post_meta( $post->ID, 'ccgal_per_page', true ) ?: 40 );
		$load_mode       = get_post_meta( $post->ID, 'ccgal_load_mode', true ) ?: 'scroll';
		$image_size      = get_post_meta( $post->ID, 'ccgal_image_size', true );
		if ( ! in_array( $image_size, array( 'small', 'medium', 'large', 'xlarge' ), true ) ) {
			$image_size = 'medium';
		}
		$allow_downloads = '0' !== get_post_meta( $post->ID, 'ccgal_allow_downloads', true );
		$allow_favorites = '0' !== get_post_meta( $post->ID, 'ccgal_allow_favorites', true );
		$cookies_enabled = '0' !== get_post_meta( $post->ID, 'ccgal_cookies_enabled', true );

		$sort_order = get_post_meta( $post->ID, 'ccgal_sort_order', true );
		if ( ! in_array( $sort_order, CCGAL_CPT::valid_sort_orders(), true ) ) {
			$sort_order = 'name_natural';
		}
		$toolbar_align = get_post_meta( $post->ID, 'ccgal_toolbar_align', true );
		if ( ! in_array( $toolbar_align, array( 'left', 'center', 'right' ), true ) ) {
			$toolbar_align = 'center';
		}
		?>
		<table class="form-table">
			<tr>
				<th><label for="ccgal_couple_name"><?php esc_html_e( 'Couple / customer (internal name)', 'customer-cloud-gallery' ); ?></label></th>
				<td>
					<input type="text" id="ccgal_couple_name" name="ccgal_couple_name" class="regular-text" value="<?php echo esc_attr( $couple_name ); ?>" placeholder="<?php esc_attr_e( 'e.g. Anna & Tom', 'customer-cloud-gallery' ); ?>" />
					<p class="description"><?php esc_html_e( 'Used in the admin dashboard and as a fallback filename for ZIP downloads.', 'customer-cloud-gallery' ); ?></p>
				</td>
			</tr>
			<tr>
				<th style="vertical-align:top;padding-top:12px;"><?php esc_html_e( 'Storage folder', 'customer-cloud-gallery' ); ?></th>
				<td>
					<?php
					// ── Google Drive ──────────────────────────────────────
					$drive_disabled = ! $drive_connected || ( $dropbox_folder_id && ! $drive_folder_id );
					?>
					<div id="ccgal-folder-row-drive" style="margin-bottom:10px;<?php echo esc_attr( $drive_disabled ? 'opacity:.45;' : '' ); ?>">
						<strong>Google Drive</strong>
						<?php if ( ! $drive_connected ) : ?>
							<span style="color:#d63638;font-size:.85em;margin-left:6px;">
								— <?php esc_html_e( 'Not connected. Connect under Settings → Google Drive.', 'customer-cloud-gallery' ); ?>
							</span>
						<?php else : ?>
							<span style="color:#1a7e3a;font-size:.85em;margin-left:6px;">● <?php esc_html_e( 'Connected', 'customer-cloud-gallery' ); ?></span>
						<?php endif; ?>
						<div style="display:flex;align-items:center;gap:6px;margin-top:4px;">
							<input type="text"
								id="ccgal_drive_folder_id"
								name="ccgal_drive_folder_id"
								class="regular-text code ccgal-folder-input"
								data-provider="google_drive"
								value="<?php echo esc_attr( $drive_folder_id ); ?>"
								placeholder="<?php esc_attr_e( 'Drive URL or folder ID', 'customer-cloud-gallery' ); ?>"
								<?php echo ( ! $drive_connected || ( $dropbox_folder_id && ! $drive_folder_id ) ) ? 'disabled' : ''; ?> />
							<?php if ( $drive_connected ) : ?>
							<button type="button" class="button ccgal-check-folder" data-provider="google_drive"><?php esc_html_e( 'Check', 'customer-cloud-gallery' ); ?></button>
							<?php if ( $drive_folder_id ) : ?>
							<button type="button" class="button ccgal-clear-folder" data-target="ccgal_drive_folder_id"><?php esc_html_e( '✕', 'customer-cloud-gallery' ); ?></button>
							<?php endif; ?>
							<?php endif; ?>
						</div>
					</div>

					<?php
					// ── Dropbox ───────────────────────────────────────────
					$dropbox_disabled = ! $dropbox_connected || ( $drive_folder_id && ! $dropbox_folder_id );
					?>
					<div id="ccgal-folder-row-dropbox" style="<?php echo esc_attr( $dropbox_disabled ? 'opacity:.45;' : '' ); ?>">
						<strong>Dropbox</strong>
						<?php if ( ! $dropbox_connected ) : ?>
							<span style="color:#d63638;font-size:.85em;margin-left:6px;">
								— <?php esc_html_e( 'Not connected. Connect under Settings → Dropbox.', 'customer-cloud-gallery' ); ?>
							</span>
						<?php else : ?>
							<span style="color:#1a7e3a;font-size:.85em;margin-left:6px;">● <?php esc_html_e( 'Connected', 'customer-cloud-gallery' ); ?></span>
						<?php endif; ?>
						<div style="display:flex;align-items:center;gap:6px;margin-top:4px;">
							<input type="text"
								id="ccgal_dropbox_folder_id"
								name="ccgal_dropbox_folder_id"
								class="regular-text code ccgal-folder-input"
								data-provider="dropbox"
								value="<?php echo esc_attr( $dropbox_folder_id ); ?>"
								placeholder="<?php esc_attr_e( 'dropbox.com/home/Folder or /Folder/Name', 'customer-cloud-gallery' ); ?>"
								<?php echo ( ! $dropbox_connected || ( $drive_folder_id && ! $dropbox_folder_id ) ) ? 'disabled' : ''; ?> />
							<?php if ( $dropbox_connected ) : ?>
							<button type="button" class="button ccgal-check-folder" data-provider="dropbox"><?php esc_html_e( 'Check', 'customer-cloud-gallery' ); ?></button>
							<?php if ( $dropbox_folder_id ) : ?>
							<button type="button" class="button ccgal-clear-folder" data-target="ccgal_dropbox_folder_id"><?php esc_html_e( '✕', 'customer-cloud-gallery' ); ?></button>
							<?php endif; ?>
							<?php endif; ?>
						</div>
					</div>
				</td>
			</tr>
			<tr>
				<th><label for="ccgal_per_page"><?php esc_html_e( 'Images per batch', 'customer-cloud-gallery' ); ?></label></th>
				<td>
					<select id="ccgal_per_page" name="ccgal_per_page">
						<?php foreach ( array( 10, 15, 20, 30, 40, 50, 75, 100 ) as $n ) : ?>
							<option value="<?php echo esc_attr( $n ); ?>" <?php selected( $per_page, $n ); ?>><?php echo esc_html( $n ); ?></option>
						<?php endforeach; ?>
					</select>
				</td>
			</tr>
			<tr>
				<th><label for="ccgal_load_mode"><?php esc_html_e( 'Loading mode', 'customer-cloud-gallery' ); ?></label></th>
				<td>
					<select id="ccgal_load_mode" name="ccgal_load_mode">
						<option value="scroll" <?php selected( $load_mode, 'scroll' ); ?>><?php esc_html_e( 'Auto-load on scroll', 'customer-cloud-gallery' ); ?></option>
						<option value="button" <?php selected( $load_mode, 'button' ); ?>><?php esc_html_e( 'Click "Load more"', 'customer-cloud-gallery' ); ?></option>
					</select>
				</td>
			</tr>
			<tr>
				<th><label for="ccgal_image_size"><?php esc_html_e( 'Grid image size', 'customer-cloud-gallery' ); ?></label></th>
				<td>
					<select id="ccgal_image_size" name="ccgal_image_size">
						<option value="small"  <?php selected( $image_size, 'small' ); ?>><?php esc_html_e( 'Small (~200 px — many per row)', 'customer-cloud-gallery' ); ?></option>
						<option value="medium" <?php selected( $image_size, 'medium' ); ?>><?php esc_html_e( 'Medium (~300 px)', 'customer-cloud-gallery' ); ?></option>
						<option value="large"  <?php selected( $image_size, 'large' ); ?>><?php esc_html_e( 'Large (~400 px)', 'customer-cloud-gallery' ); ?></option>
						<option value="xlarge" <?php selected( $image_size, 'xlarge' ); ?>><?php esc_html_e( 'Extra large (~520 px)', 'customer-cloud-gallery' ); ?></option>
					</select>
					<p class="description"><?php esc_html_e( 'The number of images per row adapts to the window width. Images keep their original aspect ratio.', 'customer-cloud-gallery' ); ?></p>
				</td>
			</tr>
			<tr>
				<th><label for="ccgal_sort_order"><?php esc_html_e( 'Sort order', 'customer-cloud-gallery' ); ?></label></th>
				<td>
					<select id="ccgal_sort_order" name="ccgal_sort_order">
						<?php foreach ( CCGAL_CPT::valid_sort_orders() as $value ) : ?>
							<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $sort_order, $value ); ?>><?php echo esc_html( CCGAL_CPT::sort_order_label( $value ) ); ?></option>
						<?php endforeach; ?>
					</select>
					<p class="description"><?php esc_html_e( 'Recommended for numeric naming (1, 2, …, 1000): "Filename (natural)". Sorts correctly even without leading zeros.', 'customer-cloud-gallery' ); ?></p>
				</td>
			</tr>
			<tr>
				<th><label for="ccgal_toolbar_align"><?php esc_html_e( 'Toolbar alignment', 'customer-cloud-gallery' ); ?></label></th>
				<td>
					<select id="ccgal_toolbar_align" name="ccgal_toolbar_align">
						<option value="left"   <?php selected( $toolbar_align, 'left' ); ?>><?php esc_html_e( 'Left', 'customer-cloud-gallery' ); ?></option>
						<option value="center" <?php selected( $toolbar_align, 'center' ); ?>><?php esc_html_e( 'Center', 'customer-cloud-gallery' ); ?></option>
						<option value="right"  <?php selected( $toolbar_align, 'right' ); ?>><?php esc_html_e( 'Right', 'customer-cloud-gallery' ); ?></option>
					</select>
					<p class="description"><?php esc_html_e( 'Alignment of the toolbar buttons (e.g. "Download all", "Show only favorites") above the grid.', 'customer-cloud-gallery' ); ?></p>
				</td>
			</tr>
			<tr>
				<th><?php esc_html_e( 'Features', 'customer-cloud-gallery' ); ?></th>
				<td>
					<label><input type="checkbox" name="ccgal_allow_downloads" value="1" <?php checked( $allow_downloads ); ?> />
						<?php esc_html_e( 'Allow downloads (single, selection, all)', 'customer-cloud-gallery' ); ?>
					</label><br />
					<label><input type="checkbox" name="ccgal_allow_favorites" value="1" <?php checked( $allow_favorites ); ?> /> <?php esc_html_e( 'Allow favorites', 'customer-cloud-gallery' ); ?></label>
				</td>
			</tr>
			<tr>
				<th style="vertical-align:top;padding-top:10px;"><?php esc_html_e( 'Cookies', 'customer-cloud-gallery' ); ?></th>
				<td>
					<label>
						<input type="checkbox" name="ccgal_cookies_enabled" value="1" id="ccgal_cookies_enabled" <?php checked( $cookies_enabled ); ?> />
						<strong><?php esc_html_e( 'Allow cookies in this gallery', 'customer-cloud-gallery' ); ?></strong>
					</label>
					<p class="description" style="margin:2px 0 8px;"><?php esc_html_e( 'Master switch. Off = no visitor cookie and no favorites in this gallery. No cookie banner needed.', 'customer-cloud-gallery' ); ?></p>
					<?php
					/**
					 * Pro statistics module renders its own sub-switch (stats opt-in)
					 * inside this slot. Free has no listeners.
					 *
					 * @param int $post_id Gallery post ID.
					 */
					do_action( 'ccgal_render_gallery_cookies_sub_options', $post->ID );
					?>
				</td>
			</tr>
		</table>
		<?php
	}

	public static function render_shortcode_box( $post ) {
		$shortcode = '[' . CCGAL_Gallery_Renderer::SHORTCODE_PRIMARY . ' id="' . (int) $post->ID . '"]';
		?>
		<p><?php esc_html_e( 'Copy this shortcode into your (password-protected) page:', 'customer-cloud-gallery' ); ?></p>
		<input type="text" readonly value="<?php echo esc_attr( $shortcode ); ?>" onclick="this.select()" style="width:100%;font-family:monospace;" />
		<?php
	}

	public static function save_gallery_meta( $post_id, $post ) {
		if ( ! isset( $_POST['ccgal_gallery_meta_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['ccgal_gallery_meta_nonce'] ) ), 'ccgal_gallery_meta' ) ) {
			return;
		}
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}

		$couple_name    = isset( $_POST['ccgal_couple_name'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_couple_name'] ) ) : '';
		// wp_unslash statt sanitize_text_field: sanitize_text_field strippt %xx-Encoding
		// (z. B. %20 in Dropbox-URLs). sanitize_folder_id() übernimmt die Validierung.
		$drive_raw      = isset( $_POST['ccgal_drive_folder_id'] )   ? wp_unslash( $_POST['ccgal_drive_folder_id'] )   : '';
		$dropbox_raw    = isset( $_POST['ccgal_dropbox_folder_id'] ) ? wp_unslash( $_POST['ccgal_dropbox_folder_id'] ) : '';

		$drive_folder   = $drive_raw   ? CCGAL_Storage::adapter( 'google_drive' )->sanitize_folder_id( $drive_raw )   : '';
		$dropbox_folder = $dropbox_raw ? CCGAL_Storage::adapter( 'dropbox' )->sanitize_folder_id( $dropbox_raw )      : '';

		// If sanitize emptied a share link etc. but there was raw input → notice
		if ( $dropbox_raw && ! $dropbox_folder ) {
			set_transient( 'ccgal_meta_notice_' . $post_id,
				__( 'Dropbox: the entered URL is not supported. Please use the navigation URL from the Dropbox address bar (dropbox.com/home/...) or a folder path like /FolderName.', 'customer-cloud-gallery' ),
				30
			);
		}

		// Mutual exclusivity: wenn beide gesetzt, Dropbox bevorzugen (da Drive disabled war)
		if ( $drive_folder && $dropbox_folder ) {
			$drive_folder = '';
		}

		// Fallback: wenn nichts im POST, behalte vorhandenen Wert (Feld war disabled)
		if ( '' === $drive_raw && '' === $dropbox_raw ) {
			$drive_folder   = get_post_meta( $post_id, 'ccgal_drive_folder_id', true )
			                ?: get_post_meta( $post_id, 'ccgal_storage_folder_id', true );
			$dropbox_folder = get_post_meta( $post_id, 'ccgal_dropbox_folder_id', true );
		}

		// Serverseitige Folder-Validierung: Ordnernamen holen und als Transient merken
		if ( $dropbox_folder && CCGAL_Dropbox_OAuth::is_connected() ) {
			$folder_name = CCGAL_Storage::adapter( 'dropbox' )->get_folder_name( $dropbox_folder );
			if ( is_wp_error( $folder_name ) ) {
				set_transient( 'ccgal_meta_notice_' . $post_id,
					sprintf( /* translators: %s: error message */ __( 'Dropbox folder not found: %s — please check the path.', 'customer-cloud-gallery' ), $folder_name->get_error_message() ),
					30
				);
			} else {
				set_transient( 'ccgal_meta_notice_ok_' . $post_id, $folder_name, 30 );
			}
		} elseif ( $drive_folder && CCGAL_Google_OAuth::is_connected() ) {
			$folder_name = CCGAL_Storage::adapter( 'google_drive' )->get_folder_name( $drive_folder );
			if ( is_wp_error( $folder_name ) ) {
				set_transient( 'ccgal_meta_notice_' . $post_id,
					sprintf( /* translators: %s: error message */ __( 'Google Drive folder not found: %s', 'customer-cloud-gallery' ), $folder_name->get_error_message() ),
					30
				);
			} else {
				set_transient( 'ccgal_meta_notice_ok_' . $post_id, $folder_name, 30 );
			}
		}
		$per_page    = isset( $_POST['ccgal_per_page'] ) ? absint( $_POST['ccgal_per_page'] ) : 40;
		$load_mode_raw = isset( $_POST['ccgal_load_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_load_mode'] ) ) : 'scroll';
		$load_mode     = in_array( $load_mode_raw, array( 'scroll', 'button' ), true ) ? $load_mode_raw : 'scroll';
		$image_size_raw = isset( $_POST['ccgal_image_size'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_image_size'] ) ) : 'medium';
		$image_size     = in_array( $image_size_raw, array( 'small', 'medium', 'large', 'xlarge' ), true ) ? $image_size_raw : 'medium';
		$downloads   = isset( $_POST['ccgal_allow_downloads'] ) ? '1' : '0';
		$favorites   = isset( $_POST['ccgal_allow_favorites'] ) ? '1' : '0';

		$sort_raw   = isset( $_POST['ccgal_sort_order'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_sort_order'] ) ) : 'name_natural';
		$sort_order = in_array( $sort_raw, CCGAL_CPT::valid_sort_orders(), true ) ? $sort_raw : 'name_natural';

		$align_raw    = isset( $_POST['ccgal_toolbar_align'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_toolbar_align'] ) ) : 'center';
		$toolbar_align = in_array( $align_raw, array( 'left', 'center', 'right' ), true ) ? $align_raw : 'center';

		update_post_meta( $post_id, 'ccgal_couple_name', $couple_name );
		update_post_meta( $post_id, 'ccgal_drive_folder_id', $drive_folder );
		update_post_meta( $post_id, 'ccgal_dropbox_folder_id', $dropbox_folder );
		update_post_meta( $post_id, 'ccgal_per_page', $per_page );
		update_post_meta( $post_id, 'ccgal_load_mode', $load_mode );
		update_post_meta( $post_id, 'ccgal_image_size', $image_size );
		update_post_meta( $post_id, 'ccgal_allow_downloads', $downloads );
		update_post_meta( $post_id, 'ccgal_allow_favorites', $favorites );
		update_post_meta( $post_id, 'ccgal_sort_order', $sort_order );
		update_post_meta( $post_id, 'ccgal_toolbar_align', $toolbar_align );

		// Cookie-Master ist in Free verfügbar (DSGVO — Cookie-Verwendung muss abstellbar sein).
		$cookies_enabled = isset( $_POST['ccgal_cookies_enabled'] ) ? '1' : '0';
		update_post_meta( $post_id, 'ccgal_cookies_enabled', $cookies_enabled );

		/**
		 * Pro statistics module saves its own sub-options here (stats opt-in).
		 * Free has no listeners.
		 *
		 * @param int    $post_id         Gallery post ID.
		 * @param string $cookies_enabled '1' or '0' — the current master setting.
		 */
		do_action( 'ccgal_save_gallery_cookies_sub_options', $post_id, $cookies_enabled );
	}

	public static function gallery_columns( $columns ) {
		$new = array();
		foreach ( $columns as $key => $label ) {
			$new[ $key ] = $label;
			if ( 'title' === $key ) {
				$new['ccgal_folder']    = __( 'Cloud folder', 'customer-cloud-gallery' );
				$new['ccgal_shortcode'] = __( 'Shortcode', 'customer-cloud-gallery' );
			}
		}
		/**
		 * Pro statistics module appends its own columns (views/favorites/downloads).
		 *
		 * @param array $new Current columns map.
		 */
		return apply_filters( 'ccgal_gallery_list_columns', $new );
	}

	public static function gallery_column_content( $column, $post_id ) {
		switch ( $column ) {
			case 'ccgal_folder':
				$cfg = CCGAL_CPT::get_gallery_config( $post_id );
				$fid = $cfg ? $cfg['folder_id'] : '';
				if ( $fid ) {
					echo '<code>' . esc_html( substr( $fid, 0, 12 ) . ( strlen( $fid ) > 12 ? '…' : '' ) ) . '</code>';
				} else {
					echo '<span style="color:#999;">—</span>';
				}
				break;
			case 'ccgal_shortcode':
				echo '<code>[' . esc_html( CCGAL_Gallery_Renderer::SHORTCODE_PRIMARY ) . ' id="' . (int) $post_id . '"]</code>';
				break;
			default:
				/**
				 * Pro statistics module renders its own column values (views/favorites/downloads).
				 *
				 * @param string $column  Column slug.
				 * @param int    $post_id Gallery post ID.
				 */
				do_action( 'ccgal_gallery_list_column_content', $column, $post_id );
				break;
		}
	}

	public static function render_settings_page() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		CCGAL_Welcome::render_panel();

		if ( ! empty( $_POST['ccgal_save_credentials'] ) && check_admin_referer( 'ccgal_save_credentials' ) ) {
			$client_id     = isset( $_POST['ccgal_client_id'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_client_id'] ) ) : '';
			$client_secret = isset( $_POST['ccgal_client_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_client_secret'] ) ) : '';
			CCGAL_Google_OAuth::save_client_credentials( $client_id, $client_secret );
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Credentials saved.', 'customer-cloud-gallery' ) . '</p></div>';
		}

		if ( ! empty( $_POST['ccgal_save_dropbox_credentials'] ) && check_admin_referer( 'ccgal_save_dropbox_credentials' ) ) {
			$app_key    = isset( $_POST['ccgal_dropbox_app_key'] )    ? sanitize_text_field( wp_unslash( $_POST['ccgal_dropbox_app_key'] ) )    : '';
			$app_secret = isset( $_POST['ccgal_dropbox_app_secret'] ) ? sanitize_text_field( wp_unslash( $_POST['ccgal_dropbox_app_secret'] ) ) : '';
			CCGAL_Dropbox_OAuth::save_app_credentials( $app_key, $app_secret );
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Dropbox credentials saved.', 'customer-cloud-gallery' ) . '</p></div>';
		}

		if ( ! empty( $_POST['ccgal_clear_cache'] ) && check_admin_referer( 'ccgal_clear_cache' ) ) {
			$count = CCGAL_Cache::clear_all();
			echo '<div class="notice notice-success is-dismissible"><p>' . sprintf( /* translators: %d: number of files */ esc_html( _n( '%d cached file deleted.', '%d cached files deleted.', $count, 'customer-cloud-gallery' ) ), (int) $count ) . '</p></div>';
		}

		if ( ! empty( $_POST['ccgal_save_design'] ) && check_admin_referer( 'ccgal_save_design' ) ) {
			$accent_raw = isset( $_POST['ccgal_accent_color'] ) ? wp_unslash( $_POST['ccgal_accent_color'] ) : '';
			$hover_raw  = isset( $_POST['ccgal_hover_color'] ) ? wp_unslash( $_POST['ccgal_hover_color'] ) : '';
			$border_raw = isset( $_POST['ccgal_border_color'] ) ? wp_unslash( $_POST['ccgal_border_color'] ) : '';
			update_option( 'ccgal_accent_color', self::sanitize_color_value( $accent_raw ) );
			update_option( 'ccgal_hover_color', self::sanitize_color_value( $hover_raw ) );
			update_option( 'ccgal_border_color', self::sanitize_color_value( $border_raw ) );
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Design saved.', 'customer-cloud-gallery' ) . '</p></div>';
		}

		if ( ! empty( $_POST['ccgal_save_text_overrides'] ) && check_admin_referer( 'ccgal_save_text_overrides' ) ) {
			// $raw_inputs is intentionally passed as the raw $_POST array — every value
			// is unslashed + wp_kses_post()-sanitized inside CCGAL_Text_Overrides::save_overrides()
			// at the per-key level. Pre-unslashing the outer array would double-unslash.
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- see comment above; values handled per-key inside save_overrides()
			$raw_inputs = isset( $_POST['ccgal_text_override'] ) && is_array( $_POST['ccgal_text_override'] )
				? $_POST['ccgal_text_override']
				: array();
			// Form encodiert msgids base64, weil msgids Sonderzeichen, Anführungszeichen
			// und Newlines enthalten können — als rohe input-name würde das brechen.
			$inputs = array();
			foreach ( $raw_inputs as $b64 => $value ) {
				$msgid = base64_decode( (string) $b64, true );
				if ( false === $msgid ) {
					continue;
				}
				$inputs[ $msgid ] = $value;
			}
			$result = CCGAL_Text_Overrides::save_overrides( $inputs );
			echo '<div class="notice notice-success is-dismissible"><p>'
				. sprintf(
					/* translators: 1: number of saved overrides, 2: number of cleared overrides. */
					esc_html__( 'Text overrides saved (%1$d active, %2$d removed).', 'customer-cloud-gallery' ),
					(int) $result['saved'],
					(int) $result['cleared']
				)
				. '</p></div>';
			if ( ! empty( $result['warnings'] ) ) {
				echo '<div class="notice notice-warning is-dismissible">';
				foreach ( $result['warnings'] as $warning ) {
					echo '<p>⚠ ' . esc_html( $warning ) . '</p>';
				}
				echo '</div>';
			}
		}

		$client_id       = CCGAL_Google_OAuth::get_client_id();
		$has_secret      = (bool) get_option( 'ccgal_oauth_client_secret' );
		$is_connected    = CCGAL_Google_OAuth::is_connected();
		$redirect_uri    = CCGAL_Google_OAuth::get_redirect_uri();
		$db_app_key      = CCGAL_Dropbox_OAuth::get_app_key();
		$db_has_secret   = (bool) get_option( 'ccgal_dropbox_app_secret' );
		$db_connected    = CCGAL_Dropbox_OAuth::is_connected();
		$db_redirect_uri = CCGAL_Dropbox_OAuth::get_redirect_uri();
		$accent_color = self::get_accent_color();
		$hover_color  = self::get_hover_color();
		$border_color = self::get_border_color();
		?>
		<div class="wrap">
			<h1><?php esc_html_e( 'Customer Cloud Gallery — Settings', 'customer-cloud-gallery' ); ?></h1>

			<h2><?php esc_html_e( 'Cloud storage', 'customer-cloud-gallery' ); ?></h2>
			<p style="max-width:800px;"><?php esc_html_e( 'You can connect Google Drive, Dropbox, or both at the same time. When creating a gallery, simply pick the folder from your preferred provider — the plugin automatically detects which one to use.', 'customer-cloud-gallery' ); ?></p>

			<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;max-width:1100px;margin:20px 0;">

				<!-- ── Google Drive ─────────────────────────────────────── -->
				<div class="postbox" id="ccgal-google-drive-section" style="margin:0;">
					<div class="postbox-header" style="border-bottom:1px solid #ddd;padding:10px 16px;display:flex;align-items:center;justify-content:space-between;">
						<h2 style="margin:0;font-size:1em;">Google Drive</h2>
						<?php if ( $is_connected ) : ?>
							<span style="color:#1a7e3a;font-weight:600;font-size:.85em;">● <?php esc_html_e( 'Connected', 'customer-cloud-gallery' ); ?></span>
						<?php else : ?>
							<span style="color:#b00020;font-size:.85em;">● <?php esc_html_e( 'Not connected', 'customer-cloud-gallery' ); ?></span>
						<?php endif; ?>
					</div>
					<div style="padding:16px;">
						<p class="description" style="margin:0 0 12px;"><?php esc_html_e( 'Client ID and Client Secret from the Google Cloud Console.', 'customer-cloud-gallery' ); ?></p>
						<?php if ( class_exists( 'CCGAL_Welcome' ) ) { CCGAL_Welcome::render_drive_setup_guide(); } ?>
						<p class="description" style="margin:12px 0 10px;">
							<strong><?php esc_html_e( 'Redirect URI:', 'customer-cloud-gallery' ); ?></strong><br />
							<input type="text" readonly value="<?php echo esc_attr( $redirect_uri ); ?>" onclick="this.select()" style="width:100%;font-family:monospace;font-size:.85em;margin-top:3px;" />
						</p>
						<form method="post" action="">
							<?php wp_nonce_field( 'ccgal_save_credentials' ); ?>
							<input type="hidden" name="ccgal_save_credentials" value="1" />
							<table class="form-table" style="margin:0;">
								<tr>
									<th style="padding:6px 10px 6px 0;width:120px;"><label for="ccgal_client_id"><?php esc_html_e( 'Client ID', 'customer-cloud-gallery' ); ?></label></th>
									<td style="padding:4px 0;"><input type="text" id="ccgal_client_id" name="ccgal_client_id" value="<?php echo esc_attr( $client_id ); ?>" class="regular-text" style="width:100%;max-width:360px;" /></td>
								</tr>
								<tr>
									<th style="padding:6px 10px 6px 0;"><label for="ccgal_client_secret"><?php esc_html_e( 'Client Secret', 'customer-cloud-gallery' ); ?></label></th>
									<td style="padding:4px 0;">
										<input type="password" id="ccgal_client_secret" name="ccgal_client_secret" value="" placeholder="<?php echo esc_attr( $has_secret ? __( '(saved)', 'customer-cloud-gallery' ) : '' ); ?>" class="regular-text" style="width:100%;max-width:360px;" autocomplete="new-password" />
									</td>
								</tr>
							</table>
							<p style="margin:10px 0 0;">
								<button type="submit" class="button"><?php esc_html_e( 'Save credentials', 'customer-cloud-gallery' ); ?></button>
							</p>
						</form>
						<div style="margin-top:10px;padding-top:10px;border-top:1px solid #f0f0f0;">
							<?php if ( $is_connected ) : ?>
								<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
									<?php wp_nonce_field( 'ccgal_oauth_disconnect' ); ?>
									<input type="hidden" name="action" value="ccgal_oauth_disconnect" />
									<button type="submit" class="button"><?php esc_html_e( 'Disconnect', 'customer-cloud-gallery' ); ?></button>
								</form>
							<?php elseif ( $client_id && $has_secret ) : ?>
								<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
									<?php wp_nonce_field( 'ccgal_oauth_start' ); ?>
									<input type="hidden" name="action" value="ccgal_oauth_start" />
									<button type="submit" class="button button-primary"><?php esc_html_e( 'Connect Google Drive', 'customer-cloud-gallery' ); ?></button>
								</form>
							<?php else : ?>
								<p class="description" style="margin:0;"><?php esc_html_e( 'Save credentials, then connect.', 'customer-cloud-gallery' ); ?></p>
							<?php endif; ?>
						</div>
					</div>
				</div>

				<!-- ── Dropbox ──────────────────────────────────────────── -->
				<div class="postbox" id="ccgal-dropbox-section" style="margin:0;">
					<div class="postbox-header" style="border-bottom:1px solid #ddd;padding:10px 16px;display:flex;align-items:center;justify-content:space-between;">
						<h2 style="margin:0;font-size:1em;">Dropbox</h2>
						<?php if ( $db_connected ) : ?>
							<span style="color:#1a7e3a;font-weight:600;font-size:.85em;">● <?php esc_html_e( 'Connected', 'customer-cloud-gallery' ); ?></span>
						<?php else : ?>
							<span style="color:#b00020;font-size:.85em;">● <?php esc_html_e( 'Not connected', 'customer-cloud-gallery' ); ?></span>
						<?php endif; ?>
					</div>
					<div style="padding:16px;">
						<p class="description" style="margin:0 0 12px;"><?php esc_html_e( 'App key and App secret from the Dropbox App Console (dropbox.com/developers). Choose "Scoped access" + "Full Dropbox".', 'customer-cloud-gallery' ); ?></p>
						<?php if ( class_exists( 'CCGAL_Welcome' ) ) { CCGAL_Welcome::render_dropbox_setup_guide(); } ?>
						<p class="description" style="margin:12px 0 10px;">
							<strong><?php esc_html_e( 'Redirect URI:', 'customer-cloud-gallery' ); ?></strong><br />
							<input type="text" readonly value="<?php echo esc_attr( $db_redirect_uri ); ?>" onclick="this.select()" style="width:100%;font-family:monospace;font-size:.85em;margin-top:3px;" />
						</p>
						<form method="post" action="">
							<?php wp_nonce_field( 'ccgal_save_dropbox_credentials' ); ?>
							<input type="hidden" name="ccgal_save_dropbox_credentials" value="1" />
							<table class="form-table" style="margin:0;">
								<tr>
									<th style="padding:6px 10px 6px 0;width:120px;"><label for="ccgal_dropbox_app_key"><?php esc_html_e( 'App Key', 'customer-cloud-gallery' ); ?></label></th>
									<td style="padding:4px 0;"><input type="text" id="ccgal_dropbox_app_key" name="ccgal_dropbox_app_key" value="<?php echo esc_attr( $db_app_key ); ?>" class="regular-text" style="width:100%;max-width:360px;" /></td>
								</tr>
								<tr>
									<th style="padding:6px 10px 6px 0;"><label for="ccgal_dropbox_app_secret"><?php esc_html_e( 'App Secret', 'customer-cloud-gallery' ); ?></label></th>
									<td style="padding:4px 0;">
										<input type="password" id="ccgal_dropbox_app_secret" name="ccgal_dropbox_app_secret" value="" placeholder="<?php echo esc_attr( $db_has_secret ? __( '(saved)', 'customer-cloud-gallery' ) : '' ); ?>" class="regular-text" style="width:100%;max-width:360px;" autocomplete="new-password" />
									</td>
								</tr>
							</table>
							<p style="margin:10px 0 0;">
								<button type="submit" class="button"><?php esc_html_e( 'Save credentials', 'customer-cloud-gallery' ); ?></button>
							</p>
						</form>
						<div style="margin-top:10px;padding-top:10px;border-top:1px solid #f0f0f0;">
							<?php if ( $db_connected ) : ?>
								<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
									<?php wp_nonce_field( 'ccgal_dropbox_oauth_disconnect' ); ?>
									<input type="hidden" name="action" value="ccgal_dropbox_oauth_disconnect" />
									<button type="submit" class="button"><?php esc_html_e( 'Disconnect', 'customer-cloud-gallery' ); ?></button>
								</form>
							<?php elseif ( $db_app_key && $db_has_secret ) : ?>
								<form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
									<?php wp_nonce_field( 'ccgal_dropbox_oauth_start' ); ?>
									<input type="hidden" name="action" value="ccgal_dropbox_oauth_start" />
									<button type="submit" class="button button-primary"><?php esc_html_e( 'Connect Dropbox', 'customer-cloud-gallery' ); ?></button>
								</form>
							<?php else : ?>
								<p class="description" style="margin:0;"><?php esc_html_e( 'Save credentials, then connect.', 'customer-cloud-gallery' ); ?></p>
							<?php endif; ?>
						</div>
					</div>
				</div>

			</div><!-- .grid -->

			<hr />

			<h2><?php esc_html_e( 'Design', 'customer-cloud-gallery' ); ?></h2>
			<p><?php esc_html_e( 'Accent color of the gallery — used for buttons, favorite heart, and other accents. The hover color can be set separately; if empty, a darker variant of the accent color is computed automatically.', 'customer-cloud-gallery' ); ?></p>
			<p><?php esc_html_e( 'Leave empty to inherit your theme primary color (supports Block themes and Astra).', 'customer-cloud-gallery' ); ?></p>
			<form method="post" action="">
				<?php wp_nonce_field( 'ccgal_save_design' ); ?>
				<input type="hidden" name="ccgal_save_design" value="1" />
				<table class="form-table">
					<tr>
						<th><label for="ccgal_accent_color"><?php esc_html_e( 'Accent color', 'customer-cloud-gallery' ); ?></label></th>
						<td>
							<input type="text" id="ccgal_accent_color" name="ccgal_accent_color" class="ccgal-color-picker" value="<?php echo esc_attr( $accent_color ); ?>" />
							<p class="description"><?php esc_html_e( 'Use "Clear" in the picker to reset — the theme color takes over.', 'customer-cloud-gallery' ); ?></p>
						</td>
					</tr>
					<tr>
						<th><label for="ccgal_hover_color"><?php esc_html_e( 'Hover color', 'customer-cloud-gallery' ); ?></label></th>
						<td>
							<input type="text" id="ccgal_hover_color" name="ccgal_hover_color" class="ccgal-color-picker" value="<?php echo esc_attr( $hover_color ); ?>" />
							<p class="description"><?php esc_html_e( 'Color for hover states of buttons and interactive elements. Leave empty for an auto-darkened accent color.', 'customer-cloud-gallery' ); ?></p>
						</td>
					</tr>
					<tr>
						<th><label for="ccgal_border_color"><?php esc_html_e( 'Button border', 'customer-cloud-gallery' ); ?></label></th>
						<td>
							<input type="text" id="ccgal_border_color" name="ccgal_border_color" class="ccgal-color-picker" value="<?php echo esc_attr( $border_color ); ?>" />
							<p class="description"><?php esc_html_e( 'Border color for inactive buttons. Leave empty for a subtle default border.', 'customer-cloud-gallery' ); ?></p>
						</td>
					</tr>
				</table>
				<p><button type="submit" class="button button-primary"><?php esc_html_e( 'Save design', 'customer-cloud-gallery' ); ?></button></p>
			</form>

			<hr />

			<h2><?php esc_html_e( 'Cache', 'customer-cloud-gallery' ); ?></h2>
			<p><?php esc_html_e( 'Thumbnails are cached under uploads/ccgal-cache/. Old files are cleaned up automatically.', 'customer-cloud-gallery' ); ?></p>
			<form method="post" action="">
				<?php wp_nonce_field( 'ccgal_clear_cache' ); ?>
				<input type="hidden" name="ccgal_clear_cache" value="1" />
				<button type="submit" class="button"><?php esc_html_e( 'Clear cache now', 'customer-cloud-gallery' ); ?></button>
			</form>

			<hr />

			<?php self::render_text_overrides_section(); ?>

			<?php if ( ! ccgal_is_pro() ) : ?>
				<hr />
				<div class="ccgal-pro-info" style="margin-top:20px;padding:18px 22px;border:1px solid #d4cbb8;background:#faf6ed;border-radius:6px;max-width:900px;">
					<h2 style="margin-top:0;"><?php esc_html_e( 'Customer Cloud Gallery Pro', 'customer-cloud-gallery' ); ?></h2>
					<p style="max-width:760px;">
						<?php esc_html_e( 'Need more? The Pro version adds visitor statistics, a built-in print-order shop with checkout, custom email templates for orders, and additional gallery analytics — all delivered as a separate paid version, not bundled with the WordPress.org build.', 'customer-cloud-gallery' ); ?>
					</p>
					<p>
						<a href="<?php echo esc_url( ccgal_pro_upgrade_url() ); ?>" class="button button-primary"><?php esc_html_e( 'Learn more about Pro', 'customer-cloud-gallery' ); ?></a>
					</p>
				</div>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * UI für Pro-Site-Override aller Plugin-Strings.
	 *
	 * Ein Override gilt global (für ALLE Galerien dieser Installation).
	 * Leeres Feld = Default-Übersetzung der WordPress-Sprache wird verwendet.
	 */
	private static function render_text_overrides_section() {
		$overrides = CCGAL_Text_Overrides::get_overrides();
		$groups    = CCGAL_Text_Overrides::get_curated_groups();
		?>
		<h2><?php esc_html_e( 'Custom button labels & text', 'customer-cloud-gallery' ); ?></h2>
		<p style="max-width:800px;">
			<?php esc_html_e( 'Override individual texts (buttons, headings, validation messages) without touching the translation files. Empty input = default translation in your WordPress language is used. Overrides apply to ALL galleries on this site.', 'customer-cloud-gallery' ); ?>
		</p>
		<p style="max-width:800px;color:#7a756c;font-size:.92em;">
			<?php
			echo esc_html( sprintf(
				/* translators: %1$s and %2$s are kept verbatim — they are literal printf-style placeholder markers (e.g. %s, %d) shown to the user as examples; do not translate or substitute them. */
				__( 'Tip: if a default text contains placeholders (%1$s, %2$s, …), keep them in your custom text — otherwise the rendered output may be incomplete.', 'customer-cloud-gallery' ),
				'%s',
				'%d'
			) );
			?>
		</p>

		<form method="post" action="">
			<?php wp_nonce_field( 'ccgal_save_text_overrides' ); ?>
			<input type="hidden" name="ccgal_save_text_overrides" value="1" />

			<?php foreach ( $groups as $group_key => $group ) : ?>
				<?php
				// Print-Order-Strings werden im Free-Build nirgends gerendert (Print-Shop ist
				// eine separate kostenpflichtige Version). Wir blenden die Gruppe komplett aus,
				// statt einen „Pro"-Lock-Marker zu zeigen.
				if ( ! ccgal_is_pro() && 0 === strpos( $group_key, 'print_' ) ) {
					continue;
				}
				?>
				<h3 style="margin-top:24px;">
					<?php echo esc_html( $group['label'] ); ?>
				</h3>
				<table class="widefat striped" style="max-width:1100px;">
					<thead>
						<tr>
							<th style="width:38%;"><?php esc_html_e( 'Default text (English)', 'customer-cloud-gallery' ); ?></th>
							<th><?php esc_html_e( 'Your custom text (leave empty for default)', 'customer-cloud-gallery' ); ?></th>
						</tr>
					</thead>
					<tbody>
						<?php foreach ( $group['strings'] as $msgid ) :
							$key      = base64_encode( $msgid );
							$current  = isset( $overrides[ $msgid ] ) ? $overrides[ $msgid ] : '';
							// WICHTIG: get_untranslated_default() umgeht unseren eigenen gettext-Filter.
							// Nur so liefert die Variable den echten .mo-Wert (oder den englischen Source-
							// String, falls keine Übersetzung). Würden wir hier translate() direkt nutzen,
							// käme der bereits gespeicherte Override zurück → Placeholder + Vorschau wären
							// identisch zum Override und User sieht nicht, was „leeren" bewirken würde.
							$default  = CCGAL_Text_Overrides::get_untranslated_default( $msgid );
							?>
							<tr>
								<td>
									<code style="display:block;font-size:.85em;color:#5a544a;line-height:1.5;"><?php echo esc_html( $msgid ); ?></code>
									<?php if ( $default !== $msgid ) : ?>
										<small style="color:#1a7e3a;display:block;margin-top:4px;">
											<?php
											echo wp_kses(
												sprintf(
													/* translators: %s: default translation in current WP language. */
													__( 'Current WP language → %s', 'customer-cloud-gallery' ),
													'<em>' . esc_html( $default ) . '</em>'
												),
												array( 'em' => array() )
											);
											?>
										</small>
									<?php endif; ?>
								</td>
								<td>
									<?php
									$is_long = strlen( $msgid ) > 60 || false !== strpos( $msgid, "\n" );
									if ( $is_long ) :
										?>
										<textarea
											name="ccgal_text_override[<?php echo esc_attr( $key ); ?>]"
											rows="2"
											style="width:100%;font-size:.92em;"
											placeholder="<?php echo esc_attr( $default ); ?>"
											data-ccgal-default="<?php echo esc_attr( $default ); ?>"
										><?php echo esc_textarea( $current ); ?></textarea>
									<?php else : ?>
										<input
											type="text"
											name="ccgal_text_override[<?php echo esc_attr( $key ); ?>]"
											value="<?php echo esc_attr( $current ); ?>"
											style="width:100%;"
											placeholder="<?php echo esc_attr( $default ); ?>"
											data-ccgal-default="<?php echo esc_attr( $default ); ?>"
										/>
									<?php endif; ?>
									<small class="ccgal-override-default-hint" style="display:none;color:#1a7e3a;margin-top:3px;">
										<?php esc_html_e( '↻ Default restored — save to apply, or type to override again.', 'customer-cloud-gallery' ); ?>
									</small>
								</td>
							</tr>
						<?php endforeach; ?>
					</tbody>
				</table>
			<?php endforeach; ?>

			<?php
			// „Erweitert"-Sektion: zeigt zusätzlich alle Overrides, die NICHT in den
			// kuratierten Gruppen liegen — z. B. wenn der User per WP-CLI o. ä. einen
			// eigenen msgid eingetragen hat. So gehen sie nicht beim Speichern verloren.
			$known = array();
			foreach ( $groups as $g ) {
				foreach ( $g['strings'] as $s ) {
					$known[ $s ] = true;
				}
			}
			$extra = array_diff_key( $overrides, $known );
			if ( ! empty( $extra ) ) :
				?>
				<h3 style="margin-top:24px;"><?php esc_html_e( 'Other overrides (set externally)', 'customer-cloud-gallery' ); ?></h3>
				<table class="widefat striped" style="max-width:1100px;">
					<tbody>
						<?php foreach ( $extra as $msgid => $value ) :
							$key = base64_encode( $msgid );
							?>
							<tr>
								<td><code style="font-size:.85em;"><?php echo esc_html( $msgid ); ?></code></td>
								<td>
									<input type="text" name="ccgal_text_override[<?php echo esc_attr( $key ); ?>]"
										value="<?php echo esc_attr( $value ); ?>" style="width:100%;" />
								</td>
							</tr>
						<?php endforeach; ?>
					</tbody>
				</table>
			<?php endif; ?>

			<p style="margin-top:18px;">
				<button type="submit" class="button button-primary"><?php esc_html_e( 'Save text overrides', 'customer-cloud-gallery' ); ?></button>
			</p>
		</form>
		<?php
	}

	public static function plugin_action_links( $links ) {
		$settings_link = '<a href="' . esc_url( admin_url( 'edit.php?post_type=' . CCGAL_CPT::POST_TYPE . '&page=ccgal-settings' ) ) . '">' . esc_html__( 'Settings', 'customer-cloud-gallery' ) . '</a>';
		array_unshift( $links, $settings_link );

		// Statischer Hinweis auf die separat angebotene Pro-Version — reine
		// „point out a separated plugin"-Variante (siehe wp.org Guidelines).
		if ( ! ccgal_is_pro() ) {
			$links[] = '<a href="' . esc_url( ccgal_pro_upgrade_url() ) . '" style="color:#d63638;font-weight:600;">' . esc_html__( 'Upgrade to Pro', 'customer-cloud-gallery' ) . '</a>';
		}

		return $links;
	}

	public static function admin_notices() {
		// One-time migration error surfaced by CCGAL_Activator::migrate_legacy_prefix()
		// when a RENAME TABLE statement fails (most commonly because the MySQL user
		// is missing the ALTER permission on shared hosting).
		$rename_failures = get_transient( 'ccgal_migration_table_rename_error' );
		if ( is_array( $rename_failures ) && ! empty( $rename_failures ) && current_user_can( 'manage_options' ) ) {
			printf(
				'<div class="notice notice-error"><p><strong>%s</strong> %s</p><p><code>%s</code></p></div>',
				esc_html__( 'Customer Cloud Gallery:', 'customer-cloud-gallery' ),
				esc_html__( 'The 1.12.1 database migration could not rename the following tables. The plugin will fall back to fresh empty tables — your existing galleries may appear empty. Most likely cause: missing ALTER permission on the database user. Please ask your host to grant ALTER on the database, then deactivate and reactivate the plugin.', 'customer-cloud-gallery' ),
				esc_html( implode( ', ', $rename_failures ) )
			);
			delete_transient( 'ccgal_migration_table_rename_error' );
		}

		if ( empty( $_GET['ccgal_msg'] ) ) {
			return;
		}
		$msg = sanitize_text_field( wp_unslash( $_GET['ccgal_msg'] ) );
		$map = array(
			'connected'           => array( 'success', __( 'Successfully connected.', 'customer-cloud-gallery' ) ),
			'disconnected'        => array( 'success', __( 'Disconnected.', 'customer-cloud-gallery' ) ),
			'oauth_error'         => array( 'error', __( 'OAuth error. Please try again.', 'customer-cloud-gallery' ) ),
			'token_error'         => array( 'error', __( 'Token could not be retrieved.', 'customer-cloud-gallery' ) ),
			'missing_credentials' => array( 'error', __( 'Credentials are missing.', 'customer-cloud-gallery' ) ),
		);
		/**
		 * Allow Pro modules to register their own admin-notice messages
		 * (e.g. statistics-reset confirmations).
		 *
		 * @param array $map Map of msg-slug => [type, translated string].
		 */
		$map = apply_filters( 'ccgal_admin_notice_map', $map );
		if ( ! isset( $map[ $msg ] ) ) {
			return;
		}
		printf(
			'<div class="notice notice-%s is-dismissible"><p>%s</p></div>',
			esc_attr( $map[ $msg ][0] ),
			esc_html( $map[ $msg ][1] )
		);
	}
}
