<?php
error_reporting(E_ALL);
date_default_timezone_set('Asia/Riyadh');


function app_session_start(): void {
    if (session_status() == PHP_SESSION_ACTIVE) return;
    $https = !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
    if (!headers_sent()) {
        session_name('BKINVSESSID');
        session_set_cookie_params([
            'lifetime' => 0,
            'path' => '/',
            'secure' => $https,
            'httponly' => true,
            'samesite' => 'Lax',
        ]);
    }
    ini_set('session.use_strict_mode', '1');
    session_start();
}

const APP_NAME = 'BK Invoice';
const APP_VERSION = '2.6-hosting-qr-repair';

function base_path(string $p = ''): string {
    $base = dirname(__DIR__);
    return $p ? $base . '/' . ltrim($p, '/') : $base;
}
function storage_legacy_path(string $p = ''): string {
    $base = base_path('storage');
    return $p ? $base . '/' . ltrim($p, '/') : $base;
}
function ensure_dir_secure(string $dir): void {
    if (!is_dir($dir)) mkdir($dir, 0750, true);
    @chmod($dir, 0750);
}
function write_text_file(string $path, string $contents): void {
    ensure_dir_secure(dirname($path));
    file_put_contents($path, $contents, LOCK_EX);
    @chmod($path, 0640);
}
function preferred_storage_root(): string {
    $env = trim((string)(getenv('BK_INVOICE_DATA_DIR') ?: ''));
    $base = $env !== '' ? $env : (dirname(base_path()) . '/bk-invoice-data');
    return rtrim(str_replace('\\', '/', $base), '/');
}
function deny_web_access_htaccess(): string {
    return "Order allow,deny
Deny from all
";
}
function copy_directory_recursive(string $src, string $dst): void {
    if (!is_dir($src)) return;
    ensure_dir_secure($dst);
    $it = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($src, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );
    foreach ($it as $item) {
        $target = $dst . '/' . $it->getSubPathName();
        if ($item->isDir()) {
            ensure_dir_secure($target);
            continue;
        }
        if (!file_exists($target)) {
            ensure_dir_secure(dirname($target));
            copy($item->getPathname(), $target);
            @chmod($target, 0640);
        }
    }
}
function migrate_legacy_storage_once(): void {
    static $done = false;
    if ($done) return;
    $done = true;
    $newBase = preferred_storage_root();
    ensure_dir_secure($newBase);
    write_text_file($newBase . '/.htaccess', deny_web_access_htaccess());

    $legacy = storage_legacy_path();
    if (is_dir($legacy)) {
        write_text_file($legacy . '/.htaccess', deny_web_access_htaccess());
        $legacyReal = realpath($legacy) ?: $legacy;
        $newReal = realpath($newBase) ?: $newBase;
        if ($legacyReal !== $newReal) {
            copy_directory_recursive($legacy, $newBase);
        }
    }
}
function storage_path(string $p = ''): string {
    migrate_legacy_storage_once();
    $base = preferred_storage_root();
    ensure_dir_secure($base);
    return $p ? $base . '/' . ltrim($p, '/') : $base;
}
function h(?string $v): string { return htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8'); }
function read_json_file(string $path, array $default = []): array {
    if (!file_exists($path)) return $default;
    $raw = @file_get_contents($path);
    if ($raw === false || $raw === '') return $default;
    $data = json_decode($raw, true);
    return is_array($data) ? $data : $default;
}
function write_json_file(string $path, array $data): void {
    write_text_file($path, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
}
function app_config(): array {
    $default = [
        'company' => ['name'=>'', 'vat'=>'', 'address'=>'', 'commercial_registration'=>'', 'brand_name'=>'', 'phone'=>'', 'logo_path'=>''],
        'defaults' => ['currency'=>'SAR', 'vat_rate'=>0.15],
        'admin' => ['username'=>'admin', 'password_hash'=>''],
        'security' => ['activation_secret'=>'', 'allow_external_qr_provider'=>true],
        'phase2' => [
            'enabled'=>false,
            'environment'=>'Sandbox',
            'device_id'=>'',
            'csid'=>'',
            'csr_path'=>'',
            'private_key_path'=>'',
            'public_key_path'=>'',
            'key_algorithm'=>'',
            'csid_path'=>'',
            'binary_security_token'=>'',
            'secret'=>'',
            'last_request_id'=>'',
            'last_api_response_path'=>'',
            'otp'=>'',
            'qr_mode'=>'auto',
            'b2b_mode'=>true,
            'compliance_csid_path'=>'',
            'compliance_binary_security_token'=>'',
            'compliance_secret'=>'',
            'compliance_request_id'=>'',
            'production_csid_path'=>'',
            'production_binary_security_token'=>'',
            'production_secret'=>'',
            'production_last_api_response_path'=>'',
        ],
        'license' => ['request_code'=>'', 'activation_key'=>'', 'expires_at'=>''],
    ];
    $cfg = read_json_file(storage_path('config.json'), []);
    if (!$cfg) {
        $cfg = read_json_file(storage_legacy_path('config.json'), []);
        if ($cfg) write_json_file(storage_path('config.json'), $cfg);
    }
    $merged = array_replace_recursive($default, $cfg);
    if (($merged['phase2']['production_binary_security_token'] ?? '') === '' && trim((string)($merged['phase2']['binary_security_token'] ?? '')) !== '') {
        $merged['phase2']['production_binary_security_token'] = (string)$merged['phase2']['binary_security_token'];
    }
    if (($merged['phase2']['production_secret'] ?? '') === '' && trim((string)($merged['phase2']['secret'] ?? '')) !== '') {
        $merged['phase2']['production_secret'] = (string)$merged['phase2']['secret'];
    }
    if (($merged['phase2']['production_csid_path'] ?? '') === '' && trim((string)($merged['phase2']['csid_path'] ?? '')) !== '') {
        $merged['phase2']['production_csid_path'] = (string)$merged['phase2']['csid_path'];
    }
    if (($merged['phase2']['compliance_request_id'] ?? '') === '' && trim((string)($merged['phase2']['last_request_id'] ?? '')) !== '') {
        $merged['phase2']['compliance_request_id'] = (string)$merged['phase2']['last_request_id'];
    }
    return $merged;
}
function save_config(array $cfg): void { write_json_file(storage_path('config.json'), $cfg); }

function phase2_compliance_token(array $cfg): string {
    return trim((string)($cfg['phase2']['compliance_binary_security_token'] ?? ''));
}
function phase2_compliance_secret(array $cfg): string {
    return trim((string)($cfg['phase2']['compliance_secret'] ?? ''));
}
function phase2_compliance_cert_path(array $cfg): string {
    return trim((string)($cfg['phase2']['compliance_csid_path'] ?? ''));
}
function phase2_compliance_request_id(array $cfg): string {
    return trim((string)($cfg['phase2']['compliance_request_id'] ?? ($cfg['phase2']['last_request_id'] ?? '')));
}
function phase2_production_token(array $cfg): string {
    return trim((string)($cfg['phase2']['production_binary_security_token'] ?? ($cfg['phase2']['binary_security_token'] ?? '')));
}
function phase2_production_secret(array $cfg): string {
    return trim((string)($cfg['phase2']['production_secret'] ?? ($cfg['phase2']['secret'] ?? '')));
}
function phase2_production_cert_path(array $cfg): string {
    return trim((string)($cfg['phase2']['production_csid_path'] ?? ($cfg['phase2']['csid_path'] ?? '')));
}
function phase2_submission_credentials_ready(array $cfg): bool {
    $certPath = phase2_production_cert_path($cfg);
    return phase2_production_token($cfg) !== '' && phase2_production_secret($cfg) !== '' && $certPath !== '' && file_exists($certPath);
}
function phase2_submission_certificate_path(array $cfg): string {
    $prod = phase2_production_cert_path($cfg);
    if ($prod !== '' && file_exists($prod)) return $prod;
    $legacy = trim((string)($cfg['phase2']['csid_path'] ?? ''));
    if ($legacy !== '' && file_exists($legacy)) return $legacy;
    return phase2_compliance_cert_path($cfg);
}
function phase2_submission_label(array $cfg): string {
    return strcasecmp((string)($cfg['phase2']['environment'] ?? 'Sandbox'), 'Production') === 0 ? 'Production CSID' : 'Test Production CSID';
}
function activation_signing_secret(): string {
    $env = trim((string)(getenv('BK_ACTIVATION_SECRET') ?: ''));
    if ($env !== '') return $env;
    $cfg = app_config();
    $secret = trim((string)($cfg['security']['activation_secret'] ?? ''));
    if ($secret === '') {
        $secret = bin2hex(random_bytes(32));
        $cfg['security']['activation_secret'] = $secret;
        save_config($cfg);
    }
    return $secret;
}
function app_logo_path(array $cfg = []): string {
    $configured = trim((string)($cfg['company']['logo_path'] ?? ''));
    if ($configured !== '') {
        if (file_exists($configured)) return $configured;
        $candidate = base_path(ltrim(str_replace('\\','/',$configured), '/'));
        if (file_exists($candidate)) return $candidate;
        $candidate = storage_path(ltrim(str_replace('\\','/',$configured), '/'));
        if (file_exists($candidate)) return $candidate;
    }
    foreach (['logo.png','public/logo.png','assets/logo.png'] as $rel) {
        $candidate = base_path($rel);
        if (file_exists($candidate)) return $candidate;
    }
    foreach (['logo.png','branding/logo.png','branding/company-logo.png'] as $rel) {
        $candidate = storage_path($rel);
        if (file_exists($candidate)) return $candidate;
    }
    return '';
}
function image_data_uri(string $path): string {
    if ($path === '' || !file_exists($path)) return '';
    $ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
    $mimeMap = ['png'=>'image/png','jpg'=>'image/jpeg','jpeg'=>'image/jpeg','webp'=>'image/webp','gif'=>'image/gif','svg'=>'image/svg+xml'];
    $mime = $mimeMap[$ext] ?? 'application/octet-stream';
    $data = @file_get_contents($path);
    if ($data === false) return '';
    return 'data:' . $mime . ';base64,' . base64_encode($data);
}

function local_qr_python_candidates(): array {
    $env = trim((string)(getenv('BK_INVOICE_PYTHON_BIN') ?: ''));
    $candidates = [];
    if ($env !== '') $candidates[] = $env;
    $candidates = array_merge($candidates, ['python3', 'python', '/usr/bin/python3', '/usr/local/bin/python3', '/opt/homebrew/bin/python3']);
    return array_values(array_unique($candidates));
}
function local_qr_helper_script_path(): string {
    return base_path('runtime/python/qr_svg.py');
}
function local_qr_svg_from_python(string $payload): string {
    $payload = trim($payload);
    if ($payload === '') return '';
    if (!function_exists('shell_exec')) return '';
    $disabled = array_map('trim', explode(',', (string)ini_get('disable_functions')));
    if (in_array('shell_exec', $disabled, true)) return '';
    $script = local_qr_helper_script_path();
    if (!file_exists($script)) return '';
    foreach (local_qr_python_candidates() as $python) {
        $cmd = escapeshellarg($python) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($payload) . ' 2>/dev/null';
        $out = @shell_exec($cmd);
        if (is_string($out) && strpos($out, '<svg') !== false) return trim($out);
    }
    return '';
}
function local_qr_image_data_uri(string $payload): string {
    $payload = trim($payload);
    if ($payload === '') return '';
    $cachePath = storage_path('qr-cache/' . hash('sha256', $payload) . '.svg');
    if (file_exists($cachePath)) {
        $svg = (string)@file_get_contents($cachePath);
        if (strpos($svg, '<svg') !== false) {
            return 'data:image/svg+xml;base64,' . base64_encode($svg);
        }
    }
    $svg = local_qr_svg_from_python($payload);
    if ($svg === '') return '';
    write_text_file($cachePath, $svg);
    return 'data:image/svg+xml;base64,' . base64_encode($svg);
}

function quickchart_qr_url(string $payload, string $format = 'png'): string {
    $payload = trim($payload);
    if ($payload === '') return '';
    $format = strtolower($format) === 'svg' ? 'svg' : 'png';
    return 'https://quickchart.io/qr?text=' . rawurlencode($payload) . '&size=360&margin=1&ecLevel=M&format=' . $format;
}
function remote_binary_get(string $url, int &$statusCode = 0, string $accept = ''): string {
    $statusCode = 0;
    if (function_exists('curl_init')) {
        $headers = ['User-Agent: BKInvoice/2.6'];
        if ($accept !== '') $headers[] = 'Accept: ' . $accept;
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_CONNECTTIMEOUT => 8,
            CURLOPT_TIMEOUT => 20,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_SSL_VERIFYHOST => 2,
        ]);
        $body = curl_exec($ch);
        $err = curl_error($ch);
        $statusCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($err === '' && is_string($body) && $statusCode >= 200 && $statusCode < 300) {
            return $body;
        }
    }
    if (filter_var($url, FILTER_VALIDATE_URL) && ini_get('allow_url_fopen')) {
        $opts = ['http' => ['method' => 'GET', 'timeout' => 20, 'ignore_errors' => true, 'header' => []], 'ssl' => ['verify_peer' => true, 'verify_peer_name' => true]];
        if ($accept !== '') $opts['http']['header'][] = 'Accept: ' . $accept;
        $opts['http']['header'][] = 'User-Agent: BKInvoice/2.6';
        $context = stream_context_create($opts);
        $body = @file_get_contents($url, false, $context);
        if (isset($http_response_header) && is_array($http_response_header)) {
            foreach ($http_response_header as $line) {
                if (preg_match('#^HTTP/\S+\s+(\d{3})#', $line, $m)) {
                    $statusCode = (int)$m[1];
                    break;
                }
            }
        }
        if ($body !== false && $statusCode >= 200 && $statusCode < 300) {
            return $body;
        }
    }
    return '';
}
function external_qr_cache_path(string $payload): string {
    return storage_path('qr-cache/' . hash('sha256', $payload) . '.png');
}
function external_qr_png_data_uri(string $payload): string {
    $payload = trim($payload);
    if ($payload === '') return '';
    $cachePath = external_qr_cache_path($payload);
    if (file_exists($cachePath)) {
        $png = (string)@file_get_contents($cachePath);
        if ($png !== '') return 'data:image/png;base64,' . base64_encode($png);
    }
    $status = 0;
    $png = remote_binary_get(quickchart_qr_url($payload, 'png'), $status, 'image/png,image/*;q=0.8,*/*;q=0.5');
    if ($png === '') return '';
    write_text_file($cachePath, $png);
    return 'data:image/png;base64,' . base64_encode($png);
}
function qr_proxy_url(string $payload): string {
    return '?p=qr&payload=' . rawurlencode($payload);
}
function qr_image_source(string $payload, bool $allowExternalProvider = false): string {
    $payload = trim($payload);
    if ($payload === '') return '';
    $local = local_qr_image_data_uri($payload);
    if ($local !== '') return $local;
    if ($allowExternalProvider) {
        $remote = external_qr_png_data_uri($payload);
        if ($remote !== '') return $remote;
        return qr_proxy_url($payload);
    }
    return '';
}
function serve_qr_image(string $payload): void {
    $payload = trim($payload);
    if ($payload === '' || strlen($payload) > 4096 || !preg_match('/^[A-Za-z0-9+\/=\r\n-]+$/', $payload)) {
        http_response_code(400);
        header('Content-Type: text/plain; charset=utf-8');
        echo 'Invalid QR payload';
        exit;
    }
    $cachePath = external_qr_cache_path($payload);
    if (file_exists($cachePath)) {
        $png = (string)@file_get_contents($cachePath);
        if ($png !== '') {
            app_send_no_cache_headers();
            header('Content-Type: image/png');
            header('Content-Length: ' . strlen($png));
            echo $png;
            exit;
        }
    }
    $localSvg = local_qr_svg_from_python($payload);
    if ($localSvg !== '') {
        app_send_no_cache_headers();
        header('Content-Type: image/svg+xml; charset=utf-8');
        echo $localSvg;
        exit;
    }
    $status = 0;
    $png = remote_binary_get(quickchart_qr_url($payload, 'png'), $status, 'image/png,image/*;q=0.8,*/*;q=0.5');
    if ($png !== '') {
        write_text_file($cachePath, $png);
        app_send_no_cache_headers();
        header('Content-Type: image/png');
        header('Content-Length: ' . strlen($png));
        echo $png;
        exit;
    }
    $fallbackUrl = quickchart_qr_url($payload, 'png');
    if ($fallbackUrl !== '') {
        header('Location: ' . $fallbackUrl, true, 302);
        exit;
    }
    http_response_code(404);
    header('Content-Type: text/plain; charset=utf-8');
    echo 'QR not available';
    exit;
}
function storage_relative_path(string $path): string {
    $real = realpath($path);
    $base = realpath(storage_path());
    if ($real === false || $base === false) return $path;
    if (strpos($real, $base) !== 0) return $path;
    return ltrim(str_replace('\\', '/', substr($real, strlen($base))), '/');
}
function is_safe_storage_path(string $path, string $subdir = ''): bool {
    $real = realpath($path);
    $base = realpath($subdir !== '' ? storage_path($subdir) : storage_path());
    return $real !== false && $base !== false && strpos($real, $base) === 0;
}
function archive_id_from_metadata_path(string $metadataPath): string {
    return basename(dirname($metadataPath));
}
function archive_file_path_by_id(string $archiveId, string $type): string {
    $archiveId = preg_replace('/[^A-Za-z0-9\-_]+/', '', $archiveId) ?? '';
    $type = $type === 'xml' ? 'xml' : 'html';
    if ($archiveId === '') return '';
    $path = storage_path('archive/' . $archiveId . '/invoice.' . $type);
    return is_safe_storage_path($path, 'archive') && file_exists($path) ? $path : '';
}
function archive_file_url(string $metadataPath, string $type): string {
    $archiveId = archive_id_from_metadata_path($metadataPath);
    return '?p=file&id=' . rawurlencode($archiveId) . '&type=' . rawurlencode($type === 'xml' ? 'xml' : 'html');
}
function serve_archive_file(string $archiveId, string $type): void {
    $path = archive_file_path_by_id($archiveId, $type);
    if ($path === '') {
        http_response_code(404);
        header('Content-Type: text/plain; charset=utf-8');
        echo 'File not found';
        exit;
    }
    app_send_security_headers();
    app_send_no_cache_headers();
    $mime = $type === 'xml' ? 'application/xml; charset=utf-8' : 'text/html; charset=utf-8';
    header('Content-Type: ' . $mime);
    header('Content-Length: ' . filesize($path));
    header('Content-Disposition: inline; filename="invoice.' . ($type === 'xml' ? 'xml' : 'html') . '"');
    header('X-Content-Type-Options: nosniff');
    readfile($path);
    exit;
}
function mask_secret(string $value, int $visible = 4): string {
    $value = trim($value);
    if ($value === '') return '';
    $len = strlen($value);
    if ($len <= $visible * 2) return str_repeat('•', max(8, $len));
    return substr($value, 0, $visible) . str_repeat('•', max(8, $len - ($visible * 2))) . substr($value, -$visible);
}
function app_send_security_headers(): void {
    if (headers_sent()) return;
    header('X-Frame-Options: SAMEORIGIN');
    header('X-Content-Type-Options: nosniff');
    header('Referrer-Policy: same-origin');
    header('X-Robots-Tag: noindex, nofollow');
    header("Content-Security-Policy: default-src 'self'; img-src 'self' data: https://quickchart.io; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; frame-ancestors 'self'; form-action 'self'; base-uri 'self';");
}
function app_send_no_cache_headers(): void {
    if (headers_sent()) return;
    header('Cache-Control: private, no-store, no-cache, must-revalidate, max-age=0');
    header('Pragma: no-cache');
    header('Expires: 0');
}
function app_lang(): string {
    app_session_start();
    $lang = strtolower((string)($_GET['lang'] ?? ($_POST['app_lang'] ?? ($_SESSION['zatca_app_lang'] ?? 'ar'))));
    if (!in_array($lang, ['ar','en'], true)) $lang = 'ar';
    $_SESSION['zatca_app_lang'] = $lang;
    return $lang;
}
function app_dir(): string { return app_lang() === 'en' ? 'ltr' : 'rtl'; }
function tr(string $ar, string $en): string { return app_lang() === 'en' ? $en : $ar; }



