<?php

namespace Devikit\Wfirma\Api;

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

class Client {

	/**
	 * wFirma API URL
	 */
	const API_URL = 'https://api2.wfirma.pl/';

	/**
	 * wFirma App Key (static application identifier)
	 */
	const APP_KEY = '9430c0eea27eac8101c89db28dead70a';

	/**
	 * Access Key
	 * 
	 * @var string
	 */
	private $access_key;

	/**
	 * Secret Key
	 * 
	 * @var string
	 */
	private $secret_key;

	/**
	 * Constructor
	 * 
	 * @param string $access_key wFirma Access Key
	 * @param string $secret_key wFirma Secret Key
	 */
	public function __construct( $access_key, $secret_key ) {
		$this->access_key = $access_key;
		$this->secret_key = $secret_key;
	}

	/**
	 * Get headers for API requests
	 * 
	 * @return array
	 */
	private function get_headers() {
		return [
			'accessKey' => $this->access_key,
			'secretKey' => $this->secret_key,
			'appKey'    => self::APP_KEY,
		];
	}

	/**
	 * Test connection to wFirma API
	 * 
	 * @return bool
	 */
	public function test_connection() {
		try {
			$response = $this->get( 'users/get' );
			return ! is_wp_error( $response );
		} catch ( \Exception $e ) {
			return false;
		}
	}

	/**
	 * Get contractor by NIP (tax ID)
	 * 
	 * @param string $nip Tax ID (NIP)
	 * @return array|null
	 */
	public function get_contractor_by_nip( $nip ) {
		$params = [
			'contractors' => [
				'parameters' => [
					'conditions' => [
						'condition' => [
							'field'    => 'nip',
							'operator' => 'eq',
							'value'    => $nip,
						],
					],
					'limit' => 1,
				],
			],
		];

		$response = $this->post( 'contractors/find', $params );

		if ( is_wp_error( $response ) ) {
			$this->log( 'get_contractor_by_nip ERROR: ' . $response->get_error_message(), 'error' );
			return null;
		}

		if ( ! empty( $response['contractors'] ) && is_array( $response['contractors'] ) ) {
			$first_contractor = reset( $response['contractors'] );
			
			// Handle nested structure: contractor might be inside ['contractor']
			if ( isset( $first_contractor['contractor'] ) ) {
				return $first_contractor['contractor'];
			}
			
			return $first_contractor;
		}

		return null;
	}

	/**
	 * Find contractor by multiple fields
	 * 
	 * @param array $contractor Contractor data with name, street, zip, city, tax_id_type, and optionally nip
	 * @return array|null Contractor data or null if not found
	 */
	public function contractors_find( $contractor ) {
		$params = [
			'contractors' => [
				'parameters' => [
					'conditions' => [
						'1' => [
							'condition' => [
								'field'    => 'name',
								'operator' => 'eq',
								'value'    => $contractor['name'],
							],
						],
						'2' => [
							'condition' => [
								'field'    => 'street',
								'operator' => 'eq',
								'value'    => $contractor['street'],
							],
						],
						'3' => [
							'condition' => [
								'field'    => 'zip',
								'operator' => 'eq',
								'value'    => $contractor['zip'],
							],
						],
						'4' => [
							'condition' => [
								'field'    => 'city',
								'operator' => 'eq',
								'value'    => $contractor['city'],
							],
						],
						'5' => [
							'condition' => [
								'field'    => 'tax_id_type',
								'operator' => 'eq',
								'value'    => $contractor['tax_id_type'],
							],
						],
					],
					'limit' => 1,
				],
			],
		];

		// If tax_id_type is 'nip', also check NIP
		if ( isset( $contractor['tax_id_type'] ) && $contractor['tax_id_type'] === 'nip' && ! empty( $contractor['nip'] ) ) {
			$params['contractors']['parameters']['conditions']['6'] = [
				'condition' => [
					'field'    => 'nip',
					'operator' => 'eq',
					'value'    => str_replace( '-', '', $contractor['nip'] ),
				],
			];
		}

		$response = $this->post( 'contractors/find', $params );

		if ( is_wp_error( $response ) ) {
			$this->log( 'contractors_find ERROR: ' . $response->get_error_message(), 'error' );
			return null;
		}

		if ( ! empty( $response['contractors'] ) && is_array( $response['contractors'] ) ) {
			$first_contractor = reset( $response['contractors'] );
			
			// Handle nested structure: contractor might be inside ['contractor']
			if ( isset( $first_contractor['contractor'] ) ) {
				return $first_contractor['contractor'];
			}
			
			return $first_contractor;
		}

		return null;
	}

	/**
	 * Create contractor
	 * 
	 * @param array $data Contractor data
	 * @return array|WP_Error
	 */
	public function create_contractor( $data ) {
		$params = [
			'contractors' => [
				'contractor' => $data,
			],
		];

		return $this->post( 'contractors/add', $params );
	}

	/**
	 * Update contractor
	 * 
	 * @param int $contractor_id Contractor ID
	 * @param array $data Contractor data
	 * @return array|WP_Error
	 */
	public function update_contractor( $contractor_id, $data ) {
		$this->log( 'Updating contractor ID: ' . $contractor_id . ' with data: ' . wp_json_encode( $data, JSON_UNESCAPED_UNICODE ), 'debug' );
		
		$params = [
			'contractors' => [
				array_merge( [ 'id' => $contractor_id ], $data ),
			],
		];

		$result = $this->post( 'contractors/edit', $params );
		
		if ( is_wp_error( $result ) ) {
			$this->log( 'Contractor update ERROR: ' . $result->get_error_message(), 'error' );
		} else {
			$this->log( 'Contractor update SUCCESS', 'debug' );
		}
		
		return $result;
	}

	/**
	 * Create invoice
	 * 
	 * @param array $data Invoice data
	 * @return array|WP_Error
	 */
	public function create_invoice( $data ) {
		$invoice = $this->normalize_invoice_for_api( $data );
		if ( is_wp_error( $invoice ) ) {
			return $invoice;
		}

		$params = [
			'invoices' => [
				'invoice' => $invoice,
			],
		];

		$this->log( 'Creating invoice with data: ' . wp_json_encode( $params, JSON_UNESCAPED_UNICODE ), 'debug' );

		return $this->post( 'invoices/add', $params );
	}

