<?php
declare(strict_types=1);

require_once dirname(__DIR__) . '/subscriber_contracts_lib.php';
require_once dirname(__DIR__) . '/subscriber_plan_schema.php';

function sct_assert(bool $condition, string $message): void
{
    if (!$condition) throw new RuntimeException($message);
}

$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:p><w:r><w:t>{{full_name}}</w:t></w:r><w:r><w:br w:type="page"/></w:r></w:p>
    <w:p><w:r><w:t>{{phone}}</w:t></w:r><w:r><w:br w:type="page"/></w:r></w:p>
    <w:p><w:r><w:t>{{address}}</w:t></w:r></w:p>
    <w:sectPr/>
  </w:body>
</w:document>
XML;

$layout = sc_docx_page_layout($xml);
sct_assert((int)$layout['count'] === 3, 'يجب اكتشاف ثلاث صفحات محفوظة.');
sct_assert(str_contains((string)$layout['page_texts'][2], '{{phone}}'), 'يجب ربط مفتاح الهاتف بالصفحة الثانية.');

$secondPage = sc_keep_docx_pages($xml, [2]);
sct_assert(str_contains($secondPage, '{{phone}}'), 'يجب الإبقاء على محتوى الصفحة الثانية.');
sct_assert(!str_contains($secondPage, '{{full_name}}'), 'يجب حذف محتوى الصفحة الأولى.');
sct_assert(!str_contains($secondPage, '{{address}}'), 'يجب حذف محتوى الصفحة الثالثة.');
sct_assert(!str_contains($secondPage, 'w:type="page"'), 'يجب إزالة فاصل الصفحة اللاحق من الملف المفرد.');

$leadingBreakXml = <<<'XML'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:p><w:r><w:t>PAGE_ONE_CONTENT</w:t></w:r></w:p>
    <w:p><w:r><w:lastRenderedPageBreak/><w:t>PAGE_TWO_HEADING</w:t></w:r></w:p>
    <w:p><w:r><w:t>PAGE_TWO_CONTENT</w:t></w:r></w:p>
    <w:sectPr/>
  </w:body>
</w:document>
XML;
$leadingLayout = sc_docx_page_layout($leadingBreakXml);
sct_assert((int)$leadingLayout['count'] === 2, 'يجب اعتبار الفاصل الموجود في بداية الفقرة بداية للصفحة الثانية.');
sct_assert(str_contains((string)$leadingLayout['page_texts'][2], 'PAGE_TWO_HEADING'), 'يجب إسناد عنوان الصفحة الثانية إلى صفحته الصحيحة.');
$firstPageOnly = sc_keep_docx_pages($leadingBreakXml, [1]);
sct_assert(str_contains($firstPageOnly, 'PAGE_ONE_CONTENT'), 'يجب الإبقاء على محتوى الصفحة الأولى.');
sct_assert(!str_contains($firstPageOnly, 'PAGE_TWO_HEADING'), 'يجب حذف أول فقرة من الصفحة الثانية مع فاصلها.');
sct_assert(!str_contains($firstPageOnly, 'PAGE_TWO_CONTENT'), 'يجب حذف بقية محتوى الصفحة الثانية.');

$spacerBoundaryXml = <<<'XML'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>PAGE_ONE</w:t></w:r></w:p><w:p/><w:p/><w:p><w:r><w:lastRenderedPageBreak/><w:t>PAGE_TWO</w:t></w:r></w:p><w:sectPr/></w:body></w:document>
XML;
$trimmedFirstPage = sc_keep_docx_pages($spacerBoundaryXml, [1]);
sct_assert(substr_count($trimmedFirstPage, '<w:p') === 1, 'يجب حذف فقرات الحشو الفارغة في نهاية الصفحة المستخرجة حتى لا تمتد إلى ورقة إضافية.');

$required = sc_template_required_fields_for_text('{{full_name}} {{phone}}', []);
sct_assert($required === ['full_name', 'phone'], 'يجب اكتشاف مفاتيح بيانات المشترك حسب ترتيب الكتالوج.');

