<?php
/**
 * Renderer faktury → PDF (Dompdf) lub print-friendly HTML jako fallback.
 *
 * Strategia:
 *   - jeśli `vendor/autoload.php` z dompdf/dompdf jest dostępne (composer install)
 *     → `stream()` wysyła binarny PDF (Content-Type: application/pdf), a
 *       `send_email()` dokleja go jako attachment .pdf
 *   - w przeciwnym razie fallback do HTML z auto window.print() i HTML body
 *     w mailu — żeby plugin działał nawet bez vendor/
 *
 * @package KSEF\WordPress
 */

declare(strict_types=1);

namespace KSEF\WordPress;

// phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders, WordPress.DB.SlowDBQuery, PluginCheck.Security.DirectDB.UnescapedDBParameter -- 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).

class Invoice_PDF {

	private const VENDOR_AUTOLOAD = '/vendor/autoload.php';

	/**
	 * Strumieniuje UPO jako PDF (lub HTML print-friendly fallback). Renderuje minimalną
	 * wizualizację Urzędowego Poświadczenia Odbioru z UPO XML KSeF.
	 */
	public static function stream_upo( int $invoice_id, string $upo_xml ): void {
		global $wpdb;
		$inv = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}gksef_outgoing_invoices WHERE id = %d", $invoice_id ), ARRAY_A );
		if ( ! $inv ) {
			wp_die( 'Faktura nie istnieje.', '', [ 'response' => 404 ] );
		}
		nocache_headers();
		$html = self::render_upo_html( $inv, $upo_xml );
		$file = 'UPO_' . preg_replace( '/[^A-Za-z0-9_\-]+/', '_', (string) ( $inv['invoice_number'] ?: $invoice_id ) ) . '.pdf';

		if ( self::dompdf_available() ) {
			$pdf = self::render_with_dompdf( $html );
			if ( '' !== $pdf ) {
				header( 'Content-Type: application/pdf' );
				header( 'Content-Disposition: inline; filename="' . $file . '"' );
				header( 'Content-Length: ' . strlen( $pdf ) );
				echo $pdf; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wyjscie pre-zbudowanego HTML/PDF; dane escapowane przy budowie (esc_html/esc_attr).
				exit;
			}
		}
		header( 'Content-Type: text/html; charset=utf-8' );
		echo str_replace( '</body>', '<script>setTimeout(function(){window.print();},400);</script></body>', $html ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wyjscie pre-zbudowanego HTML/PDF; dane escapowane przy budowie (esc_html/esc_attr).
		exit;
	}