	/**
	 * Create receipt (paragon)
	 * 
	 * @param array $data Receipt data
	 * @return array|WP_Error
	 */
	public function create_receipt( $data ) {
		// Receipt is a special type of invoice in wFirma
		// Use type from data if provided (e.g., receipt_fiscal_normal, receipt_fiscal_bill)
		// Otherwise default to 'paragon' for backward compatibility
		$receipt_data = $data;
		if ( ! isset( $receipt_data['type'] ) ) {
			$receipt_data['type'] = 'paragon';
		}

		$invoice = $this->normalize_invoice_for_api( $receipt_data );
		if ( is_wp_error( $invoice ) ) {
			return $invoice;
		}

		$params = [
			'invoices' => [
				'invoice' => $invoice,
			],
		];

		$this->log( 'Creating receipt with data: ' . wp_json_encode( $params, JSON_UNESCAPED_UNICODE ), 'debug' );

		return $this->post( 'invoices/add', $params );
	}

	/**
	 * Normalize invoice payload to wFirma API structure.
	 *
	 * We keep internal data formats flexible (snake_case, simplified invoicecontents),
	 * but we send a strict payload expected by wFirma API.
	 *
	 * @param array $data
	 * @return array|\WP_Error
	 */
	private function normalize_invoice_for_api( $data ) {
		if ( ! is_array( $data ) ) {
			return new \WP_Error( 'wfirma_invoice_invalid', __( 'Invalid invoice payload (not an array).', 'invoicing-integration-for-wfirma-and-woocommerce' ) );
		}

		$invoice = $data;

		$moss_country_iso = null;
		if ( ! empty( $invoice['moss'] ) && (int) $invoice['moss'] === 1 ) {
			if ( ! empty( $invoice['moss_country_code'] ) ) {
				$moss_country_iso = strtoupper( sanitize_text_field( (string) $invoice['moss_country_code'] ) );
			}
			unset( $invoice['moss_country_code'] );
		}

		// Map contractor_id -> contractor[id].
		if ( isset( $invoice['contractor_id'] ) && ! isset( $invoice['contractor'] ) ) {
			$invoice['contractor'] = [ 'id' => absint( $invoice['contractor_id'] ) ];
			unset( $invoice['contractor_id'] );
		}

		// Map common snake_case keys to wFirma API keys.
		if ( isset( $invoice['invoice_type'] ) && ! isset( $invoice['type'] ) ) {
			$invoice['type'] = $invoice['invoice_type'];
			unset( $invoice['invoice_type'] );
		}
		if ( isset( $invoice['payment_method'] ) && ! isset( $invoice['paymentmethod'] ) ) {
			$invoice['paymentmethod'] = $invoice['payment_method'];
			unset( $invoice['payment_method'] );
		}
		if ( isset( $invoice['payment_date'] ) && ! isset( $invoice['paymentdate'] ) ) {
			$invoice['paymentdate'] = $invoice['payment_date'];
			unset( $invoice['payment_date'] );
		}
		if ( isset( $invoice['disposal_date'] ) && ! isset( $invoice['disposaldate'] ) ) {
			$invoice['disposaldate'] = $invoice['disposal_date'];
			unset( $invoice['disposal_date'] );
		}

		// Map dekretacja/classification to a more explicit key if possible.
		// Keep backward compatibility: if classification exists, map to ledger_operation_schema_id.
		if ( isset( $invoice['classification'] ) && ! isset( $invoice['ledger_operation_schema_id'] ) ) {
			$invoice['ledger_operation_schema_id'] = absint( $invoice['classification'] );
			unset( $invoice['classification'] );
		}
		
		// Map company_account_id -> company_account{id}
		if ( isset( $invoice['company_account_id'] ) && ! isset( $invoice['company_account'] ) ) {
			$invoice['company_account'] = [ 'id' => absint( $invoice['company_account_id'] ) ];
			unset( $invoice['company_account_id'] );
		}

		// Convert goods to invoicecontents if needed (for receipts)
		if ( isset( $invoice['goods'] ) && is_array( $invoice['goods'] ) && ! isset( $invoice['invoicecontents'] ) ) {
			$invoice['invoicecontents'] = [];
			foreach ( $invoice['goods'] as $good ) {
				$item = [
					'name' => $good['name'] ?? '',
					'count' => isset( $good['quantity'] ) ? (float) $good['quantity'] : ( isset( $good['count'] ) ? (float) $good['count'] : 1 ),
					'unit' => $good['unit'] ?? 'szt.',
					'price' => isset( $good['price_net'] ) ? number_format( (float) $good['price_net'], 2, '.', '' ) : ( isset( $good['price'] ) ? number_format( (float) $good['price'], 2, '.', '' ) : '0.00' ),
				];
				
				// Handle VAT
				if ( isset( $good['vat'] ) ) {
					$vat_symbol = $good['vat'];
					$vat_code_id = $this->resolve_vat_code_id_from_symbol( $vat_symbol, $moss_country_iso );
					if ( is_wp_error( $vat_code_id ) ) {
						return $vat_code_id;
					}
					if ( $vat_code_id ) {
						$item['vat_code'] = [ 'id' => $vat_code_id ];
					}
				} elseif ( isset( $good['vat_code'] ) ) {
					$item['vat_code'] = $good['vat_code'];
				}
				
				// Copy good mapping if present (for warehouse integration)
				if ( isset( $good['good'] ) && is_array( $good['good'] ) ) {
					$item['good'] = $good['good'];
				}
				
				// Copy lump sum code if present
				if ( isset( $good['lumpcode'] ) && ! empty( $good['lumpcode'] ) ) {
					$item['lumpcode'] = $good['lumpcode'];
				}
				
				$invoice['invoicecontents'][] = [ 'invoicecontent' => $item ];
			}
			unset( $invoice['goods'] );
		}

		// Normalize invoicecontents to expected nested structure.
		if ( isset( $invoice['invoicecontents'] ) && is_array( $invoice['invoicecontents'] ) ) {
			$normalized_contents = [];

			foreach ( $invoice['invoicecontents'] as $row ) {
				// Allow already-normalized structure.
				if ( is_array( $row ) && isset( $row['invoicecontent'] ) && is_array( $row['invoicecontent'] ) ) {
					$normalized_contents[] = $row;
					continue;
				}

				if ( ! is_array( $row ) ) {
					continue;
				}

				$item = $row;

				// Normalize numeric fields.
				if ( isset( $item['count'] ) ) {
					$item['count'] = (float) $item['count'];
				}
				if ( isset( $item['price'] ) ) {
					// Send as string with 2 decimals to avoid float precision issues in JSON encoding.
					$item['price'] = number_format( (float) $item['price'], 2, '.', '' );
				}

				// Normalize VAT: prefer vat_code[id]. If we got a simplified "vat" symbol, resolve it.
				if ( ! isset( $item['vat_code'] ) ) {
					$vat_symbol = $item['vat'] ?? null;
					$vat_code_id = $this->resolve_vat_code_id_from_symbol( $vat_symbol, $moss_country_iso );
					if ( is_wp_error( $vat_code_id ) ) {
						return $vat_code_id;
					}
					if ( $vat_code_id ) {
						$item['vat_code'] = [ 'id' => $vat_code_id ];
					}
					unset( $item['vat'] );
				}

				$normalized_contents[] = [ 'invoicecontent' => $item ];
			}

			$invoice['invoicecontents'] = $normalized_contents;
		}

		return $invoice;
	}