$spacedDocumentLabelRequired = sc_template_required_fields_for_text(
    'نوع وثيقة الثبوتيات: هوية شخصية رقم الوثيقة : 123456',
    [['keyword_text' => 'رقم الوثيقة:', 'subscriber_field' => 'document_number']]
);
sct_assert(
    in_array('document_number', $spacedDocumentLabelRequired, true),
    'يجب أن تتعرف المطابقة على المسافة الاختيارية قبل النقطتين في رقم الوثيقة.'
);
$documentMap = sc_apply_template_fields(
    sc_subscriber_placeholders(['document_number' => '123456'], []),
    [['keyword_text' => 'رقم الوثيقة:', 'subscriber_field' => 'document_number']]
);
$spacedDocumentXml = '<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:t>رقم الوثيقة :</w:t></w:r></w:p></w:body></w:document>';
$spacedDocumentResult = html_entity_decode(sc_replace_docx_xml($spacedDocumentXml, $documentMap), ENT_QUOTES | ENT_XML1, 'UTF-8');
sct_assert(
    str_contains($spacedDocumentResult, 'رقم الوثيقة : 123456'),
    'يجب تعبئة رقم الوثيقة حتى عندما يحتوي القالب مسافة قبل النقطتين.'
);

$splitRunXml = <<<'XML'
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"><w:body><w:p><w:r><w:rPr><w:b/></w:rPr><w:t>{{full_</w:t></w:r><w:r><w:rPr><w:i/></w:rPr><w:t>name}}</w:t></w:r><w:r><w:t> ثابت</w:t></w:r></w:p><w:sectPr/></w:body></w:document>
XML;
$splitRunResult = sc_replace_docx_xml($splitRunXml, ['{{full_name}}'=>'اسم المشترك الطويل']);
sct_assert(substr_count($splitRunResult, '<w:r>') === 3, 'يجب الحفاظ على أجزاء وتنسيق فقرة Word عند التعبئة.');
sct_assert(str_contains($splitRunResult, '<w:b/>') && str_contains($splitRunResult, '<w:i/>'), 'يجب ألا تفقد أنماط Word أثناء استبدال مفتاح موزع بين أجزاء النص.');
sct_assert(str_contains($splitRunResult, 'اسم المشترك الطويل') && str_contains($splitRunResult, ' ثابت'), 'يجب استبدال المفتاح مع إبقاء النص المحيط دون دمجه في جزء واحد.');

$subscriber = ['id' => 7, 'full_name' => 'مشترك مكتمل الاسم', 'phone' => ''];
$missing = sc_contract_missing_fields($subscriber, $required);
sct_assert($missing === ['phone'], 'يجب اعتبار الهاتف فقط معلومة ناقصة.');

$bulkRows = [];
for ($id = 1; $id <= 563; $id++) {
    $bulkRows[] = [
        'id' => $id,
        'full_name' => 'مشترك ' . $id,
        'phone' => $id <= 268 ? '09' . str_pad((string)$id, 8, '0', STR_PAD_LEFT) : '',
    ];
}
$filteredBulk = sc_partition_contract_export_rows($bulkRows, ['phone'], true, ['phone' => 'رقم الهاتف']);
sct_assert((int)$filteredBulk['selected_count'] === 563, 'يجب حفظ العدد المحدد قبل الاستثناء.');
sct_assert((int)$filteredBulk['eligible_count'] === 268, 'يجب احتساب العدد النهائي بعد استبعاد البيانات الناقصة.');
sct_assert((int)$filteredBulk['excluded_count'] === 295, 'يجب إحصاء المستبعدين بدقة.');
sct_assert(count($filteredBulk['eligible']) <= 300, 'يجب السماح بالتصدير عندما يصبح العدد النهائي أقل من الحد.');

$unfilteredBulk = sc_partition_contract_export_rows($bulkRows, ['phone'], false, ['phone' => 'رقم الهاتف']);
sct_assert((int)$unfilteredBulk['eligible_count'] === 563, 'عند تعطيل الاستثناء يجب تطبيق الحد على كامل الاختيار.');
sct_assert(count($unfilteredBulk['eligible']) > 300, 'يجب رفض العدد النهائي الذي يتجاوز حد التصدير.');