	private static function render_upo_html( array $inv, string $upo_xml ): string {
		$dom = new \DOMDocument();
		@$dom->loadXML( $upo_xml );
		$xp  = new \DOMXPath( $dom );
		// UPO ma własny namespace
		$xp->registerNamespace( 'upo', 'http://crd.gov.pl/wzor/2023/06/29/12648/' );
		$xp->registerNamespace( 'doc', 'http://www.w3.org/2000/09/xmldsig#' );

		$ksef_number = trim( (string) ( $inv['ksef_reference_number'] ?? '' ) );
		$session     = trim( (string) ( $inv['ksef_session_id'] ?? '' ) );
		// próba z XML — pierwsze NumerKSeF jakie znajdziemy
		if ( '' === $ksef_number ) {
			foreach ( $xp->query( '//*[local-name()="NumerKSeFDokumentu"]' ) ?: [] as $n ) { $ksef_number = trim( $n->textContent ); break; }
		}
		$data_przyjecia = '';
		foreach ( $xp->query( '//*[local-name()="DataPrzyjeciaWKSeF"]' ) ?: [] as $n ) { $data_przyjecia = trim( $n->textContent ); break; }

		ob_start();
		?>
<!DOCTYPE html><html lang="pl"><head><meta charset="utf-8"><title>UPO — <?php echo esc_html( (string) ( $inv['invoice_number'] ?? '' ) ); ?></title>
<style>
@page { size: A4; margin: 18mm; }
body { font-family: 'DejaVu Sans', Arial, sans-serif; color: #222; font-size: 12px; padding: 0; margin: 0; }
.h { text-align: center; border-bottom: 2px solid #1d4e89; padding-bottom: 10px; margin-bottom: 20px; }
.h h1 { margin: 0 0 4px; font-size: 22px; color: #1d4e89; }
.h .sub { color: #555; font-size: 12px; }
.box { border: 1px solid #cbd5e1; border-radius: 6px; padding: 14px 18px; margin-bottom: 12px; }
.box h3 { margin: 0 0 10px; font-size: 14px; color: #1d4e89; border-bottom: 1px dashed #cbd5e1; padding-bottom: 4px; }
.kv { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.kv td { padding: 3px 14px 3px 0; vertical-align: top; }
.kv td.k { color: #64748b; width: 190px; }
.kv code { font-family: 'DejaVu Sans Mono', monospace; font-size: 11.5px; background: #f1f5f9; padding: 1px 6px; border-radius: 3px; }
.stamp { text-align: center; margin: 28px 0 6px; }
.stamp .badge { display: inline-block; padding: 10px 22px; border: 2px solid #16a34a; color: #16a34a; border-radius: 8px; font-weight: 700; letter-spacing: 0.04em; font-size: 14px; }
.footer { text-align: center; font-size: 10.5px; color: #888; margin-top: 30px; }
</style>
</head><body>
<div class="h">
	<h1>UPO — Urzędowe Poświadczenie Odbioru</h1>
	<div class="sub">Krajowy System e-Faktur (KSeF) · Ministerstwo Finansów</div>
</div>

<div class="stamp"><span class="badge">✓ FAKTURA PRZYJĘTA DO KSeF</span></div>

<div class="box">
	<h3>Identyfikacja dokumentu</h3>
	<table class="kv">
		<tr><td class="k">Numer KSeF</td><td><code><?php echo esc_html( $ksef_number ); ?></code></td></tr>
		<tr><td class="k">Numer faktury</td><td><strong><?php echo esc_html( (string) ( $inv['invoice_number'] ?? '' ) ); ?></strong></td></tr>
		<tr><td class="k">Data przyjęcia w KSeF</td><td><?php echo esc_html( $data_przyjecia ); ?></td></tr>
		<tr><td class="k">Numer sesji</td><td><code><?php echo esc_html( $session ); ?></code></td></tr>
	</table>
</div>

<div class="box">
	<h3>Strony</h3>
	<table class="kv">
		<tr><td class="k">Sprzedawca NIP</td><td><?php echo esc_html( (string) ( $inv['seller_nip'] ?? '' ) ); ?></td></tr>
		<tr><td class="k">Nabywca NIP</td><td><?php echo esc_html( (string) ( $inv['buyer_nip'] ?? '' ) ); ?></td></tr>
		<tr><td class="k">Nabywca</td><td><?php echo esc_html( (string) ( $inv['buyer_name'] ?? '' ) ); ?></td></tr>
	</table>
</div>

<div class="box">
	<h3>Kwoty</h3>
	<table class="kv">
		<tr><td class="k">Netto</td><td><?php echo esc_html( number_format( (float) $inv['net_amount'], 2, ',', ' ' ) ); ?> <?php echo esc_html( (string) ( $inv['currency'] ?: 'PLN' ) ); ?></td></tr>
		<tr><td class="k">VAT</td><td><?php echo esc_html( number_format( (float) $inv['vat_amount'], 2, ',', ' ' ) ); ?> <?php echo esc_html( (string) ( $inv['currency'] ?: 'PLN' ) ); ?></td></tr>
		<tr><td class="k">Brutto</td><td><strong><?php echo esc_html( number_format( (float) $inv['gross_amount'], 2, ',', ' ' ) ); ?> <?php echo esc_html( (string) ( $inv['currency'] ?: 'PLN' ) ); ?></strong></td></tr>
	</table>
</div>

<div class="footer">
	Dokument wygenerowany automatycznie z UPO XML otrzymanego od KSeF.<br>
	Wersja XML dostępna jest do pobrania jako oryginał.
</div>
</body></html>
		<?php
		return (string) ob_get_clean();
	}

	public static function stream( int $invoice_id, string $type = 'outgoing', bool $preview = false ): void {
		$invoice = self::load( $invoice_id, $type );
		if ( ! $invoice ) {
			wp_die( 'Faktura nie istnieje.', '', [ 'response' => 404 ] );
		}
		nocache_headers();

		$html = self::render_html( $invoice, false );
		$file = self::filename_for( $invoice );

		if ( self::dompdf_available() ) {
			$pdf = self::render_with_dompdf( $html );
			if ( '' !== $pdf ) {
				header( 'Content-Type: application/pdf' );
				header( 'Content-Disposition: inline; filename="' . $file . '"' );
				header( 'Content-Length: ' . strlen( $pdf ) );
				echo $pdf; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wyjscie pre-zbudowanego HTML/PDF; dane escapowane przy budowie (esc_html/esc_attr).
				exit;
			}
		}

		// Fallback bez dompdf: HTML. W trybie preview (iframe) BEZ auto-druku,
		// przy pobieraniu — z auto-drukiem (okno drukowania → zapis do PDF).
		header( 'Content-Type: text/html; charset=utf-8' );
		echo self::render_html( $invoice, ! $preview ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- wyjscie pre-zbudowanego HTML/PDF; dane escapowane przy budowie (esc_html/esc_attr).
		exit;
	}

	public static function send_email( int $invoice_id, string $to, ?string $subject_override = null, ?string $body_override = null ) {
		$invoice = self::load( $invoice_id );
		if ( ! $invoice ) {
			return new \WP_Error( 'not_found', 'Faktura nie istnieje.' );
		}
		$html    = self::render_html( $invoice, false );

		// Temat + treść z konfigurowalnych szablonów (zmienne {numer}, {kwota}, ...).
		$vars    = self::email_vars( $invoice );
		$subject = ( null !== $subject_override && '' !== trim( $subject_override ) ) ? $subject_override : self::render_template( self::email_subject_template(), $vars );
		$body_tpl = ( null !== $body_override && '' !== trim( $body_override ) ) ? $body_override : self::render_template( self::email_body_template(), $vars );

		$from    = (string) get_option( 'gksef_seller_email', get_option( 'admin_email' ) );
		$headers = [];
		if ( is_email( $from ) ) {
			$headers[] = 'From: ' . get_bloginfo( 'name' ) . ' <' . $from . '>';
			$headers[] = 'Reply-To: ' . $from;
		}

		$attachments = [];
		$pdf_path    = '';

		if ( self::dompdf_available() ) {
			$pdf = self::render_with_dompdf( $html );
			if ( '' !== $pdf ) {
				$pdf_path = self::persist_pdf( $invoice_id, $invoice, $pdf );
				if ( '' !== $pdf_path ) {
					$attachments[] = $pdf_path;
				}
			}
		}

		// Ładny HTML wrapper wokół treści szablonu. Gdy PDF się nie wyrenderował,
		// dołączamy pełny widok faktury w treści (żeby klient i tak dostał fakturę).
		$body = self::wrap_email_html( $body_tpl, $invoice, '' === $pdf_path ? $html : '' );

		$headers[] = 'Content-Type: text/html; charset=UTF-8';

		// Wersja tekstowa (AltBody) — wymaganie „html i tekstowej".
		$plain = ( null !== $body_override && '' !== trim( $body_override ) ) ? $body_override : self::render_template( self::email_body_plain_template(), $vars );
		$alt_cb = static function ( $phpmailer ) use ( $plain ) {
			$phpmailer->AltBody = $plain;
		};
		add_action( 'phpmailer_init', $alt_cb );
		$ok = wp_mail( $to, $subject, $body, $headers, $attachments );
		remove_action( 'phpmailer_init', $alt_cb );

		if ( ! $ok ) {
			return new \WP_Error( 'mail_failed', 'wp_mail() zwrócił false.' );
		}

		global $wpdb;
		$wpdb->update(
			$wpdb->prefix . 'gksef_outgoing_invoices',
			[
				'pdf_sent_at' => current_time( 'mysql', true ),
				'pdf_sent_to' => $to,
				'pdf_path'    => $pdf_path ?: null,
			],
			[ 'id' => $invoice_id ]
		);
		return [
			'sent_to'        => $to,
			'invoice_number' => $invoice['invoice_number'] ?? '',
			'attached_pdf'   => $pdf_path !== '',
		];
	}

	/**
	 * Auto-wysyłka faktury do nabywcy po wystawieniu — wywoływana z Invoice_Sender.
	 * Respektuje przełącznik w Ustawieniach (domyślnie WŁĄCZONA) i wymaga adresu
	 * email nabywcy (z payloadu / kontrahenta). Best-effort: błąd tylko logujemy.
	 *
	 * @param int                 $invoice_id   ID faktury w bazie wtyczki.
	 * @param array<string,mixed> $invoice_data Payload faktury (zawiera buyer.email).
	 */
	public static function maybe_send_after_issue( int $invoice_id, array $invoice_data ): void {
		// Domyślnie włączone (1).
		if ( ! (bool) get_option( 'gksef_auto_email_enabled', 1 ) ) {
			return;
		}
		$to = sanitize_email( (string) ( $invoice_data['buyer']['email'] ?? '' ) );
		if ( ! is_email( $to ) ) {
			return; // brak adresu — nic nie wysyłamy (cicho).
		}
		try {
			$res = self::send_email( $invoice_id, $to );
			if ( is_wp_error( $res ) ) {
				ksef_efaktury_log( '[KSEF] Auto-email faktury #' . $invoice_id . ' nieudany: ' . $res->get_error_message() );
			}
		} catch ( \Throwable $e ) {
			ksef_efaktury_log( '[KSEF] Auto-email faktury #' . $invoice_id . ' wyjątek: ' . $e->getMessage() );
		}
	}

	/** Domyślny szablon TEMATU maila do klienta. */
	public static function default_email_subject(): string {
		return 'Faktura {numer} od {sprzedawca}';
	}

	/** Domyślny szablon TREŚCI (HTML wewnętrzny — wstawiany do ładnego wrappera). */
	public static function default_email_body(): string {
		return "Dzień dobry,\n\n"
			. "w załączniku przesyłamy fakturę {numer} na kwotę {kwota}.\n"
			. "{platnosc}\n\n"
			. "Dziękujemy za współpracę.\n"
			. "{sprzedawca}";
	}

	/**
	 * Dane do modala wysylki: wyrenderowany temat + tresc (zmienne podstawione)
	 * + sugerowany email nabywcy (z payloadu / poprzedniej wysylki).
	 *
	 * @return array{subject:string,body:string,to:string}
	 */
	public static function email_preview( int $invoice_id ): array {
		$invoice = self::load( $invoice_id );
		if ( ! $invoice ) {
			return [ 'subject' => '', 'body' => '', 'to' => '' ];
		}
		$vars = self::email_vars( $invoice );
		$to   = '';
		if ( ! empty( $invoice['payload_json'] ) ) {
			$payload = json_decode( (string) \KSEF\WordPress\Crypto::decrypt_from_storage( (string) $invoice['payload_json'] ), true );
			if ( is_array( $payload ) ) {
				$to = sanitize_email( (string) ( $payload['buyer']['email'] ?? '' ) );
			}
		}
		if ( '' === $to && ! empty( $invoice['pdf_sent_to'] ) ) {
			$to = sanitize_email( (string) $invoice['pdf_sent_to'] );
		}
		return [
			'subject' => self::render_template( self::email_subject_template(), $vars ),
			'body'    => self::render_template( self::email_body_template(), $vars ),
			'to'      => is_email( $to ) ? $to : '',
		];
	}
	private static function email_subject_template(): string {
		$t = trim( (string) get_option( 'gksef_auto_email_subject', '' ) );
		return '' !== $t ? $t : self::default_email_subject();
	}

	private static function email_body_template(): string {
		$t = trim( (string) get_option( 'gksef_auto_email_body', '' ) );
		return '' !== $t ? $t : self::default_email_body();
	}

	private static function email_body_plain_template(): string {
		// Plain-text = ta sama treść co HTML, ze zmiennymi (HTML wrapper i tak nie dotyczy).
		return self::email_body_template();
	}

	/**
	 * Precyzyjna informacja o płatności do maila — uwzględnia REALNY status zapłaty
	 * (Payments_Repository::summary: zapłacona / częściowo / do zapłaty), a nie tylko termin.
	 * Zwraca krótką etykietę + pełne zdanie + kwotę do zapłaty.
	 *
	 * @param array<string,mixed> $invoice
	 * @return array{status:string,label:string,sentence:string,due:string,outstanding:string}
	 */
	private static function payment_info( array $invoice ): array {
		$status      = (string) ( $invoice['payment_status'] ?? 'unpaid' );
		$currency    = (string) ( $invoice['currency'] ?? 'PLN' );
		$outstanding = (float) ( $invoice['gross_amount'] ?? 0 );
		$due = ( ! empty( $invoice['due_date'] ) && '0000-00-00' !== $invoice['due_date'] ) ? (string) $invoice['due_date'] : '';
		// Najdokładniejszy stan z rejestru płatności (tylko faktury sprzedaży/outgoing).
		if ( ! empty( $invoice['id'] ) && 'incoming' !== (string) ( $invoice['_pdf_type'] ?? '' ) && class_exists( '\\KSEF\\WordPress\\Payments_Repository' ) ) {
			$sum = \KSEF\WordPress\Payments_Repository::summary( (int) $invoice['id'] );
			if ( ! empty( $sum ) ) {
				$status      = (string) ( $sum['payment_status'] ?: $status );
				$outstanding = (float) ( $sum['outstanding'] ?? $outstanding );
				$currency    = (string) ( $sum['currency'] ?: $currency );
				if ( '' === $due && ! empty( $sum['due_date'] ) && '0000-00-00' !== $sum['due_date'] ) {
					$due = (string) $sum['due_date'];
				}
			}
		}
		$amount = number_format( $outstanding, 2, ',', ' ' ) . ' ' . $currency;
		if ( 'paid' === $status ) {
			return [ 'status' => 'paid', 'label' => 'Zapłacono', 'sentence' => 'Faktura została opłacona — dziękujemy.', 'due' => $due, 'outstanding' => $amount ];
		}
		if ( 'partial' === $status ) {
			$s = '' !== $due
				? sprintf( 'Faktura częściowo opłacona — do zapłaty pozostało %1$s (termin: %2$s).', $amount, $due )
				: sprintf( 'Faktura częściowo opłacona — do zapłaty pozostało %s.', $amount );
			return [ 'status' => 'partial', 'label' => 'Częściowo opłacona', 'sentence' => $s, 'due' => $due, 'outstanding' => $amount ];
		}
		$s = '' !== $due ? sprintf( 'Termin płatności: %s.', $due ) : 'Faktura do zapłaty.';
		return [ 'status' => 'unpaid', 'label' => 'Do zapłaty', 'sentence' => $s, 'due' => $due, 'outstanding' => $amount ];
	}

	/**
	 * Zmienne dostępne w szablonach maila.
	 *
	 * @param array<string,mixed> $invoice
	 * @return array<string,string>
	 */
	private static function email_vars( array $invoice ): array {
		$num     = (string) ( $invoice['invoice_number'] ?: ( '#' . $invoice['id'] ) );
		$gross   = number_format( (float) ( $invoice['gross_amount'] ?? 0 ), 2, ',', ' ' ) . ' ' . (string) ( $invoice['currency'] ?? 'PLN' );
		$due     = ( ! empty( $invoice['due_date'] ) && '0000-00-00' !== $invoice['due_date'] ) ? (string) $invoice['due_date'] : '—';
		$ksef    = (string) ( $invoice['ksef_reference_number'] ?? '' );
		$issued  = ! empty( $invoice['created_at'] ) ? gmdate( 'Y-m-d', strtotime( (string) $invoice['created_at'] ) ) : gmdate( 'Y-m-d' );
		$pay     = self::payment_info( $invoice );
		return [
			'{numer}'            => $num,
			'{kwota}'            => $gross,
			'{termin}'           => $due,
			'{platnosc}'         => $pay['sentence'],
			'{status_platnosci}' => $pay['label'],
			'{ksef}'             => $ksef,
			'{data}'             => $issued,
			'{nabywca}'          => (string) ( $invoice['buyer_name'] ?? '' ),
			'{sprzedawca}'       => (string) get_bloginfo( 'name' ),
		];
	}

	/**
	 * Podstaw zmienne {klucz} w szablonie.
	 *
	 * @param array<string,string> $vars
	 */
	private static function render_template( string $tpl, array $vars ): string {
		return strtr( $tpl, $vars );
	}

	/**
	 * Owija treść szablonu (newline-separated) w ładny responsywny HTML.
	 * Gdy $fallback_invoice_html != '' (brak PDF) — dokleja pełny widok faktury.
	 *
	 * @param array<string,mixed> $invoice
	 */
	private static function wrap_email_html( string $body_text, array $invoice, string $fallback_invoice_html = '' ): string {
		$blog   = esc_html( (string) get_bloginfo( 'name' ) );
		$num    = esc_html( (string) ( $invoice['invoice_number'] ?: ( '#' . $invoice['id'] ) ) );
		$gross  = esc_html( number_format( (float) ( $invoice['gross_amount'] ?? 0 ), 2, ',', ' ' ) . ' ' . (string) ( $invoice['currency'] ?? 'PLN' ) );
		$due    = ( ! empty( $invoice['due_date'] ) && '0000-00-00' !== $invoice['due_date'] ) ? esc_html( (string) $invoice['due_date'] ) : '—';
		$ksef   = (string) ( $invoice['ksef_reference_number'] ?? '' );
		// Wiersz płatności wg REALNEGO statusu (zapłacona / częściowo / do zapłaty), nie zawsze „termin".
		$pay = self::payment_info( $invoice );
		if ( 'paid' === $pay['status'] ) {
			$pay_row = '<tr><td style="padding:6px 0;color:#6b7280;">Płatność</td><td style="padding:6px 0;text-align:right;color:#059669;font-weight:600;">&#10004; Zapłacono</td></tr>';
		} elseif ( 'partial' === $pay['status'] ) {
			$pay_row = '<tr><td style="padding:6px 0;color:#6b7280;">Do zapłaty</td><td style="padding:6px 0;text-align:right;color:#b45309;font-weight:600;">' . esc_html( $pay['outstanding'] )
				. ( '' !== $pay['due'] ? ' <span style="font-weight:400;color:#6b7280;">(termin ' . esc_html( $pay['due'] ) . ')</span>' : '' ) . '</td></tr>';
		} else {
			$pay_row = '<tr><td style="padding:6px 0;color:#6b7280;">Termin płatności</td><td style="padding:6px 0;text-align:right;color:#111827;">' . $due . '</td></tr>';
		}
		// Treść szablonu: zamień podwójne newline na akapity, pojedyncze na <br>.
		$paras = array_filter( array_map( 'trim', preg_split( '/\n{2,}/', $body_text ) ?: [] ), static fn( $p ) => '' !== $p );
		$body_html = '';
		foreach ( $paras as $p ) {
			$body_html .= '<p style="margin:0 0 14px;line-height:1.6;color:#1f2937;">' . nl2br( esc_html( $p ) ) . '</p>';
		}
		$ksef_row = '' !== $ksef
			? '<tr><td style="padding:6px 0;color:#6b7280;">Numer KSeF</td><td style="padding:6px 0;text-align:right;font-family:monospace;font-size:12px;color:#111827;">' . esc_html( $ksef ) . '</td></tr>'
			: '';
		$fallback = '' !== $fallback_invoice_html
			? '<div style="margin-top:24px;border-top:1px solid #e5e7eb;padding-top:18px;">' . $fallback_invoice_html . '</div>'
			: '';

		return '<!DOCTYPE html><html lang="pl"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>'
			. '<body style="margin:0;background:#f3f4f6;padding:24px 0;font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;">'
			. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;"><tr><td align="center">'
			. '<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px;width:100%;background:#ffffff;border-radius:14px;overflow:hidden;box-shadow:0 4px 16px rgba(0,0,0,.06);">'
			. '<tr><td style="background:#5B5BD6;padding:24px 28px;"><span style="color:#fff;font-size:18px;font-weight:600;">' . $blog . '</span></td></tr>'
			. '<tr><td style="padding:28px;">'
			. '<h1 style="margin:0 0 18px;font-size:20px;color:#111827;">Faktura ' . $num . '</h1>'
			. $body_html
			. '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:20px 0;border-top:1px solid #e5e7eb;border-bottom:1px solid #e5e7eb;font-size:14px;">'
			. '<tr><td style="padding:6px 0;color:#6b7280;">Kwota brutto</td><td style="padding:6px 0;text-align:right;font-weight:600;color:#111827;">' . $gross . '</td></tr>'
			. $pay_row
			. $ksef_row
			. '</table>'
			. '<p style="margin:0;color:#6b7280;font-size:13px;">Fakturę w formacie PDF znajdziesz w załączniku.</p>'
			. $fallback
			. '</td></tr>'
			. '<tr><td style="padding:18px 28px;background:#f9fafb;color:#9ca3af;font-size:12px;text-align:center;">Wiadomość wygenerowana automatycznie przez ' . $blog . '.</td></tr>'
			. '</table></td></tr></table></body></html>';
	}

	/**
	 * Zwraca true gdy vendor/dompdf jest zainstalowane.
	 */
	private static function dompdf_available(): bool {
		if ( class_exists( '\Dompdf\Dompdf' ) ) {
			return true;
		}
		$autoload = KSEF_WORDPRESS_PATH . ltrim( self::VENDOR_AUTOLOAD, '/' );
		if ( file_exists( $autoload ) ) {
			require_once $autoload;
			return class_exists( '\Dompdf\Dompdf' );
		}
		return false;
	}

	/**
	 * Renderuje HTML do binarnego PDF przez Dompdf. Zwraca '' przy błędzie.
	 */
	private static function render_with_dompdf( string $html ): string {
		try {
			// Cache fontów i pliki tymczasowe trzymaj w uploads (zapisywalne dla PHP
			// i odporne na nadpisanie przy aktualizacji wtyczki).
			$updir = wp_upload_dir();
			$cache = ! empty( $updir['basedir'] ) ? trailingslashit( $updir['basedir'] ) . 'ksef-dompdf' : '';
			if ( '' !== $cache && ! is_dir( $cache ) ) {
				wp_mkdir_p( $cache );
			}
			$opts = [
				'isHtml5ParserEnabled' => true,
				'isRemoteEnabled'      => false, // brak pobierania zdalnych zasobów (anty-SSRF)
				'isPhpEnabled'         => false, // brak wykonywania PHP w dompdf (anty-RCE, audyt L-3)
				'isJavascriptEnabled'  => false, // brak JS w PDF
				'defaultFont'          => 'DejaVu Sans',
				'defaultMediaType'     => 'print',
			];
			if ( '' !== $cache && is_dir( $cache ) ) {
				$opts['tempDir']   = $cache;
				$opts['fontCache'] = $cache;
			}
			$dompdf = new \Dompdf\Dompdf( $opts );
			$dompdf->loadHtml( $html, 'UTF-8' );
			$dompdf->setPaper( 'A4', 'portrait' );
			$dompdf->render();
			return (string) $dompdf->output();
		} catch ( \Throwable $e ) {
			ksef_efaktury_log( '[KSEF] Dompdf render failed: ' . $e->getMessage() );
			return '';
		}
	}

	/**
	 * Zapisuje PDF do wp-content/uploads/ksef-pdf/{Y}/{M}/{filename}.pdf i zwraca pełną ścieżkę.
	 */
	private static function persist_pdf( int $invoice_id, array $invoice, string $pdf ): string {
		$uploads = wp_upload_dir();
		if ( empty( $uploads['basedir'] ) ) {
			return '';
		}
		$root = trailingslashit( $uploads['basedir'] ) . 'ksef-pdf';
		$y    = gmdate( 'Y' );
		$m    = gmdate( 'm' );
		$dir  = $root . '/' . $y . '/' . $m;
		if ( ! wp_mkdir_p( $dir ) ) {
			return '';
		}
		// Twardo zablokuj direct-access do całego drzewa cache PDF.
		self::ensure_dir_locked( $root );
		self::ensure_dir_locked( $root . '/' . $y );
		self::ensure_dir_locked( $dir );

		$file = trailingslashit( $dir ) . self::disk_filename_for( $invoice );
		if ( false === file_put_contents( $file, $pdf ) ) {
			return '';
		}
		@chmod( $file, 0600 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- lokalne operacje na wlasnych plikach wtyczki (cache/upload).
		return $file;
	}

	/**
	 * Jednorazowa migracja: zabezpiecza już istniejący katalog cache PDF
	 * przed direct-access (drop .htaccess w każdym podkatalogu).
	 */
	public static function lock_existing_cache(): void {
		$uploads = wp_upload_dir();
		if ( empty( $uploads['basedir'] ) ) {
			return;
		}
		$root = trailingslashit( $uploads['basedir'] ) . 'ksef-pdf';
		if ( ! is_dir( $root ) ) {
			return;
		}
		self::ensure_dir_locked( $root );
		$iter = new \RecursiveIteratorIterator(
			new \RecursiveDirectoryIterator( $root, \FilesystemIterator::SKIP_DOTS ),
			\RecursiveIteratorIterator::SELF_FIRST
		);
		foreach ( $iter as $entry ) {
			if ( $entry->isDir() ) {
				self::ensure_dir_locked( $entry->getPathname() );
			} elseif ( $entry->isFile() && '.pdf' === strtolower( substr( (string) $entry->getFilename(), -4 ) ) ) {
				@chmod( $entry->getPathname(), 0600 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod -- lokalne operacje na wlasnych plikach wtyczki (cache/upload).
			}
		}
	}

	/**
	 * Tworzy w katalogu .htaccess (Apache/LiteSpeed: Deny from all) oraz pusty index.html
	 * — chroni przed direct-access do plików PDF nawet jeśli ktoś zna URL.
	 */
	private static function ensure_dir_locked( string $dir ): void {
		$ht = trailingslashit( $dir ) . '.htaccess';
		if ( ! file_exists( $ht ) ) {
			@file_put_contents( $ht, "Order Allow,Deny\nDeny from all\n<IfModule mod_authz_core.c>\n\tRequire all denied\n</IfModule>\n" );
		}
		$idx = trailingslashit( $dir ) . 'index.html';
		if ( ! file_exists( $idx ) ) {
			@file_put_contents( $idx, '' );
		}
	}

	/**
	 * Nazwa pliku przy zapisie na dysk — z losowym 16-znakowym sufiksem,
	 * aby plik był nieprzewidywalny URL-em (defense-in-depth obok .htaccess).
	 */
	private static function disk_filename_for( array $invoice ): string {
		$num  = (string) ( $invoice['invoice_number'] ?: ( 'faktura-' . (int) $invoice['id'] ) );
		$slug = trim( (string) preg_replace( '/[^A-Za-z0-9_\-]+/', '_', $num ), '_' );
		try {
			$rand = bin2hex( random_bytes( 8 ) );
		} catch ( \Throwable $e ) {
			$rand = substr( hash( 'sha256', $slug . microtime( true ) . wp_generate_password( 16, false ) ), 0, 16 );
		}
		return $slug . '-' . $rand . '.pdf';
	}

	/**
	 * Nazwa pliku widoczna dla użytkownika (Content-Disposition) — bez sufiksu.
	 */
	private static function filename_for( array $invoice ): string {
		$num = (string) ( $invoice['invoice_number'] ?: ( 'faktura-' . (int) $invoice['id'] ) );
		$slug = preg_replace( '/[^A-Za-z0-9_\-]+/', '_', $num );
		return trim( (string) $slug, '_' ) . '.pdf';
	}

	private static function email_body_plain( array $invoice ): string {
		$num = $invoice['invoice_number'] ?: ( '#' . $invoice['id'] );
		ob_start();
		?>
		<p>Dzień dobry,</p>
		<p>w załączniku przesyłam fakturę <strong><?php echo esc_html( $num ); ?></strong>.</p>
		<p><?php echo esc_html( self::payment_info( $invoice )['sentence'] ); ?></p>
		<?php if ( ! empty( $invoice['ksef_reference_number'] ) ) : ?>
			<p>Numer KSeF: <code><?php echo esc_html( (string) $invoice['ksef_reference_number'] ); ?></code></p>
		<?php endif; ?>
		<p>Pozdrawiam,<br><?php echo esc_html( get_bloginfo( 'name' ) ); ?></p>
		<?php
		return (string) ob_get_clean();
	}

	private static function load( int $invoice_id, string $type = 'outgoing' ): ?array {
		global $wpdb;
		if ( 'incoming' === $type ) {
			$row = $wpdb->get_row(
				$wpdb->prepare( "SELECT * FROM {$wpdb->prefix}gksef_incoming_invoices WHERE id = %d", $invoice_id ),
				ARRAY_A
			);
			if ( $row ) {
				// Faktury zakupu trzymają pełny FA(3) w raw_xml; PDF buduje się z payload_json,
				// więc gdy go brak — sparsuj raw_xml do payloadu w locie.
				if ( empty( $row['payload_json'] ) && ! empty( $row['raw_xml'] ) ) {
					$xml = (string) Crypto::decrypt_from_storage( $row['raw_xml'] );
					if ( '' !== $xml && strpos( ltrim( $xml ), '<' ) === 0 ) {
						require_once KSEF_WORDPRESS_PATH . 'includes/v2/class-fa3-parser.php';
						$parsed = \KSEF\WordPress\V2\Fa3_Parser::parse( $xml );
						if ( is_array( $parsed ) ) {
							$row['payload_json'] = Crypto::encrypt_for_storage( wp_json_encode( $parsed ) );
						}
					}
				}
				$row['_pdf_type'] = 'incoming';
			}
			return $row ?: null;
		}
		$row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$wpdb->prefix}gksef_outgoing_invoices WHERE id = %d",
				$invoice_id
			),
			ARRAY_A
		);
		if ( $row ) {
			$row['_pdf_type'] = 'outgoing';
		}
		return $row ?: null;
	}

	/** Etykieta stawki VAT w stylu oficjalnej wizualizacji KSeF (np. 8% → „8% lub 7%"). */
	private static function ksef_rate_label( $rate ): string {
		if ( is_numeric( $rate ) ) {
			$r = (int) round( (float) $rate );
			$map = [ 23 => '23% lub 22%', 8 => '8% lub 7%', 5 => '5%', 0 => '0%' ];
			return $map[ $r ] ?? ( $r . '%' );
		}
		$code = strtolower( trim( (string) $rate ) );
		$labels = [ 'zw' => 'zw.', 'np' => 'np.', 'oo' => 'oo' ];
		return $labels[ $code ] ?? strtoupper( (string) $rate );
	}

	/** Stawka VAT pozycji (krótka, np. „8%"). */
	private static function rate_short( $rate ): string {
		if ( is_numeric( $rate ) ) {
			return ( (int) round( (float) $rate ) ) . '%';
		}
		$code = strtolower( trim( (string) $rate ) );
		$labels = [ 'zw' => 'zw.', 'np' => 'np.', 'oo' => 'oo' ];
		return $labels[ $code ] ?? strtoupper( (string) $rate );
	}

	/** Data w formacie DD-MM-RRRR (dla linku QR P_1). */
	private static function date_ddmmyyyy( string $date ): string {
		$ts = strtotime( $date );
		return $ts ? gmdate( 'd-m-Y', $ts ) : $date;
	}

	/**
	 * Link weryfikacyjny KSeF (KOD I, online): {env}/invoice/{NIP}/{DD-MM-RRRR}/{base64url(sha256(xml))}.
	 * Zwraca '' gdy brak danych (np. brak XML).
	 */
	private static function qr_verify_url( array $inv, array $seller, string $issue_date ): string {
		$xml = (string) ( $inv['_render_xml'] ?? '' );
		if ( '' === $xml ) {
			$xml = (string) Crypto::decrypt_from_storage( (string) ( $inv['generated_xml'] ?? '' ) );
		}
		$nip = preg_replace( '/\D+/', '', (string) ( $seller['nip'] ?? '' ) );
		if ( '' === $xml || strpos( ltrim( $xml ), '<' ) !== 0 || '' === (string) $nip ) {
			return '';
		}
		$hash = rtrim( strtr( base64_encode( hash( 'sha256', $xml, true ) ), '+/', '-_' ), '=' );
		$env  = (string) ( $inv['_ksef_env'] ?? 'prod' );
		$base = 'test' === $env ? 'https://qr-test.ksef.mf.gov.pl' : 'https://qr.ksef.mf.gov.pl';
		return $base . '/invoice/' . rawurlencode( (string) $nip ) . '/' . self::date_ddmmyyyy( $issue_date ) . '/' . $hash;
	}

	private static function render_html( array $inv, bool $auto_print ): string {
		require_once __DIR__ . '/v2/class-fa3-parser.php';
		require_once __DIR__ . '/v2/class-qr-encoder.php';
		// Zrodlo prawdy = XML FA(3) (wyslany / pobrany z KSeF) — render parsuje dokladnie ten
		// dokument, ktory jest w KSeF. Fallback: payload_json (np. szkic jeszcze bez XML).
		$xml = (string) Crypto::decrypt_from_storage( (string) ( $inv['generated_xml'] ?? '' ) );
		if ( '' === $xml || strpos( ltrim( $xml ), '<' ) !== 0 ) {
			$xml = (string) Crypto::decrypt_from_storage( (string) ( $inv['raw_xml'] ?? '' ) );
		}
		$parsed = ( '' !== $xml && strpos( ltrim( $xml ), '<' ) === 0 ) ? \KSEF\WordPress\V2\Fa3_Parser::parse( $xml ) : null;
		if ( is_array( $parsed ) && ! empty( $parsed ) ) {
			$payload = $parsed;
			$inv['_render_xml'] = $xml;
		} else {
			$payload = is_string( $inv['payload_json'] ?? '' ) ? json_decode( (string) Crypto::decrypt_from_storage( $inv['payload_json'] ), true ) : null;
			$payload = is_array( $payload ) ? $payload : [];
		}
		$items   = $payload['items'] ?? [];
		$seller  = $payload['seller'] ?? [];
		$buyer   = $payload['buyer'] ?? [];
		$vatSum  = $payload['vat_summary'] ?? [];
		$markers = $payload['markers'] ?? [];
		$rel     = $payload['related_docs'] ?? [];
		$extra   = $payload['extra_data'] ?? [];
		$del     = $payload['delivery_address'] ?? null;
		$notes   = (string) ( $payload['internal_notes'] ?? $payload['notes'] ?? '' );
		$ksef_notes = $payload['ksef_custom_notes'] ?? [];
		$deposit_amount = (float) ( $payload['deposit_amount'] ?? 0 );
		$deposit_label  = (string) ( $payload['deposit_label'] ?? '' );
		$bank = $payload['bank_account'] ?? null;

		// Komplet danych do wizualizacji: parsed XML = źródło, payload_json uzupełnia pola
		// lokalne (kaucja, notatki) oraz pełny zestaw znaczników z formularza.
		$pj = is_string( $inv['payload_json'] ?? '' ) ? json_decode( (string) Crypto::decrypt_from_storage( (string) $inv['payload_json'] ), true ) : null;
		$pj = is_array( $pj ) ? $pj : [];
		$markers        = array_merge( (array) ( $pj['markers'] ?? [] ), is_array( $markers ) ? $markers : [] );
		$period_from    = (string) ( $payload['period_from'] ?? $pj['period_from'] ?? '' );
		$period_to      = (string) ( $payload['period_to'] ?? $pj['period_to'] ?? '' );
		$deposit_amount = (float) ( $payload['deposit_amount'] ?? $pj['deposit_amount'] ?? $deposit_amount );
		$deposit_label  = (string) ( $payload['deposit_label'] ?? $pj['deposit_label'] ?? $deposit_label );
		$del            = ! empty( $del ) ? $del : ( $pj['delivery_address'] ?? null );
		$extra          = ! empty( $extra ) ? $extra : (array) ( $pj['extra_data'] ?? [] );
		$ksef_notes     = ! empty( $ksef_notes ) ? $ksef_notes : (array) ( $pj['ksef_custom_notes'] ?? [] );
		// related_docs: payload_json ma kanoniczną strukturę order/wz (parser bywa rozbieżny) — preferuj ją.
		$rel            = ! empty( $pj['related_docs'] ) ? (array) $pj['related_docs'] : ( is_array( $rel ) ? $rel : [] );
		$seller_email   = '' !== (string) ( $seller['email'] ?? '' ) ? (string) $seller['email'] : (string) ( $pj['seller']['email'] ?? '' );
		$seller_phone   = '' !== (string) ( $seller['phone'] ?? '' ) ? (string) $seller['phone'] : (string) ( $pj['seller']['phone'] ?? '' );
		$buyer_email    = '' !== (string) ( $buyer['email'] ?? '' ) ? (string) $buyer['email'] : (string) ( $pj['buyer']['email'] ?? '' );
		// Numer klienta: pierwsza niepusta wartość (parsed extra bywa pustym stringiem → „??" nie wystarcza).
		$customer_no = '';
		foreach ( [ (string) ( $extra['customer_no'] ?? '' ), (string) ( $buyer['customer_no'] ?? '' ), (string) ( $pj['extra_data']['customer_no'] ?? '' ) ] as $cn ) {
			if ( '' !== $cn ) { $customer_no = $cn; break; }
		}

		// Płatności rejestrowane lokalnie dotyczą tylko faktur sprzedaży (gksef_invoice_payments).
		if ( 'incoming' === ( $inv['_pdf_type'] ?? 'outgoing' ) ) {
			$payments = [];
			$summary  = [];
		} else {
			$payments = Payments_Repository::list_for_invoice( (int) $inv['id'] );
			$summary  = Payments_Repository::summary( (int) $inv['id'] );
		}

		$currency = $inv['currency'] ?: 'PLN';
		$num_invoice = (string) ( $inv['invoice_number'] ?: '#' . $inv['id'] );
		$issue_date = (string) ( $payload['issue_date'] ?? gmdate( 'Y-m-d', strtotime( (string) $inv['created_at'] ) ) );
		$sale_date  = (string) ( $payload['sale_date'] ?? $issue_date );
		$fmt_date = static function ( $d ) { $ts = strtotime( (string) $d ); return $ts ? gmdate( 'd.m.Y', $ts ) : (string) $d; };
		$money = static function ( $v ) { return number_format( (float) $v, 2, ',', ' ' ); };
		$price = static function ( $v ) {
			$s = rtrim( number_format( (float) $v, 8, ',', '' ), '0' );
			if ( substr( $s, -1 ) === ',' ) { $s .= '00'; }
			$p = explode( ',', $s );
			if ( isset( $p[1] ) && strlen( $p[1] ) < 2 ) { $p[1] = str_pad( $p[1], 2, '0', STR_PAD_RIGHT ); }
			return implode( ',', $p );
		};
		// Wartość brutto pozycji: użyj line_gross jeśli sensowny (> netto); inaczej policz
		// netto + round(netto × stawka) w groszach. Faktury pobrane/sparsowane z XML nie mają
		// pola brutto (FA(3) wiersz ma tylko netto), więc bez tego brutto=netto i VAT=0.
		$gross_of = static function ( $item ) {
			$net = (float) ( $item['line_net'] ?? 0 );
			$g   = (float) ( $item['line_gross'] ?? 0 );
			if ( $g > $net + 0.0001 ) {
				return $g;
			}
			$rate = is_numeric( $item['vat_rate'] ?? null ) ? (float) $item['vat_rate'] : 0.0;
			$ng   = (int) round( $net * 100 );
			return ( $ng + (int) round( $ng * $rate / 100 ) ) / 100;
		};
		$rodzaj = strtoupper( (string) ( $payload['rodzaj_faktury'] ?? 'VAT' ) );
		$rodzaj_labels = [ 'VAT' => 'Faktura podstawowa', 'KOR' => 'Faktura korygująca', 'ZAL' => 'Faktura zaliczkowa', 'ROZ' => 'Faktura rozliczeniowa', 'UPR' => 'Faktura uproszczona', 'KOR_ZAL' => 'Korekta faktury zaliczkowej', 'KOR_ROZ' => 'Korekta faktury rozliczeniowej' ];
		$rodzaj_label = $rodzaj_labels[ $rodzaj ] ?? 'Faktura podstawowa';
		$ksef_ref = (string) ( $inv['ksef_reference_number'] ?? '' );
		$qr_url = self::qr_verify_url( $inv, $seller, $issue_date );
		$paid = ( (string) ( $summary['payment_status'] ?? $inv['payment_status'] ?? 'unpaid' ) ) === 'paid';
		$paid_date = (string) ( $inv['paid_date'] ?? '' );
		$saddr = $seller['address'] ?? [];
		$baddr = $buyer['address'] ?? [];
		ob_start();
		?>
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<title>Faktura <?php echo esc_html( $num_invoice ); ?></title>
<style>
@page { size: A4; margin: 12mm; }
* { box-sizing: border-box; }
body { font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; color: #1a1a1a; font-size: 10px; margin: 0; padding: 0; line-height: 1.4; }
.ks-title { font-size: 19px; font-weight: 400; margin: 0; white-space: nowrap; }
.ks-title b { color: #1a1a1a; font-weight: 700; }
.hdr { display: table; width: 100%; }
.hdr-l { display: table-cell; vertical-align: top; }
.hdr-r { display: table-cell; vertical-align: top; text-align: right; }
.fa-label { font-size: 9.5px; color: #555; }
.fa-num { font-size: 15px; font-weight: 700; margin: 2px 0; }
.fa-sub { font-size: 9.5px; color: #555; }
.fa-ksef { font-size: 9.5px; font-weight: 700; margin-top: 2px; }
hr { border: 0; border-top: 1px solid #e2e2e2; margin: 10px 0; }
h2 { font-size: 12.5px; font-weight: 400; margin: 0 0 7px; color: #222; }
.two { display: table; width: 100%; table-layout: fixed; }
.two > div { display: table-cell; vertical-align: top; padding-right: 28px; }
.lbl { color: #555; }
.b { font-weight: 700; }
table.t { width: 100%; border-collapse: collapse; font-size: 9px; margin-top: 4px; }
table.t th, table.t td { border: 1px solid #d8d8d8; padding: 5px 6px; text-align: left; vertical-align: top; }
table.t th { background: #f3f3f3; font-weight: 700; }
.r { text-align: right; }
.tot { text-align: right; font-size: 12.5px; font-weight: 700; margin: 10px 0; }
.kv { border-collapse: collapse; max-width: 580px; width: 100%; }
.kv td { border: 1px solid #d8d8d8; padding: 5px 6px; }
.kv td.k { background: #f7f7f7; font-weight: 700; width: 200px; }
.qr { display: table; width: 100%; }
.qr > div { display: table-cell; vertical-align: top; }
.foot { margin-top: 22px; padding-top: 9px; border-top: 1px solid #e2e2e2; font-size: 9px; color: #555; }
@media print { .no-print { display:none; } }
</style>
</head>
<body>
<?php if ( $auto_print ) : ?>
<div class="no-print" style="text-align:right;margin:8px;"><button onclick="window.print();return false;" style="padding:6px 12px;">🖨 Wydrukuj / Zapisz jako PDF</button></div>
<script>setTimeout(function(){ window.print(); }, 400);</script>
<?php endif; ?>

<div class="hdr">
	<div class="hdr-l"><div class="ks-title">Krajowy System <b>e-Faktur</b></div></div>
	<div class="hdr-r">
		<div class="fa-label">Numer Faktury:</div>
		<div class="fa-num"><?php echo esc_html( $num_invoice ); ?></div>
		<div class="fa-sub"><?php echo esc_html( $rodzaj_label ); ?></div>
		<?php if ( '' !== $ksef_ref ) : ?><div class="fa-ksef">Numer KSeF: <?php echo esc_html( $ksef_ref ); ?></div><?php endif; ?>
	</div>
</div>
<hr>

<div class="two">
	<div>
		<h2>Sprzedawca</h2>
		<div><span class="b">NIP:</span> <?php echo esc_html( (string) ( $seller['nip'] ?? '' ) ); ?></div>
		<div><span class="b">Nazwa:</span> <?php echo esc_html( (string) ( $seller['name'] ?? '' ) ); ?></div>
		<div style="margin-top:8px;" class="b">Adres</div>
		<div><?php echo esc_html( trim( (string) ( $saddr['street'] ?? '' ) . ' ' . (string) ( $saddr['street_number'] ?? '' ) ) ); ?></div>
		<div><?php echo esc_html( trim( (string) ( $saddr['postal_code'] ?? '' ) . ' ' . (string) ( $saddr['city'] ?? '' ) ) ); ?></div>
		<div><?php echo esc_html( ( ! empty( $saddr['country'] ) && 'PL' !== $saddr['country'] ) ? (string) $saddr['country'] : 'Polska' ); ?></div>
	</div>
	<div>
		<h2>Nabywca</h2>
		<?php if ( ! empty( $buyer['nip'] ) ) : ?><div><span class="b">NIP:</span> <?php echo esc_html( (string) $buyer['nip'] ); ?></div>
		<?php elseif ( ! empty( $buyer['vat_ue'] ) ) : ?><div><span class="b">VAT-UE:</span> <?php echo esc_html( (string) ( $buyer['country_ue'] ?? '' ) . ' ' . (string) $buyer['vat_ue'] ); ?></div><?php endif; ?>
		<div><span class="b">Nazwa:</span> <?php echo esc_html( (string) ( $buyer['name'] ?? '' ) ); ?></div>
		<div style="margin-top:8px;" class="b">Adres</div>
		<div><?php echo esc_html( trim( (string) ( $baddr['street'] ?? '' ) . ' ' . (string) ( $baddr['street_number'] ?? '' ) ) ); ?></div>
		<div><?php echo esc_html( trim( (string) ( $baddr['postal_code'] ?? '' ) . ' ' . (string) ( $baddr['city'] ?? '' ) ) ); ?></div>
		<div><?php echo esc_html( ( ! empty( $baddr['country'] ) && 'PL' !== $baddr['country'] ) ? (string) $baddr['country'] : 'Polska' ); ?></div>
		<div style="margin-top:8px;">Faktura dotyczy jednostki podrzędnej JST: <?php echo ! empty( $buyer['jst'] ) ? 'TAK' : 'NIE'; ?></div>
		<div>Faktura dotyczy członka grupy GV: <?php echo ! empty( $buyer['gv'] ) ? 'TAK' : 'NIE'; ?></div>
	</div>
</div>
<hr>

<h2>Szczegóły</h2>
<div class="two">
	<div>
		<div><span class="lbl">Data wystawienia, z zastrzeżeniem art. 106na ust. 1 ustawy:</span> <?php echo esc_html( $fmt_date( $issue_date ) ); ?></div>
		<div style="margin-top:4px;"><span class="lbl">Data dokonania lub zakończenia dostawy towarów lub wykonania usługi:</span> <?php echo esc_html( $fmt_date( $sale_date ) ); ?></div>
	</div>
	<div>
		<?php if ( ! empty( $extra['issue_place'] ) ) : ?><div><span class="lbl">Miejsce wystawienia:</span> <?php echo esc_html( (string) $extra['issue_place'] ); ?></div><?php endif; ?>
		<div style="margin-top:4px;"><span class="lbl">Kod waluty:</span> <?php echo esc_html( $currency ); ?></div>
	</div>
</div>
<hr>

<h2>Pozycje</h2>
<div style="margin-bottom:6px;">Faktura wystawiona w walucie <?php echo esc_html( $currency ); ?></div>
<table class="t">
	<thead><tr>
		<th>Lp.</th><th>Nazwa towaru lub usługi</th><th>PKWiU/CN</th><th class="r">Cena jedn. netto</th><th class="r">Ilość</th><th>Miara</th><th>Stawka</th><th class="r">Rabat</th><th class="r">Wartość netto</th><th class="r">Wartość brutto</th><th>GTU</th>
	</tr></thead>
	<tbody>
	<?php foreach ( $items as $i => $item ) : ?>
		<tr>
			<td><?php echo (int) ( $i + 1 ); ?></td>
			<td><?php echo esc_html( (string) ( $item['description'] ?? '' ) ); ?></td>
			<td><?php echo esc_html( '' !== (string) ( $item['pkwiu'] ?? '' ) ? (string) $item['pkwiu'] : '—' ); ?></td>
			<td class="r"><?php echo esc_html( $price( $item['unit_price_net'] ?? 0 ) ); ?></td>
			<td class="r"><?php echo esc_html( rtrim( rtrim( number_format( (float) ( $item['quantity'] ?? 0 ), 4, ',', ' ' ), '0' ), ',' ) ); ?></td>
			<td><?php echo esc_html( (string) ( $item['unit'] ?? 'szt.' ) ); ?></td>
			<td><?php echo esc_html( self::rate_short( $item['vat_rate'] ?? 23 ) ); ?></td>
			<td class="r"><?php $dp = (float) ( $item['discount_pct'] ?? 0 ); echo $dp > 0 ? esc_html( rtrim( rtrim( number_format( $dp, 2, ',', '' ), '0' ), ',' ) . '%' ) : '—'; ?></td>
			<td class="r"><?php echo esc_html( $money( $item['line_net'] ?? 0 ) ); ?></td>
			<td class="r"><?php echo esc_html( $money( $gross_of( $item ) ) ); ?></td>
			<td><?php echo esc_html( '' !== (string) ( $item['gtu'] ?? '' ) ? (string) $item['gtu'] : '—' ); ?></td>
		</tr>
	<?php endforeach; ?>
	</tbody>
</table>

<?php
// Podsumowanie VAT — liczymy z POZYCJI (wszystkie stawki, w groszach), żeby było kompletne i
// spójne z tabelą pozycji. payload['vat_summary'] dla faktur z WC bywał niepełny (brak dostawy/opłat).
$vs_calc = [];
$sum_net_g = 0;
$sum_vat_g = 0;
foreach ( $items as $vi ) {
	$rr = $vi['vat_rate'] ?? 0;
	$rk = is_numeric( $rr ) ? (string) (int) round( (float) $rr ) : (string) $rr;
	if ( ! isset( $vs_calc[ $rk ] ) ) {
		$vs_calc[ $rk ] = [ 'rate' => is_numeric( $rr ) ? (float) $rr : $rr, 'net_g' => 0, 'vat_g' => 0 ];
	}
	$ng = (int) round( (float) ( $vi['line_net'] ?? 0 ) * 100 );
	$gg = (int) round( $gross_of( $vi ) * 100 );
	$vs_calc[ $rk ]['net_g'] += $ng;
	$vs_calc[ $rk ]['vat_g'] += ( $gg - $ng );
	$sum_net_g              += $ng;
	$sum_vat_g              += ( $gg - $ng );
}
$vatSum = [];
foreach ( $vs_calc as $b ) {
	$vatSum[] = [ 'rate' => $b['rate'], 'base_net' => $b['net_g'] / 100, 'vat_amount' => $b['vat_g'] / 100 ];
}
$pdf_total_gross = ( $sum_net_g + $sum_vat_g ) / 100;
?>
<div class="tot">Kwota należności ogółem: <?php echo esc_html( $money( $pdf_total_gross ) ); ?> <?php echo esc_html( $currency ); ?></div>

<h2>Podsumowanie stawek podatku</h2>
<table class="t">
	<thead><tr><th>Lp.</th><th>Stawka</th><th class="r">Kwota netto</th><th class="r">Kwota VAT</th><th class="r">Kwota brutto</th></tr></thead>
	<tbody>
	<?php foreach ( $vatSum as $j => $v ) : ?>
		<tr>
			<td><?php echo (int) ( $j + 1 ); ?></td>
			<td><?php echo esc_html( self::ksef_rate_label( $v['rate'] ?? '' ) ); ?></td>
			<td class="r"><?php echo esc_html( $money( $v['base_net'] ?? 0 ) ); ?></td>
			<td class="r"><?php echo esc_html( $money( $v['vat_amount'] ?? 0 ) ); ?></td>
			<td class="r"><?php echo esc_html( $money( (float) ( $v['base_net'] ?? 0 ) + (float) ( $v['vat_amount'] ?? 0 ) ) ); ?></td>
		</tr>
	<?php endforeach; ?>
		<tr style="font-weight:bold;border-top:2px solid #333;">
			<td></td><td>Razem</td>
			<td class="r"><?php echo esc_html( $money( $sum_net_g / 100 ) ); ?></td>
			<td class="r"><?php echo esc_html( $money( $sum_vat_g / 100 ) ); ?></td>
			<td class="r"><?php echo esc_html( $money( ( $sum_net_g + $sum_vat_g ) / 100 ) ); ?></td>
		</tr>
	</tbody>
</table>

<h2 style="margin-top:20px;">Płatność</h2>
<div>Informacja o płatności: <?php echo $paid ? 'Zapłacono' : 'Do zapłaty'; ?></div>
<?php if ( $paid && '' !== $paid_date ) : ?><div>Data zapłaty: <?php echo esc_html( $fmt_date( $paid_date ) ); ?></div><?php endif; ?>
<div>Forma płatności: <?php echo esc_html( self::payment_form_label( (string) ( $payload['payment']['form'] ?? '6' ) ) ); ?></div>
<?php if ( ! empty( $payload['payment']['due_date'] ) ) : ?>
<table class="t" style="max-width:360px;">
	<thead><tr><th>Termin płatności</th></tr></thead>
	<tbody><tr><td><?php echo esc_html( $fmt_date( $payload['payment']['due_date'] ) ); ?></td></tr></tbody>
</table>
<?php endif; ?>
<?php if ( ! empty( $payments ) && is_array( $payments ) ) : ?>
<div style="margin-top:8px;" class="lbl">Zarejestrowane wpłaty</div>
<table class="t" style="max-width:420px;">
	<thead><tr><th>Data</th><th>Forma</th><th class="r">Kwota</th></tr></thead>
	<tbody>
	<?php $psum = 0.0; foreach ( $payments as $ppay ) : $psum += (float) ( $ppay['amount'] ?? 0 ); ?>
		<tr><td><?php echo esc_html( $fmt_date( $ppay['payment_date'] ?? '' ) ); ?></td><td><?php echo esc_html( (string) ( $ppay['payment_method'] ?? '' ) ); ?></td><td class="r"><?php echo esc_html( $money( $ppay['amount'] ?? 0 ) ); ?></td></tr>
	<?php endforeach; ?>
	</tbody>
	<tfoot><tr><td colspan="2" class="r b">Razem wpłacono:</td><td class="r b"><?php echo esc_html( $money( $psum ) ); ?></td></tr></tfoot>
</table>
<?php endif; ?>

<?php if ( is_array( $bank ) && ! empty( $bank['account_number'] ) ) : ?>
<h2 style="margin-top:20px;">Numer rachunku bankowego</h2>
<table class="kv">
	<tr><td class="k">Pełny numer rachunku</td><td><?php echo esc_html( (string) $bank['account_number'] ); ?></td></tr>
	<tr><td class="k">Kod SWIFT</td><td><?php echo esc_html( (string) ( $bank['swift'] ?? '' ) ); ?></td></tr>
	<tr><td class="k">Nazwa banku</td><td><?php echo esc_html( (string) ( $bank['bank_name'] ?? '' ) ); ?></td></tr>
	<tr><td class="k">Opis rachunku</td><td><?php echo esc_html( (string) ( $bank['description'] ?? '' ) ); ?></td></tr>
</table>
<?php endif; ?>

<?php if ( ! empty( $rel['order']['number'] ) || ! empty( $rel['wz']['number'] ) ) : ?>
<h2 style="margin-top:20px;">Warunki transakcji</h2>
<?php if ( ! empty( $rel['order']['number'] ) ) : ?>
<div class="lbl" style="margin-bottom:2px;">Zamówienie</div>
<table class="t" style="max-width:520px;margin-bottom:12px;">
	<thead><tr><th>Data zamówienia</th><th>Numer zamówienia</th></tr></thead>
	<tbody><tr><td><?php echo esc_html( $fmt_date( $rel['order']['date'] ?? '' ) ); ?></td><td><?php echo esc_html( (string) $rel['order']['number'] ); ?></td></tr></tbody>
</table>
<?php endif; ?>
<?php if ( ! empty( $rel['wz']['number'] ) ) : ?>
<div class="lbl" style="margin-bottom:2px;">Numery dokumentów magazynowych WZ</div>
<table class="t" style="max-width:340px;">
	<thead><tr><th>Numer WZ</th></tr></thead>
	<tbody><tr><td><?php echo esc_html( (string) $rel['wz']['number'] ); ?></td></tr></tbody>
</table>
<?php endif; ?>
<?php endif; ?>

<?php
$marker_labels = [
	'FP' => 'Faktura do paragonu (FP)',
	'P_16' => 'Metoda kasowa (P_16)',
	'P_17' => 'Samofakturowanie (P_17)',
	'P_18' => 'Odwrotne obciążenie (P_18)',
	'P_18A' => 'Mechanizm podzielonej płatności (P_18A)',
	'P_19' => 'Zwolnienie z VAT (P_19)',
	'P_22' => 'WDT nowych środków transportu (P_22)',
	'P_23' => 'Procedura uproszczona WTT (P_23)',
	'TP' => 'Powiązania między stronami (TP)',
	'PMarzy' => 'Procedura marży',
	'OSS' => 'Procedura OSS (WSTO_EE)',
	'fa_za_okres' => 'Faktura za okres',
];
$active_markers = [];
foreach ( $marker_labels as $mk_code => $mk_label ) {
	if ( ! empty( $markers[ $mk_code ] ) && '2' !== (string) $markers[ $mk_code ] ) { $active_markers[] = $mk_label; }
}
// Pod-szczegóły oznaczeń: podstawa zwolnienia (P_19), rodzaj marży (PMarzy), nowy środek transportu (P_22).
$adn         = is_array( $payload['adnotacje'] ?? null ) ? $payload['adnotacje'] : [];
$adn_pj      = is_array( $pj['adnotacje'] ?? null ) ? $pj['adnotacje'] : [];
$nst_data    = ( is_array( $payload['nst'] ?? null ) && ! empty( $payload['nst'] ) ) ? $payload['nst'] : ( is_array( $pj['nst'] ?? null ) ? $pj['nst'] : [] );
$detail_rows = [];
$p19_basis   = (string) ( $adn_pj['P_19_basis'] ?? '' );
$p19_kind    = strtoupper( (string) ( $adn_pj['P_19_kind'] ?? '' ) );
if ( '' === $p19_basis && is_array( $adn['Zwolnienie'] ?? null ) ) {
	foreach ( [ 'A', 'B', 'C' ] as $bk ) {
		if ( '' !== (string) ( $adn['Zwolnienie'][ 'P_19' . $bk ] ?? '' ) ) { $p19_basis = (string) $adn['Zwolnienie'][ 'P_19' . $bk ]; $p19_kind = $bk; break; }
	}
}
if ( '' !== $p19_basis ) {
	$detail_rows[] = [ 'Podstawa zwolnienia z VAT' . ( '' !== $p19_kind ? ' (P_19' . $p19_kind . ')' : '' ), $p19_basis ];
}
$pmarzy_kind = strtolower( trim( (string) ( $adn_pj['P_PMarzy'] ?? '' ) ) );
if ( '' === $pmarzy_kind && is_array( $adn['PMarzy'] ?? null ) ) {
	if ( '1' === (string) ( $adn['PMarzy']['P_PMarzy_2'] ?? '' ) ) { $pmarzy_kind = 'turystyka'; } elseif ( '1' === (string) ( $adn['PMarzy']['P_PMarzy_3'] ?? '' ) ) { $pmarzy_kind = 'uzywane'; }
}
$pmarzy_labels = [
	'turystyka' => 'Procedura marży dla biur podróży (art. 119)', 'biuro' => 'Procedura marży dla biur podróży (art. 119)', 'biuro_podrozy' => 'Procedura marży dla biur podróży (art. 119)',
	'uzywane' => 'Procedura marży — towary używane (art. 120)', 'towary_uzywane' => 'Procedura marży — towary używane (art. 120)',
	'sztuka' => 'Procedura marży — dzieła sztuki (art. 120)', 'dziela_sztuki' => 'Procedura marży — dzieła sztuki (art. 120)',
	'kolekcjonerskie' => 'Procedura marży — przedmioty kolekcjonerskie i antyki (art. 120)', 'antyki' => 'Procedura marży — przedmioty kolekcjonerskie i antyki (art. 120)',
];
if ( '' !== $pmarzy_kind ) {
	$detail_rows[] = [ 'Rodzaj procedury marży', $pmarzy_labels[ $pmarzy_kind ] ?? $pmarzy_kind ];
}
$nst_d = (string) ( $nst_data['data_dopuszczenia'] ?? $nst_data['P_22A'] ?? '' );
if ( '' !== $nst_d ) {
	$nst_parts = [ 'data dopuszczenia: ' . $nst_d ];
	$nst_m = (string) ( $nst_data['przebieg'] ?? $nst_data['P_22B'] ?? '' );
	if ( '' !== $nst_m ) { $nst_parts[] = 'przebieg/godziny pracy: ' . $nst_m; }
	$nst_v = (string) ( $nst_data['vin'] ?? '' );
	if ( '' !== $nst_v ) { $nst_parts[] = 'VIN/nr: ' . $nst_v; }
	$detail_rows[] = [ 'WDT — nowy środek transportu', implode( ', ', $nst_parts ) ];
}
if ( ! empty( $active_markers ) || ! empty( $detail_rows ) ) : ?>
<h2 style="margin-top:20px;">Oznaczenia faktury (FA(3))</h2>
<table class="kv">
	<?php foreach ( $active_markers as $am ) : ?>
	<tr><td class="k"><?php echo esc_html( $am ); ?></td><td>Tak</td></tr>
	<?php endforeach; ?>
	<?php foreach ( $detail_rows as $dr ) : ?>
	<tr><td class="k"><?php echo esc_html( $dr[0] ); ?></td><td><?php echo esc_html( $dr[1] ); ?></td></tr>
	<?php endforeach; ?>
</table>
<?php endif; ?>

<?php if ( '' !== $period_from || '' !== $period_to ) : ?>
<h2 style="margin-top:20px;">Faktura za okres</h2>
<div><span class="lbl">Od:</span> <?php echo esc_html( $fmt_date( $period_from ) ); ?> &nbsp;&nbsp; <span class="lbl">Do:</span> <?php echo esc_html( $fmt_date( $period_to ) ); ?></div>
<?php endif; ?>

<?php if ( $deposit_amount > 0 ) : ?>
<h2 style="margin-top:20px;">Kaucja / opakowania zwrotne</h2>
<div><?php echo esc_html( '' !== $deposit_label ? $deposit_label : 'Kaucja' ); ?>: <span class="b"><?php echo esc_html( $money( $deposit_amount ) . ' ' . $currency ); ?></span> <span class="lbl">(nie podlega VAT — doliczana do kwoty)</span></div>
<?php endif; ?>

<?php if ( is_array( $del ) && ( ! empty( $del['name'] ) || ! empty( $del['street'] ) || ! empty( $del['city'] ) ) ) : ?>
<h2 style="margin-top:20px;">Adres dostawy</h2>
<?php if ( ! empty( $del['name'] ) ) : ?><div class="b"><?php echo esc_html( (string) $del['name'] ); ?></div><?php endif; ?>
<div><?php echo esc_html( trim( (string) ( $del['street'] ?? '' ) ) ); ?></div>
<div><?php echo esc_html( trim( (string) ( $del['postal_code'] ?? '' ) . ' ' . (string) ( $del['city'] ?? '' ) ) ); ?></div>
<?php if ( ! empty( $del['country'] ) && 'PL' !== $del['country'] ) : ?><div><?php echo esc_html( (string) $del['country'] ); ?></div><?php endif; ?>
<?php endif; ?>

<?php if ( '' !== $seller_email || '' !== $seller_phone || '' !== $customer_no || '' !== $buyer_email ) : ?>
<h2 style="margin-top:20px;">Dane dodatkowe</h2>
<?php if ( '' !== $seller_email ) : ?><div><span class="lbl">Email sprzedawcy:</span> <?php echo esc_html( $seller_email ); ?></div><?php endif; ?>
<?php if ( '' !== $seller_phone ) : ?><div><span class="lbl">Telefon sprzedawcy:</span> <?php echo esc_html( $seller_phone ); ?></div><?php endif; ?>
<?php if ( '' !== $customer_no ) : ?><div><span class="lbl">Numer klienta:</span> <?php echo esc_html( $customer_no ); ?></div><?php endif; ?>
<?php if ( '' !== $buyer_email ) : ?><div><span class="lbl">Email nabywcy:</span> <?php echo esc_html( $buyer_email ); ?></div><?php endif; ?>
<?php endif; ?>

<?php
$kn_rows = [];
if ( is_array( $ksef_notes ) ) {
	foreach ( $ksef_notes as $kv ) {
		$kn_k = trim( (string) ( $kv['key'] ?? '' ) ); $kn_v = trim( (string) ( $kv['value'] ?? '' ) );
		if ( '' !== $kn_k || '' !== $kn_v ) { $kn_rows[] = [ $kn_k, $kn_v ]; }
	}
}
if ( ! empty( $kn_rows ) ) : ?>
<h2 style="margin-top:20px;">Dodatkowy opis (Notatki KSeF)</h2>
<table class="kv">
	<?php foreach ( $kn_rows as $kr ) : ?>
	<tr><td class="k"><?php echo esc_html( $kr[0] ); ?></td><td><?php echo esc_html( $kr[1] ); ?></td></tr>
	<?php endforeach; ?>
</table>
<?php endif; ?>

<?php if ( '' !== $qr_url ) : ?>
<div style="page-break-inside:avoid; margin-top:18px;">
<h2 style="margin-top:0;">Sprawdź, czy Twoja faktura znajduje się w KSeF!</h2>
<div class="qr">
	<div style="width:170px;">
		<img src="<?php echo esc_attr( \KSEF\WordPress\V2\QR_Encoder::png_data_uri( $qr_url, 6 ) ); ?>" width="150" height="150" alt="QR KSeF" style="border:1px solid #eee;">
		<div style="font-size:9px;word-break:break-all;margin-top:4px;"><?php echo esc_html( '' !== $ksef_ref ? $ksef_ref : 'OFFLINE' ); ?></div>
	</div>
	<div style="padding-left:22px;">
		<div>Nie możesz zeskanować kodu z obrazka? Kliknij w link weryfikacyjny i przejdź do weryfikacji faktury!</div>
		<div style="margin-top:8px;word-break:break-all;"><a href="<?php echo esc_url( $qr_url ); ?>"><?php echo esc_html( $qr_url ); ?></a></div>
	</div>
</div>
</div>
<?php endif; ?>

<div class="foot"><span class="b">Wytworzona w:</span> KSeF e-Faktury — <a href="https://prostafakturaksef.pl" style="color:inherit;text-decoration:none;">prostafakturaksef.pl</a> — wizualizacja faktury zgodna ze wzorem Krajowego Systemu e-Faktur (KSeF).</div>

</body>
</html>
		<?php
		return (string) ob_get_clean();
	}

	private static function payment_form_label( string $code ): string {
		$map = [
			// FA(3) TFormaPlatnosci 1–7 + legacy (9/10/99 ze starszych zapisów).
			'1' => 'Gotówka', '2' => 'Karta', '3' => 'Bon', '4' => 'Czek', '5' => 'Kredyt', '6' => 'Przelew', '7' => 'Płatność mobilna (BLIK)',
			'9' => 'Bon', '10' => 'Czek', '99' => 'Inna',
		];
		return $map[ $code ] ?? ( '' !== $code ? ( 'Forma ' . $code ) : '' );
	}
}