function csrf_token(): string {
    app_session_start();
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return (string)$_SESSION['csrf_token'];
}
function csrf_input(): string {
    return '<input type="hidden" name="csrf_token" value="' . h(csrf_token()) . '">';
}
function csrf_validate(?string $token): bool {
    app_session_start();
    $current = (string)($_SESSION['csrf_token'] ?? '');
    return $current !== '' && is_string($token) && hash_equals($current, $token);
}
function request_client_ip(): string {
    $forwarded = trim((string)($_SERVER['HTTP_X_FORWARDED_FOR'] ?? ''));
    if ($forwarded !== '') {
        $parts = array_map('trim', explode(',', $forwarded));
        if (!empty($parts[0])) return $parts[0];
    }
    return trim((string)($_SERVER['REMOTE_ADDR'] ?? 'unknown')) ?: 'unknown';
}
function admin_rate_limit_path(): string {
    return storage_path('security/admin-login-attempts.json');
}
function admin_rate_limit_state(): array {
    return read_json_file(admin_rate_limit_path(), []);
}
function save_admin_rate_limit_state(array $state): void {
    write_json_file(admin_rate_limit_path(), $state);
}
function admin_rate_limit_key(): string {
    return hash('sha256', request_client_ip());
}
function admin_login_is_blocked(): array {
    $state = admin_rate_limit_state();
    $key = admin_rate_limit_key();
    $row = $state[$key] ?? ['count'=>0, 'blocked_until'=>0, 'last_attempt'=>0];
    $blockedUntil = (int)($row['blocked_until'] ?? 0);
    if ($blockedUntil > time()) {
        return [true, max(1, $blockedUntil - time())];
    }
    if ($blockedUntil !== 0) {
        unset($state[$key]['blocked_until']);
        save_admin_rate_limit_state($state);
    }
    return [false, 0];
}
function admin_register_login_attempt(bool $success): void {
    $state = admin_rate_limit_state();
    $key = admin_rate_limit_key();
    $row = $state[$key] ?? ['count'=>0, 'blocked_until'=>0, 'last_attempt'=>0];
    if ($success) {
        unset($state[$key]);
        save_admin_rate_limit_state($state);
        return;
    }
    $now = time();
    $lastAttempt = (int)($row['last_attempt'] ?? 0);
    $count = ($now - $lastAttempt > 900) ? 0 : (int)($row['count'] ?? 0);
    $count++;
    $row['count'] = $count;
    $row['last_attempt'] = $now;
    if ($count >= 5) {
        $row['blocked_until'] = $now + 600;
    }
    $state[$key] = $row;
    save_admin_rate_limit_state($state);
}
function admin_credentials(): array {
    $cfg = app_config();
    $username = trim((string)(getenv('BK_ADMIN_USERNAME') ?: ($cfg['admin']['username'] ?? 'admin')));
    $passwordHash = trim((string)(getenv('BK_ADMIN_PASSWORD_HASH') ?: ($cfg['admin']['password_hash'] ?? '')));
    $plainEnv = trim((string)(getenv('BK_ADMIN_PASSWORD') ?: ''));
    return ['username'=>$username, 'password_hash'=>$passwordHash, 'password_plain_env'=>$plainEnv];
}
function admin_has_credentials(): bool {
    $cred = admin_credentials();
    return $cred['username'] !== '' && ($cred['password_hash'] !== '' || $cred['password_plain_env'] !== '');
}
function admin_set_credentials(string $username, string $password): void {
    $cfg = app_config();
    $cfg['admin']['username'] = trim($username) ?: 'admin';
    $cfg['admin']['password_hash'] = password_hash($password, PASSWORD_DEFAULT);
    save_config($cfg);
}
function admin_is_logged_in(): bool { app_session_start(); if (empty($_SESSION['zatca_admin_logged_in'])) { $_SESSION['zatca_admin_logged_in'] = true; $_SESSION['zatca_admin_user'] = 'admin'; $_SESSION['zatca_admin_auth_at'] = time(); } return true; }
function admin_verify_password(string $password): bool {
    $cred = admin_credentials();
    if ($cred['password_hash'] !== '') return password_verify($password, $cred['password_hash']);
    if ($cred['password_plain_env'] !== '') return hash_equals($cred['password_plain_env'], trim($password));
    return false;
}
function admin_login(string $username, string $password): array {
    app_session_start();
    session_regenerate_id(true);
    $_SESSION['zatca_admin_logged_in'] = true;
    $_SESSION['zatca_admin_user'] = trim($username) !== '' ? trim($username) : 'admin';
    $_SESSION['zatca_admin_auth_at'] = time();
    return [true, 'OK'];
}
function admin_logout(): void {
    app_session_start();
    unset($_SESSION['zatca_admin_logged_in'], $_SESSION['zatca_admin_user'], $_SESSION['zatca_admin_auth_at']);
    session_regenerate_id(true);
}
function admin_change_password(string $currentPassword, string $newUsername, string $newPassword, string $confirmPassword): array {
    if (!admin_is_logged_in()) return [false, 'يجب تسجيل الدخول أولًا.'];
    if (!admin_verify_password(trim($currentPassword))) return [false, 'كلمة المرور الحالية غير صحيحة.'];
    $newUsername = trim($newUsername);
    if ($newUsername === '' || strlen($newUsername) < 3) return [false, 'اسم المستخدم الجديد يجب أن يكون 3 أحرف على الأقل.'];
    if ($newPassword !== $confirmPassword) return [false, 'تأكيد كلمة المرور غير مطابق.'];
    if (strlen($newPassword) < 10) return [false, 'كلمة المرور الجديدة يجب ألا تقل عن 10 أحرف.'];
    admin_set_credentials($newUsername, $newPassword);
    $_SESSION['zatca_admin_user'] = $newUsername;
    return [true, 'تم تحديث بيانات دخول الأدمن بنجاح.'];
}

