<?php
/**
 * Strona ustawień ogólnych KSeF — wybór trigger, domyślny kredencjał, polityka klient prywatny.
 *
 * @package KSEF\WordPress\Admin
 */

declare(strict_types=1);

namespace KSEF\WordPress\Admin;

// phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders, WordPress.DB.SlowDBQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.Security.NonceVerification -- Wlasne tabele gksef_*: wartosci przez $wpdb->prepare()/esc_like(), nazwy tabel z $wpdb->prefix, paginacja rzutowana (int); nonce weryfikowany w handlerze (check_ajax_referer/check_admin_referer).

use KSEF\WordPress\WC\Order_Trigger;
use KSEF\WordPress\V2\Invoice_Numbering;

final class Settings_Page {

	public static function register(): void {
		add_action( 'admin_init', [ self::class, 'register_settings' ] );
	}

	public static function register_settings(): void {
		register_setting( 'ksef_settings', Order_Trigger::OPTION_AUTO_TRIGGER, [
			'type'              => 'string',
			'default'           => 'off',
			'sanitize_callback' => static function ( $v ) {
				$v = (string) $v;
				if ( 'off' === $v ) {
					return 'off';
				}
				// 'on_<wc-status-slug>' — slug może być dowolny zdefiniowany w WC.
				if ( preg_match( '/^on_[a-z0-9_-]+$/', $v ) ) {
					return $v;
				}
				return 'off';
			},
		] );
		register_setting( 'ksef_general', Order_Trigger::OPTION_AUTO_EMAIL, [
			'type'              => 'string',
			'default'           => '1',
			'sanitize_callback' => static fn ( $v ) => '1' === (string) $v ? '1' : '0',
		] );
		// Multi-select statusów WC wyzwalających auto-wystawianie (lista slugów bez prefiksu wc-)
		register_setting( 'ksef_general', Order_Trigger::OPTION_AUTO_STATUSES, [
			'type'              => 'array',
			'default'           => [],
			'sanitize_callback' => static function ( $v ) {
				if ( ! is_array( $v ) ) {
					return [];
				}
				return array_values( array_filter( array_map(
					static fn ( $s ) => preg_replace( '/[^a-z0-9_-]/', '', (string) $s ),
					$v
				) ) );
			},
		] );
		// Dane firmy do PDF/XML (Email, Telefon, NazwaFirmy, MiejsceWystawienia)
		foreach ( [
			'gksef_seller_name', 'gksef_seller_email', 'gksef_seller_phone',
			'gksef_seller_street', 'gksef_seller_street_number', 'gksef_seller_postal_code',
			'gksef_seller_city', 'gksef_seller_country',
		] as $opt ) {
			register_setting( 'ksef_settings', $opt, [
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => 'sanitize_text_field',
			] );
		}
		register_setting( 'ksef_sales', Order_Trigger::OPTION_DEFAULT_CRED, [
			'type'              => 'integer',
			'default'           => 0,
			'sanitize_callback' => 'absint',
		] );
		register_setting( 'ksef_general', Order_Trigger::OPTION_PRIVATE_MODE, [
			'type'              => 'string',
			'default'           => 'fa_imienna',
			// fa_imienna     — wystawiaj imienną tylko gdy klient zaznaczy „Chcę fakturę"
			// fa_imienna_all — zawsze wystawiaj imienną dla każdego B2C
			// skip           — pomijaj B2C, wystawiaj wyłącznie dla firm
			'sanitize_callback' => static fn ( $v ) => in_array( $v, [ 'fa_imienna', 'fa_imienna_all', 'skip' ], true ) ? $v : 'fa_imienna',
		] );
		// Numeracja faktur
		register_setting( 'ksef_settings', Invoice_Numbering::OPT_TEMPLATE, [
			'type'              => 'string',
			'default'           => Invoice_Numbering::DEFAULT_TEMPLATE,
			'sanitize_callback' => 'sanitize_text_field',
		] );
		register_setting( 'ksef_settings', Invoice_Numbering::OPT_RESET_PERIOD, [
			'type'              => 'string',
			'default'           => 'yearly',
			'sanitize_callback' => static fn ( $v ) => in_array( $v, [ 'never', 'yearly', 'monthly' ], true ) ? $v : 'yearly',
		] );
		register_setting( 'ksef_settings', Invoice_Numbering::OPT_COUNTER_PADDING, [
			'type'              => 'integer',
			'default'           => 4,
			'sanitize_callback' => static fn ( $v ) => max( 1, min( 10, (int) $v ) ),
		] );
		register_setting( 'ksef_settings', Invoice_Numbering::OPT_PREFIX, [
			'type'              => 'string',
			'default'           => '',
			'sanitize_callback' => 'sanitize_text_field',
		] );
		// v7 — jedno-radio polityka auto-wysyłki: '' (off) | 'b2b' | 'b2c' | 'all'.
		// Zastępuje wcześniejsze niezależne checkboxy ksef_auto_send_pl_nip / ksef_auto_send_b2c.
		register_setting( 'ksef_general', 'ksef_auto_send_policy', [
			'type'              => 'string',
			'default'           => '',
			'sanitize_callback' => static fn ( $v ) => in_array( (string) $v, [ '', 'b2b', 'b2c', 'all' ], true ) ? (string) $v : '',
		] );
		register_setting( 'ksef_settings', 'ksef_default_payment_due_days', [
			'type'              => 'integer',
			'default'           => 14,
			'sanitize_callback' => static fn ( $v ) => max( 0, min( 180, (int) $v ) ),
		] );
		register_setting( 'ksef_settings', 'ksef_default_bank_account_id', [
			'type'              => 'integer',
			'default'           => 0,
			'sanitize_callback' => 'absint',
		] );
		// Kategorie wydatków (faktury zakupu) — jedna na linię. Puste = domyślne.
		register_setting( 'ksef_expenses', 'gksef_expense_categories', [
			'type'              => 'string',
			'default'           => '',
			'sanitize_callback' => 'sanitize_textarea_field',
		] );
		register_setting( 'ksef_expenses', 'gksef_incoming_module_enabled', [
			'type'              => 'string',
			'default'           => '1',
			'sanitize_callback' => static function ( $v ) { return '0' === (string) $v ? '0' : '1'; },
		] );
		// Czy przy odinstalowaniu wtyczki usunąć wszystkie dane (tabele, opcje, pliki).
		// Domyślnie NIE — faktury są wspólne z Pro i podlegają obowiązkowi przechowywania.
		register_setting( 'ksef_uninstall', 'ksef_delete_data_on_uninstall', [
			'type'              => 'string',
			'default'           => '0',
			'sanitize_callback' => static fn ( $v ) => '1' === (string) $v ? '1' : '0',
		] );
		// Tryb osobnego adresu rozliczeniowego na checkoucie: default | disabled | default_same.
		register_setting( 'ksef_general', \KSEF\WordPress\WC\Billing_Address_Mode::OPTION, [
			'type'              => 'string',
			'default'           => 'default',
			'sanitize_callback' => static fn ( $v ) => in_array( (string) $v, [ 'default', 'disabled', 'default_same' ], true ) ? (string) $v : 'default',
		] );
	}

	public function render_page(): void {
		if ( ! current_user_can( 'ksef_view_settings' ) ) {
			wp_die( esc_html__( 'Brak uprawnień.', 'prostafaktura-ksef' ) );
		}
		$tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'general';
		$tabs = [
			'general'     => __( 'Ogólne', 'prostafaktura-ksef' ),
			'sales'       => __( 'Faktury sprzedaży', 'prostafaktura-ksef' ),
			'numbering'   => __( 'Numeracja faktur', 'prostafaktura-ksef' ),
			'taxes'       => __( 'Podatki', 'prostafaktura-ksef' ),
			'expenses'    => __( 'Wydatki', 'prostafaktura-ksef' ),
			'ksef'        => __( 'KSeF', 'prostafaktura-ksef' ),
			'email'       => __( 'Email', 'prostafaktura-ksef' ),
			'permissions' => __( 'Uprawnienia', 'prostafaktura-ksef' ),
			'uninstall'   => __( 'Odinstalowanie', 'prostafaktura-ksef' ),
		];
		Modern_Shell::open( 'ustawienia', [
			'crumbs'   => [ __( 'Konfiguracja', 'prostafaktura-ksef' ), __( 'Ustawienia', 'prostafaktura-ksef' ), $tabs[ $tab ] ?? __( 'Ogólne', 'prostafaktura-ksef' ) ],
			'title'    => 'Ustawienia | KSeF',
			'subtitle' => __( 'Numeracja faktur, dane sprzedawcy, polityka B2C i integracja email. Ustawienia per środowisko (test/prod) konfigurujesz w Kredencjałach KSeF.', 'prostafaktura-ksef' ),
		] );
		try {
			echo '<h2 class="nav-tab-wrapper" style="margin-bottom:20px;">';
			foreach ( $tabs as $slug => $label ) {
				$url = admin_url( 'admin.php?page=ksef-settings' . ( 'general' === $slug ? '' : '&tab=' . $slug ) );
				$cls = $tab === $slug ? ' nav-tab-active' : '';
				echo '<a href="' . esc_url( $url ) . '" class="nav-tab' . esc_attr( $cls ) . '">' . esc_html( $label ) . '</a>';
			}
			echo '</h2>';
			switch ( $tab ) {
				case 'sales':     $this->render_tab_sales(); break;
				case 'numbering': $this->render_tab_numbering(); break;
				case 'taxes':     $this->render_tab_taxes(); break;
				case 'expenses':  $this->render_tab_expenses(); break;
				case 'uninstall': $this->render_tab_uninstall(); break;
				case 'ksef':      $this->render_tab_ksef(); break;
				case 'email':
					if ( class_exists( __NAMESPACE__ . '\\Email_Settings' ) ) {
						( new Email_Settings() )->render_page();
					}
					break;
				case 'permissions':
					$this->render_tab_permissions();
					break;
				case 'license':
					if ( class_exists( __NAMESPACE__ . '\\License_Settings' ) ) {
						( new License_Settings() )->render_page();
					}
					break;
				default: $this->render_tab_general(); break;
			}
		} finally {
			Modern_Shell::close();
		}
	}