$allFilter = sc_filter_contract_export_rows($bulkRows, ['phone'], 'all', ['phone' => 'رقم الهاتف']);
sct_assert((int)$allFilter['eligible_count'] === 563, 'فلتر الجميع يجب أن يتضمن العقود الكاملة وغير المكتملة.');
$completeFilter = sc_filter_contract_export_rows($bulkRows, ['phone'], 'complete', ['phone' => 'رقم الهاتف']);
sct_assert((int)$completeFilter['eligible_count'] === 268, 'فلتر العقود الكاملة يجب أن يعتمد العدد بعد فحص مفاتيح القالب.');
sct_assert((int)$completeFilter['incomplete_count'] === 295, 'يجب إحصاء العقود غير المكتملة بصورة مستقلة.');
$incompleteFilter = sc_filter_contract_export_rows($bulkRows, ['phone'], 'incomplete', ['phone' => 'رقم الهاتف']);
sct_assert((int)$incompleteFilter['eligible_count'] === 295, 'فلتر العقود غير المكتملة يجب أن يعرض السجلات الناقصة فقط.');
sct_assert((int)$incompleteFilter['complete_count'] === 268, 'يجب الحفاظ على إحصاء العقود الكاملة عند اختيار غير المكتملة.');

$selection = sc_contract_selected_pages([
    'page_selectable' => true,
    'layout_page_count' => 3,
], 'page:2');
sct_assert($selection === [2], 'يجب تحويل نطاق الصفحة إلى رقم الصفحة المحددة.');
sct_assert(sc_contract_selected_pages([], 'all') === null, 'التصدير الكامل لا يحدد صفحات جزئية.');

$scopeCatalog = [
    'all_required_fields' => ['full_name', 'phone'],
    'page_required_fields' => [
        1 => ['full_name'],
        2 => ['phone'],
    ],
];
sct_assert(
    sc_contract_filter_required_fields($scopeCatalog) === ['full_name', 'phone'],
    'يجب أن يعتمد فلتر اكتمال العقد على اتحاد مفاتيح جميع صفحات القالب.'
);
sct_assert(
    sc_contract_render_required_fields($scopeCatalog, [1]) === ['full_name'],
    'يجب أن يبقى تحقق الملف الناتج مرتبطاً بمفاتيح الصفحة المصدّرة فقط.'
);
$scopeSubscriber = ['id' => 8, 'full_name' => 'مشترك كامل الاسم', 'phone' => ''];
$scopeMissingOnPageOne = sc_contract_missing_fields(
    $scopeSubscriber,
    sc_contract_filter_required_fields($scopeCatalog)
);
$scopeMissingOnPageTwo = sc_contract_missing_fields(
    $scopeSubscriber,
    sc_contract_filter_required_fields($scopeCatalog)
);
sct_assert(
    $scopeMissingOnPageOne === ['phone'] && $scopeMissingOnPageTwo === ['phone'],
    'يجب أن تبقى نتيجة مطابقة العقد ثابتة عند تبديل صفحة التصدير.'
);

$identity = sc_subscriber_placeholders([
    'document_type' => 'جواز سفر',
    'document_number' => 'P-445566',
    'national_id' => '9988776655',
], []);
sct_assert($identity['{{document_type}}'] === 'جواز سفر', 'يجب أخذ نوع الوثيقة من حقله المستقل.');
sct_assert($identity['{{document_number}}'] === 'P-445566', 'يجب أخذ رقم الوثيقة من حقله المستقل.');
sct_assert($identity['رقم الوثيقة:'] === 'رقم الوثيقة: P-445566', 'يجب ألا يستبدل الرقم الوطني رقم الوثيقة داخل العقود.');
sct_assert(!str_contains($identity['رقم الوثيقة:'], '9988776655'), 'يجب ألا يتسرب الرقم الوطني إلى حقل رقم الوثيقة.');
sct_assert($identity['{{national_id}}'] === '9988776655', 'يجب إبقاء الرقم الوطني مستقلاً عن رقم الوثيقة.');

sct_assert(subscriber_document_type_normalize('إخراج قيد') === 'إخراج قيد', 'يجب قبول نوع الوثيقة المعتمد.');
sct_assert(subscriber_document_type_normalize('نوع غير معروف', false) === '', 'يجب رفض نوع الوثيقة غير المعتمد.');

echo "subscriber_contracts_export_test: OK\n";