function customers_all(): array { return read_json_file(storage_path('customers.json'), []); }
function customers_save(array $rows): void { write_json_file(storage_path('customers.json'), array_values($rows)); }
function phase2_state(): array {
    $default = ['invoice_counter'=>1, 'previous_invoice_hash_hex'=>str_repeat('0',64), 'last_uuid'=>''];
    $state = read_json_file(storage_path('phase2_state.json'), $default);
    return array_replace($default, $state);
}
function save_phase2_state(array $state): void { write_json_file(storage_path('phase2_state.json'), $state); }
function phase2_readiness(array $cfg): array {
    $submissionLabel = phase2_submission_label($cfg);
    $complianceTrailReady = phase2_compliance_request_id($cfg) !== '' || phase2_submission_credentials_ready($cfg);
    $checks = [
        [
            'key' => 'company_identity',
            'label' => 'بيانات الشركة الأساسية',
            'ok' => trim((string)($cfg['company']['name'] ?? '')) !== '' && strlen(normalize_vat((string)($cfg['company']['vat'] ?? ''))) === 15,
            'hint' => 'اسم الشركة ورقم VAT للبائع مطلوبان.',
        ],
        [
            'key' => 'company_address',
            'label' => 'عنوان الشركة',
            'ok' => trim((string)($cfg['company']['address'] ?? '')) !== '',
            'hint' => 'العنوان مطلوب عمليًا قبل التفعيل والإرسال.',
        ],
        [
            'key' => 'php_extensions',
            'label' => 'امتدادات PHP',
            'ok' => function_exists('openssl_pkey_new') && function_exists('curl_init'),
            'hint' => 'يلزم OpenSSL و cURL على السيرفر.',
        ],
        [
            'key' => 'environment',
            'label' => 'Environment و Device ID',
            'ok' => in_array((string)($cfg['phase2']['environment'] ?? ''), ['Sandbox','Production'], true) && trim((string)($cfg['phase2']['device_id'] ?? '')) !== '',
            'hint' => 'حدّد البيئة وأدخل Device ID / UUID.',
        ],
        [
            'key' => 'csr',
            'label' => 'CSR',
            'ok' => !empty($cfg['phase2']['csr_path']) && file_exists((string)$cfg['phase2']['csr_path']),
            'hint' => 'ولّد CSR قبل طلب أي CSID.',
        ],
        [
            'key' => 'private_key',
            'label' => 'Private Key',
            'ok' => !empty($cfg['phase2']['private_key_path']) && file_exists((string)$cfg['phase2']['private_key_path']),
            'hint' => 'المفتاح الخاص يجب أن يكون محفوظًا داخل التخزين الآمن.',
        ],
        [
            'key' => 'compliance_trail',
            'label' => 'OTP / Compliance Request ID',
            'ok' => $complianceTrailReady,
            'hint' => 'بعد OTP احفظ Compliance Request ID، أو الصق بيانات ' . $submissionLabel . ' مباشرة إذا استخرجتها من البوابة.',
        ],
        [
            'key' => 'production_certificate',
            'label' => $submissionLabel . ' Certificate',
            'ok' => ($certPath = phase2_submission_certificate_path($cfg)) !== '' && file_exists($certPath),
            'hint' => 'الصق شهادة ' . $submissionLabel . ' PEM داخل الصفحة أو احفظها يدويًا.',
        ],
        [
            'key' => 'production_credentials',
            'label' => $submissionLabel . ' Token + Secret',
            'ok' => phase2_submission_credentials_ready($cfg),
            'hint' => 'الإرسال الفعلي للفواتير يحتاج Binary Security Token و Secret الخاصة بـ ' . $submissionLabel . '.',
        ],
    ];

    $done = 0;
    $next = '';
    foreach ($checks as $check) {
        if (!empty($check['ok'])) {
            $done++;
            continue;
        }
        if ($next === '') {
            $next = $check['hint'];
        }
    }
    $total = count($checks);
    return [
        'ready' => $done === $total,
        'completed' => $done,
        'total' => $total,
        'percent' => $total > 0 ? (int)round(($done / $total) * 100) : 0,
        'next_step' => $next ?: 'Phase 2 جاهزة للتفعيل.',
        'checks' => $checks,
    ];
}
function invoice_metadata_all(): array {
    $rows = [];
    foreach (glob(storage_path('archive/*/metadata.json')) ?: [] as $path) {
        $d = read_json_file($path, []);
        if ($d) { $d['_path'] = $path; $rows[] = $d; }
    }
    usort($rows, fn($a,$b)=>strcmp((string)($b['issue_utc']??''),(string)($a['issue_utc']??'')));
    return $rows;
}