	/**
	 * Zakładka „Faktury sprzedaży": konta bankowe i zestawy danych kontaktowych,
	 * przypisane do kont KSeF (kredencjałów). Reużywa istniejących AJAX-ów
	 * ksef_list/save/delete_bank_account oraz ksef_list/save/delete_extra_preset.
	 */
	private function render_tab_sales(): void {
		global $wpdb;
		$creds = $wpdb->get_results( "SELECT id, nip, name, ksef_environment FROM {$wpdb->prefix}gksef_credentials ORDER BY id ASC", ARRAY_A );
		$creds = is_array( $creds ) ? $creds : [];
		$ajax  = admin_url( 'admin-ajax.php' );
		$nonce = wp_create_nonce( 'ksef-nonce' );
		$cred_js = array_map( static function ( $c ) {
			return [
				'id'    => (int) $c['id'],
				'label' => 'NIP ' . $c['nip'] . ( $c['name'] ? ' (' . $c['name'] . ')' : '' ) . ' · ' . strtoupper( (string) $c['ksef_environment'] ),
			];
		}, $creds );
		?>
		<?php if ( empty( $creds ) ) : ?>
			<div class="notice notice-warning inline" style="margin:8px 0 18px;"><p><?php
				printf(
					/* translators: %s: link to add credential */
					esc_html__( 'Brak kont KSeF (kredencjałów). %s, aby móc przypisywać konta bankowe i dane kontaktowe do sprzedawców.', 'prostafaktura-ksef' ),
					'<a href="' . esc_url( admin_url( 'admin.php?page=ksef-credentials&action=add' ) ) . '">' . esc_html__( 'Dodaj konto KSeF', 'prostafaktura-ksef' ) . '</a>'
				);
			?></p></div>
		<?php endif; ?>

		<?php if ( isset( $_GET['settings-updated'] ) ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Zapisano ustawienia.', 'prostafaktura-ksef' ); ?></p></div>
		<?php endif; ?>
		<div style="background:#fff;border:1px solid #e2e4e7;border-radius:10px;padding:18px 22px;margin-bottom:18px;max-width:1040px;">
			<form method="post" action="options.php" style="margin:0;">
				<?php settings_fields( 'ksef_sales' ); ?>
				<h2 style="margin-top:0;font-size:15px;"><?php esc_html_e( 'Domyślne konto KSeF (sprzedawca)', 'prostafaktura-ksef' ); ?></h2>
				<p class="description" style="margin-bottom:10px;"><?php esc_html_e( 'Konto KSeF używane domyślnie przy wystawianiu faktur z zamówień WooCommerce.', 'prostafaktura-ksef' ); ?></p>
				<?php $default_cred = (int) get_option( Order_Trigger::OPTION_DEFAULT_CRED, 0 ); ?>
				<select name="<?php echo esc_attr( Order_Trigger::OPTION_DEFAULT_CRED ); ?>" style="min-width:340px;">
					<option value="0"><?php esc_html_e( '— wybierz —', 'prostafaktura-ksef' ); ?></option>
					<?php foreach ( $creds as $c ) : ?>
						<option value="<?php echo esc_attr( (string) $c['id'] ); ?>" <?php selected( $default_cred, (int) $c['id'] ); ?>>
							NIP <?php echo esc_html( $c['nip'] ); ?> <?php echo $c['name'] ? '(' . esc_html( $c['name'] ) . ')' : ''; ?> · <?php echo esc_html( strtoupper( (string) $c['ksef_environment'] ) ); ?>
						</option>
					<?php endforeach; ?>
				</select>
				<?php submit_button( __( 'Zapisz', 'prostafaktura-ksef' ), 'primary', 'submit', false ); ?>
			</form>
		</div>

		<div style="background:#fff;border:1px solid #e2e4e7;border-radius:10px;padding:18px 22px;margin-bottom:18px;max-width:1040px;">
			<h2 style="margin-top:0;font-size:15px;"><?php esc_html_e( 'Konta bankowe', 'prostafaktura-ksef' ); ?></h2>
			<p class="description" style="margin-bottom:12px;"><?php esc_html_e( 'Dodawaj rachunki bankowe i przypisuj je do kont KSeF (sprzedawców). Domyślny rachunek danego konta podpowiada się przy wystawianiu faktury.', 'prostafaktura-ksef' ); ?></p>
			<div id="ksef-bank-mgr" data-ajax="<?php echo esc_attr( $ajax ); ?>" data-nonce="<?php echo esc_attr( $nonce ); ?>" data-creds="<?php echo esc_attr( (string) wp_json_encode( $cred_js ) ); ?>"></div>
		</div>

		<div style="background:#fff;border:1px solid #e2e4e7;border-radius:10px;padding:18px 22px;margin-bottom:18px;max-width:1040px;">
			<h2 style="margin-top:0;font-size:15px;"><?php esc_html_e( 'Dane kontaktowe', 'prostafaktura-ksef' ); ?></h2>
			<p class="description" style="margin-bottom:12px;"><?php esc_html_e( 'Zestawy danych dodatkowych (Email sprzedawcy, Telefon, Miejsce wystawienia) przypisane do kont KSeF. Przy wystawianiu faktury podpowiadają się wg wybranego sprzedawcy.', 'prostafaktura-ksef' ); ?></p>
			<div id="ksef-contact-mgr" data-ajax="<?php echo esc_attr( $ajax ); ?>" data-nonce="<?php echo esc_attr( $nonce ); ?>" data-creds="<?php echo esc_attr( (string) wp_json_encode( $cred_js ) ); ?>"></div>
		</div>

		<?php ob_start(); ?>
		(function(){
			function esc(s){ return String(s==null?'':s).replace(/[&<>"']/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c];}); }
			function makeApi(ajax, nonce){
				return function(action, data, method){
					data = data || {};
					if ((method||'POST')==='GET'){
						var q = new URLSearchParams(); q.set('action',action); q.set('_ajax_nonce',nonce);
						Object.keys(data).forEach(function(k){ q.set(k, data[k]); });
						return fetch(ajax+'?'+q.toString(), { credentials:'same-origin' }).then(function(r){return r.json();});
					}
					var fd = new FormData(); fd.append('action',action); fd.append('_ajax_nonce',nonce);
					Object.keys(data).forEach(function(k){ fd.append(k, data[k]); });
					return fetch(ajax, { method:'POST', body:fd, credentials:'same-origin' }).then(function(r){return r.json();});
				};
			}

			// ---------- Konta bankowe ----------
			var bankWrap = document.getElementById('ksef-bank-mgr');
			if (bankWrap){
				var AJAX=bankWrap.dataset.ajax, NONCE=bankWrap.dataset.nonce, api=makeApi(AJAX,NONCE);
				var CREDS=[]; try{ CREDS=JSON.parse(bankWrap.dataset.creds||'[]'); }catch(e){ CREDS=[]; }
				if (!CREDS.length){ bankWrap.innerHTML='<p class="description">Najpierw dodaj konto KSeF.</p>'; }
				else {
					var curCred=CREDS[0].id, editId=0, rows=[];
					bankWrap.innerHTML=''
						+ '<div style="margin-bottom:10px;"><label>Konto KSeF: <select id="bm-cred">'+CREDS.map(function(c){return '<option value="'+c.id+'">'+esc(c.label)+'</option>';}).join('')+'</select></label></div>'
						+ '<div id="bm-list"></div>'
						+ '<div id="bm-form" style="margin-top:12px;background:#fff;border:1px solid #e7eaee;border-radius:8px;padding:14px 16px;max-width:760px;"></div>';
					var credSel=bankWrap.querySelector('#bm-cred'), listEl=bankWrap.querySelector('#bm-list'), formEl=bankWrap.querySelector('#bm-form');
					function bFormHtml(b){ b=b||{};
						return '<div style="display:grid;grid-template-columns:2fr 1.4fr 1fr 0.8fr;gap:10px;align-items:end;">'
						+ '<label style="font-size:11px;color:#5a6473;">Numer rachunku<br><input type="text" id="bm-acc" value="'+esc(b.account_number||'')+'" style="width:100%;"></label>'
						+ '<label style="font-size:11px;color:#5a6473;">Nazwa banku<br><input type="text" id="bm-bank" value="'+esc(b.bank_name||'')+'" style="width:100%;"></label>'
						+ '<label style="font-size:11px;color:#5a6473;">SWIFT/BIC<br><input type="text" id="bm-swift" value="'+esc(b.swift||'')+'" style="width:100%;"></label>'
						+ '<label style="font-size:11px;color:#5a6473;">Waluta<br><input type="text" id="bm-cur" maxlength="3" value="'+esc(b.currency||'PLN')+'" style="width:100%;text-transform:uppercase;"></label>'
						+ '</div><div style="margin-top:10px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;">'
						+ '<label><input type="checkbox" id="bm-def" '+(b.is_default==1?'checked':'')+'> Domyślny dla tego konta</label>'
						+ '<button type="button" class="button button-primary" id="bm-save">'+(editId?'Zapisz zmiany':'Dodaj rachunek')+'</button>'
						+ (editId?'<button type="button" class="button" id="bm-cancel">Anuluj</button>':'')
						+ '<span id="bm-msg" style="margin-left:4px;color:#5a6473;"></span></div>';
					}
					function bRenderForm(b){ formEl.innerHTML=bFormHtml(b);
						formEl.querySelector('#bm-save').addEventListener('click', bSave);
						var c=formEl.querySelector('#bm-cancel'); if(c)c.addEventListener('click',function(){editId=0;bRenderForm();});
					}
					function bRow(b){
						return '<tr><td>'+esc(b.account_number)+(b.is_default==1?' <span title="Domyślny">★</span>':'')+'</td><td>'+esc(b.bank_name||'')+'</td><td>'+esc(b.swift||'')+'</td><td>'+esc(b.currency||'PLN')+'</td>'
						+ '<td style="text-align:right;white-space:nowrap;"><button type="button" class="button button-small bm-edit" data-id="'+b.id+'">Edytuj</button> <button type="button" class="button button-small bm-del" data-id="'+b.id+'" style="color:#a00;">Usuń</button></td></tr>';
					}
					function bLoad(){
						api('ksef_list_bank_accounts',{credential_id:curCred},'GET').then(function(j){
							rows=(j&&j.success&&j.data&&j.data.items)?j.data.items:[];
							if(!rows.length){ listEl.innerHTML='<p class="description">Brak rachunków dla tego konta. Dodaj poniżej.</p>'; return; }
							listEl.innerHTML='<table class="widefat striped" style="max-width:760px;"><thead><tr><th>Rachunek</th><th>Bank</th><th>SWIFT</th><th>Waluta</th><th></th></tr></thead><tbody>'+rows.map(bRow).join('')+'</tbody></table>';
							listEl.querySelectorAll('.bm-edit').forEach(function(b){ b.addEventListener('click',function(){ editId=parseInt(b.dataset.id,10); bRenderForm(rows.filter(function(x){return x.id==editId;})[0]); }); });
							listEl.querySelectorAll('.bm-del').forEach(function(b){ b.addEventListener('click',function(){ bDel(parseInt(b.dataset.id,10)); }); });
						});
					}
					function bSave(){
						var data={ credential_id:curCred, id:editId,
							account_number:formEl.querySelector('#bm-acc').value, bank_name:formEl.querySelector('#bm-bank').value,
							swift:formEl.querySelector('#bm-swift').value, currency:formEl.querySelector('#bm-cur').value };
						if(formEl.querySelector('#bm-def').checked) data.is_default=1;
						var msg=formEl.querySelector('#bm-msg'); msg.textContent='Zapisywanie…';
						api('ksef_save_bank_account',data).then(function(j){ if(j&&j.success){ editId=0; bRenderForm(); bLoad(); } else { msg.textContent=(j&&j.data&&j.data.message)||'Błąd zapisu'; } });
					}
					function bDel(id){ if(!confirm('Usunąć ten rachunek?'))return; api('ksef_delete_bank_account',{id:id}).then(function(){ if(editId===id){editId=0;bRenderForm();} bLoad(); }); }
					credSel.addEventListener('change',function(){ curCred=parseInt(credSel.value,10); editId=0; bRenderForm(); bLoad(); });
					bRenderForm(); bLoad();
				}
			}

			// ---------- Dane kontaktowe (presety) ----------
			var cWrap = document.getElementById('ksef-contact-mgr');
			if (cWrap){
				var cAJAX=cWrap.dataset.ajax, cNONCE=cWrap.dataset.nonce, cApi=makeApi(cAJAX,cNONCE);
				var CCREDS=[]; try{ CCREDS=JSON.parse(cWrap.dataset.creds||'[]'); }catch(e){ CCREDS=[]; }
				var cEdit=0, presets=[];
				cWrap.innerHTML='<div id="cm-list"></div><div id="cm-form" style="margin-top:12px;background:#fff;border:1px solid #e7eaee;border-radius:8px;padding:14px 16px;max-width:980px;"></div>';
				var cListEl=cWrap.querySelector('#cm-list'), cFormEl=cWrap.querySelector('#cm-form');
				function credLabel(id){ id=parseInt(id||0,10); if(!id) return 'Wszystkie konta'; var c=CCREDS.filter(function(x){return x.id==id;})[0]; return c?c.label:('#'+id); }
				function credOptions(sel){ sel=parseInt(sel||0,10); return '<option value="0"'+(!sel?' selected':'')+'>— wszystkie konta —</option>'+CCREDS.map(function(c){return '<option value="'+c.id+'"'+(sel==c.id?' selected':'')+'>'+esc(c.label)+'</option>';}).join(''); }
				function cFormHtml(p){ p=p||{};
					return '<div style="display:grid;grid-template-columns:1.3fr 1.6fr;gap:10px;">'
					+ '<label style="font-size:11px;color:#5a6473;">Nazwa zestawu<br><input type="text" id="cm-name" value="'+esc(p.name||'')+'" style="width:100%;"></label>'
					+ '<label style="font-size:11px;color:#5a6473;">Przypisz do konta KSeF<br><select id="cm-cred" style="width:100%;">'+credOptions(p.credential_id||0)+'</select></label>'
					+ '</div><div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:10px;">'
					+ '<label style="font-size:11px;color:#5a6473;">Email sprzedawcy<br><input type="email" id="cm-email" value="'+esc(p.seller_email||'')+'" style="width:100%;"></label>'
					+ '<label style="font-size:11px;color:#5a6473;">Telefon sprzedawcy<br><input type="text" id="cm-phone" value="'+esc(p.seller_phone||'')+'" style="width:100%;"></label>'
					+ '<label style="font-size:11px;color:#5a6473;">Miejsce wystawienia<br><input type="text" id="cm-place" value="'+esc(p.issue_place||'')+'" style="width:100%;"></label>'
					+ '</div><div style="margin-top:10px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;">'
					+ '<label><input type="checkbox" id="cm-def" '+(p.is_default==1?'checked':'')+'> Domyślny dla przypisanego konta</label>'
					+ '<button type="button" class="button button-primary" id="cm-save">'+(cEdit?'Zapisz zmiany':'Dodaj zestaw')+'</button>'
					+ (cEdit?'<button type="button" class="button" id="cm-cancel">Anuluj</button>':'')
					+ '<span id="cm-msg" style="margin-left:4px;color:#5a6473;"></span></div>';
				}
				function cRenderForm(p){ cFormEl.innerHTML=cFormHtml(p);
					cFormEl.querySelector('#cm-save').addEventListener('click', cSave);
					var c=cFormEl.querySelector('#cm-cancel'); if(c)c.addEventListener('click',function(){cEdit=0;cRenderForm();});
				}
				function cRow(p){
					return '<tr><td>'+esc(p.name)+(p.is_default==1?' ★':'')+'</td><td>'+esc(credLabel(p.credential_id||0))+'</td><td>'+esc(p.seller_email||'')+'</td><td>'+esc(p.seller_phone||'')+'</td><td>'+esc(p.issue_place||'')+'</td>'
					+ '<td style="text-align:right;white-space:nowrap;"><button type="button" class="button button-small cm-edit" data-id="'+p.id+'">Edytuj</button> <button type="button" class="button button-small cm-del" data-id="'+p.id+'" style="color:#a00;">Usuń</button></td></tr>';
				}
				function cLoad(){
					cApi('ksef_list_extra_presets',{},'GET').then(function(j){
						presets=(j&&j.success&&j.data&&j.data.items)?j.data.items:[];
						if(!presets.length){ cListEl.innerHTML='<p class="description">Brak zestawów. Dodaj poniżej.</p>'; return; }
						cListEl.innerHTML='<table class="widefat striped" style="max-width:980px;"><thead><tr><th>Nazwa</th><th>Konto KSeF</th><th>Email</th><th>Telefon</th><th>Miejsce</th><th></th></tr></thead><tbody>'+presets.map(cRow).join('')+'</tbody></table>';
						cListEl.querySelectorAll('.cm-edit').forEach(function(b){ b.addEventListener('click',function(){ cEdit=parseInt(b.dataset.id,10); cRenderForm(presets.filter(function(x){return x.id==cEdit;})[0]); }); });
						cListEl.querySelectorAll('.cm-del').forEach(function(b){ b.addEventListener('click',function(){ cDel(parseInt(b.dataset.id,10)); }); });
					});
				}
				function cSave(){
					var name=cFormEl.querySelector('#cm-name').value.trim(), msg=cFormEl.querySelector('#cm-msg');
					if(!name){ msg.textContent='Podaj nazwę zestawu.'; return; }
					var data={ id:cEdit, name:name, credential_id:cFormEl.querySelector('#cm-cred').value,
						seller_email:cFormEl.querySelector('#cm-email').value, seller_phone:cFormEl.querySelector('#cm-phone').value,
						issue_place:cFormEl.querySelector('#cm-place').value };
					if(cFormEl.querySelector('#cm-def').checked) data.is_default=1;
					msg.textContent='Zapisywanie…';
					cApi('ksef_save_extra_preset',data).then(function(j){ if(j&&j.success){ cEdit=0; cRenderForm(); cLoad(); } else { msg.textContent=(j&&j.data&&j.data.message)||'Błąd zapisu'; } });
				}
				function cDel(id){ if(!confirm('Usunąć ten zestaw?'))return; cApi('ksef_delete_extra_preset',{id:id}).then(function(){ if(cEdit===id){cEdit=0;cRenderForm();} cLoad(); }); }
				cRenderForm(); cLoad();
			}
		})();
		<?php Modern_Shell::inline_script( 'ksef-settings-sales', ob_get_clean() ); ?>
		<?php
	}

	private function render_tab_general(): void {
		global $wpdb;
		$creds = $wpdb->get_results( "SELECT id, nip, name, ksef_environment FROM {$wpdb->prefix}gksef_credentials ORDER BY id ASC", ARRAY_A );

		$trigger      = get_option( Order_Trigger::OPTION_AUTO_TRIGGER, 'off' );
		$default_cred = (int) get_option( Order_Trigger::OPTION_DEFAULT_CRED, 0 );
		$private_mode = get_option( Order_Trigger::OPTION_PRIVATE_MODE, 'fa_imienna' );

		$num_template = (string) get_option( Invoice_Numbering::OPT_TEMPLATE, Invoice_Numbering::DEFAULT_TEMPLATE );
		$num_reset    = (string) get_option( Invoice_Numbering::OPT_RESET_PERIOD, 'yearly' );
		$num_padding  = (int) get_option( Invoice_Numbering::OPT_COUNTER_PADDING, 4 );
		$num_prefix   = (string) get_option( Invoice_Numbering::OPT_PREFIX, '' );
		$num_preview  = Invoice_Numbering::preview();
		?>
		<?php if ( isset( $_GET['settings-updated'] ) ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Zapisano ustawienia.', 'prostafaktura-ksef' ); ?></p></div>
		<?php endif; ?>

		<form method="post" action="options.php">
			<?php settings_fields( 'ksef_general' ); ?>
			<table class="form-table">
				<tr>
					<th><?php esc_html_e( 'Automatyczne wystawianie faktur', 'prostafaktura-ksef' ); ?> <span style="color:#b26a00;font-weight:normal;font-size:12px;">🔒 Pro</span></th>
					<td>
						<?php $wc_statuses = function_exists( 'wc_get_order_statuses' ) ? wc_get_order_statuses() : []; ?>
						<p class="description" style="margin:0 0 8px;"><?php esc_html_e( 'Zaznacz statusy zamówienia WooCommerce, po przejściu w które faktura ma być automatycznie wystawiona i wysłana do KSeF. Można wybrać kilka.', 'prostafaktura-ksef' ); ?></p>
						<?php if ( empty( $wc_statuses ) ) : ?>
							<em><?php esc_html_e( 'WooCommerce nieaktywne — brak statusów.', 'prostafaktura-ksef' ); ?></em>
						<?php else : ?>
							<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px 18px;max-width:620px;opacity:.6;">
							<?php foreach ( $wc_statuses as $slug => $label ) : ?>
								<label style="display:flex;align-items:center;gap:7px;padding:3px 0;"><input type="checkbox" disabled> <span><?php echo esc_html( $label ); ?></span></label>
							<?php endforeach; ?>
							</div>
						<?php endif; ?>
						<p class="description" style="margin-top:8px;color:#646970;"><?php esc_html_e( '🔒 Funkcja Pro. W wersji darmowej wystawiasz faktury ręcznie z poziomu zamówienia (metabox „KSeF").', 'prostafaktura-ksef' ); ?> <a href="https://prostafakturaksef.pl/cennik" target="_blank" rel="noopener"><?php esc_html_e( 'Zobacz Pro →', 'prostafaktura-ksef' ); ?></a></p>
					</td>
				</tr>
				<tr>
					<th><?php esc_html_e( 'Auto-wysyłka PDF do nabywcy', 'prostafaktura-ksef' ); ?> <span style="color:#b26a00;font-weight:normal;font-size:12px;">🔒 Pro</span></th>
					<td>
						<label style="opacity:.6;"><input type="checkbox" disabled> <?php esc_html_e( 'Po wystawieniu faktury wyślij PDF e-mailem na adres nabywcy z zamówienia WC', 'prostafaktura-ksef' ); ?></label>
						<p class="description" style="margin-top:6px;color:#646970;"><?php esc_html_e( '🔒 Funkcja Pro.', 'prostafaktura-ksef' ); ?> <a href="https://prostafakturaksef.pl/cennik" target="_blank" rel="noopener"><?php esc_html_e( 'Zobacz Pro →', 'prostafaktura-ksef' ); ?></a></p>
					</td>
				</tr>
				<tr>
					<th><?php esc_html_e( 'Auto-wysyłka do KSeF', 'prostafaktura-ksef' ); ?> <span style="color:#b26a00;font-weight:normal;font-size:12px;">🔒 Pro</span></th>
					<td>
						<p class="description" style="margin:0 0 8px;"><?php esc_html_e( 'Które faktury wysyłać automatycznie do KSeF po wystawieniu w sklepie.', 'prostafaktura-ksef' ); ?></p>
						<div style="opacity:.6;">
						<label style="display:block;margin-bottom:6px;"><input type="radio" disabled> <?php esc_html_e( 'Wyłączone (nic nie wysyłaj automatycznie)', 'prostafaktura-ksef' ); ?></label>
						<label style="display:block;margin-bottom:6px;"><input type="radio" disabled> <?php esc_html_e( 'Tylko faktury dla firm (z NIP)', 'prostafaktura-ksef' ); ?></label>
						<label style="display:block;margin-bottom:6px;"><input type="radio" disabled> <?php esc_html_e( 'Tylko faktury dla osób fizycznych (B2C — bez NIP)', 'prostafaktura-ksef' ); ?></label>
						<label style="display:block;"><input type="radio" disabled> <?php esc_html_e( 'Wszystkie faktury (firm i osób prywatnych)', 'prostafaktura-ksef' ); ?></label>
						</div>
						<p class="description" style="margin-top:8px;color:#646970;"><?php esc_html_e( '🔒 Funkcja Pro.', 'prostafaktura-ksef' ); ?> <a href="https://prostafakturaksef.pl/cennik" target="_blank" rel="noopener"><?php esc_html_e( 'Zobacz Pro →', 'prostafaktura-ksef' ); ?></a></p>
					</td>
				</tr>
					<tr>
						<th><?php esc_html_e( 'Polityka dla klientów prywatnych', 'prostafaktura-ksef' ); ?></th>
						<td>
							<label><input type="radio" name="<?php echo esc_attr( Order_Trigger::OPTION_PRIVATE_MODE ); ?>" value="fa_imienna"     <?php checked( $private_mode, 'fa_imienna' ); ?>> <?php esc_html_e( 'Wystawiaj fakturę imienną (jeśli klient zaznaczył „Chcę fakturę")', 'prostafaktura-ksef' ); ?></label><br>
							<label><input type="radio" name="<?php echo esc_attr( Order_Trigger::OPTION_PRIVATE_MODE ); ?>" value="fa_imienna_all" <?php checked( $private_mode, 'fa_imienna_all' ); ?>> <?php esc_html_e( 'Wystawiaj fakturę imienną (zawsze dla każdego)', 'prostafaktura-ksef' ); ?></label><br>
							<label><input type="radio" name="<?php echo esc_attr( Order_Trigger::OPTION_PRIVATE_MODE ); ?>" value="skip"           <?php checked( $private_mode, 'skip' ); ?>> <?php esc_html_e( 'Wystawiaj tylko faktury dla firm', 'prostafaktura-ksef' ); ?></label>
							<p class="description"><?php esc_html_e( 'Zgodnie z ustawą KSeF dla osób prywatnych B2C faktura jest na żądanie.', 'prostafaktura-ksef' ); ?></p>
						</td>
					</tr>
				</table>

				<?php submit_button(); ?>
			</form>

			<?php ob_start(); ?>
			(function(){
				const wrap = document.getElementById('ksef-num-schemes');
				if (!wrap) return;
				let schemes = [];
				try { schemes = JSON.parse(wrap.dataset.schemes || '[]'); } catch(e) { schemes = []; }
				if (!Array.isArray(schemes) || !schemes.length) {
					schemes = [
						{ id:'default', name:'Domyślna', kind:'sale', template:'FV/{Y}/{m}/{n}', prefix:'', reset_period:'monthly', padding:4, is_default:true },
						{ id:'default_kor', name:'Korygująca', kind:'correction', template:'KOR/{Y}/{m}/{n}', prefix:'', reset_period:'monthly', padding:4, is_default:true }
					];
				}
				schemes.forEach(s=>{ if (s.kind!=='correction') s.kind='sale'; });
				const AJAX = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;
				const NONCE = <?php echo wp_json_encode( wp_create_nonce( 'ksef-nonce' ) ); ?>;
				const IS_PRO = wrap.dataset.pro === '1';
					const KIND_LABEL = { sale:'Faktury sprzedaży', correction:'Faktury korygujące' };

				function previewOf(tpl, prefix, pad){
					const now = new Date();
					const Y = String(now.getFullYear()), y = Y.slice(2);
					const m = String(now.getMonth()+1).padStart(2,'0'), d = String(now.getDate()).padStart(2,'0');
					let s2 = String(tpl||'').replace(/\{Y\}/g,Y).replace(/\{y\}/g,y).replace(/\{m\}/g,m).replace(/\{d\}/g,d).replace(/\{prefix\}/g,prefix||'');
					s2 = s2.replace(/\{n:(\d+)\}/g,(_,n)=>String(1).padStart(parseInt(n,10),'0'));
					s2 = s2.replace(/\{n\}/g,String(1).padStart(Math.max(1,Math.min(10,parseInt(pad||4,10))),'0'));
					return s2;
				}
				function warnFor(sc){
					const t = String(sc.template||'');
					if (sc.reset_period==='monthly' && !(t.includes('{m}')||t.includes('{d}'))) return '⚠ Reset miesięczny wymaga {m} (lub {d}) w szablonie — inaczej numery powtórzą się co miesiąc. Dodaj {m}, np. FV/{Y}/{m}/{n}.';
					if (sc.reset_period==='yearly' && !(t.includes('{Y}')||t.includes('{y}'))) return '⚠ Reset roczny wymaga {Y} (lub {y}) w szablonie — inaczej numery powtórzą się co rok.';
					return '';
				}
				function rowHtml(sc,i){
					const sameKind = schemes.filter(x=>x.kind===sc.kind).length;
					return '<div class="ksef-num-row" data-i="'+i+'" style="background:#fff;border:1px solid #e7eaee;border-radius:8px;padding:14px 16px;margin-bottom:10px;">'
					+ '<div style="display:grid;grid-template-columns:1.4fr 2fr 1fr 1.3fr 0.7fr;gap:10px;align-items:end;">'
					+ '<label style="font-size:11px;color:#5a6473;">Nazwa<br><input type="text" class="ns-name" value="'+esc(sc.name||'')+'" style="width:100%;"></label>'
					+ '<label style="font-size:11px;color:#5a6473;">Szablon<br><input type="text" class="ns-template" value="'+esc(sc.template||'')+'" style="width:100%;"></label>'
					+ '<label style="font-size:11px;color:#5a6473;">Prefix<br><input type="text" class="ns-prefix" value="'+esc(sc.prefix||'')+'" style="width:100%;"></label>'
					+ '<label style="font-size:11px;color:#5a6473;">Reset<br><select class="ns-reset" style="width:100%;">'
					+ '<option value="monthly"'+(sc.reset_period==='monthly'?' selected':'')+'>Co miesiąc</option>'
					+ '<option value="yearly"'+(sc.reset_period==='yearly'?' selected':'')+'>Co rok</option>'
					+ '<option value="never"'+(sc.reset_period==='never'?' selected':'')+'>Nigdy</option>'
					+ '</select></label>'
					+ '<label style="font-size:11px;color:#5a6473;">Padding<br><input type="number" min="1" max="10" class="ns-padding" value="'+(sc.padding||4)+'" style="width:100%;"></label>'
					+ '</div>'
					+ '<div style="margin-top:8px;display:flex;align-items:center;gap:14px;font-size:12px;">'
					+ '<label><input type="radio" name="ns-default-'+sc.kind+'" class="ns-default"'+(sc.is_default?' checked':'')+'> Domyślna</label>'
					+ '<span>Podgląd: <code class="ns-preview">'+esc(previewOf(sc.template,sc.prefix,sc.padding))+'</code></span>'
					+ '<span class="ns-warn" style="color:#b8451a;font-weight:600;flex-basis:100%;">'+esc(warnFor(sc))+'</span>'
					+ (sameKind>1?'<button type="button" class="button button-small ns-remove" style="margin-left:auto;color:#a00;">Usuń</button>':'')
					+ '</div></div>';
				}
				function render(){
					let html = '<p class="description" style="margin:0 0 12px;">Reset numeru: <b>Co miesiąc</b> — licznik wraca do 1 każdego miesiąca (szablon musi zawierać <code>{m}</code>); <b>Co rok</b> — co rok (musi zawierać <code>{Y}</code>); <b>Nigdy</b> — numeracja ciągła. Domyślnie: co miesiąc.</p>';
					['sale','correction'].forEach(kind=>{
						const addLabel = kind==='correction' ? '+ Dodaj numerację korekt' : '+ Dodaj numerację sprzedaży';
						html += '<h3 style="margin:18px 0 8px;font-size:13px;color:#1d2327;display:flex;align-items:center;gap:12px;flex-wrap:wrap;">'
							+ '<span>'+KIND_LABEL[kind]+'</span>'
							+ (IS_PRO ? '<button type="button" class="button button-small ns-add" data-kind="'+kind+'">'+addLabel+'</button>' : '<a href="https://prostafakturaksef.pl/cennik" target="_blank" rel="noopener" class="button button-small" title="Wiele schematów numeracji jest w wersji Pro">🔒 Wiele schematów — Pro</a>')+'</h3>';
						const idx = schemes.map((s,i)=>i).filter(i=>schemes[i].kind===kind);
						if (!idx.length) html += '<p class="description" style="margin:0 0 8px;">Brak schematów — dodaj poniżej.</p>';
						else idx.forEach(i=>{ html += rowHtml(schemes[i],i); });
					});
					wrap.innerHTML = html;
					wrap.querySelectorAll('.ns-add').forEach(b=>b.addEventListener('click',()=>addScheme(b.dataset.kind)));
					wrap.querySelectorAll('.ksef-num-row').forEach(row=>{
						const i = parseInt(row.dataset.i,10);
						const upd = ()=>{
							schemes[i].name = row.querySelector('.ns-name').value;
							schemes[i].template = row.querySelector('.ns-template').value;
							schemes[i].prefix = row.querySelector('.ns-prefix').value;
							schemes[i].reset_period = row.querySelector('.ns-reset').value;
							schemes[i].padding = parseInt(row.querySelector('.ns-padding').value,10)||4;
							row.querySelector('.ns-preview').textContent = previewOf(schemes[i].template,schemes[i].prefix,schemes[i].padding);
							const _w = row.querySelector('.ns-warn'); if (_w) _w.textContent = warnFor(schemes[i]);
						};
						row.querySelectorAll('input,select').forEach(el=>el.addEventListener('input',upd));
						row.querySelector('.ns-default').addEventListener('change',()=>{
							const k = schemes[i].kind;
							schemes.forEach((x,j)=>{ if (x.kind===k) x.is_default=(j===i); });
						});
						const rm = row.querySelector('.ns-remove');
						if (rm) rm.addEventListener('click',()=>{
							const k = schemes[i].kind;
							schemes.splice(i,1);
							if (!schemes.some(x=>x.kind===k && x.is_default)){
								const first = schemes.find(x=>x.kind===k);
								if (first) first.is_default=true;
							}
							render();
						});
					});
				}
				function esc(s2){ return String(s2==null?'':s2).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
				function addScheme(kind){
					const isFirst = !schemes.some(x=>x.kind===kind);
					schemes.push({
						id:'scheme_'+(Date.now()),
						name: kind==='correction'?'Nowa numeracja korekt':'Nowa numeracja',
						kind: kind,
						template: kind==='correction'?'KOR/{Y}/{m}/{n}':'FV/{Y}/{m}/{n}',
						prefix:'', reset_period:'monthly', padding:4, is_default:isFirst
					});
					render();
				}
				document.getElementById('ksef-num-save').addEventListener('click',async ()=>{
					const msg = document.getElementById('ksef-num-msg');
					msg.textContent = 'Zapisuję…'; msg.style.color='#2271b1';
					try {
						const fd = new FormData();
						fd.append('action','ksef_save_numbering_schemes');
						fd.append('_ajax_nonce',NONCE);
						fd.append('schemes',JSON.stringify(schemes));
						const r = await fetch(AJAX,{method:'POST',body:fd,credentials:'same-origin'});
						const j = await r.json();
						if (j.success){ msg.textContent='✓ Zapisano'; msg.style.color='#16a34a'; }
						else { msg.textContent='Błąd: '+(j.data&&j.data.message||'nieznany'); msg.style.color='#dc2626'; }
					} catch(e){ msg.textContent='Network: '+e; msg.style.color='#dc2626'; }
				});
				render();
			})();
			<?php Modern_Shell::inline_script( 'ksef-settings-numbering', ob_get_clean() ); ?>
		<?php
	}

	/**
	 * Zakładka: KSeF — pełne dane sprzedawcy + domyślny termin + rachunek + auto-send dla NIP/B2C.
	 */
	private function render_tab_numbering(): void {
		$ksef_pro_num = true; // wersja darmowa: numeracja w pełni odblokowana (wiele schematów).
		$schemes_js = array_map( static function ( $s ) {
			$s['_next'] = \KSEF\WordPress\V2\Invoice_Numbering::current_next( $s );
			return $s;
		}, \KSEF\WordPress\V2\Invoice_Numbering::get_schemes() );
		?>
		<p class="description">
			<?php echo $ksef_pro_num
				? esc_html__( 'Zdefiniuj wiele schematów numeracji — osobno dla faktur sprzedaży i faktur korygujących. W każdej grupie jeden schemat jest domyślny (używany przy wystawianiu). Domyślny reset: co miesiąc.', 'prostafaktura-ksef' )
				: esc_html__( 'Ustaw schemat numeracji faktur sprzedaży i korekt. W wersji darmowej jeden schemat każdego typu — wiele schematów numeracji jest w wersji Pro.', 'prostafaktura-ksef' ); ?>
			<?php if ( ! $ksef_pro_num ) : ?><a href="https://prostafakturaksef.pl/cennik" target="_blank" rel="noopener">🔒 Pro</a><?php endif; ?>
		</p>
		<div id="ksef-num-schemes" data-pro="<?php echo $ksef_pro_num ? '1' : '0'; ?>" data-schemes='<?php echo esc_attr( wp_json_encode( $schemes_js ) ); ?>'></div>
		<p>
			<button type="button" class="button button-primary" id="ksef-num-save"><?php esc_html_e( 'Zapisz numerację', 'prostafaktura-ksef' ); ?></button>
			<span id="ksef-num-msg" style="margin-left:10px;"></span>
		</p>
		<p class="description">
			<?php esc_html_e( 'Pola: Nazwa szablonu · Szablon · Reset (kiedy licznik wraca do 1) · Kolejny numer (numer następnej faktury). W szablonie użyj: {Y} rok 4-cyfr · {y} rok 2-cyfr · {m} miesiąc · {d} dzień · {n} numer · {n:4} numer uzupełniony zerami do 4 znaków.', 'prostafaktura-ksef' ); ?>
		</p>
		<?php ob_start(); ?>
		(function(){
			const wrap = document.getElementById('ksef-num-schemes');
			if (!wrap) return;
			let schemes = [];
			try { schemes = JSON.parse(wrap.dataset.schemes || '[]'); } catch(e) { schemes = []; }
			if (!Array.isArray(schemes) || !schemes.length) {
				schemes = [
					{ id:'default', name:'Domyślna', kind:'sale', template:'FV/{Y}/{m}/{n}', prefix:'', reset_period:'monthly', padding:1, is_default:true, _next:1 },
					{ id:'default_kor', name:'Korygująca', kind:'correction', template:'KOR/{Y}/{m}/{n}', prefix:'', reset_period:'monthly', padding:1, is_default:true, _next:1 }
				];
			}
			schemes.forEach(s=>{ if (s.kind!=='correction') s.kind='sale'; if (s.padding==null) s.padding=1; if (s._next==null) s._next=1; });
			const AJAX = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;
			const NONCE = <?php echo wp_json_encode( wp_create_nonce( 'ksef-nonce' ) ); ?>;
			const IS_PRO = wrap.dataset.pro === '1';
			const KIND_LABEL = { sale:'Faktury sprzedaży', correction:'Faktury korygujące' };
			function esc(s2){ return String(s2==null?'':s2).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
			function previewOf(sc){
				const now = new Date();
				const Y = String(now.getFullYear()), y = Y.slice(2);
				const m = String(now.getMonth()+1).padStart(2,'0'), d = String(now.getDate()).padStart(2,'0');
				const n = Math.max(1, parseInt(sc._next,10)||1);
				let s2 = String(sc.template||'').replace(/\{Y\}/g,Y).replace(/\{y\}/g,y).replace(/\{m\}/g,m).replace(/\{d\}/g,d).replace(/\{prefix\}/g,sc.prefix||'');
				s2 = s2.replace(/\{n:(\d+)\}/g,(_,p)=>String(n).padStart(parseInt(p,10),'0'));
				s2 = s2.replace(/\{n\}/g,String(n).padStart(Math.max(1,Math.min(10,parseInt(sc.padding,10)||1)),'0'));
				return s2;
			}
			function warnFor(sc){
				const t = String(sc.template||'');
				if (sc.reset_period==='monthly' && !(t.includes('{m}')||t.includes('{d}'))) return '⚠ Reset miesięczny wymaga {m} (lub {d}) w szablonie — inaczej numery powtórzą się co miesiąc. Dodaj {m}, np. FV/{Y}/{m}/{n}.';
				if (sc.reset_period==='yearly' && !(t.includes('{Y}')||t.includes('{y}'))) return '⚠ Reset roczny wymaga {Y} (lub {y}) w szablonie — inaczej numery powtórzą się co rok.';
				return '';
			}
			function rowHtml(sc,i){
				const sameKind = schemes.filter(x=>x.kind===sc.kind).length;
				return '<div class="ksef-num-row" data-i="'+i+'" style="background:#fff;border:1px solid #e7eaee;border-radius:8px;padding:14px 16px;margin-bottom:10px;">'
				+ '<div style="display:grid;grid-template-columns:1.4fr 2.2fr 1.2fr 1fr;gap:10px;align-items:end;">'
				+ '<label style="font-size:11px;color:#5a6473;">Nazwa szablonu<br><input type="text" class="ns-name" value="'+esc(sc.name||'')+'" style="width:100%;"></label>'
				+ '<label style="font-size:11px;color:#5a6473;">Szablon<br><input type="text" class="ns-template" value="'+esc(sc.template||'')+'" style="width:100%;font-family:monospace;"></label>'
				+ '<label style="font-size:11px;color:#5a6473;">Reset<br><select class="ns-reset" style="width:100%;">'
				+ '<option value="monthly"'+(sc.reset_period==='monthly'?' selected':'')+'>Co miesiąc</option>'
				+ '<option value="yearly"'+(sc.reset_period==='yearly'?' selected':'')+'>Co rok</option>'
				+ '<option value="never"'+(sc.reset_period==='never'?' selected':'')+'>Nigdy</option>'
				+ '</select></label>'
				+ '<label style="font-size:11px;color:#5a6473;">Kolejny numer<br><input type="number" min="1" class="ns-next" value="'+(parseInt(sc._next,10)||1)+'" style="width:100%;"></label>'
				+ '</div>'
				+ '<div style="margin-top:8px;display:flex;align-items:center;gap:14px;font-size:12px;">'
				+ '<label><input type="radio" name="ns-default-'+sc.kind+'" class="ns-default"'+(sc.is_default?' checked':'')+'> Domyślna</label>'
				+ '<span>Podgląd następnej: <code class="ns-preview">'+esc(previewOf(sc))+'</code></span>'
				+ '<span class="ns-warn" style="color:#b8451a;font-weight:600;flex-basis:100%;">'+esc(warnFor(sc))+'</span>'
				+ (sameKind>1?'<button type="button" class="button button-small ns-remove" style="margin-left:auto;color:#a00;">Usuń</button>':'')
				+ '</div></div>';
			}
			function render(){
				let html = '<p class="description" style="margin:0 0 12px;">Reset numeru: <b>Co miesiąc</b> — licznik wraca do 1 każdego miesiąca (szablon musi zawierać <code>{m}</code>); <b>Co rok</b> — co rok (musi zawierać <code>{Y}</code>); <b>Nigdy</b> — numeracja ciągła. Domyślnie: co miesiąc.</p>';
				['sale','correction'].forEach(kind=>{
					const addLabel = kind==='correction' ? '+ Dodaj numerację korekt' : '+ Dodaj numerację sprzedaży';
					html += '<h3 style="margin:18px 0 8px;font-size:13px;color:#1d2327;display:flex;align-items:center;gap:12px;flex-wrap:wrap;">'
						+ '<span>'+KIND_LABEL[kind]+'</span>'
						+ (IS_PRO ? '<button type="button" class="button button-small ns-add" data-kind="'+kind+'">'+addLabel+'</button>' : '<a href="https://prostafakturaksef.pl/cennik" target="_blank" rel="noopener" class="button button-small" title="Wiele schematów numeracji jest w wersji Pro">🔒 Wiele schematów — Pro</a>')+'</h3>';
					const idx = schemes.map((s,i)=>i).filter(i=>schemes[i].kind===kind);
					if (!idx.length) html += '<p class="description" style="margin:0 0 8px;">Brak schematów — dodaj poniżej.</p>';
					else idx.forEach(i=>{ html += rowHtml(schemes[i],i); });
				});
				wrap.innerHTML = html;
				wrap.querySelectorAll('.ns-add').forEach(b=>b.addEventListener('click',()=>addScheme(b.dataset.kind)));
				wrap.querySelectorAll('.ksef-num-row').forEach(row=>{
					const i = parseInt(row.dataset.i,10);
					const upd = ()=>{
						schemes[i].name = row.querySelector('.ns-name').value;
						schemes[i].template = row.querySelector('.ns-template').value;
						schemes[i].reset_period = row.querySelector('.ns-reset').value;
						row.querySelector('.ns-preview').textContent = previewOf(schemes[i]);
						const _w = row.querySelector('.ns-warn'); if (_w) _w.textContent = warnFor(schemes[i]);
					};
					['.ns-name','.ns-template','.ns-reset'].forEach(sel=>{ const el=row.querySelector(sel); if (el) el.addEventListener('input',upd); });
					const nextEl = row.querySelector('.ns-next');
					nextEl.addEventListener('input',()=>{ schemes[i]._next = parseInt(nextEl.value,10)||1; schemes[i]._next_dirty = true; row.querySelector('.ns-preview').textContent = previewOf(schemes[i]); });
					row.querySelector('.ns-default').addEventListener('change',()=>{
						const k = schemes[i].kind;
						schemes.forEach((x,j)=>{ if (x.kind===k) x.is_default=(j===i); });
					});
					const rm = row.querySelector('.ns-remove');
					if (rm) rm.addEventListener('click',()=>{
						const k = schemes[i].kind;
						schemes.splice(i,1);
						if (!schemes.some(x=>x.kind===k && x.is_default)){
							const first = schemes.find(x=>x.kind===k);
							if (first) first.is_default=true;
						}
						render();
					});
				});
			}
			function addScheme(kind){
				const isFirst = !schemes.some(x=>x.kind===kind);
				schemes.push({
					id:'scheme_'+(Date.now()),
					name: kind==='correction'?'Nowa numeracja korekt':'Nowa numeracja',
					kind: kind,
					template: kind==='correction'?'KOR/{Y}/{m}/{n}':'FV/{Y}/{m}/{n}',
					prefix:'', reset_period:'monthly', padding:1, is_default:isFirst, _next:1
				});
				render();
			}
			document.getElementById('ksef-num-save').addEventListener('click',async ()=>{
				const msg = document.getElementById('ksef-num-msg');
				msg.textContent = 'Zapisuję…'; msg.style.color='#2271b1';
				try {
					const fd = new FormData();
					fd.append('action','ksef_save_numbering_schemes');
					fd.append('_ajax_nonce',NONCE);
					fd.append('schemes',JSON.stringify(schemes));
					const r = await fetch(AJAX,{method:'POST',body:fd,credentials:'same-origin'});
					const j = await r.json();
					if (j.success){ msg.textContent='✓ Zapisano'; msg.style.color='#16a34a'; if (j.data && Array.isArray(j.data.schemes)) { schemes = j.data.schemes.map(s=>{ if(s._next==null) s._next=1; return s; }); render(); } }
					else { msg.textContent='Błąd: '+(j.data&&j.data.message||'nieznany'); msg.style.color='#dc2626'; }
				} catch(e){ msg.textContent='Network: '+e; msg.style.color='#dc2626'; }
			});
			render();
		})();
		<?php Modern_Shell::inline_script( 'ksef-settings-numbering2', ob_get_clean() ); ?>
		<?php
	}

	/**
	 * Zakładka „Wydatki" — kategorie wydatków (używane przy fakturach zakupu — funkcja Pro).
	 */
	/**
	 * Zakładka „Podatki" — skąd biorą się stawki VAT na fakturze + instrukcja konfiguracji WooCommerce.
	 */
	private function render_tab_taxes(): void {
		$wc_active   = class_exists( 'WooCommerce' );
		$tax_on      = $wc_active && 'yes' === get_option( 'woocommerce_calc_taxes' );
		$prices_incl = 'yes' === get_option( 'woocommerce_prices_include_tax' );
		$general_url = admin_url( 'admin.php?page=wc-settings&tab=general' );
		$tax_url     = admin_url( 'admin.php?page=wc-settings&tab=tax' );
		$std_url     = admin_url( 'admin.php?page=wc-settings&tab=tax&section=standard' );
		?>
		<div style="max-width:780px;line-height:1.65;font-size:13.5px;">
			<p style="font-size:14px;margin-top:0;"><?php esc_html_e( 'Stawki VAT na fakturze pobierane są automatycznie z WooCommerce — z tej samej konfiguracji, której WooCommerce używa do naliczania podatku w zamówieniu.', 'prostafaktura-ksef' ); ?></p>
			<p style="margin-bottom:4px;"><?php esc_html_e( 'Stawka zależy od:', 'prostafaktura-ksef' ); ?></p>
			<ul style="list-style:disc;margin:0 0 14px 24px;">
				<li><strong><?php esc_html_e( 'klasy podatkowej produktu', 'prostafaktura-ksef' ); ?></strong> <?php esc_html_e( '— standardowa (23%), obniżona (8% / 5%) lub zerowa (0%),', 'prostafaktura-ksef' ); ?></li>
				<li><strong><?php esc_html_e( 'kraju nabywcy', 'prostafaktura-ksef' ); ?></strong> <?php esc_html_e( '— stawki możesz definiować osobno dla każdego kraju.', 'prostafaktura-ksef' ); ?></li>
			</ul>

			<?php if ( ! $wc_active ) : ?>
				<div style="padding:10px 14px;border-radius:8px;background:#fef2f2;border:1px solid #fecaca;color:#991b1b;margin-bottom:16px;">⚠ <?php esc_html_e( 'WooCommerce jest nieaktywne — stawki VAT nie będą pobierane.', 'prostafaktura-ksef' ); ?></div>
			<?php elseif ( ! $tax_on ) : ?>
				<div style="padding:10px 14px;border-radius:8px;background:#fffbeb;border:1px solid #fde68a;color:#92400e;margin-bottom:16px;">⚠ <?php esc_html_e( 'Podatki w WooCommerce są WYŁĄCZONE. Bez nich pozycje nie mają naliczonego VAT — włącz je w kroku 1 poniżej, inaczej faktura przyjmie 0%.', 'prostafaktura-ksef' ); ?></div>
			<?php else : ?>
				<div style="padding:10px 14px;border-radius:8px;background:#ecfdf5;border:1px solid #a7f3d0;color:#065f46;margin-bottom:16px;">✓ <?php esc_html_e( 'Podatki w WooCommerce są włączone.', 'prostafaktura-ksef' ); ?> <?php echo esc_html( $prices_incl ? __( 'Ceny produktów wprowadzasz z podatkiem (brutto).', 'prostafaktura-ksef' ) : __( 'Ceny produktów wprowadzasz bez podatku (netto).', 'prostafaktura-ksef' ) ); ?></div>
			<?php endif; ?>

			<h3 style="margin:18px 0 8px;"><?php esc_html_e( 'Jak skonfigurować (krok po kroku)', 'prostafaktura-ksef' ); ?></h3>
			<ol style="margin:0 0 14px 20px;padding:0;">
				<li style="margin-bottom:10px;"><strong><?php esc_html_e( 'Włącz podatki.', 'prostafaktura-ksef' ); ?></strong> <?php esc_html_e( 'WooCommerce → Ustawienia → Ogólne → zaznacz „Włącz stawki i naliczanie podatku".', 'prostafaktura-ksef' ); ?> <a href="<?php echo esc_url( $general_url ); ?>" class="button button-small"><?php esc_html_e( 'Otwórz ustawienia ogólne WC', 'prostafaktura-ksef' ); ?></a></li>
				<li style="margin-bottom:10px;"><strong><?php esc_html_e( 'Dodaj stawki VAT.', 'prostafaktura-ksef' ); ?></strong> <?php esc_html_e( 'WooCommerce → Ustawienia → Podatki → „Stawki standardowe" (np. 23%); w razie potrzeby dodaj klasy „Obniżona" (8% / 5%) i „Zerowa" (0%). Dla każdej stawki podaj kod kraju (np. PL) i wartość.', 'prostafaktura-ksef' ); ?> <a href="<?php echo esc_url( $tax_url ); ?>" class="button button-small"><?php esc_html_e( 'Otwórz Podatki', 'prostafaktura-ksef' ); ?></a> <a href="<?php echo esc_url( $std_url ); ?>" class="button button-small"><?php esc_html_e( 'Stawki standardowe', 'prostafaktura-ksef' ); ?></a></li>
				<li style="margin-bottom:10px;"><strong><?php esc_html_e( 'Przypisz klasę podatkową do produktów.', 'prostafaktura-ksef' ); ?></strong> <?php esc_html_e( 'Produkty → edytuj produkt → „Dane produktu" → zakładka „Ogólne" → pole „Klasa podatkowa". Produkty bez zmian używają klasy standardowej.', 'prostafaktura-ksef' ); ?></li>
				<li style="margin-bottom:10px;"><strong><?php esc_html_e( 'Sprawdź sposób wprowadzania cen.', 'prostafaktura-ksef' ); ?></strong> <?php esc_html_e( 'WooCommerce → Ustawienia → Podatki → „Ceny wprowadzone z podatkiem" — ustaw zgodnie z tym, jak podajesz ceny produktów (brutto czy netto).', 'prostafaktura-ksef' ); ?> <a href="<?php echo esc_url( $tax_url ); ?>" class="button button-small"><?php esc_html_e( 'Otwórz Podatki', 'prostafaktura-ksef' ); ?></a></li>
				<li style="margin-bottom:10px;"><strong><?php esc_html_e( 'VAT dla dostawy.', 'prostafaktura-ksef' ); ?></strong> <?php esc_html_e( 'WooCommerce → Ustawienia → Podatki → „Klasa podatkowa wysyłki" — domyślnie „Na podstawie produktów w koszyku" (przesyłka dostaje stawkę towaru). Możesz też wymusić konkretną klasę (np. 23%). Sama kwota dostawy wynika z metody wysyłki (WooCommerce → Ustawienia → Wysyłka).', 'prostafaktura-ksef' ); ?> <a href="<?php echo esc_url( $tax_url ); ?>" class="button button-small"><?php esc_html_e( 'Otwórz Podatki', 'prostafaktura-ksef' ); ?></a></li>
			</ol>
			<p style="color:#5a6473;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:10px 14px;"><?php esc_html_e( 'Jak liczona jest stawka: VAT pozycji ÷ wartość netto pozycji (czyli dokładnie to, co WooCommerce naliczył w zamówieniu). Jeśli pozycja ma 0 zł podatku, wtyczka odczyta stawkę z konfiguracji WooCommerce dla klasy produktu i kraju nabywcy (np. 0%). Faktura odwzorowuje stawki z WooCommerce 1:1 — zadbaj, aby były tam poprawne.', 'prostafaktura-ksef' ); ?></p>
		</div>
		<?php
	}

	private function render_tab_expenses(): void {
		$cat_raw = (string) get_option( 'gksef_expense_categories', '' );
		if ( '' === trim( $cat_raw ) ) {
			$cat_raw = implode( "\n", \KSEF\WordPress\Admin\Manual_Invoice_Page::default_expense_categories() );
		}
		?>
		<?php if ( isset( $_GET['settings-updated'] ) ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Zapisano ustawienia.', 'prostafaktura-ksef' ); ?></p></div>
		<?php endif; ?>
		<?php $incoming_on = '0' !== (string) get_option( 'gksef_incoming_module_enabled', '1' ); ?>
		<form method="post" action="options.php">
			<?php settings_fields( 'ksef_expenses' ); ?>
			<table class="form-table">
				<tr>
					<th scope="row"><label for="gksef_incoming_module_enabled"><?php esc_html_e( 'Włącz moduł faktury Zakupu', 'prostafaktura-ksef' ); ?></label></th>
					<td>
						<select id="gksef_incoming_module_enabled" name="gksef_incoming_module_enabled">
							<option value="1" <?php selected( $incoming_on, true ); ?>><?php esc_html_e( 'TAK', 'prostafaktura-ksef' ); ?></option>
							<option value="0" <?php selected( $incoming_on, false ); ?>><?php esc_html_e( 'NIE', 'prostafaktura-ksef' ); ?></option>
						</select>
						<p class="description"><?php esc_html_e( 'Gdy „NIE" — moduł faktur zakupu jest wyłączony i nie pojawia się w menu („Faktury zakupu"). Domyślnie włączony.', 'prostafaktura-ksef' ); ?></p>
					</td>
				</tr>
			</table>
			<p class="description" style="margin:14px 0 10px;"><?php esc_html_e( 'Lista kategorii do przypisywania faktur zakupu (jedna na linię). 🔒 Faktury zakupu (kosztowe) są w wersji Pro — w wersji darmowej lista jest tylko zapisywana na przyszłość. Wyczyść pole, aby wrócić do domyślnych.', 'prostafaktura-ksef' ); ?></p>
			<textarea id="gksef_expense_categories" name="gksef_expense_categories" rows="10" class="large-text code" style="max-width:520px;"><?php echo esc_textarea( $cat_raw ); ?></textarea>
			<?php submit_button(); ?>
		</form>
		<?php
	}

	/**
	 * Zakładka „Odinstalowanie" — czy usuwać wszystkie dane przy deinstalacji.
	 */
	private function render_tab_uninstall(): void {
		$ksef_del_data = (string) get_option( 'ksef_delete_data_on_uninstall', '0' );
		?>
		<?php if ( isset( $_GET['settings-updated'] ) ) : ?>
			<div class="notice notice-success is-dismissible"><p><?php esc_html_e( 'Zapisano ustawienia.', 'prostafaktura-ksef' ); ?></p></div>
		<?php endif; ?>
		<form method="post" action="options.php">
			<?php settings_fields( 'ksef_uninstall' ); ?>
			<table class="form-table">
				<tr>
					<th><?php esc_html_e( 'Dane przy usuwaniu wtyczki', 'prostafaktura-ksef' ); ?></th>
					<td>
						<label><input type="checkbox" name="ksef_delete_data_on_uninstall" value="1" <?php checked( $ksef_del_data, '1' ); ?>>
							<?php esc_html_e( 'Usuń wszystkie dane (faktury, kontrahentów, kredencjały, pliki) przy odinstalowaniu wtyczki', 'prostafaktura-ksef' ); ?>
						</label>
						<p class="description" style="color:#b32d2e;">
							<?php esc_html_e( 'Domyślnie wyłączone — dane są zachowywane (faktury podlegają obowiązkowi przechowywania i są wspólne z wersją Pro). Zaznacz tylko jeśli na pewno chcesz trwale usunąć wszystko przy deinstalacji. Operacja nieodwracalna.', 'prostafaktura-ksef' ); ?>
						</p>
					</td>
				</tr>
			</table>
			<?php submit_button(); ?>
		</form>
		<?php
	}

	private function render_tab_ksef(): void {
		?>
		<h2 style="margin-top:6px;"><?php esc_html_e( 'Kredencjały KSeF', 'prostafaktura-ksef' ); ?></h2>
		<p class="description"><?php esc_html_e( 'Klucze API, tokeny tożsamości i środowiska (test/prod). Możesz mieć wiele kont KSeF per NIP — wtyczka wybiera właściwe automatycznie. Domyślny termin płatności i rachunek bankowy ustawiasz teraz per konto KSeF.', 'prostafaktura-ksef' ); ?></p>
		<p>
			<a href="<?php echo esc_url( admin_url( 'admin.php?page=ksef-credentials' ) ); ?>" class="button button-primary">
				<?php esc_html_e( 'Otwórz zarządzanie kredencjałami →', 'prostafaktura-ksef' ); ?>
			</a>
		</p>
		<?php
	}

	/**
	 * Zakładka Uprawnienia — admin może per rola i per user zaznaczyć dostęp do
	 * poszczególnych elementów wtyczki. Override zapisywany w wp_options.
	 */
	private function render_tab_permissions(): void {
		// Tylko user z 'manage_options' (admin) może zmieniać uprawnienia — nie
		// 'ksef_view_settings'. Inaczej każdy z dostępem do ustawień mógłby
		// nadać sobie wszystkie cap.
		if ( ! current_user_can( 'manage_options' ) ) {
			echo '<div class="notice notice-error"><p>' . esc_html__( 'Zarządzanie uprawnieniami wymaga konta administratora WordPress.', 'prostafaktura-ksef' ) . '</p></div>';
			return;
		}

		// Obsługa zapisu
		if ( 'POST' === sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ?? '' ) ) && check_admin_referer( 'ksef_save_permissions' ) ) {
			$this->handle_save_permissions();
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Zapisano uprawnienia.', 'prostafaktura-ksef' ) . '</p></div>';
		}

		$all_caps       = \KSEF\WordPress\Capabilities::ALL_CAPS;
		$role_overrides = (array) get_option( 'ksef_role_caps', [] );
		$user_overrides = (array) get_option( 'ksef_user_caps', [] );

		// Lista ról WP (filtrujemy 'subscriber' bez sensu — może admin go i tak chce)
		$wp_roles = wp_roles();
		$roles    = $wp_roles->roles;
		?>
		<form method="post" action="">
			<?php wp_nonce_field( 'ksef_save_permissions' ); ?>

			<div class="ksef-card" style="background:#fff;border:1px solid #e7e5e1;border-radius:14px;padding:18px 22px;margin-bottom:18px;">
				<h2 style="margin:0 0 6px;font-size:15px;font-weight:600;letter-spacing:-.01em;"><?php esc_html_e( 'Uprawnienia per rola', 'prostafaktura-ksef' ); ?></h2>
				<p style="margin:0 0 14px;color:#5a6473;font-size:12.5px;">
					<?php esc_html_e( 'Zaznacz, do których elementów wtyczki ma dostęp każda rola WordPress. Administrator (manage_options) zawsze ma pełen dostęp niezależnie od tych ustawień, żeby nie zablokować się przypadkiem.', 'prostafaktura-ksef' ); ?>
				</p>
				<div style="overflow-x:auto;">
					<table class="wp-list-table widefat striped" style="font-size:13px;">
						<thead>
							<tr>
								<th style="width:170px;"><?php esc_html_e( 'Rola', 'prostafaktura-ksef' ); ?></th>
								<?php foreach ( $all_caps as $cap => $meta ) : ?>
									<th style="text-align:center;font-size:11px;text-transform:uppercase;" title="<?php echo esc_attr( $meta['desc'] ); ?>"><?php echo esc_html( $meta['label'] ); ?></th>
								<?php endforeach; ?>
							</tr>
						</thead>
						<tbody>
							<?php foreach ( $roles as $role_key => $role ) :
								if ( 'administrator' === $role_key ) {
									continue; // admin zawsze ma wszystko
								}
								$role_caps = isset( $role['capabilities'] ) ? (array) $role['capabilities'] : [];
								$ov        = $role_overrides[ $role_key ] ?? [];
							?>
								<tr>
									<td><strong><?php echo esc_html( $role['name'] ?? $role_key ); ?></strong><br><small style="color:#888"><?php echo esc_html( $role_key ); ?></small></td>
									<?php foreach ( $all_caps as $cap => $meta ) :
										// Domyślna wartość: override > capability w roli > false
										if ( array_key_exists( $cap, $ov ) ) {
											$checked = (bool) $ov[ $cap ];
										} else {
											$checked = ! empty( $role_caps[ $cap ] );
										}
									?>
										<td style="text-align:center;"><input type="checkbox" name="role_caps[<?php echo esc_attr( $role_key ); ?>][<?php echo esc_attr( $cap ); ?>]" value="1" <?php checked( $checked ); ?>></td>
									<?php endforeach; ?>
								</tr>
							<?php endforeach; ?>
						</tbody>
					</table>
				</div>
			</div>

			<div class="ksef-card" style="background:#fff;border:1px solid #e7e5e1;border-radius:14px;padding:18px 22px;margin-bottom:18px;">
				<h2 style="margin:0 0 6px;font-size:15px;font-weight:600;letter-spacing:-.01em;"><?php esc_html_e( 'Wyjątki per użytkownik', 'prostafaktura-ksef' ); ?></h2>
				<p style="margin:0 0 14px;color:#5a6473;font-size:12.5px;">
					<?php esc_html_e( 'Override dla konkretnych użytkowników — silniejszy priorytet niż rola. Pokazani są tylko użytkownicy z jakimkolwiek uprawnieniem KSeF.', 'prostafaktura-ksef' ); ?>
				</p>
				<?php
				// Lista użytkowników z dowolnym KSeF cap (czyli ci, dla których ma sens override)
				$users = get_users( [
					'capability__in' => array_keys( $all_caps ),
					'fields'         => [ 'ID', 'display_name', 'user_login' ],
					'number'         => 100,
				] );
				if ( empty( $users ) ) :
					?>
					<p style="color:#888;"><?php esc_html_e( 'Brak użytkowników z uprawnieniami KSeF.', 'prostafaktura-ksef' ); ?></p>
				<?php else : ?>
					<div style="overflow-x:auto;">
						<table class="wp-list-table widefat striped" style="font-size:13px;">
							<thead>
								<tr>
									<th style="width:200px;"><?php esc_html_e( 'Użytkownik', 'prostafaktura-ksef' ); ?></th>
									<?php foreach ( $all_caps as $cap => $meta ) : ?>
										<th style="text-align:center;font-size:11px;text-transform:uppercase;"><?php echo esc_html( $meta['label'] ); ?></th>
									<?php endforeach; ?>
								</tr>
							</thead>
							<tbody>
								<?php foreach ( $users as $u ) :
									$ov = $user_overrides[ $u->ID ] ?? [];
									$user_obj = get_user_by( 'id', $u->ID );
								?>
									<tr>
										<td><strong><?php echo esc_html( $u->display_name ); ?></strong><br><small style="color:#888"><?php echo esc_html( $u->user_login ); ?></small></td>
										<?php foreach ( $all_caps as $cap => $meta ) :
											if ( array_key_exists( $cap, $ov ) ) {
												$checked = (bool) $ov[ $cap ];
												$is_override = true;
											} else {
												$checked = $user_obj ? $user_obj->has_cap( $cap ) : false;
												$is_override = false;
											}
										?>
											<td style="text-align:center;" title="<?php echo $is_override ? esc_attr__( 'Override per-user (wyższy priorytet niż rola)', 'prostafaktura-ksef' ) : esc_attr__( 'Dziedziczone z roli', 'prostafaktura-ksef' ); ?>">
												<input type="checkbox" name="user_caps[<?php echo (int) $u->ID; ?>][<?php echo esc_attr( $cap ); ?>]" value="1" <?php checked( $checked ); ?>>
											</td>
										<?php endforeach; ?>
									</tr>
								<?php endforeach; ?>
							</tbody>
						</table>
					</div>
				<?php endif; ?>
			</div>

			<?php submit_button( __( 'Zapisz uprawnienia', 'prostafaktura-ksef' ) ); ?>
		</form>
		<?php
	}

	private function handle_save_permissions(): void {
		$all_caps = \KSEF\WordPress\Capabilities::ALL_CAPS;

		// Role overrides
		$role_input = isset( $_POST['role_caps'] ) && is_array( $_POST['role_caps'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['role_caps'] ) ) : [];
		$wp_roles   = wp_roles();
		$out_roles  = [];
		foreach ( $wp_roles->roles as $role_key => $role ) {
			if ( 'administrator' === $role_key ) {
				continue;
			}
			foreach ( array_keys( $all_caps ) as $cap ) {
				$out_roles[ $role_key ][ $cap ] = ! empty( $role_input[ $role_key ][ $cap ] );
			}
		}
		update_option( 'ksef_role_caps', $out_roles, false );

		// User overrides — tylko dla userów którzy są wymienieni w formularzu
		$user_input = isset( $_POST['user_caps'] ) && is_array( $_POST['user_caps'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['user_caps'] ) ) : [];
		$out_users  = [];
		foreach ( $user_input as $uid => $caps_input ) {
			$uid = (int) $uid;
			if ( $uid <= 0 ) {
				continue;
			}
			foreach ( array_keys( $all_caps ) as $cap ) {
				$out_users[ $uid ][ $cap ] = ! empty( $caps_input[ $cap ] );
			}
		}
		update_option( 'ksef_user_caps', $out_users, false );
	}
}
