<?php
namespace R2_Offload\Admin;

use R2_Offload\Logger;
use R2_Offload\Migration\Migrator;
use R2_Offload\Migration\Scanner;
use R2_Offload\Settings;
use R2_Offload\Storage\Providers;
use R2_Offload\Storage\Storage_Client;

defined( 'ABSPATH' ) || exit;

final class Admin {

	private Settings $settings;
	private Logger $logger;
	private Storage_Client $storage;
	private Scanner $scanner;
	private Migrator $migrator;

	public function __construct( Settings $settings, Logger $logger, Storage_Client $storage, Scanner $scanner, Migrator $migrator ) {
		$this->settings = $settings;
		$this->logger   = $logger;
		$this->storage  = $storage;
		$this->scanner  = $scanner;
		$this->migrator = $migrator;
	}

	public function register(): void {
		add_action( 'admin_menu', [ $this, 'menu' ] );
		add_action( 'admin_init', [ $this, 'handle_post' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
		add_action( 'admin_notices', [ $this, 'deps_notice' ] );
	}

	public function deps_notice(): void {
		// GCS is the only backend needing a composer SDK. Skip the notice otherwise.
		if ( $this->settings->provider() !== 'gcs' || \R2_Offload\Installer::vendor_ready() ) {
			return;
		}
		$state = \R2_Offload\Installer::state();
		?>
		<div class="notice notice-warning r2-deps-notice">
			<p><strong><?php esc_html_e( 'R2 Offload:', 'r2-offload' ); ?></strong>
				<?php esc_html_e( 'Google Cloud Storage SDK is not installed.', 'r2-offload' ); ?>
				<button class="button button-primary" data-r2-action="install-deps"><?php esc_html_e( 'Install dependencies now', 'r2-offload' ); ?></button>
				<span data-r2-deps-status></span>
			</p>
			<?php if ( ! empty( $state['message'] ) && $state['status'] === 'error' ) : ?>
				<pre style="white-space:pre-wrap;max-height:200px;overflow:auto;"><?php echo esc_html( $state['message'] ); ?></pre>
				<p><em><?php
					printf(
						/* translators: %s path */
						esc_html__( 'Fallback: run `composer install` manually inside %s', 'r2-offload' ),
						'<code>' . esc_html( R2_OFFLOAD_DIR ) . '</code>'
					);
				?></em></p>
			<?php endif; ?>
		</div>
		<?php
	}

	public function enqueue_assets( string $hook ): void {
		if ( strpos( $hook, 'r2-offload' ) === false ) {
			return;
		}
		wp_enqueue_style( 'r2-offload-admin', R2_OFFLOAD_URL . 'assets/admin.css', [], R2_OFFLOAD_VERSION );
		wp_enqueue_script( 'r2-offload-admin', R2_OFFLOAD_URL . 'assets/admin.js', [], R2_OFFLOAD_VERSION, true );
		wp_localize_script(
			'r2-offload-admin',
			'R2Offload',
			[
				'ajaxUrl'   => admin_url( 'admin-ajax.php' ),
				'nonce'     => wp_create_nonce( 'r2_offload_ajax' ),
				'regions'   => self::provider_regions_map(),
				'guides'    => self::provider_guides_map(),
			]
		);
	}

	private static function provider_regions_map(): array {
		$out = [];
		foreach ( Providers::all() as $id => $p ) {
			$out[ $id ] = array_values( (array) ( $p['regions'] ?? [] ) );
		}
		return $out;
	}

	/** Per-provider setup-guide payload for live switching in the settings form. */
	private static function provider_guides_map(): array {
		$out = [];
		foreach ( Providers::all() as $id => $p ) {
			$out[ $id ] = [
				/* translators: %s provider label */
				'summary' => sprintf( __( 'Setup guide: %s', 'r2-offload' ), $p['label'] ),
				'steps'   => array_values( (array) ( $p['guide'] ?? [] ) ),
			];
		}
		return $out;
	}

	public function menu(): void {
		add_menu_page(
			__( 'R2 Offload', 'r2-offload' ),
			__( 'R2 Offload', 'r2-offload' ),
			'manage_options',
			'r2-offload',
			[ $this, 'render_settings' ],
			'dashicons-cloud-upload',
			80
		);
		add_submenu_page( 'r2-offload', __( 'Settings', 'r2-offload' ), __( 'Settings', 'r2-offload' ), 'manage_options', 'r2-offload', [ $this, 'render_settings' ] );
		add_submenu_page( 'r2-offload', __( 'Migration', 'r2-offload' ), __( 'Migration', 'r2-offload' ), 'manage_options', 'r2-offload-migration', [ $this, 'render_migration' ] );
		add_submenu_page( 'r2-offload', __( 'Providers & Pricing', 'r2-offload' ), __( 'Providers & Pricing', 'r2-offload' ), 'manage_options', 'r2-offload-providers', [ $this, 'render_providers' ] );
		add_submenu_page( 'r2-offload', __( 'Logs', 'r2-offload' ), __( 'Logs', 'r2-offload' ), 'manage_options', 'r2-offload-logs', [ $this, 'render_logs' ] );
	}

	public function render_migration(): void {
		$page = new Migration_Page( $this->scanner, $this->migrator );
		$page->render();
	}

	public function render_providers(): void {
		$page = new Providers_Page( $this->settings, $this->migrator );
		$page->render();
	}

	public function handle_post(): void {
		if ( empty( $_POST['r2_offload_nonce'] ) || ! current_user_can( 'manage_options' ) ) {
			return;
		}
		if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['r2_offload_nonce'] ) ), 'r2_offload_save' ) ) {
			return;
		}

		$provider = sanitize_key( wp_unslash( $_POST['provider'] ?? 'gcs' ) );
		if ( ! Providers::exists( $provider ) ) {
			$provider = 'gcs';
		}

		$values = [
			'provider'           => $provider,
			'bucket'             => sanitize_text_field( wp_unslash( $_POST['bucket'] ?? '' ) ),
			'object_prefix'      => trim( sanitize_text_field( wp_unslash( $_POST['object_prefix'] ?? '' ) ), '/' ),
			'cdn_domain'         => esc_url_raw( wp_unslash( $_POST['cdn_domain'] ?? '' ) ),
			'credentials_json'   => trim( (string) wp_unslash( $_POST['credentials_json'] ?? '' ) ),
			'access_key'         => trim( sanitize_text_field( wp_unslash( $_POST['access_key'] ?? '' ) ) ),
			'region'             => trim( sanitize_text_field( wp_unslash( $_POST['region'] ?? '' ) ) ),
			'account_id'         => trim( sanitize_text_field( wp_unslash( $_POST['account_id'] ?? '' ) ) ),
			'r2_jurisdiction'    => sanitize_key( wp_unslash( $_POST['r2_jurisdiction'] ?? '' ) ),
			'bunny_storage_zone' => trim( sanitize_text_field( wp_unslash( $_POST['bunny_storage_zone'] ?? '' ) ) ),
			'bunny_region'       => trim( sanitize_text_field( wp_unslash( $_POST['bunny_region'] ?? '' ) ) ),
			'rewrite_urls'       => empty( $_POST['rewrite_urls'] ) ? 0 : 1,
			'keep_local'         => empty( $_POST['keep_local'] ) ? 0 : 1,
			'private_mode'       => empty( $_POST['private_mode'] ) ? 0 : 1,
			'offload_on_upload'  => empty( $_POST['offload_on_upload'] ) ? 0 : 1,
			'use_legacy_acl'     => empty( $_POST['use_legacy_acl'] ) ? 0 : 1,
			'auto_prune'         => empty( $_POST['auto_prune'] ) ? 0 : 1,
			'archive_originals'  => empty( $_POST['archive_originals'] ) ? 0 : 1,
			'originals_prefix'   => trim( sanitize_text_field( wp_unslash( $_POST['originals_prefix'] ?? 'wp-content/uploads-originals' ) ), '/' ),
			'wait_for_imagify'   => empty( $_POST['wait_for_imagify'] ) ? 0 : 1,
			'imagify_fallback_delay' => max( 60, min( 900, (int) ( $_POST['imagify_fallback_delay'] ?? 180 ) ) ),
			'multisite_prefix'   => empty( $_POST['multisite_prefix'] ) ? 0 : 1,
			'rate_ms'            => max( 0, min( 5000, (int) ( $_POST['rate_ms'] ?? 150 ) ) ),
			'max_retries'        => max( 0, min( 10, (int) ( $_POST['max_retries'] ?? 4 ) ) ),
			'backoff_base_ms'    => max( 50, min( 10000, (int) ( $_POST['backoff_base_ms'] ?? 500 ) ) ),
			'extra_upload_dirs'  => sanitize_text_field( wp_unslash( $_POST['extra_upload_dirs'] ?? '' ) ),
		];

		// Cloudflare R2 always signs with region "auto" (no region field shown).
		if ( $provider === 'r2' ) {
			$values['region'] = 'auto';
			$j = (string) ( $values['r2_jurisdiction'] ?? '' );
			if ( ! in_array( $j, [ '', 'eu', 'fedramp' ], true ) ) {
				$values['r2_jurisdiction'] = '';
			}
			// Accept pasted endpoint URL/host in Account ID field.
			$aid = (string) $values['account_id'];
			if ( preg_match( '#([a-f0-9]{32})\.(?:eu\.|fedramp\.)?r2\.cloudflarestorage\.com#i', $aid, $m ) ) {
				$values['account_id'] = strtolower( $m[1] );
				// Infer jurisdiction from a pasted jurisdiction endpoint when field left default.
				if ( ( $values['r2_jurisdiction'] ?? '' ) === '' && preg_match( '#\.(eu|fedramp)\.r2\.cloudflarestorage\.com#i', $aid, $jm ) ) {
					$values['r2_jurisdiction'] = strtolower( $jm[1] );
				}
			}
		}

		// Secret key: blank submission keeps the stored value (never echoed back).
		$secret = (string) wp_unslash( $_POST['secret_key'] ?? '' );
		if ( $secret !== '' ) {
			$values['secret_key'] = trim( $secret );
		}

		$this->settings->update( $values );

		if ( ! empty( $_POST['test_connection'] ) ) {
			// Rebuild the client from the just-saved settings — the boot-time
			// instance may have cached a different (previous) provider.
			$storage = \R2_Offload\Storage\Storage_Factory::make( $this->settings, $this->logger );
			$result  = $storage->test_connection();
			add_settings_error( 'r2_offload', 'test', $result['message'], $result['ok'] ? 'success' : 'error' );
		} else {
			add_settings_error( 'r2_offload', 'saved', __( 'Settings saved.', 'r2-offload' ), 'success' );
		}
	}

	public function render_settings(): void {
		$s        = $this->settings->all();
		$provider = $this->settings->provider();
		$guide    = (array) ( Providers::get( $provider )['guide'] ?? [] );
		$has_secret = ! empty( $s['secret_key'] );
		?>
		<div class="wrap" id="r2-offload-settings" data-provider="<?php echo esc_attr( $provider ); ?>">
			<h1><?php esc_html_e( 'R2 Offload — Settings', 'r2-offload' ); ?></h1>
			<?php settings_errors( 'r2_offload' ); ?>

			<p><?php
				printf(
					/* translators: %s link */
					esc_html__( 'Need help choosing or registering a bucket? See %s for setup guides, pros/cons and a live cost estimate.', 'r2-offload' ),
					'<a href="' . esc_url( admin_url( 'admin.php?page=r2-offload-providers' ) ) . '">' . esc_html__( 'Providers & Pricing', 'r2-offload' ) . '</a>'
				);
			?></p>

			<div class="r2-serve-switch">
				<strong><?php esc_html_e( 'Serve media from:', 'r2-offload' ); ?></strong>
				<label class="r2-seg">
					<input type="radio" name="r2-serve-source" value="local" <?php checked( ! $s['rewrite_urls'] ); ?>>
					<span><?php esc_html_e( 'WordPress (local)', 'r2-offload' ); ?></span>
				</label>
				<label class="r2-seg">
					<input type="radio" name="r2-serve-source" value="remote" <?php checked( (bool) $s['rewrite_urls'] ); ?>>
					<span><?php echo esc_html( Providers::label( $provider ) ); ?> <?php esc_html_e( '(remote)', 'r2-offload' ); ?></span>
				</label>
				<span class="r2-serve-hint"><?php esc_html_e( 'Switches instantly. Applies to offloaded library media, WebP/AVIF sidecars, and extra folders.', 'r2-offload' ); ?></span>
			</div>

			<form method="post">
				<?php wp_nonce_field( 'r2_offload_save', 'r2_offload_nonce' ); ?>
				<table class="form-table" role="presentation">
					<tr>
						<th><label for="provider"><?php esc_html_e( 'Storage provider', 'r2-offload' ); ?></label></th>
						<td>
							<select name="provider" id="provider">
								<?php foreach ( Providers::all() as $id => $p ) : ?>
									<option value="<?php echo esc_attr( $id ); ?>" <?php selected( $provider, $id ); ?>><?php echo esc_html( $p['label'] ); ?></option>
								<?php endforeach; ?>
							</select>
							<p class="description r2-prov-guide"></p>
						</td>
					</tr>

					<tr data-prov="all">
						<th><label for="bucket"><?php esc_html_e( 'Bucket / Space name', 'r2-offload' ); ?></label></th>
						<td><input name="bucket" id="bucket" type="text" class="regular-text" value="<?php echo esc_attr( $s['bucket'] ); ?>"></td>
					</tr>

					<!-- GCS -->
					<tr data-prov="gcs">
						<th><label for="credentials_json"><?php esc_html_e( 'Service account JSON', 'r2-offload' ); ?></label></th>
						<td><textarea name="credentials_json" id="credentials_json" rows="8" class="large-text code" spellcheck="false"><?php echo esc_textarea( $s['credentials_json'] ); ?></textarea></td>
					</tr>

					<!-- S3-compatible -->
					<tr data-prov="s3 r2 b2 wasabi spaces">
						<th><label for="access_key"><?php esc_html_e( 'Access Key ID', 'r2-offload' ); ?></label></th>
						<td><input name="access_key" id="access_key" type="text" class="regular-text" value="<?php echo esc_attr( $s['access_key'] ); ?>" autocomplete="off"></td>
					</tr>
					<tr data-prov="s3 r2 b2 wasabi spaces bunny">
						<th><label for="secret_key"><?php esc_html_e( 'Secret Access Key', 'r2-offload' ); ?></label></th>
						<td>
							<input name="secret_key" id="secret_key" type="password" class="regular-text" value="" autocomplete="off"
								placeholder="<?php echo $has_secret ? esc_attr__( '•••••••• saved — leave blank to keep', 'r2-offload' ) : ''; ?>">
							<p class="description r2-prov-bunny-note" style="display:none"><?php esc_html_e( 'For Bunny.net this is the Storage Zone password (FTP & API Access).', 'r2-offload' ); ?></p>
						</td>
					</tr>
					<tr data-prov="s3 b2 wasabi spaces">
						<th><label for="region"><?php esc_html_e( 'Region', 'r2-offload' ); ?></label></th>
						<td>
							<input name="region" id="region" type="text" class="regular-text" value="<?php echo esc_attr( $s['region'] ); ?>" list="r2-region-list" autocomplete="off">
							<datalist id="r2-region-list"></datalist>
						</td>
					</tr>
					<tr data-prov="r2">
						<th><label for="account_id"><?php esc_html_e( 'Cloudflare Account ID', 'r2-offload' ); ?></label></th>
						<td>
							<input name="account_id" id="account_id" type="text" class="regular-text" value="<?php echo esc_attr( $s['account_id'] ); ?>" autocomplete="off" placeholder="32-char hex from R2 → Overview">
							<p class="description"><?php esc_html_e( 'Not the r2.dev hostname. Paste either the Account ID or the full S3 API endpoint.', 'r2-offload' ); ?></p>
						</td>
					</tr>
					<tr data-prov="r2">
						<th><label for="r2_jurisdiction"><?php esc_html_e( 'R2 jurisdiction', 'r2-offload' ); ?></label></th>
						<td>
							<select name="r2_jurisdiction" id="r2_jurisdiction">
								<?php foreach ( (array) ( Providers::get( 'r2' )['jurisdictions'] ?? [ '' => 'Default' ] ) as $jid => $jlabel ) : ?>
									<option value="<?php echo esc_attr( $jid ); ?>" <?php selected( (string) ( $s['r2_jurisdiction'] ?? '' ), (string) $jid ); ?>><?php echo esc_html( $jlabel ); ?></option>
								<?php endforeach; ?>
							</select>
							<p class="description"><?php esc_html_e( 'Must match how the bucket was created. Jurisdiction-locked buckets (e.g. EU) return HTTP 404 NoSuchBucket on the default endpoint.', 'r2-offload' ); ?></p>
						</td>
					</tr>

					<!-- Bunny -->
					<tr data-prov="bunny">
						<th><label for="bunny_storage_zone"><?php esc_html_e( 'Storage Zone name', 'r2-offload' ); ?></label></th>
						<td><input name="bunny_storage_zone" id="bunny_storage_zone" type="text" class="regular-text" value="<?php echo esc_attr( $s['bunny_storage_zone'] ); ?>"></td>
					</tr>
					<tr data-prov="bunny">
						<th><label for="bunny_region"><?php esc_html_e( 'Storage region', 'r2-offload' ); ?></label></th>
						<td>
							<input name="bunny_region" id="bunny_region" type="text" class="regular-text" value="<?php echo esc_attr( $s['bunny_region'] ); ?>" list="r2-bunny-region-list" placeholder="<?php esc_attr_e( 'blank = default (DE)', 'r2-offload' ); ?>">
							<datalist id="r2-bunny-region-list">
								<?php foreach ( (array) Providers::get( 'bunny' )['regions'] as $r ) : ?>
									<option value="<?php echo esc_attr( $r ); ?>"></option>
								<?php endforeach; ?>
							</datalist>
						</td>
					</tr>

					<tr data-prov="all">
						<th><label for="object_prefix"><?php esc_html_e( 'Object prefix', 'r2-offload' ); ?></label></th>
						<td><input name="object_prefix" id="object_prefix" type="text" class="regular-text" value="<?php echo esc_attr( $s['object_prefix'] ); ?>"></td>
					</tr>
					<tr data-prov="all">
						<th><label for="cdn_domain"><?php esc_html_e( 'CDN / public domain', 'r2-offload' ); ?></label></th>
						<td>
							<input name="cdn_domain" id="cdn_domain" type="text" class="regular-text" value="<?php echo esc_attr( $s['cdn_domain'] ); ?>" placeholder="cdn.example.com">
							<p class="description r2-prov-cdn-required" style="display:none"><strong><?php esc_html_e( 'Required for this provider — it has no public bucket URL (e.g. R2 needs an r2.dev or custom domain; Bunny needs its pull-zone host).', 'r2-offload' ); ?></strong></p>
						</td>
					</tr>
					<tr data-prov="all">
						<th><label for="extra_upload_dirs"><?php esc_html_e( 'Extra uploads folders', 'r2-offload' ); ?></label></th>
						<td>
							<input name="extra_upload_dirs" id="extra_upload_dirs" type="text" class="large-text" value="<?php echo esc_attr( $s['extra_upload_dirs'] ); ?>">
							<p class="description"><?php esc_html_e( 'Comma-separated top-level folders under wp-content/uploads to offload beyond the Media Library (BuddyBoss avatars, documents, etc.). WebP/AVIF sidecars next to library images are always included.', 'r2-offload' ); ?></p>
						</td>
					</tr>

					<tr>
						<th><?php esc_html_e( 'Rate limiting', 'r2-offload' ); ?></th>
						<td>
							<label><?php esc_html_e( 'Min delay between API calls (ms)', 'r2-offload' ); ?>
								<input type="number" name="rate_ms" min="0" max="5000" value="<?php echo esc_attr( $s['rate_ms'] ); ?>" style="width:100px">
							</label>
							&nbsp;
							<label><?php esc_html_e( 'Max retries', 'r2-offload' ); ?>
								<input type="number" name="max_retries" min="0" max="10" value="<?php echo esc_attr( $s['max_retries'] ); ?>" style="width:80px">
							</label>
							&nbsp;
							<label><?php esc_html_e( 'Backoff base (ms)', 'r2-offload' ); ?>
								<input type="number" name="backoff_base_ms" min="50" max="10000" value="<?php echo esc_attr( $s['backoff_base_ms'] ); ?>" style="width:100px">
							</label>
							<p class="description"><?php esc_html_e( 'Sequential migration + throttle keeps load under provider quotas. Retries use exponential backoff with jitter on 408/429/5xx.', 'r2-offload' ); ?></p>
						</td>
					</tr>
					<tr>
						<th><?php esc_html_e( 'Behavior', 'r2-offload' ); ?></th>
						<td>
							<label><input type="checkbox" name="offload_on_upload" value="1" <?php checked( $s['offload_on_upload'] ); ?>> <?php esc_html_e( 'Offload new uploads automatically', 'r2-offload' ); ?></label><br>
							<label><input type="checkbox" name="rewrite_urls" value="1" <?php checked( $s['rewrite_urls'] ); ?>> <?php esc_html_e( 'Rewrite media URLs to remote', 'r2-offload' ); ?></label><br>
							<label><input type="checkbox" name="keep_local" value="1" <?php checked( $s['keep_local'] ); ?>> <?php esc_html_e( 'Keep local copy after offload', 'r2-offload' ); ?></label>
							<p class="description"><?php esc_html_e( 'Informational for now — local files stay unless Auto-prune is enabled.', 'r2-offload' ); ?></p>
							<label><input type="checkbox" name="private_mode" value="1" <?php checked( $s['private_mode'] ); ?>> <?php esc_html_e( 'Private mode (no public-read ACL)', 'r2-offload' ); ?></label><br>
							<label data-prov="gcs s3 r2 b2 wasabi spaces"><input type="checkbox" name="use_legacy_acl" value="1" <?php checked( $s['use_legacy_acl'] ); ?>> <?php esc_html_e( 'Send per-object public-read ACL (only for ACL-enabled buckets; leave OFF for uniform/bucket-policy access and R2)', 'r2-offload' ); ?></label><br>
							<label><input type="checkbox" name="auto_prune" value="1" <?php checked( $s['auto_prune'] ); ?>> <?php esc_html_e( 'Auto-prune local files after verified offload (irreversible without restore)', 'r2-offload' ); ?></label>
							<p class="description"><?php esc_html_e( 'Conflicts with “Keep local copy”. If both are on, Auto-prune wins and deletes local files after a successful offload.', 'r2-offload' ); ?></p>
							<label><input type="checkbox" name="archive_originals" value="1" <?php checked( $s['archive_originals'] ); ?>> <?php esc_html_e( 'Archive pristine originals to remote (before Imagify)', 'r2-offload' ); ?></label>
							<p class="description"><?php esc_html_e( 'Uploads the untouched original to a separate R2 prefix (like Imagify’s local backup folder, but on Cloudflare). Local file stays until serve-offload + auto-prune.', 'r2-offload' ); ?></p>
							<label><?php esc_html_e( 'Originals prefix', 'r2-offload' ); ?>
								<input type="text" name="originals_prefix" value="<?php echo esc_attr( (string) $s['originals_prefix'] ); ?>" class="regular-text" placeholder="wp-content/uploads-originals">
							</label><br>
							<label><input type="checkbox" name="wait_for_imagify" value="1" <?php checked( $s['wait_for_imagify'] ); ?>> <?php esc_html_e( 'Wait for Imagify before serve offload / auto-prune', 'r2-offload' ); ?></label>
							<p class="description"><?php esc_html_e( 'Prevents “file does not seem to exist” and ensures R2 gets the optimized (+ webp) files. User still sees the upload immediately from local until offload finishes.', 'r2-offload' ); ?></p>
							<label><?php esc_html_e( 'Imagify fallback delay (seconds)', 'r2-offload' ); ?>
								<input type="number" name="imagify_fallback_delay" value="<?php echo esc_attr( (string) (int) $s['imagify_fallback_delay'] ); ?>" min="60" max="900" step="10" class="small-text">
							</label>
							<p class="description"><?php esc_html_e( 'If Imagify never completes, offload whatever is local after this many seconds (default 180).', 'r2-offload' ); ?></p>
							<?php if ( is_multisite() ) : ?>
								<label><input type="checkbox" name="multisite_prefix" value="1" <?php checked( $s['multisite_prefix'] ); ?>> <?php esc_html_e( 'Multisite: add per-site prefix (site-<blog_id>)', 'r2-offload' ); ?></label>
							<?php endif; ?>
						</td>
					</tr>
				</table>

				<details class="r2-setup-guide" <?php echo empty( $s['bucket'] ) && empty( $s['bunny_storage_zone'] ) ? 'open' : ''; ?>>
					<summary><strong><?php
						/* translators: %s provider label */
						printf( esc_html__( 'Setup guide: %s', 'r2-offload' ), esc_html( Providers::label( $provider ) ) );
					?></strong></summary>
					<ol class="r2-guide-steps">
						<?php foreach ( $guide as $step ) : ?>
							<li><?php echo wp_kses_post( make_clickable( esc_html( $step ) ) ); ?></li>
						<?php endforeach; ?>
					</ol>
				</details>

				<p>
					<?php submit_button( __( 'Save Settings', 'r2-offload' ), 'primary', 'save', false ); ?>
					<?php submit_button( __( 'Save & Test Connection', 'r2-offload' ), 'secondary', 'test_connection', false ); ?>
				</p>
			</form>
		</div>
		<?php
	}

	public function render_logs(): void {
		$logs = $this->logger->recent( 200 );
		?>
		<div class="wrap">
			<h1><?php esc_html_e( 'R2 Offload — Logs', 'r2-offload' ); ?></h1>
			<table class="widefat striped">
				<thead><tr>
					<th><?php esc_html_e( 'Time', 'r2-offload' ); ?></th>
					<th><?php esc_html_e( 'Level', 'r2-offload' ); ?></th>
					<th><?php esc_html_e( 'Context', 'r2-offload' ); ?></th>
					<th><?php esc_html_e( 'Message', 'r2-offload' ); ?></th>
				</tr></thead>
				<tbody>
				<?php foreach ( $logs as $log ) : ?>
					<tr>
						<td><?php echo esc_html( $log->created_at ); ?></td>
						<td><?php echo esc_html( $log->level ); ?></td>
						<td><?php echo esc_html( $log->context ); ?></td>
						<td><?php echo esc_html( $log->message ); ?></td>
					</tr>
				<?php endforeach; ?>
				<?php if ( empty( $logs ) ) : ?>
					<tr><td colspan="4"><?php esc_html_e( 'No log entries yet.', 'r2-offload' ); ?></td></tr>
				<?php endif; ?>
				</tbody>
			</table>
		</div>
		<?php
	}
}