	/**
	 * Resolve wFirma vat_code id from a simplified VAT symbol (e.g. "23", "8", "5", "0", "zw").
	 *
	 * @param mixed       $vat_symbol       VAT rate/code from invoice payload.
	 * @param string|null $moss_country_iso Billing country ISO code for OSS/MOSS invoices.
	 * @return int|\WP_Error
	 */
	private function resolve_vat_code_id_from_symbol( $vat_symbol, $moss_country_iso = null ) {
		// No VAT symbol provided - let API decide (but usually it will error).
		if ( $vat_symbol === null || $vat_symbol === '' ) {
			return 0;
		}

		$vat_symbol = is_string( $vat_symbol ) ? trim( $vat_symbol ) : $vat_symbol;

		$rate = 23;
		$zw = false;
		$eu = false;
		$np = false; // "np" - nie podlega (not subject to VAT)

		if ( is_numeric( $vat_symbol ) ) {
			$rate = $this->normalize_vat_rate_value( $vat_symbol );
		} elseif ( $vat_symbol === 'zw' || $vat_symbol === 'ZW' ) {
			$rate = 0;
			$zw = true;
		} elseif ( $vat_symbol === '0' ) {
			$rate = 0;
		} elseif ( $vat_symbol === 'np' || $vat_symbol === 'NP' ) {
			$rate = 0;
			$np = true; // "np" - nie podlega, not WDT
		}

		// OSS/MOSS: use destination country VAT code from wFirma (not Polish 23%).
		if ( ! empty( $moss_country_iso ) && is_numeric( $vat_symbol ) && ! $zw && ! $np ) {
			$country_id = $this->get_declaration_country_id_by_iso( $moss_country_iso );
			if ( is_wp_error( $country_id ) ) {
				return $country_id;
			}

			if ( $country_id > 0 ) {
				$this->log(
					sprintf(
						'Looking for MOSS VAT code: country=%s, country_id=%d, rate=%s',
						$moss_country_iso,
						$country_id,
						(string) $rate
					),
					'debug'
				);

				$response = $this->get_vat_codes( $country_id, $rate, false, false, false, false );
				if ( is_wp_error( $response ) ) {
					$this->log( 'MOSS VAT codes API error: ' . $response->get_error_message(), 'error' );
					return $response;
				}

				$vat_code_id = $this->extract_vat_code_id_from_response( $response, $rate );
				if ( $vat_code_id ) {
					$this->log(
						sprintf(
							'Found MOSS VAT code ID: %d for country %s rate %s',
							$vat_code_id,
							$moss_country_iso,
							(string) $rate
						),
						'debug'
					);
					return $vat_code_id;
				}

				return new \WP_Error(
					'wfirma_moss_vat_code_not_found',
					sprintf(
						/* translators: 1: country ISO code, 2: VAT rate */
						__( 'Nie znaleziono stawki VAT OSS w wFirma dla kraju %1$s (%2$s%%).', 'invoicing-integration-for-wfirma-and-woocommerce' ),
						$moss_country_iso,
						(string) $rate
					)
				);
			}
		}

		// For local transactions (PL), use country_id = 0
		$country_id = 0;
		
		$this->log( sprintf( 'Looking for VAT code: country_id=%d, rate=%d, zw=%s, eu=%s, np=%s', $country_id, $rate, $zw ? 'true' : 'false', $eu ? 'true' : 'false', $np ? 'true' : 'false' ), 'debug' );

		$response = $this->get_vat_codes( $country_id, $rate, $zw, $eu, false, $np );
		if ( is_wp_error( $response ) ) {
			$this->log( 'VAT codes API error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		$this->log( 'VAT codes API response: ' . wp_json_encode( $response, JSON_UNESCAPED_UNICODE ), 'debug' );

		$vat_code_id = $this->extract_vat_code_id_from_response( $response, $rate );
		if ( $vat_code_id ) {
			$this->log( sprintf( 'Found VAT code ID: %d for rate %d', $vat_code_id, $rate ), 'debug' );
			return $vat_code_id;
		}

		$this->log( sprintf( 'VAT code not found in response for rate: %d', $rate ), 'error' );
		return new \WP_Error(
			'wfirma_vat_code_not_found',
			sprintf( 'Nie znaleziono kodu VAT w wFirma dla stawki: %s', is_scalar( $vat_symbol ) ? (string) $vat_symbol : 'unknown' )
		);
	}

	/**
	 * Extract first matching vat_code id from wFirma API response.
	 *
	 * @param array $response API response.
	 * @param int   $rate     Expected rate (for logging only).
	 * @return int
	 */
	private function extract_vat_code_id_from_response( $response, $rate ) {
		$vat_codes_data = isset( $response['vat_codes'] ) ? $response['vat_codes'] : $response;
		
		if ( ! empty( $vat_codes_data ) && is_array( $vat_codes_data ) ) {
			foreach ( $vat_codes_data as $key => $vat_code_item ) {
				if ( $key === 'parameters' ) {
					continue;
				}
				if ( isset( $vat_code_item['vat_code']['id'] ) ) {
					return absint( $vat_code_item['vat_code']['id'] );
				}
			}
		}

		return 0;
	}

	/**
	 * Normalize VAT rate for wFirma API queries (integer for whole rates, one decimal otherwise).
	 *
	 * @param mixed $rate VAT rate.
	 * @return float|int
	 */
	private function normalize_vat_rate_value( $rate ) {
		$rate = (float) $rate;

		if ( abs( $rate - round( $rate ) ) < 0.001 ) {
			return (int) round( $rate );
		}

		return round( $rate, 1 );
	}

	/**
	 * Resolve wFirma declaration country ID from WooCommerce ISO country code.
	 *
	 * @param string $iso_code ISO 3166-1 alpha-2 country code.
	 * @return int|\WP_Error
	 */
	public function get_declaration_country_id_by_iso( $iso_code ) {
		$iso_code = strtoupper( sanitize_text_field( (string) $iso_code ) );
		if ( $iso_code === '' ) {
			return new \WP_Error( 'wfirma_country_invalid', __( 'Invalid country code.', 'invoicing-integration-for-wfirma-and-woocommerce' ) );
		}

		$cache_key = 'devikit_wfirma_declaration_country_' . $iso_code;
		$cached    = get_transient( $cache_key );
		if ( false !== $cached ) {
			return (int) $cached;
		}

		$candidate_codes = array_unique( [ $iso_code ] );
		if ( $iso_code === 'GR' ) {
			$candidate_codes[] = 'EL';
		} elseif ( $iso_code === 'EL' ) {
			$candidate_codes[] = 'GR';
		}

		foreach ( $candidate_codes as $candidate_code ) {
			$response = $this->find_declaration_country_by_code( $candidate_code );
			if ( is_wp_error( $response ) ) {
				continue;
			}

			$country_id = $this->extract_declaration_country_id_from_response( $response );
			if ( $country_id > 0 ) {
				set_transient( $cache_key, $country_id, WEEK_IN_SECONDS );
				return $country_id;
			}
		}

		return new \WP_Error(
			'wfirma_country_not_found',
			sprintf(
				/* translators: %s: country ISO code */
				__( 'Nie znaleziono kraju deklaracji w wFirma dla kodu: %s', 'invoicing-integration-for-wfirma-and-woocommerce' ),
				$iso_code
			)
		);
	}

	/**
	 * Find declaration country by ISO code in wFirma.
	 *
	 * @param string $iso_code Country code.
	 * @return array|\WP_Error
	 */
	private function find_declaration_country_by_code( $iso_code ) {
		$params = [
			'declaration_countries' => [
				'parameters' => [
					'conditions' => [
						'condition' => [
							'field'    => 'code',
							'operator' => 'eq',
							'value'    => $iso_code,
						],
					],
					'limit' => 1,
				],
			],
		];

		return $this->post( 'declaration_countries/find', $params );
	}

	/**
	 * Extract declaration country ID from API response.
	 *
	 * @param array $response API response.
	 * @return int
	 */
	private function extract_declaration_country_id_from_response( $response ) {
		if ( ! is_array( $response ) ) {
			return 0;
		}

		$countries = $response['declaration_countries'] ?? $response;
		if ( ! is_array( $countries ) ) {
			return 0;
		}

		foreach ( $countries as $key => $item ) {
			if ( $key === 'parameters' || ! is_array( $item ) ) {
				continue;
			}

			$country = $item['declaration_country'] ?? $item;
			if ( is_array( $country ) && ! empty( $country['id'] ) ) {
				return absint( $country['id'] );
			}
		}

		return 0;
	}

	/**
	 * Get invoice
	 * 
	 * @param int $invoice_id Invoice ID
	 * @return array|WP_Error
	 */
	public function get_invoice( $invoice_id ) {
		return $this->get( 'invoices/get/' . $invoice_id );
	}

	/**
	 * Download invoice PDF
	 * 
	 * @param int $invoice_id Invoice ID
	 * @return string|WP_Error PDF content or error
	 */
	public function download_invoice_pdf( $invoice_id ) {
		if ( empty( $this->access_key ) || empty( $this->secret_key ) ) {
			return new \WP_Error( 'no_credentials', __( 'Access and Secret keys are required', 'invoicing-integration-for-wfirma-and-woocommerce' ) );
		}

		// Use POST to invoices/download/{id} endpoint
		$url = self::API_URL . 'invoices/download/' . $invoice_id;
		
		// Parameters for download endpoint
		$params = [
			'invoices' => [
				'parameters' => [
					'1' => [
						'parameter' => [
							'name'  => 'page',
							'value' => 'invoice',
						],
					],
					'2' => [
						'parameter' => [
							'name'  => 'duplicate',
							'value' => 0,
						],
					],
				],
			],
		];
		
		$query_args = [
			'inputFormat'  => 'json',
			'outputFormat' => 'json', // API will return PDF based on content-type
		];
		
		// Add company_id if selected
		$settings = get_option( 'devikit_wfirma_settings', [] );
		if ( ! empty( $settings['selected_company_id'] ) ) {
			$query_args['company_id'] = $settings['selected_company_id'];
		}
		
		$url = add_query_arg( $query_args, $url );
		
		$args = [
			'headers'   => $this->get_headers(),
			'body'      => wp_json_encode( $params ),
			'timeout'   => 30,
			'sslverify' => false,
		];

		$response = wp_remote_post( $url, $args );

		if ( is_wp_error( $response ) ) {
			$this->log( 'PDF Download Error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		$code = wp_remote_retrieve_response_code( $response );
		$content_type = wp_remote_retrieve_header( $response, 'content-type' );
		
		if ( $code !== 200 ) {
		$body = wp_remote_retrieve_body( $response );
		$this->log( 'PDF Download Error. Status: ' . $code . ', Body: ' . substr( $body, 0, 500 ), 'error' );
		/* translators: %d: HTTP status code */
		return new \WP_Error( 'pdf_download_failed', sprintf( __( 'Failed to download PDF. Status: %d', 'invoicing-integration-for-wfirma-and-woocommerce' ), $code ) );
		}

		// Check if response is actually PDF
		if ( $content_type === 'application/pdf' ) {
			return wp_remote_retrieve_body( $response );
		}
		
		// Might be JSON error
		$body = wp_remote_retrieve_body( $response );
		$error_data = json_decode( $body, true );
		if ( isset( $error_data['status'] ) && $error_data['status']['code'] !== 'OK' ) {
		$error_message = $error_data['status']['message'] ?? 'Unknown error';
		$this->log( 'PDF Download API Error: ' . $error_message, 'error' );
		/* translators: %s: Error message */
		return new \WP_Error( 'pdf_download_failed', sprintf( __( 'PDF download error: %s', 'invoicing-integration-for-wfirma-and-woocommerce' ), $error_message ) );
		}
		
		// If we get here, response is not PDF and not a known error
		$this->log( 'PDF Download Error: Unexpected response type: ' . $content_type, 'error' );
		/* translators: %s: Content type */
		return new \WP_Error( 'pdf_download_failed', sprintf( __( 'Unexpected response type: %s', 'invoicing-integration-for-wfirma-and-woocommerce' ), $content_type ) );
	}

	/**
	 * Download receipt PDF
	 * 
	 * @param int $receipt_id Receipt ID
	 * @return string|WP_Error PDF content or error
	 */
	public function download_receipt_pdf( $receipt_id ) {
		if ( empty( $this->access_key ) || empty( $this->secret_key ) ) {
			return new \WP_Error( 'no_credentials', __( 'Access and Secret keys are required', 'invoicing-integration-for-wfirma-and-woocommerce' ) );
		}

		// Try same approach as invoices - use invoices/download/{id} endpoint
		// Fiscal receipts might not support PDF download via API
		// If download fails, we'll return error and user can access receipt in wFirma
		$url = self::API_URL . 'invoices/download/' . $receipt_id;
		
		// Parameters for download endpoint - use 'invoice' page parameter
		$params = [
			'invoices' => [
				'parameters' => [
					'1' => [
						'parameter' => [
							'name'  => 'page',
							'value' => 'invoice',
						],
					],
					'2' => [
						'parameter' => [
							'name'  => 'duplicate',
							'value' => 0,
						],
					],
				],
			],
		];
		
		$query_args = [
			'inputFormat'  => 'json',
			'outputFormat' => 'json', // API will return PDF based on content-type
		];
		
		// Add company_id if selected
		$settings = get_option( 'devikit_wfirma_settings', [] );
		if ( ! empty( $settings['selected_company_id'] ) ) {
			$query_args['company_id'] = $settings['selected_company_id'];
		}
		
		$url = add_query_arg( $query_args, $url );
		
		$args = [
			'headers'   => $this->get_headers(),
			'body'      => wp_json_encode( $params ),
			'timeout'   => 30,
			'sslverify' => false,
		];

		$response = wp_remote_post( $url, $args );

		if ( is_wp_error( $response ) ) {
			$this->log( 'PDF Download Error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		$code = wp_remote_retrieve_response_code( $response );
		$content_type = wp_remote_retrieve_header( $response, 'content-type' );
		
		if ( $code !== 200 ) {
		$body = wp_remote_retrieve_body( $response );
		$this->log( 'PDF Download Error. Status: ' . $code . ', Body: ' . substr( $body, 0, 500 ), 'error' );
		/* translators: %d: HTTP status code */
		return new \WP_Error( 'pdf_download_failed', sprintf( __( 'Failed to download PDF. Status: %d', 'invoicing-integration-for-wfirma-and-woocommerce' ), $code ) );
		}

		// Check if response is actually PDF
		if ( $content_type === 'application/pdf' ) {
			return wp_remote_retrieve_body( $response );
		}
		
		// Might be JSON error
		$body = wp_remote_retrieve_body( $response );
		$error_data = json_decode( $body, true );
		if ( isset( $error_data['status'] ) && $error_data['status']['code'] !== 'OK' ) {
		$error_message = $error_data['status']['message'] ?? 'Unknown error';
		$this->log( 'PDF Download API Error: ' . $error_message, 'error' );
		/* translators: %s: Error message */
		return new \WP_Error( 'pdf_download_failed', sprintf( __( 'PDF download error: %s', 'invoicing-integration-for-wfirma-and-woocommerce' ), $error_message ) );
		}
		
		// If we get here, response is not PDF and not a known error
		$this->log( 'PDF Download Error: Unexpected response type: ' . $content_type, 'error' );
		/* translators: %s: Content type */
		return new \WP_Error( 'pdf_download_failed', sprintf( __( 'Unexpected response type: %s', 'invoicing-integration-for-wfirma-and-woocommerce' ), $content_type ) );
	}

	/**
	 * Find invoice series
	 *
	 * @param string $type Series type (normal, proforma, receipt_fiscal, receipt, etc.)
	 * @return array|WP_Error
	 */
	public function find_series( $type = 'normal' ) {
		$params = [
			'series' => [
				'parameters' => [
					'conditions' => [
						'condition' => [
							'field'    => 'type',
							'operator' => 'eq',
							'value'    => $type,
						],
					],
				],
			],
		];

		$response = $this->post( 'series/find', $params );
		
		// Return response as-is for error handling in caller
		if ( is_wp_error( $response ) ) {
			return $response;
		}
		
		// Parse the series structure to make it easier to use
		if ( isset( $response['series'] ) && is_array( $response['series'] ) ) {
			$parsed_series = [];
			foreach ( $response['series'] as $key => $serie_data ) {
				// Skip parameters key
				if ( $key === 'parameters' ) {
					continue;
				}
				
				// Structure: ['series' => ['id' => ..., 'name' => ..., 'template' => ...]]
				if ( isset( $serie_data['series'] ) ) {
					$parsed_series[] = $serie_data['series'];
				}
			}
			$response['series_parsed'] = $parsed_series;
		}
		
		return $response;
	}

	/**
	 * Get series (alias for find_series)
	 *
	 * @param string $type Series type
	 * @return array|WP_Error
	 */
	public function get_series( $type = 'normal' ) {
		return $this->find_series( $type );
	}

	/**
	 * Get company accounts (bank accounts)
	 * 
	 * @param bool $refresh Force refresh from API (no cache)
	 * @return array
	 */
	public function get_company_accounts( $refresh = false ) {
		// Always refresh - no cache for admin settings to show latest data from wFirma
		$params = [
			'company_accounts' => [
				'parameters' => [
					'conditions' => [],
				],
			],
		];

		$response = $this->post( 'company_accounts/find', $params );
		
		if ( is_wp_error( $response ) ) {
			$this->log( 'Company Accounts API Error: ' . $response->get_error_message() );
			return [];
		}

		if ( empty( $response['company_accounts'] ) ) {
			return [];
		}

		$accounts = [];
		foreach ( $response['company_accounts'] as $key => $account_data ) {
			// Skip parameters key
			if ( $key === 'parameters' ) {
				continue;
			}
			
			// Structure: ['company_account' => ['id' => ..., 'name' => ..., 'number' => ..., 'currency' => ...]]
			if ( isset( $account_data['company_account'] ) ) {
				$account = $account_data['company_account'];
				$accounts[] = [
					'id'       => $account['id'] ?? '',
					'name'     => $account['name'] ?? '',
					'number'   => $account['number'] ?? '',
					'currency' => $account['currency'] ?? 'PLN',
				];
			}
		}

		return $accounts;
	}

	/**
	 * Get VAT codes
	 * 
	 * @param int $declaration_country_id Country ID (191 for Poland)
	 * @param float $rate VAT rate
	 * @param bool $zw Is VAT exempt (zwolnione)
	 * @param bool $eu Is EU transaction
	 * @param bool $refresh Force refresh
	 * @param bool $np Is not subject to VAT (nie podlega)
	 * @return array|WP_Error
	 */
	public function get_vat_codes( $declaration_country_id = 191, $rate = 23, $zw = false, $eu = false, $refresh = false, $np = false ) {
		// v2 - added better filtering for plain 0%
		$cache_key = 'devikit_wfirma_vat_codes_v2_' . md5( $declaration_country_id . '_' . $rate . '_' . (int) $zw . '_' . (int) $eu . '_' . (int) $np );
		
		if ( ! $refresh ) {
			$cached = get_transient( $cache_key );
			if ( false !== $cached ) {
				return $cached;
			}
		}

		if ( $eu && ! $np ) {
			$declaration_country_id = 0;
		}

		$conditions = [
			'1' => [
				'condition' => [
					'field'    => 'declaration_country_id',
					'operator' => 'eq',
					'value'    => $declaration_country_id,
				],
			],
			'2' => [
				'condition' => [
					'field'    => 'rate',
					'operator' => 'eq',
					'value'    => $this->normalize_vat_rate_value( $rate ),
				],
			],
		];

		if ( $rate == 0 ) {
			// For "np" (nie podlega), search for code = 'NP'
			if ( $np ) {
				$conditions['3'] = [
					'condition' => [
						'field'    => 'code',
						'operator' => 'eq',
						'value'    => 'NP',
					],
				];
			} elseif ( $zw ) {
				// For "zw" (zwolnione), search for code = 'ZW'
				$conditions['3'] = [
					'condition' => [
						'field'    => 'code',
						'operator' => 'eq',
						'value'    => 'ZW',
					],
				];
			} elseif ( $eu ) {
				// For "eu" (WDT), search for code = 'WDT'
				$conditions['3'] = [
					'condition' => [
						'field'    => 'code',
						'operator' => 'eq',
						'value'    => 'WDT',
					],
				];
			} else {
				// For plain "0%" (not zw, not eu, not np), exclude special codes
				// Exclude: ZW, WDT, NP, EXP (export)
				// Get more results and filter on PHP side to find the right one
				$conditions['3'] = [
					'condition' => [
						'field'    => 'code',
						'operator' => 'ne',
						'value'    => 'ZW',
					],
				];
				$conditions['4'] = [
					'condition' => [
						'field'    => 'code',
						'operator' => 'ne',
						'value'    => 'WDT',
					],
				];
				$conditions['5'] = [
					'condition' => [
						'field'    => 'code',
						'operator' => 'ne',
						'value'    => 'NP',
					],
				];
				$conditions['6'] = [
					'condition' => [
						'field'    => 'code',
						'operator' => 'ne',
						'value'    => 'EXP',
					],
				];
			}
		}

		// For plain 0%, get more results to filter properly
		$limit = ( $rate == 0 && ! $zw && ! $eu && ! $np ) ? 20 : 1;

		$params = [
			'vat_codes' => [
				'parameters' => [
					'conditions' => $conditions,
					'limit'      => $limit,
				],
			],
		];

		$response = $this->post( 'vat_codes/find', $params );
		
		// For plain 0%, filter results to find code with empty code field or lowest priority
		if ( $rate == 0 && ! $zw && ! $eu && ! $np && ! is_wp_error( $response ) ) {
			$vat_codes_data = isset( $response['vat_codes'] ) ? $response['vat_codes'] : $response;
			$best_code = null;
			$lowest_priority = PHP_INT_MAX;
			
			if ( ! empty( $vat_codes_data ) && is_array( $vat_codes_data ) ) {
				foreach ( $vat_codes_data as $key => $vat_code_item ) {
					if ( $key === 'parameters' ) {
						continue;
					}
					
					$vat_code = isset( $vat_code_item['vat_code'] ) ? $vat_code_item['vat_code'] : $vat_code_item;
					$code = $vat_code['code'] ?? '';
					$priority = isset( $vat_code['priority'] ) ? (int) $vat_code['priority'] : PHP_INT_MAX;
					
					// Prefer code with empty code field, or lowest priority
					if ( empty( $code ) || ( $priority < $lowest_priority && ! in_array( $code, [ 'ZW', 'WDT', 'NP', 'EXP' ] ) ) ) {
						$best_code = $vat_code;
						$lowest_priority = empty( $code ) ? 0 : $priority;
					}
				}
				
				// If we found a better code, replace the response
				if ( $best_code ) {
					$response = [
						'vat_codes' => [
							'0' => [
								'vat_code' => $best_code,
							],
						],
						'parameters' => $response['parameters'] ?? [],
					];
				}
			}
		}

		if ( ! is_wp_error( $response ) && ! empty( $response['vat_codes'] ) ) {
			set_transient( $cache_key, $response['vat_codes'], WEEK_IN_SECONDS );
		}

		return $response;
	}

	/**
	 * Clear API cache
	 */
	public static function clear_cache() {
		global $wpdb;
		
		// Delete all transients starting with our prefix
		// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( '_transient_devikit_wfirma_' ) . '%'
			)
		);
		$wpdb->query(
			$wpdb->prepare(
				"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s",
				$wpdb->esc_like( '_transient_timeout_devikit_wfirma_' ) . '%'
			)
		);
		// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	/**
	 * Send GET request
	 * 
	 * @param string $endpoint API endpoint (without base URL)
	 * @param bool $pdf Whether to expect PDF response
	 * @return array|WP_Error
	 */
	private function get( $endpoint, $pdf = false ) {
		$url = self::API_URL . $endpoint;
		
		$query_args = [
			'inputFormat'  => 'json',
			'outputFormat' => $pdf ? 'pdf' : 'json',
		];
		
		// Add company_id if a company is selected
		$settings = get_option( 'devikit_wfirma_settings', [] );
		if ( ! empty( $settings['selected_company_id'] ) ) {
			$query_args['company_id'] = $settings['selected_company_id'];
		}
		
		$url = add_query_arg( $query_args, $url );

		$args = [
			'headers'   => $this->get_headers(),
			'timeout'   => 30,
			'sslverify' => false,
		];

		$response = wp_remote_get( $url, $args );

		if ( is_wp_error( $response ) ) {
			$this->log( 'GET Error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		return $this->parse_response( $response, $pdf );
	}

	/**
	 * Send POST request
	 * 
	 * @param string $endpoint API endpoint (without base URL)
	 * @param array $params Request parameters
	 * @param bool $pdf Whether to expect PDF response
	 * @return array|WP_Error
	 */
	private function post( $endpoint, $params = [], $pdf = false ) {
		$url = self::API_URL . $endpoint;
		
		$query_args = [
			'inputFormat'  => 'json',
			'outputFormat' => $pdf ? 'pdf' : 'json',
		];
		
		// Add company_id if a company is selected
		$settings = get_option( 'devikit_wfirma_settings', [] );
		if ( ! empty( $settings['selected_company_id'] ) ) {
			$query_args['company_id'] = $settings['selected_company_id'];
		}
		
		$url = add_query_arg( $query_args, $url );

		$args = [
			'headers'   => $this->get_headers(),
			'body'      => wp_json_encode( $params ),
			'timeout'   => 30,
			'sslverify' => false,
		];

		$response = wp_remote_post( $url, $args );

		if ( is_wp_error( $response ) ) {
			$this->log( 'POST Error: ' . $response->get_error_message(), 'error' );
			return $response;
		}

		return $this->parse_response( $response, $pdf );
	}

	/**
	 * Parse API response
	 * 
	 * @param array $response WordPress HTTP response
	 * @param bool $pdf Whether to expect PDF response
	 * @return array|string|WP_Error
	 */
	private function parse_response( $response, $pdf = false ) {
		$code = wp_remote_retrieve_response_code( $response );
		$body = wp_remote_retrieve_body( $response );

		if ( $code !== 200 ) {
			$this->log( 'API Response Error. Status: ' . $code . ', Body: ' . $body, 'error' );
			/* translators: %d: HTTP status code */
			return new \WP_Error( 'wfirma_api_error', sprintf( __( 'API returned status %d', 'invoicing-integration-for-wfirma-and-woocommerce' ), $code ) );
		}

		// Handle PDF response
		if ( $pdf ) {
			$content_type = wp_remote_retrieve_header( $response, 'content-type' );
			if ( $content_type === 'application/pdf' ) {
				return $body;
			}
		}

		// Parse JSON response
		$data = json_decode( $body, true );

		if ( json_last_error() !== JSON_ERROR_NONE ) {
			$this->log( 'JSON Parse Error: ' . json_last_error_msg(), 'error' );
			return new \WP_Error( 'wfirma_json_error', __( 'Failed to parse JSON response', 'invoicing-integration-for-wfirma-and-woocommerce' ) );
		}

		// Check wFirma API status
		if ( isset( $data['status'] ) ) {
			$status_code = $data['status']['code'] ?? '';

			if ( $status_code === 'AUTH' ) {
				return new \WP_Error( 'wfirma_auth_error', __( 'Authentication failed. Check your Access and Secret keys.', 'invoicing-integration-for-wfirma-and-woocommerce' ) );
			}

			if ( $status_code !== 'OK' ) {
				$error_message = $this->format_errors( $data );
				$full_message = 'wFirma API status: ' . $status_code . ( $error_message ? ' — ' . wp_strip_all_tags( $error_message ) : '' );
				$this->log( 'wFirma API Error Response: ' . wp_json_encode( $data, JSON_UNESCAPED_UNICODE ), 'error' );
				return new \WP_Error( 'wfirma_api_error', $full_message );
			}
		}

		return $data;
	}

	/**
	 * Format error messages from wFirma API response
	 * 
	 * @param array $data API response data
	 * @return string Formatted error message
	 */
	private function format_errors( $data ) {
		$errors = [];

		// Recursively find all error structures in the response
		$this->extract_errors_recursive( $data, $errors );

		// If no errors found, try status message
		if ( empty( $errors ) && isset( $data['status']['message'] ) ) {
			$errors[] = $data['status']['message'];
		}

		return ! empty( $errors ) ? implode( ' ', $errors ) : __( 'Unknown error', 'invoicing-integration-for-wfirma-and-woocommerce' );
	}

	/**
	 * Recursively extract error messages from API response structure
	 * 
	 * @param mixed $data Data to search through
	 * @param array $errors Array to collect errors into
	 */
	private function extract_errors_recursive( $data, &$errors ) {
		if ( ! is_array( $data ) ) {
			return;
		}

		// Check for direct errors array
		if ( isset( $data['errors'] ) && is_array( $data['errors'] ) ) {
			foreach ( $data['errors'] as $error_item ) {
				if ( isset( $error_item['error'] ) ) {
					$field = $error_item['error']['field'] ?? '';
					$message = $error_item['error']['message'] ?? '';
					if ( $message ) {
						// Format: "Field: Message" or just "Message" if no field
						$errors[] = $field ? $field . ': ' . $message : $message;
					}
				}
			}
		}

		// Recursively search through nested structures
		foreach ( $data as $key => $value ) {
			if ( is_array( $value ) ) {
				// Skip status and parameters to avoid infinite loops
				if ( $key !== 'status' && $key !== 'parameters' ) {
					$this->extract_errors_recursive( $value, $errors );
				}
			}
		}
	}

	/**
	 * Log message
	 * 
	 * @param string $message Log message
	 * @param string $level Log level: 'error', 'warning', 'info', 'debug'
	 */
	public function log( $message, $level = 'error' ) {
		if ( ! class_exists( 'WC_Logger' ) ) {
			return;
		}

		$logger = wc_get_logger();
		
		// Check if debug logging is enabled
		$settings = get_option( 'devikit_wfirma_settings', [] );
		$debug_enabled = isset( $settings['enable_debug_logging'] ) && $settings['enable_debug_logging'] === 'yes';
		
		if ( $debug_enabled ) {
			// Log to debug file when enabled
			$logger->log( $level, $message, [ 'source' => 'devikit_wfirma_debug' ] );
		} elseif ( $level === 'error' ) {
			// Always log errors to main file
			$logger->error( $message, [ 'source' => 'invoicing-integration-for-wfirma-and-woocommerce' ] );
		}
	}

	/**
	 * Find product (good) by SKU in wFirma warehouse
	 * 
	 * @param string $sku Product SKU
	 * @return array|null Product data or null if not found
	 */
	public function find_good_by_sku( $sku ) {
		$params = [
			'parameters' => [
				'conditions' => [
					'condition' => [
						'field'    => 'code',
						'operator' => 'eq',
						'value'    => $sku,
					],
				],
				'limit' => 1, // Limit to 1 result for performance
			],
		];

		$response = $this->post( 'goods/find', [ 'goods' => $params ] );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		// Handle different response structures
		if ( isset( $response['goods'] ) ) {
			// Check for single good (array with 'id' key)
			if ( isset( $response['goods']['good'] ) ) {
				$good = $response['goods']['good'];
				
				// Single product found (direct structure)
				if ( isset( $good['id'] ) ) {
					return $good;
				}
				
				// Multiple products found (array of goods)
				if ( is_array( $good ) && isset( $good[0] ) && isset( $good[0]['id'] ) ) {
					return $good[0];
				}
			}
			
			// Check for array structure with numeric keys
			if ( isset( $response['goods'][0] ) && isset( $response['goods'][0]['good'] ) ) {
				$good = $response['goods'][0]['good'];
				if ( isset( $good['id'] ) ) {
					return $good;
				}
			}
		}

		return null;
	}

	/**
	 * Get single product (good) by ID from wFirma
	 * 
	 * @param int $good_id Product ID
	 * @return array|WP_Error Product data or WP_Error
	 */
	public function get_good( $good_id ) {
		$response = $this->post( "goods/get/$good_id", [] );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		// Handle different response structures
		if ( isset( $response['goods'] ) ) {
			if ( isset( $response['goods']['good'] ) ) {
				$good = $response['goods']['good'];
				
				// Single product found (direct structure)
				if ( isset( $good['id'] ) ) {
					return $good;
				}
				
				// Array of goods (shouldn't happen for get, but handle it)
				if ( is_array( $good ) && isset( $good[0] ) && isset( $good[0]['id'] ) ) {
					return $good[0];
				}
			}
			
			// Check for array structure with numeric keys
			if ( isset( $response['goods'][0] ) && isset( $response['goods'][0]['good'] ) ) {
				$good = $response['goods'][0]['good'];
				if ( isset( $good['id'] ) ) {
					return $good;
				}
			}
		}

		return new \WP_Error( 'good_not_found', 'Product not found in response' );
	}

	/**
	 * Get all products from wFirma warehouse
	 * 
	 * @return array|WP_Error Array of products or WP_Error
	 */
	public function get_all_goods() {
		$response = $this->post( 'goods/find', [ 'goods' => [ 'parameters' => [] ] ] );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		$goods = [];
		if ( isset( $response['goods']['good'] ) ) {
			$goods_data = isset( $response['goods']['good'][0] ) ? $response['goods']['good'] : [ $response['goods']['good'] ];
			$goods = $goods_data;
		}

		return $goods;
	}

	/**
	 * Update product stock in wFirma warehouse
	 * 
	 * @param int $product_id wFirma product ID
	 * @param int $quantity   New stock quantity
	 * @return array|WP_Error Response or WP_Error
	 */
	public function update_good_stock( $product_id, $quantity ) {
		$data = [
			'quantity' => absint( $quantity ),
		];

		$response = $this->post( "goods/edit/$product_id", [ 'good' => $data ] );

		if ( is_wp_error( $response ) ) {
			$this->log( 'Error updating product stock: ' . $response->get_error_message(), 'error' );
		} else {
			$this->log( "Product $product_id stock updated to $quantity", 'debug' );
		}

		return $response;
	}

	/**
	 * Test warehouse connection
	 * 
	 * @return bool|WP_Error True if successful, WP_Error otherwise
	 */
	public function test_warehouse_connection() {
		$response = $this->post( 'goods/find', [ 
			'goods' => [ 
				'parameters' => [
					'limit' => 1,
				],
			],
		] );

		if ( is_wp_error( $response ) ) {
			return $response;
		}

		return true;
	}

	/**
	 * Get ledger operation schemas (dekretacja/schematy księgowe)
	 * 
	 * @param int $page Page number
	 * @param int $limit Results per page
	 * @return array|WP_Error
	 */
	public function get_ledger_operation_schemas( $page = 1, $limit = 100 ) {
		$params = [
			'ledger_operation_schemas' => [
				'parameters' => [
					'page' => $page,
					'limit' => $limit
				]
			]
		];
		
		return $this->post( 'ledger_operation_schemas/find', $params );
	}

	/**
	 * Get warehouses list
	 * 
	 * @param int $page Page number
	 * @param int $limit Results per page
	 * @return array|WP_Error
	 */
	public function get_warehouses( $page = 1, $limit = 100 ) {
		$params = [
			'warehouses' => [
				'parameters' => [
					'page' => $page,
					'limit' => $limit
				]
			]
		];
		
		return $this->post( 'warehouses/find', $params );
	}

	/**
	 * Get user companies (for multi-company accounts)
	 *
	 * @return array|WP_Error
	 */
	public function get_user_companies() {
		$params = [
			'user_companies' => [
				'parameters' => [
					'page' => 1,
					'limit' => 100
				]
			]
		];
		
		return $this->post( 'user_companies/find', $params );
	}

	/**
	 * Get OSS/MOSS sale type options for admin settings (code => label).
	 *
	 * @return array<string, string>
	 */
	public function get_moss_sale_type_options() {
		return $this->get_moss_sale_type_fallback_options();
	}

	/**
	 * Get OSS/MOSS evidence type options for admin settings (code => label).
	 *
	 * @return array<string, string>
	 */
	public function get_moss_evidence_type_options() {
		return $this->get_moss_evidence_type_fallback_options();
	}

	/**
	 * Sanitize MOSS/OSS code value from settings form.
	 *
	 * @param mixed  $value   Raw input.
	 * @param string $default Default when invalid.
	 * @return string
	 */
	public static function sanitize_moss_code( $value, $default = '' ) {
		$value = strtoupper( sanitize_text_field( wp_unslash( (string) $value ) ) );
		// wFirma UI label is WSTO, API code is WO.
		if ( $value === 'WSTO' ) {
			$value = 'WO';
		}
		if ( $value === '' || ! preg_match( '/^[A-Z0-9_]+$/', $value ) ) {
			return $default;
		}

		return $value;
	}

	/**
	 * Normalize MOSS sale type code for wFirma API payload.
	 *
	 * @param string $code Raw code from settings.
	 * @return string
	 */
	public static function normalize_moss_sale_type_for_api( $code ) {
		return self::sanitize_moss_code( $code, 'WO' );
	}

	/**
	 * Sanitize MOSS/OSS evidence code (A-F).
	 *
	 * @param mixed  $value   Raw input.
	 * @param string $default Default when invalid.
	 * @return string
	 */
	public static function sanitize_moss_evidence_code( $value, $default = 'A' ) {
		return self::sanitize_moss_code( $value, $default );
	}

	/**
	 * Built-in OSS/MOSS sale type options (wFirma codes).
	 *
	 * @return array<string, string>
	 */
	private function get_moss_sale_type_fallback_options() {
		return [
			'WO'   => 'WO - Distance selling of goods (WSTO)',
			'SA'   => 'SA - Website services, hosting, remote software and hardware maintenance',
			'SB'   => 'SB - Software delivery and updates',
			'SC'   => 'SC - Images, texts, information and database access',
			'SD'   => 'SD - Music, films, games, gambling and political, cultural, artistic, sporting, scientific and entertainment programmes and events',
			'SE'   => 'SE - Distance learning',
			'TA'   => 'TA - Fixed and mobile telephony for voice, data and image transmission and switching, including videophone services',
			'TB'   => 'TB - Internet telephony (VoIP)',
			'TC'   => 'TC - Voicemail, call waiting, call forwarding, caller ID, three-way calling and other call management services',
			'TD'   => 'TD - Paging services',
			'TE'   => 'TE - Audiotext services',
			'TF'   => 'TF - Fax, telegraph, telex',
			'TG'   => 'TG - Telephone technical support for radio, TV, internet or similar electronic networks',
			'TH'   => 'TH - Internet access, including WWW',
			'TJ'   => 'TJ - Private network connections providing exclusive telecom lines for the customer',
			'TK'   => 'TK - Further supply of audio and audiovisual content by a different entity via communication networks',
			'BA'   => 'BA - Radio or TV programmes transmitted or retransmitted via radio or TV networks',
			'BB'   => 'BB - Radio or TV programmes distributed via internet or similar network (IP streaming) when broadcast simultaneously with radio/TV transmission',
			'INNE' => 'INNE - Other VAT OSS services (e.g. services related to immovable property)',
		];
	}

	/**
	 * Built-in OSS/MOSS evidence type options (wFirma codes).
	 *
	 * @return array<string, string>
	 */
	private function get_moss_evidence_type_fallback_options() {
		return [
			'A' => 'A - Customer billing address',
			'B' => 'B - IP address or geolocation',
			'C' => 'C - Bank account information',
			'D' => 'D - MCC code or SIM card',
			'E' => 'E - Fixed line location',
			'F' => 'F - Other commercial information',
		];
	}
}