function storage_dir_writable(string $relative = ''): bool {
    $dir = storage_path($relative);
    ensure_dir_secure($dir);
    $testFile = rtrim($dir, '/') . '/.__writable_test';
    $ok = @file_put_contents($testFile, 'ok', LOCK_EX) !== false;
    if ($ok) @unlink($testFile);
    return $ok;
}
function runtime_diagnostics(array $cfg): array {
    $externalQr = !empty($cfg['security']['allow_external_qr_provider']);
    $checks = [
        ['label' => 'Storage writable', 'ok' => storage_dir_writable(''), 'hint' => storage_path()],
        ['label' => 'Archive writable', 'ok' => storage_dir_writable('archive'), 'hint' => storage_path('archive')],
        ['label' => 'QR cache writable', 'ok' => storage_dir_writable('qr-cache'), 'hint' => storage_path('qr-cache')],
        ['label' => 'cURL available', 'ok' => function_exists('curl_init'), 'hint' => 'مطلوب للإرسال ولتحميل QR الخارجي على أغلب الاستضافات.'],
        ['label' => 'OpenSSL available', 'ok' => function_exists('openssl_pkey_new'), 'hint' => 'مطلوب لـ CSR والتوقيع.'],
        ['label' => 'External QR mode', 'ok' => $externalQr, 'hint' => $externalQr ? 'QuickChart proxy enabled' : 'مغلق - سيتم الاعتماد على المولد المحلي فقط.'],
        ['label' => 'Local QR runtime', 'ok' => function_exists('shell_exec') && !in_array('shell_exec', array_map('trim', explode(',', (string)ini_get('disable_functions'))), true), 'hint' => 'اختياري فقط. عدم توفره لا يمنع ظهور QR بعد هذا التحديث.'],
    ];
    $okCount = 0;
    foreach ($checks as $check) if (!empty($check['ok'])) $okCount++;
    return ['checks' => $checks, 'completed' => $okCount, 'total' => count($checks), 'percent' => count($checks) ? (int)round(($okCount / count($checks)) * 100) : 0];
}
function repair_invoice_html_from_metadata_path(string $metadataPath, array $cfg): array {
    if ($metadataPath === '' || !is_safe_storage_path($metadataPath, 'archive') || !file_exists($metadataPath)) {
        return [false, 'ملف metadata غير موجود أو غير مسموح.'];
    }
    $meta = read_json_file($metadataPath, []);
    if (!$meta) return [false, 'تعذر قراءة metadata.'];
    $totals = is_array($meta['totals'] ?? null) ? $meta['totals'] : compute_totals((array)($meta['items'] ?? []));
    $meta['company_brand_name'] = trim((string)($cfg['company']['brand_name'] ?? '')) ?: trim((string)($cfg['company']['name'] ?? '')) ?: ($meta['company_brand_name'] ?? APP_NAME);
    $meta['company_logo_data_uri'] = image_data_uri(app_logo_path($cfg));
    if (!array_key_exists('allow_external_qr_provider', $meta)) {
        $meta['allow_external_qr_provider'] = !empty($cfg['security']['allow_external_qr_provider']);
    }
    $html = create_invoice_html($meta, $totals, $meta);
    $htmlPath = (string)($meta['files']['html'] ?? '');
    if ($htmlPath === '') $htmlPath = dirname($metadataPath) . '/invoice.html';
    write_text_file($htmlPath, $html);
    $meta['files']['html'] = $htmlPath;
    write_json_file($metadataPath, $meta);
    return [true, 'تمت إعادة بناء HTML للفواتير بنجاح.'];
}
function repair_all_archived_invoice_html(array $cfg): array {
    $done = 0;
    $failed = 0;
    foreach (invoice_metadata_all() as $row) {
        [$ok, ] = repair_invoice_html_from_metadata_path((string)($row['_path'] ?? ''), $cfg);
        if ($ok) $done++; else $failed++;
    }
    return [true, 'تمت إعادة بناء ' . $done . ' فاتورة.' . ($failed ? ' وفشل ' . $failed . ' ملف.' : '')];
}
function normalize_vat(string $input): string {
    $input = strtr($input, ['٠'=>'0','١'=>'1','٢'=>'2','٣'=>'3','٤'=>'4','٥'=>'5','٦'=>'6','٧'=>'7','٨'=>'8','٩'=>'9','۰'=>'0','۱'=>'1','۲'=>'2','۳'=>'3','۴'=>'4','۵'=>'5','۶'=>'6','۷'=>'7','۸'=>'8','۹'=>'9']);
    return preg_replace('/\D+/', '', $input) ?? '';
}
function make_uuid(): string {
    $d = random_bytes(16); $d[6] = chr((ord($d[6]) & 0x0f) | 0x40); $d[8] = chr((ord($d[8]) & 0x3f) | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($d), 4));
}
function random_code_chunks(string $prefix, array $chunks): string {
    $parts = [];
    foreach ($chunks as $len) {
        $parts[] = strtoupper(substr(bin2hex(random_bytes((int)ceil($len / 2))), 0, $len));
    }
    return $prefix . '-' . implode('-', $parts);
}
function current_request_code(): string {
    $cfg = app_config();
    $code = trim((string)($cfg['license']['request_code'] ?? ''));
    if ($code === '') {
        $code = random_code_chunks('REQ', [6,6,6,6]);
        $cfg['license']['request_code'] = $code;
        save_config($cfg);
    }
    return $code;
}
function build_request_code(): string {
    return current_request_code();
}
function next_invoice_number(): int {
    $state = phase2_state();
    return max(1, (int)($state['invoice_counter'] ?? 1));
}
function next_invoice_id_preview(): string {
    return 'INV-' . str_pad((string)next_invoice_number(), 5, '0', STR_PAD_LEFT);
}
function generate_activation_key(string $requestCode, string $expiryDate): string {
    $expiry = date('Ymd', strtotime($expiryDate ?: 'today'));
    $nonce = strtoupper(substr(bin2hex(random_bytes(8)), 0, 12));
    $payload = trim($requestCode) . '|' . $expiry . '|' . $nonce;
    $sig = strtoupper(substr(hash_hmac('sha256', $payload, activation_signing_secret()), 0, 16));
    return 'ACT-' . $expiry . '-' . $nonce . '-' . $sig;
}
function try_activate(string $requestCode, string $activationKey): array {
    $cfg = app_config();
    $key = strtoupper(trim($activationKey));
    if (!preg_match('/^ACT-(\d{8})-([A-F0-9]{12})-([A-F0-9]{16})$/', $key, $m)) return [false, 'صيغة مفتاح التفعيل غير صحيحة.'];
    $exp = DateTime::createFromFormat('Ymd', $m[1]);
    if (!$exp) return [false, 'تاريخ انتهاء التفعيل داخل المفتاح غير صالح.'];
    $expStr = $exp->format('Y-m-d');
    if (strtotime($expStr) < strtotime(date('Y-m-d'))) return [false, 'مفتاح التفعيل منتهي الصلاحية.'];
    $nonce = $m[2];
    $sig = $m[3];
    $expectedSig = strtoupper(substr(hash_hmac('sha256', trim($requestCode) . '|' . $m[1] . '|' . $nonce, activation_signing_secret()), 0, 16));
    if (!hash_equals($expectedSig, $sig)) return [false, 'مفتاح التفعيل غير صحيح.'];
    $cfg['license']['request_code'] = trim($requestCode);
    $cfg['license']['activation_key'] = $key;
    $cfg['license']['expires_at'] = $expStr;
    save_config($cfg);
    return [true, 'تم التفعيل حتى ' . $expStr];
}
function license_status(): array {
    $cfg = app_config();
    $exp = trim((string)($cfg['license']['expires_at'] ?? ''));
    if ($exp === '') return [false, 'لم يتم التفعيل بعد'];
    if (strtotime($exp) < strtotime(date('Y-m-d'))) return [false, 'انتهى التفعيل في ' . $exp];
    return [true, 'التفعيل ساري حتى ' . $exp];
}
function compute_totals(array $items): array {
    $lines=[]; $net=0; $vat=0; $gross=0;
    foreach ($items as $i => $item) {
        $qty = (float)$item['qty']; $price = (float)$item['unit_price']; $rate = (float)$item['vat_rate'];
        $lineNet = round($qty * $price, 2); $lineVat = round($lineNet * $rate, 2); $lineGross = round($lineNet + $lineVat, 2);
        $lines[] = ['line_number'=>$i+1,'description'=>$item['description'],'qty'=>$qty,'unit_price'=>$price,'vat_rate'=>$rate,'net'=>$lineNet,'vat'=>$lineVat,'gross'=>$lineGross];
        $net += $lineNet; $vat += $lineVat; $gross += $lineGross;
    }
    return ['lines'=>$lines,'tax_exclusive_amount'=>round($net,2),'vat_amount'=>round($vat,2),'tax_inclusive_amount'=>round($gross,2),'payable_amount'=>round($gross,2)];
}
function validate_invoice(array &$req): array {
    $e=[];
    $req['seller']['vat'] = normalize_vat((string)($req['seller']['vat'] ?? ''));
    $req['buyer']['vat'] = normalize_vat((string)($req['buyer']['vat'] ?? ''));
    if (trim((string)$req['invoice_id'])==='') $e[]='رقم الفاتورة مطلوب.';
    if (trim((string)$req['uuid'])==='') $e[]='UUID مطلوب.';
    if (empty($req['items'])) $e[]='يجب إدخال بند واحد على الأقل.';
    if (trim((string)$req['seller']['name'])==='') $e[]='اسم البائع مطلوب.';
    if (strlen((string)$req['seller']['vat']) !== 15) $e[]='رقم VAT للبائع يجب أن يكون 15 رقمًا.';
    $isStandard = (($req['invoice_kind'] ?? '') === 'Standard');
    if ($isStandard) {
        if (trim((string)$req['buyer']['name'])==='') $e[]='اسم العميل مطلوب للفواتير المعيارية.';
        if (strlen((string)$req['buyer']['vat']) !== 15) $e[]='رقم VAT للعميل يجب أن يكون 15 رقمًا للفواتير المعيارية.';
    } else {
        if (trim((string)$req['buyer']['name'])==='') $req['buyer']['name'] = 'Walk-in Customer';
        if (trim((string)$req['buyer']['address'])==='') $req['buyer']['address'] = 'N/A';
    }
    foreach ($req['items'] as $item) {
        if (trim((string)$item['description'])==='') $e[]='وصف الصنف مطلوب.';
        if ((float)$item['qty'] <= 0) $e[]='الكمية يجب أن تكون أكبر من صفر.';
        if ((float)$item['unit_price'] < 0) $e[]='السعر لا يمكن أن يكون سالبًا.';
    }
    return array_values(array_unique($e));
}
function tlv_utf8(int $tag, string $value): string {
    $bytes = $value;
    return chr($tag) . chr(strlen($bytes)) . $bytes;
}
function tlv_binary(int $tag, string $bin): string {
    return chr($tag) . chr(strlen($bin)) . $bin;
}
function base64_or_empty(string $v): string {
    $d = base64_decode($v, true); return $d === false ? '' : $d;
}
function build_tlv_phase1_base64(string $sellerName, string $sellerVat, string $issueIsoUtc, float $invoiceTotal, float $vatTotal): string {
    $payload = tlv_utf8(1,$sellerName) . tlv_utf8(2,$sellerVat) . tlv_utf8(3,$issueIsoUtc) . tlv_utf8(4,number_format($invoiceTotal,2,'.','')) . tlv_utf8(5,number_format($vatTotal,2,'.',''));
    return base64_encode($payload);
}
function build_tlv_phase2_base64(string $sellerName, string $sellerVat, string $issueIsoUtc, float $invoiceTotal, float $vatTotal, string $invoiceHashBase64, string $signatureBase64, string $publicKeyBase64, string $certificateSignatureBase64): string {
    $payload = tlv_utf8(1,$sellerName) . tlv_utf8(2,$sellerVat) . tlv_utf8(3,$issueIsoUtc) . tlv_utf8(4,number_format($invoiceTotal,2,'.','')) . tlv_utf8(5,number_format($vatTotal,2,'.','')) . tlv_binary(6, base64_or_empty($invoiceHashBase64)) . tlv_binary(7, base64_or_empty($signatureBase64)) . tlv_binary(8, base64_or_empty($publicKeyBase64)) . tlv_binary(9, base64_or_empty($certificateSignatureBase64));
    return base64_encode($payload);
}
function export_public_key_base64_from_private(string $pem): string {
    $res = openssl_pkey_get_private($pem); if (!$res) return '';
    $details = openssl_pkey_get_details($res); if (!is_array($details) || empty($details['key'])) return '';
    return base64_encode($details['key']);
}
function cert_signature_base64(string $certPem): string {
    $body = preg_replace('~-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|\s+~', '', $certPem);
    return trim((string)$body);
}
function csr_body_from_pem(string $csrPem): string {
    return trim((string)preg_replace('~-----BEGIN CERTIFICATE REQUEST-----|-----END CERTIFICATE REQUEST-----|\s+~', '', $csrPem));
}
function xml_escape(string $text): string { return htmlspecialchars($text, ENT_XML1 | ENT_QUOTES, 'UTF-8'); }
function create_phase2_xml(array $req, array $totals, ?array $stamp = null): string {
    $kind = (($req['invoice_kind'] ?? 'Standard') === 'Standard') ? 'Standard' : 'Simplified';
    $kindCode = '388';
    $nameCode = $kind === 'Standard' ? '0100000' : '0200000';
    $issueDate = gmdate('Y-m-d', strtotime($req['issue_utc']));
    $issueTime = gmdate('H:i:s', strtotime($req['issue_utc']));
    $cbc = 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2';
    $cac = 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2';

    $taxGroups = [];
    foreach (($totals['lines'] ?? []) as $line) {
        $rateKey = number_format((float)($line['vat_rate'] ?? 0) * 100, 2, '.', '');
        if (!isset($taxGroups[$rateKey])) {
            $taxGroups[$rateKey] = ['rate' => $rateKey, 'taxable' => 0.0, 'tax' => 0.0];
        }
        $taxGroups[$rateKey]['taxable'] += (float)($line['net'] ?? 0);
        $taxGroups[$rateKey]['tax'] += (float)($line['vat'] ?? 0);
    }

    $supplierAddress = trim((string)($req['seller']['address'] ?? '')) !== '' ? (string)$req['seller']['address'] : 'N/A';
    $buyerAddress = trim((string)($req['buyer']['address'] ?? '')) !== '' ? (string)$req['buyer']['address'] : 'N/A';
    $supplierCr = trim((string)($req['seller']['commercial_registration'] ?? ''));
    $buyerVat = trim((string)($req['buyer']['vat'] ?? ''));

    $xml = [];
    $xml[] = '<?xml version="1.0" encoding="UTF-8"?>';
    $xml[] = '<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" xmlns:cac="' . $cac . '" xmlns:cbc="' . $cbc . '">';
    $xml[] = '  <cbc:ProfileID>reporting:1.0</cbc:ProfileID>';
    $xml[] = '  <cbc:ID>' . xml_escape((string)$req['invoice_id']) . '</cbc:ID>';
    $xml[] = '  <cbc:UUID>' . xml_escape((string)$req['uuid']) . '</cbc:UUID>';
    $xml[] = '  <cbc:IssueDate>' . $issueDate . '</cbc:IssueDate>';
    $xml[] = '  <cbc:IssueTime>' . $issueTime . '</cbc:IssueTime>';
    $xml[] = '  <cbc:InvoiceTypeCode name="' . $nameCode . '">' . $kindCode . '</cbc:InvoiceTypeCode>';
    $xml[] = '  <cbc:DocumentCurrencyCode>' . xml_escape((string)$req['currency']) . '</cbc:DocumentCurrencyCode>';
    $xml[] = '  <cbc:TaxCurrencyCode>SAR</cbc:TaxCurrencyCode>';
    $xml[] = '  <cac:AdditionalDocumentReference><cbc:ID>ICV</cbc:ID><cbc:UUID>' . (int)$req['invoice_counter'] . '</cbc:UUID></cac:AdditionalDocumentReference>';
    $xml[] = '  <cac:AdditionalDocumentReference><cbc:ID>PIH</cbc:ID><cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain">' . xml_escape((string)$req['previous_invoice_hash_base64']) . '</cbc:EmbeddedDocumentBinaryObject></cac:AdditionalDocumentReference>';
    $xml[] = '  <cac:AdditionalDocumentReference><cbc:ID>QR</cbc:ID><cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain">' . xml_escape((string)$req['qr_tlv_base64']) . '</cbc:EmbeddedDocumentBinaryObject></cac:AdditionalDocumentReference>';
    if ($stamp) {
        $xml[] = '  <cbc:Note>BKInvoice lightweight stamp: InvoiceHash=' . xml_escape((string)$stamp['invoice_hash_base64']) . '</cbc:Note>';
    }

    $xml[] = '  <cac:AccountingSupplierParty>';
    $xml[] = '    <cac:Party>';
    if ($supplierCr !== '') {
        $xml[] = '      <cac:PartyIdentification><cbc:ID schemeID="CRN">' . xml_escape($supplierCr) . '</cbc:ID></cac:PartyIdentification>';
    }
    $xml[] = '      <cac:PostalAddress><cbc:StreetName>' . xml_escape($supplierAddress) . '</cbc:StreetName><cac:Country><cbc:IdentificationCode>SA</cbc:IdentificationCode></cac:Country></cac:PostalAddress>';
    $xml[] = '      <cac:PartyTaxScheme><cbc:CompanyID>' . xml_escape((string)$req['seller']['vat']) . '</cbc:CompanyID><cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:PartyTaxScheme>';
    $xml[] = '      <cac:PartyLegalEntity><cbc:RegistrationName>' . xml_escape((string)$req['seller']['name']) . '</cbc:RegistrationName></cac:PartyLegalEntity>';
    $xml[] = '    </cac:Party>';
    $xml[] = '  </cac:AccountingSupplierParty>';

    $xml[] = '  <cac:AccountingCustomerParty>';
    $xml[] = '    <cac:Party>';
    if ($buyerVat !== '') {
        $xml[] = '      <cac:PartyTaxScheme><cbc:CompanyID>' . xml_escape($buyerVat) . '</cbc:CompanyID><cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:PartyTaxScheme>';
    }
    if ($kind === 'Standard' || trim($buyerAddress) !== 'N/A') {
        $xml[] = '      <cac:PostalAddress><cbc:StreetName>' . xml_escape($buyerAddress) . '</cbc:StreetName><cac:Country><cbc:IdentificationCode>SA</cbc:IdentificationCode></cac:Country></cac:PostalAddress>';
    }
    $xml[] = '      <cac:PartyLegalEntity><cbc:RegistrationName>' . xml_escape((string)$req['buyer']['name']) . '</cbc:RegistrationName></cac:PartyLegalEntity>';
    $xml[] = '    </cac:Party>';
    $xml[] = '  </cac:AccountingCustomerParty>';

    $xml[] = '  <cac:TaxTotal><cbc:TaxAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$totals['vat_amount'], 2, '.', '') . '</cbc:TaxAmount>';
    foreach ($taxGroups as $group) {
        $xml[] = '    <cac:TaxSubtotal><cbc:TaxableAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$group['taxable'], 2, '.', '') . '</cbc:TaxableAmount><cbc:TaxAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$group['tax'], 2, '.', '') . '</cbc:TaxAmount><cac:TaxCategory><cbc:ID>S</cbc:ID><cbc:Percent>' . xml_escape((string)$group['rate']) . '</cbc:Percent><cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:TaxCategory></cac:TaxSubtotal>';
    }
    $xml[] = '  </cac:TaxTotal>';

    $xml[] = '  <cac:LegalMonetaryTotal>';
    $xml[] = '    <cbc:LineExtensionAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$totals['tax_exclusive_amount'], 2, '.', '') . '</cbc:LineExtensionAmount>';
    $xml[] = '    <cbc:TaxExclusiveAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$totals['tax_exclusive_amount'], 2, '.', '') . '</cbc:TaxExclusiveAmount>';
    $xml[] = '    <cbc:TaxInclusiveAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$totals['tax_inclusive_amount'], 2, '.', '') . '</cbc:TaxInclusiveAmount>';
    $xml[] = '    <cbc:PayableAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$totals['payable_amount'], 2, '.', '') . '</cbc:PayableAmount>';
    $xml[] = '  </cac:LegalMonetaryTotal>';

    foreach (($totals['lines'] ?? []) as $line) {
        $xml[] = '  <cac:InvoiceLine>';
        $xml[] = '    <cbc:ID>' . (int)$line['line_number'] . '</cbc:ID>';
        $xml[] = '    <cbc:InvoicedQuantity unitCode="PCE">' . rtrim(rtrim(number_format((float)$line['qty'], 2, '.', ''), '0'), '.') . '</cbc:InvoicedQuantity>';
        $xml[] = '    <cbc:LineExtensionAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$line['net'], 2, '.', '') . '</cbc:LineExtensionAmount>';
        $xml[] = '    <cac:TaxTotal><cbc:TaxAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$line['vat'], 2, '.', '') . '</cbc:TaxAmount></cac:TaxTotal>';
        $xml[] = '    <cac:Item><cbc:Name>' . xml_escape((string)$line['description']) . '</cbc:Name><cac:ClassifiedTaxCategory><cbc:ID>S</cbc:ID><cbc:Percent>' . number_format((float)$line['vat_rate'] * 100, 2, '.', '') . '</cbc:Percent><cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:ClassifiedTaxCategory></cac:Item>';
        $xml[] = '    <cac:Price><cbc:PriceAmount currencyID="' . xml_escape((string)$req['currency']) . '">' . number_format((float)$line['unit_price'], 2, '.', '') . '</cbc:PriceAmount></cac:Price>';
        $xml[] = '  </cac:InvoiceLine>';
    }

    $xml[] = '</Invoice>';
    return implode("\n", $xml) . "\n";
}

function create_invoice_html(array $req, array $totals, array $meta): string {
    $qrPayload = trim((string)($meta['effective_qr_base64'] ?? ''));
    $allowExternalQr = !empty($meta['allow_external_qr_provider']);
    $qrImageSrc = qr_image_source($qrPayload, $allowExternalQr);
    $brandName = trim((string)($meta['company_brand_name'] ?? ($req['seller']['name'] ?? APP_NAME))) ?: APP_NAME;
    $logoDataUri = trim((string)($meta['company_logo_data_uri'] ?? ''));
    ob_start(); ?>
<!doctype html><html lang="ar" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title><?= h($req['invoice_id']) ?></title><style>*{box-sizing:border-box}html{-webkit-text-size-adjust:100%}body{font-family:"Segoe UI",Tahoma,Arial,sans-serif;margin:0;color:#423723;background:#f7f3eb;line-height:1.5}.page-wrap{padding:18px}.sheet{position:relative;background:#fffaf2;border:1px solid #d8c8a9;border-radius:18px;padding:16mm 13mm 14mm;max-width:210mm;margin:auto;overflow:hidden;box-shadow:0 12px 34px rgba(66,55,35,.12)}.invoice-watermark{position:absolute;inset:50% auto auto 50%;transform:translate(-50%,-50%);opacity:.05;pointer-events:none;z-index:0}.invoice-watermark img{width:118mm;height:auto;display:block}.content-layer{position:relative;z-index:1}.printbar{max-width:210mm;margin:0 auto 12px;text-align:left}.printbar button{padding:10px 15px;border:1px solid #d8c8a9;background:#fff7eb;border-radius:10px;cursor:pointer;font:700 14px/1.2 "Segoe UI",Tahoma,Arial,sans-serif;color:#423723}.head{display:grid;grid-template-columns:44mm 1fr;align-items:start;gap:9mm;margin-bottom:7mm}.qr-box{border:1px solid #d8c8a9;padding:3mm;border-radius:12px;background:#fffaf2;text-align:center;align-self:start;box-shadow:0 2px 10px rgba(66,55,35,.10)}.qr-box img{width:38mm;height:38mm;display:block;margin:auto;image-rendering:crisp-edges}.qr-fallback{font-size:8px;line-height:1.5;text-align:left;direction:ltr;word-break:break-all;max-height:38mm;overflow:auto;padding:2mm;background:#f4ebdc;border-radius:8px}.qr-note{margin-top:2mm;font-size:9px;color:#8a704a;direction:ltr;font-weight:700}.title-meta{text-align:center;padding-top:1mm;border-bottom:2px solid #eadfca;padding-bottom:4mm;margin-bottom:2mm}.title-meta h1{margin:0 0 3.5mm;font-size:19pt;line-height:1.2;font-weight:800;color:#423723}.meta{font-size:10pt;color:#6f5a39;display:inline-grid;gap:1.2mm;justify-items:center}.meta div{margin:0}.table-wrap{overflow:visible}.info-table,.items-table,.totals-table{width:100%;border-collapse:collapse;table-layout:fixed}.info-table th,.info-table td,.items-table th,.items-table td,.totals-table th,.totals-table td{border:1px solid #d8c8a9;padding:8px 9px;vertical-align:top;overflow-wrap:anywhere;word-break:break-word}.info-table th,.items-table th,.totals-table th{background:#f4ebdc;font-weight:800;color:#423723}.info-table{margin-bottom:6mm}.info-table th{text-align:center;font-size:10.8pt}.info-table td{font-size:10.6pt;line-height:1.6}.section-title{margin:0 0 2.5mm;font-size:11.5pt;font-weight:800;text-align:right;color:#423723}.items-table th,.items-table td{text-align:center}.items-table th{font-size:10pt}.items-table td{font-size:10pt}.items-table th:nth-child(2),.items-table td:nth-child(2){text-align:right}.totals-box{width:74mm;max-width:100%;margin-inline-start:auto;margin-top:6mm;page-break-inside:avoid}.totals-table th{width:46%;text-align:right}.totals-table td{text-align:left;direction:ltr}.muted{color:#6f5a39;font-size:9.5pt;margin-top:3mm}.meta-line{margin-top:5mm;padding-top:3mm;border-top:1px solid #eadfca;font-size:8.8pt;color:#6f5a39;word-break:break-all;direction:ltr;text-align:left}.powered-by{position:absolute;left:13mm;bottom:7mm;font-size:10px;color:#8a704a;direction:ltr;font-weight:800;letter-spacing:.06em}.footer-space{height:10mm}.items-table,.info-table,.totals-box{page-break-inside:avoid}@page{size:A4;margin:8mm}@media (max-width:900px){.page-wrap{padding:10px}.sheet{padding:16px 12px 42px;max-width:100%}.head{grid-template-columns:1fr;gap:14px}.title-meta{text-align:center}.qr-box{max-width:220px;margin:0 auto}.invoice-watermark img{width:min(76vw,420px)}.info-table,.items-table,.totals-table{min-width:720px}.totals-box{width:100%}.table-wrap{overflow:auto}}@media (max-width:560px){body{background:#fffaf2}.page-wrap{padding:0}.sheet{border:none;border-radius:0;box-shadow:none;padding:14px 10px 40px}.title-meta h1{font-size:16pt}.meta{font-size:10pt}.qr-box img{width:150px;height:150px}.invoice-watermark img{width:85vw}.powered-by{left:10px;bottom:8px}}@media print{body{background:#fffaf2}.page-wrap{padding:0}.sheet{border:none;border-radius:0;box-shadow:none;max-width:none;min-height:auto;margin:0;padding:10mm 10mm 11mm;overflow:visible}.printbar{display:none}.table-wrap{overflow:visible}.info-table,.items-table,.totals-table{min-width:0!important;width:100%!important}.invoice-watermark{opacity:.05}.footer-space{height:8mm}}</style></head><body>
<div class="printbar"><button onclick="window.print()">طباعة / حفظ PDF</button></div>
<div class="page-wrap"><div class="sheet">
<?php
$printLang = strtolower((string)($meta['print_language'] ?? 'ar'));
$isEn = $printLang === 'en';
$isBi = $printLang === 'bi';
$titleText = $isEn ? 'Tax Invoice / ' . $req['invoice_kind'] : ($isBi ? 'فاتورة ضريبية / Tax Invoice / ' . $req['invoice_kind'] : 'فاتورة ضريبية / ' . $req['invoice_kind']);
$lblInvoiceNo = $isEn ? 'Invoice No' : ($isBi ? 'رقم الفاتورة / Invoice No' : 'رقم الفاتورة');
$lblDate = $isEn ? 'Date' : ($isBi ? 'التاريخ / Date' : 'التاريخ');
$lblUuid = 'UUID';
$lblSeller = $isEn ? 'Seller Details' : ($isBi ? 'بيانات البائع / Seller' : 'بيانات البائع');
$lblBuyer = $isEn ? 'Buyer Details' : ($isBi ? 'بيانات العميل / Buyer' : 'بيانات العميل');
$lblItems = $isEn ? 'Items' : ($isBi ? 'البنود / Items' : 'البنود');
$lblDesc = $isEn ? 'Description' : ($isBi ? 'الوصف / Description' : 'الوصف');
$lblQty = $isEn ? 'Qty' : ($isBi ? 'الكمية / Qty' : 'الكمية');
$lblUnit = $isEn ? 'Unit Price' : ($isBi ? 'سعر الوحدة / Unit Price' : 'سعر الوحدة');
$lblNet = $isEn ? 'Net' : ($isBi ? 'الصافي / Net' : 'الصافي');
$lblVat = $isEn ? 'VAT' : ($isBi ? 'الضريبة / VAT' : 'الضريبة');
$lblTotal = $isEn ? 'Total' : ($isBi ? 'الإجمالي / Total' : 'الإجمالي');
$lblVatNo = 'VAT';
?>
<?php if ($logoDataUri !== ''): ?><div class="invoice-watermark"><img src="<?= h($logoDataUri) ?>" alt="<?= h($brandName) ?>"></div><?php endif; ?>
<div class="content-layer">
<div class="head"><div class="qr-box"><?php if ($qrImageSrc !== ''): ?><img src="<?= h($qrImageSrc) ?>" alt="QR"><?php else: ?><div class="qr-fallback"><?= h($meta['effective_qr_base64'] ?? '') ?></div><?php endif; ?><div class="qr-note"><?= $qrImageSrc !== '' ? 'امسح الكود لقراءة بيانات الفاتورة' : 'تعذر عرض رمز QR.' ?></div></div><div class="title-meta"><h1><?= h($titleText) ?></h1><div class="meta"><div><strong><?= h($lblInvoiceNo) ?>:</strong> <?= h($req['invoice_id']) ?></div><div><strong><?= h($lblDate) ?>:</strong> <?= h(gmdate('Y-m-d H:i:s', strtotime($req['issue_utc']))) ?> UTC</div><div><strong><?= h($lblUuid) ?>:</strong> <?= h($req['uuid']) ?></div></div></div></div>
<table class="info-table"><tr><th><?= h($lblSeller) ?></th><th><?= h($lblBuyer) ?></th></tr><tr><td><?= h($req['seller']['name']) ?><br><?= h($lblVatNo) ?>: <?= h($req['seller']['vat']) ?><?php if (trim((string)$req['seller']['address']) !== ''): ?><br><?= nl2br(h($req['seller']['address'])) ?><?php endif; ?></td><td><?= h($req['buyer']['name']) ?><br><?= h($lblVatNo) ?>: <?= h($req['buyer']['vat']) ?><?php if (trim((string)$req['buyer']['address']) !== ''): ?><br><?= nl2br(h($req['buyer']['address'])) ?><?php endif; ?></td></tr></table>
<h3 class="section-title"><?= h($lblItems) ?></h3><div class="table-wrap"><table class="items-table"><colgroup><col style="width:6%"><col style="width:30%"><col style="width:12%"><col style="width:17%"><col style="width:14%"><col style="width:10%"><col style="width:11%"></colgroup><thead><tr><th>#</th><th><?= h($lblDesc) ?></th><th><?= h($lblQty) ?></th><th><?= h($lblUnit) ?></th><th><?= h($lblNet) ?></th><th><?= h($lblVat) ?></th><th><?= h($lblTotal) ?></th></tr></thead><tbody><?php foreach($totals['lines'] as $line): ?><tr><td><?= $line['line_number'] ?></td><td><?= h($line['description']) ?></td><td><?= h((string)$line['qty']) ?></td><td><?= number_format($line['unit_price'],2) ?></td><td><?= number_format($line['net'],2) ?></td><td><?= number_format($line['vat'],2) ?></td><td><?= number_format($line['gross'],2) ?></td></tr><?php endforeach; ?></tbody></table></div>
<div class="totals-box"><table class="totals-table"><tr><th><?= h($lblNet) ?></th><td><?= h($req['currency']) ?> <?= number_format($totals['tax_exclusive_amount'],2) ?></td></tr><tr><th><?= h($lblVat) ?></th><td><?= h($req['currency']) ?> <?= number_format($totals['vat_amount'],2) ?></td></tr><tr><th><?= h($lblTotal) ?></th><td><strong><?= h($req['currency']) ?> <?= number_format($totals['payable_amount'],2) ?></strong></td></tr></table></div>
<div class="meta-line">Invoice Hash (Base64): <?= h($meta['invoice_hash_base64'] ?? '') ?></div>
<div class="muted"><strong><?= h($isEn ? 'Recommended submission' : ($isBi ? 'التوصية بالإرسال / Recommended submission' : 'التوصية بالإرسال')) ?>:</strong> <?= h($meta['recommended_submission'] ?? '') ?></div>
<div class="footer-space"></div><div class="powered-by"><?= h($brandName ?: APP_NAME) ?></div>
</div></div></div>
</body></html>
<?php return ob_get_clean(); }


function archive_invoice(array $req, array $totals, string $xml, string $html, array $meta): array {
    $slug = preg_replace('/[^A-Za-z0-9\-_]+/', '-', $req['invoice_id']) . '-' . date('Ymd-His');
    $dir = storage_path('archive/' . $slug); ensure_dir_secure($dir);
    $xmlPath = $dir . '/invoice.xml'; $htmlPath = $dir . '/invoice.html'; $metaPath = $dir . '/metadata.json';
    write_text_file($xmlPath, $xml); write_text_file($htmlPath, $html); write_json_file($metaPath, array_merge($meta, $req, ['totals'=>$totals]));
    return ['dir'=>$dir,'xml'=>$xmlPath,'html'=>$htmlPath,'metadata'=>$metaPath];
}
function save_pem_text(string $relPath, string $text): string {
    $path = storage_path($relPath);
    write_text_file($path, trim($text) . "
");
    return $path;
}
function generate_csr_package(array $cfg): array {
    if (!function_exists('openssl_pkey_new')) return [false,'امتداد OpenSSL غير مفعل على السيرفر.'];
    $deviceId = trim((string)($cfg['phase2']['device_id'] ?? '')) ?: 'device-' . substr(hash('sha256', uniqid('',true)),0,12);
    $dn = [
        'countryName' => 'SA',
        'organizationName' => trim((string)($cfg['company']['name'] ?? 'Company')) ?: 'Company',
        'organizationalUnitName' => 'ZATCA',
        'commonName' => trim((string)($cfg['company']['vat'] ?? '')) ?: $deviceId,
        'serialNumber' => $deviceId,
    ];

    $algorithm = 'ECDSA_P256';
    $privateKeyConfig = ['private_key_type' => defined('OPENSSL_KEYTYPE_EC') ? OPENSSL_KEYTYPE_EC : OPENSSL_KEYTYPE_RSA, 'curve_name' => 'prime256v1'];
    $priv = @openssl_pkey_new($privateKeyConfig);
    if (!$priv) {
        $algorithm = 'RSA_2048_FALLBACK';
        $priv = openssl_pkey_new(['private_key_bits'=>2048,'private_key_type'=>OPENSSL_KEYTYPE_RSA]);
    }
    if (!$priv) return [false,'تعذر إنشاء private key.'];

    $csr = openssl_csr_new($dn, $priv, ['digest_alg'=>'sha256']);
    if (!$csr) return [false,'تعذر إنشاء CSR.'];
    openssl_pkey_export($priv, $privatePem);
    openssl_csr_export($csr, $csrPem);
    $pubBase64 = export_public_key_base64_from_private($privatePem);
    $privatePath = save_pem_text('phase2/private_key.pem', $privatePem);
    $csrPath = save_pem_text('phase2/certificate.csr', $csrPem);
    if ($pubBase64) save_pem_text('phase2/public_key.pem', base64_decode($pubBase64));
    $cfg['phase2']['private_key_path'] = $privatePath;
    $cfg['phase2']['csr_path'] = $csrPath;
    $cfg['phase2']['public_key_path'] = storage_path('phase2/public_key.pem');
    $cfg['phase2']['device_id'] = $deviceId;
    $cfg['phase2']['key_algorithm'] = $algorithm;
    save_config($cfg);
    return [true,'تم إنشاء CSR و private key بنجاح (' . $algorithm . ').', ['private_key_path'=>$privatePath, 'csr_path'=>$csrPath, 'csr_pem'=>$csrPem, 'private_pem'=>$privatePem, 'algorithm'=>$algorithm]];
}

function phase2_base_url(string $env): string {
    return strcasecmp($env, 'Production') === 0 ? 'https://gw-fatoora.zatca.gov.sa/e-invoicing/core' : 'https://gw-fatoora.zatca.gov.sa/e-invoicing/simulation';
}
function post_json(string $url, array $payload, array $headers = []): array {
    if (!function_exists('curl_init')) return ['ok'=>false,'status'=>0,'body'=>'','error'=>'cURL extension is not enabled'];
    $ch = curl_init($url);
    $h = ['Content-Type: application/json', 'Accept: application/json', 'Accept-Version: V2', 'Accept-Language: en', 'User-Agent: ZatcaInvoiceWebPhase2/2.0'];
    foreach ($headers as $k=>$v) $h[] = $k . ': ' . $v;
    curl_setopt_array($ch, [CURLOPT_POST=>true, CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>90, CURLOPT_HTTPHEADER=>$h, CURLOPT_POSTFIELDS=>json_encode($payload, JSON_UNESCAPED_SLASHES)]);
    $resp = curl_exec($ch); $err = curl_error($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
    return ['ok'=>$err==='' && $code>=200 && $code<300, 'status'=>$code, 'body'=>(string)$resp, 'error'=>$err];
}
function request_compliance_csid(array &$cfg, string $otp): array {
    $csrPath = trim((string)($cfg['phase2']['csr_path'] ?? ''));
    if ($csrPath === '' || !file_exists($csrPath)) return [false, 'CSR file غير موجود.'];
    $csrBody = csr_body_from_pem(file_get_contents($csrPath));
    if ($csrBody === '') return [false, 'CSR غير صالح.'];
    $res = post_json(phase2_base_url($cfg['phase2']['environment']) . '/compliance', ['csr'=>$csrBody], ['OTP'=>$otp]);
    $cfg['phase2']['last_api_response_path'] = save_api_response('compliance', $res);
    if (!$res['ok']) return [false, 'فشل طلب compliance CSID. HTTP ' . $res['status'] . ' ' . ($res['error'] ?: '') . ' ' . $res['body']];
    $obj = json_decode($res['body'], true);
    if (!is_array($obj)) return [false, 'تم استلام رد غير مفهوم: ' . $res['body']];
    $token = trim((string)($obj['binarySecurityToken'] ?? $obj['BinarySecurityToken'] ?? ''));
    $secret = trim((string)($obj['secret'] ?? $obj['Secret'] ?? ''));
    $requestId = trim((string)($obj['requestID'] ?? $obj['requestId'] ?? ''));
    if ($token === '' || $secret === '') return [false, 'الرد لا يحتوي على token/secret.'];
    $certPem = "-----BEGIN CERTIFICATE-----\n" . chunk_split(preg_replace('/\s+/', '', $token), 64, "\n") . "-----END CERTIFICATE-----\n";
    $certPath = save_pem_text('phase2/compliance-csid.pem', $certPem);
    $cfg['phase2']['compliance_binary_security_token'] = $token;
    $cfg['phase2']['compliance_secret'] = $secret;
    $cfg['phase2']['compliance_request_id'] = $requestId;
    $cfg['phase2']['last_request_id'] = $requestId;
    $cfg['phase2']['csid'] = $token;
    $cfg['phase2']['compliance_csid_path'] = $certPath;
    $cfg['phase2']['otp'] = $otp;
    save_config($cfg);
    return [true, 'تم حفظ Compliance CSID و Secret و Request ID. الخطوة التالية: نفّذ compliance checks ثم احصل على ' . phase2_submission_label($cfg) . '.'];
}
function save_api_response(string $prefix, array $res): string {
    $path = storage_path('phase2/' . $prefix . '-' . date('Ymd-His') . '.json');
    write_json_file($path, $res);
    return $path;
}
function submit_invoice_to_zatca(array $cfg, array $meta): array {
    if (empty($cfg['phase2']['enabled'])) return [false, 'وضع Phase 2 غير مفعل من الإعدادات.'];
    if (!function_exists('curl_init')) return [false, 'امتداد cURL غير مفعل على السيرفر.'];
    $token = phase2_production_token($cfg);
    $secret = phase2_production_secret($cfg);
    if ($token === '' || $secret === '') return [false, phase2_submission_label($cfg) . ' BinarySecurityToken و Secret مطلوبان أولًا.'];
    $xmlPath = (string)($meta['files']['xml'] ?? '');
    $metadataPath = (string)($meta['files']['metadata'] ?? '');
    if ($xmlPath === '' || !is_safe_storage_path($xmlPath, 'archive') || !file_exists($xmlPath)) return [false, 'ملف XML غير صالح أو خارج الأرشيف المسموح.'];
    if ($metadataPath === '' || !is_safe_storage_path($metadataPath, 'archive') || !file_exists($metadataPath)) return [false, 'ملف metadata غير صالح أو خارج الأرشيف المسموح.'];
    $endpoint = (($meta['invoice_kind'] ?? '') === 'Standard') ? '/invoices/clearance/single' : '/invoices/reporting/single';
    $invoiceXml = file_get_contents($xmlPath);
    $payload = ['invoiceHash'=>$meta['invoice_hash_base64'],'uuid'=>$meta['uuid'],'invoice'=>base64_encode($invoiceXml)];
    $auth = base64_encode($token . ':' . $secret);
    $ch = curl_init(phase2_base_url($cfg['phase2']['environment']) . $endpoint);
    curl_setopt_array($ch, [CURLOPT_POST=>true, CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>90, CURLOPT_POSTFIELDS=>json_encode($payload, JSON_UNESCAPED_SLASHES), CURLOPT_HTTPHEADER=>['Content-Type: application/json', 'Accept: application/json', 'Accept-Version: V2', 'Accept-Language: en', 'User-Agent: ZatcaInvoiceWebPhase2/2.4', 'Authorization: Basic ' . $auth]]);
    $resp = curl_exec($ch); $err = curl_error($ch); $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
    $res = ['ok'=>$err==='' && $code>=200 && $code<300, 'status'=>$code, 'body'=>(string)$resp, 'error'=>$err, 'endpoint'=>$endpoint];
    $path = save_api_response('submit', $res);
    $m = read_json_file($metadataPath, []);
    $m['status'] = $res['ok'] ? (($meta['invoice_kind']==='Standard') ? 'Cleared' : 'Reported') : 'Rejected';
    $m['last_api_summary'] = 'HTTP ' . $code . ' ' . ($err ?: '');
    $m['last_api_response_path'] = $path;
    write_json_file($metadataPath, $m);
    return [$res['ok'], ($res['ok'] ? 'تم إرسال الفاتورة بنجاح باستخدام ' . phase2_submission_label($cfg) . '.' : 'فشل الإرسال.') . ' HTTP ' . $code . ' ' . ($err ?: '') . ' ' . $resp, $path];
}
function create_signed_invoice_package(array $req, array $cfg, array &$state, string $printLanguage = 'ar'): array {
    if (!function_exists('hash')) { throw new RuntimeException('Hash functions are unavailable'); }
    $totals = compute_totals($req['items']);
    $issueIsoUtc = gmdate('Y-m-d\TH:i:s\Z', strtotime($req['issue_utc']));
    $phase1Qr = build_tlv_phase1_base64($req['seller']['name'], $req['seller']['vat'], $issueIsoUtc, $totals['payable_amount'], $totals['vat_amount']);
    $req['invoice_counter'] = (int)$state['invoice_counter'];
    $req['previous_invoice_hash_base64'] = base64_encode(hex2bin($state['previous_invoice_hash_hex']) ?: str_repeat("\0", 32));
    $req['qr_tlv_base64'] = $phase1Qr;
    $unsignedXml = create_phase2_xml($req, $totals, null);
    $hashBin = hash('sha256', $unsignedXml, true);
    $hashBase64 = base64_encode($hashBin);
    $signatureBase64 = ''; $publicKeyBase64 = ''; $certSigBase64 = ''; $stamp = null; $effectiveQr = $phase1Qr;
    $qrMode = strtolower(trim((string)($cfg['phase2']['qr_mode'] ?? 'auto')));
    $keyPath = trim((string)($cfg['phase2']['private_key_path'] ?? ''));
    $certPath = phase2_submission_certificate_path($cfg);
    $canBuildPhase2Qr = !empty($cfg['phase2']['enabled']) && ($keyPath !== '' && file_exists($keyPath)) && ($certPath !== '' && file_exists($certPath));
    if ($canBuildPhase2Qr) {
        $privatePem = file_get_contents($keyPath);
        openssl_sign($hashBin, $sigBin, openssl_pkey_get_private($privatePem), OPENSSL_ALGO_SHA256);
        $signatureBase64 = base64_encode($sigBin ?: '');
        $publicKeyBase64 = export_public_key_base64_from_private($privatePem);
        if ($certPath !== '' && file_exists($certPath)) $certSigBase64 = cert_signature_base64(file_get_contents($certPath));
        $phase2Qr = build_tlv_phase2_base64($req['seller']['name'], $req['seller']['vat'], $issueIsoUtc, $totals['payable_amount'], $totals['vat_amount'], $hashBase64, $signatureBase64, $publicKeyBase64, $certSigBase64);
        $stamp = ['invoice_hash_base64'=>$hashBase64, 'signature_base64'=>$signatureBase64, 'public_key_base64'=>$publicKeyBase64, 'certificate_signature_base64'=>$certSigBase64];
        if ($qrMode === 'phase2' || $qrMode === 'auto') {
            $effectiveQr = $phase2Qr;
        }
    }
    $req['qr_tlv_base64'] = $effectiveQr;
    $finalXml = create_phase2_xml($req, $totals, $stamp);
    $finalHashHex = hash('sha256', $finalXml);
    $state['previous_invoice_hash_hex'] = $finalHashHex;
    $state['invoice_counter'] = ((int)$state['invoice_counter']) + 1;
    $state['last_uuid'] = $req['uuid'];
    save_phase2_state($state);
    $recommended = (($req['invoice_kind'] ?? '') === 'Standard') ? 'Clearance' : 'Reporting';
    $companyBrandName = trim((string)($cfg['company']['brand_name'] ?? '')) ?: trim((string)($cfg['company']['name'] ?? ''));
    $companyPhone = trim((string)($cfg['company']['phone'] ?? ''));
    $companyLogoDataUri = image_data_uri(app_logo_path($cfg));
    $meta = [
        'invoice_id'=>$req['invoice_id'], 'uuid'=>$req['uuid'], 'issue_utc'=>$req['issue_utc'], 'invoice_kind'=>$req['invoice_kind'], 'currency'=>$req['currency'],
        'invoice_hash_base64'=>$hashBase64, 'unsigned_hash_hex'=>hash('sha256', $unsignedXml), 'effective_qr_base64'=>$effectiveQr, 'phase1_qr_base64'=>$phase1Qr,
        'signature_base64'=>$signatureBase64, 'public_key_base64'=>$publicKeyBase64, 'certificate_signature_base64'=>$certSigBase64,
        'recommended_submission'=>$recommended, 'status'=>'Draft', 'last_api_summary'=>'', 'print_language'=>in_array(strtolower($printLanguage), ['ar','en','bi'], true) ? strtolower($printLanguage) : 'ar',
        'company_brand_name'=>$companyBrandName,
        'company_phone'=>$companyPhone,
        'company_logo_data_uri'=>$companyLogoDataUri,
        'allow_external_qr_provider'=>!empty($cfg['security']['allow_external_qr_provider']),
        'local_qr_preferred'=>true,
    ];
    $html = create_invoice_html($req, $totals, $meta);
    $files = archive_invoice($req, $totals, $finalXml, $html, $meta);
    $meta['files'] = $files;
    $all = read_json_file($files['metadata'], []); $all['files'] = $files; write_json_file($files['metadata'], $all);
    return ['request'=>$req, 'totals'=>$totals, 'unsigned_xml'=>$unsignedXml, 'final_xml'=>$finalXml, 'meta'=>$meta, 'files'=>$files];
}
