<?php
/**
 * Services Walkthrough Manual — Slice 8S-10.
 *
 * Generates a portrait A4 PDF that walks the user through every field on a
 * Services tender detail page (Tenders/view/{id}) and the full WON → FM →
 * Exec → PM/PC/FS operational handoff.
 *
 * Fed by a Services tender's live data so all values shown in the manual
 * are grounded in what the reader can actually click on. Written primarily
 * against the Vodacom Tower PM sample (tender 400) but works with any
 * Services tender.
 */
if (!defined('FPDF_FONTPATH')) define('FPDF_FONTPATH', ROOT . '/fonts/fpdf/');
if (!class_exists('FPDF'))     require_once ROOT . '/core/FPDF.php';

class ServicesWalkthroughManual extends FPDF
{
    private array $cInk       = [51, 65, 85];
    private array $cMuted     = [100, 116, 139];
    private array $cAccent    = [122, 139, 161];
    private array $cLine      = [226, 232, 240];
    private array $cSectionBg = [241, 245, 249];
    private array $cCalloutBg = [253, 247, 236];
    private array $cCalloutBd = [212, 165, 116];
    private array $cSage      = [122, 158, 126];
    private array $cRose      = [196, 133, 121];

    private string $manualTitle    = 'Services Walkthrough Manual';
    private string $manualSubtitle = '';

    public function __construct(string $orientation = 'P', string $unit = 'mm', string $size = 'A4')
    {
        parent::__construct($orientation, $unit, $size);
        $this->AddFont('Roboto_Condensed-Regular',    '', 'Roboto_Condensed-Regular.php');
        $this->AddFont('Roboto_Condensed-Bold',       '', 'Roboto_Condensed-Bold.php');
        $this->AddFont('Roboto_Condensed-Italic',     '', 'Roboto_Condensed-Italic.php');
        $this->AddFont('Roboto_Condensed-BoldItalic', '', 'Roboto_Condensed-BoldItalic.php');
        $this->SetMargins(15, 20, 15);
        $this->SetAutoPageBreak(true, 20);
    }

    public function setMeta(string $title, string $subtitle): void
    {
        $this->manualTitle    = $this->safe($title);
        $this->manualSubtitle = $this->safe($subtitle);
        $this->SetTitle($this->manualTitle);
        $this->SetAuthor('RS Flow');
    }

    /* ── FPDF hooks ────────────────────────────────────────────────────── */

    function Header(): void
    {
        if ($this->PageNo() === 1) return;
        $this->SetFont('Roboto_Condensed-Regular', '', 8);
        $this->SetTextColor(...$this->cMuted);
        $this->Cell(0, 5, $this->manualTitle . '  ·  ' . $this->manualSubtitle, 0, 1, 'L');
        $this->SetDrawColor(...$this->cLine);
        $this->SetLineWidth(0.2);
        $this->Line($this->lMargin, $this->GetY() + 1, $this->GetPageWidth() - $this->rMargin, $this->GetY() + 1);
        $this->Ln(3);
    }

    function Footer(): void
    {
        $this->SetY(-15);
        $this->SetFont('Roboto_Condensed-Regular', '', 8);
        $this->SetTextColor(...$this->cMuted);
        $this->Cell(0, 5, 'RS Flow · Services Walkthrough Manual', 0, 0, 'L');
        $this->Cell(0, 5, 'Page ' . $this->PageNo() . ' / {nb}', 0, 0, 'R');
    }

    /* ── Public writing helpers ────────────────────────────────────────── */

    public function coverPage(array $t, array $spec, array $items, ?array $version, array $overheads): void
    {
        $this->AddPage();
        $this->SetFont('Roboto_Condensed-Bold', '', 22);
        $this->SetTextColor(...$this->cInk);
        $this->Ln(30);
        $this->MultiCell(0, 10, $this->safe('Services Tender'), 0, 'C');
        $this->SetFont('Roboto_Condensed-Bold', '', 22);
        $this->MultiCell(0, 10, $this->safe('Walkthrough Manual'), 0, 'C');
        $this->Ln(4);
        $this->SetFont('Roboto_Condensed-Regular', '', 12);
        $this->SetTextColor(...$this->cMuted);
        $this->MultiCell(0, 6, $this->safe('Every field on Tenders/view/' . $t['c0d'] . ' + the WON to convert to project handoff'), 0, 'C');

        $this->Ln(20);
        $this->SetDrawColor(...$this->cAccent);
        $this->SetLineWidth(0.4);
        $x1 = $this->lMargin + 30; $x2 = $this->GetPageWidth() - $this->rMargin - 30;
        $this->Line($x1, $this->GetY(), $x2, $this->GetY());
        $this->Ln(4);

        $this->SetFont('Roboto_Condensed-Bold', '', 11);
        $this->SetTextColor(...$this->cInk);
        $this->MultiCell(0, 6, $this->safe('Sample tender in this manual'), 0, 'C');
        $this->Ln(1);
        $this->SetFont('Roboto_Condensed-Regular', '', 10);
        $this->SetTextColor(...$this->cMuted);
        $rows = [
            'Tender number'     => (string)($t['tender_no']       ?? '—'),
            'Title'             => (string)($t['tender_title']    ?? '—'),
            'Buyer'             => (string)($t['buyer_name']      ?? '—'),
            'Industry / type'   => (string)($t['tender_industry'] ?? '—') . ' · ' . (string)($t['project_type'] ?? '—'),
            'Region · district' => (string)($t['region']          ?? '—') . '  ·  ' . (string)($t['district'] ?? '—'),
            'Sites'             => isset($spec['no_of_sites']) ? (int)$spec['no_of_sites'] . '  ·  ' . (string)($spec['sites_notes'] ?? '') : '—',
            'Items'             => count($items) . ' buyer-list line(s)',
            'Overhead lines'    => count($overheads) . ' overhead pool line(s)',
        ];
        if ($version) {
            $rows['Pricing version'] = 'v' . (int)$version['version_no'] . ' · ' . $version['status'] . ' · sell ' . number_format((float)$version['sum_price']) . ' TZS';
        }
        foreach ($rows as $k => $v) {
            $this->SetFont('Roboto_Condensed-Bold', '', 10);
            $this->Cell(50, 5.5, $this->safe($k), 0, 0, 'R');
            $this->SetFont('Roboto_Condensed-Regular', '', 10);
            $this->Cell(0, 5.5, $this->safe(' ' . $v), 0, 1, 'L');
        }

        $this->Ln(20);
        $this->SetFont('Roboto_Condensed-Italic', '', 10);
        $this->SetTextColor(...$this->cMuted);
        $this->MultiCell(0, 5, $this->safe('This manual is regenerated live on download so all numbers reflect the current state of the tender you clicked from. Screenshots are not embedded on purpose — the manual references what you see in the app so keep this open in a second window as you click.'), 0, 'C');

        // Foot
        $this->SetY(-30);
        $this->SetFont('Roboto_Condensed-Regular', '', 8);
        $this->Cell(0, 5, $this->safe('Generated ' . date('d M Y H:i')), 0, 1, 'C');
    }

    public function toc(array $sections): void
    {
        $this->AddPage();
        $this->h1('Table of Contents');
        $this->SetFont('Roboto_Condensed-Regular', '', 10);
        $this->SetTextColor(...$this->cInk);
        foreach ($sections as $i => $s) {
            $this->Cell(10, 6, ($i + 1) . '.', 0, 0, 'R');
            $this->Cell(0, 6, $this->safe(' ' . $s), 0, 1, 'L');
        }
        $this->Ln(4);
        $this->calloutBox(
            'How to use this manual',
            "Open Tenders/view/{tid} in one window and this PDF beside it. Each section walks you through one tab (or one post-conversion role step) in the order a CRM user encounters them. Section 11 onwards covers the handoff after the tender is WON.",
            ['{tid}' => (string)($this->extractedTenderId ?? 'N')]
        );
    }

    private ?int $extractedTenderId = null;
    public function setTenderIdRef(int $tid): void { $this->extractedTenderId = $tid; }

    /* ── Layout primitives ─────────────────────────────────────────────── */

    public function h1(string $text, ?string $sub = null): void
    {
        $this->ensureSpace(20);
        $this->SetFont('Roboto_Condensed-Bold', '', 16);
        $this->SetTextColor(...$this->cInk);
        $this->MultiCell(0, 8, $this->safe($text));
        if ($sub) {
            $this->SetFont('Roboto_Condensed-Italic', '', 9.5);
            $this->SetTextColor(...$this->cMuted);
            $this->MultiCell(0, 5, $this->safe($sub));
        }
        $this->SetDrawColor(...$this->cAccent);
        $this->SetLineWidth(0.5);
        $this->Line($this->lMargin, $this->GetY() + 1, $this->GetPageWidth() - $this->rMargin, $this->GetY() + 1);
        $this->SetLineWidth(0.1);
        $this->Ln(5);
    }

    public function h2(string $text): void
    {
        $this->ensureSpace(14);
        $this->Ln(2);
        $this->SetFont('Roboto_Condensed-Bold', '', 12);
        $this->SetTextColor(...$this->cInk);
        $this->MultiCell(0, 6.5, $this->safe($text));
        $this->Ln(1);
    }

    public function h3(string $text): void
    {
        $this->ensureSpace(10);
        $this->SetFont('Roboto_Condensed-Bold', '', 10);
        $this->SetTextColor(...$this->cAccent);
        $this->MultiCell(0, 5, $this->safe(strtoupper($text)));
        $this->Ln(0.5);
    }

    public function p(string $text): void
    {
        $this->SetFont('Roboto_Condensed-Regular', '', 10);
        $this->SetTextColor(...$this->cInk);
        $this->MultiCell(0, 5.2, $this->safe($text));
        $this->Ln(1);
    }

    public function bullet(string $text): void
    {
        $this->SetFont('Roboto_Condensed-Regular', '', 10);
        $this->SetTextColor(...$this->cInk);
        $x = $this->GetX();
        $this->Cell(4, 5, $this->safe('- '), 0, 0);
        $w = $this->GetPageWidth() - $this->rMargin - $this->GetX();
        $this->MultiCell($w, 5, $this->safe($text));
    }

    public function bullets(array $items): void
    {
        foreach ($items as $it) $this->bullet($it);
        $this->Ln(1);
    }

    /** Two-column field:value table with muted labels on the left. */
    public function fieldTable(array $rows, float $labelW = 60): void
    {
        $this->SetFont('Roboto_Condensed-Regular', '', 9.5);
        $bodyW = $this->GetPageWidth() - $this->rMargin - $this->lMargin - $labelW;
        foreach ($rows as $k => $v) {
            $this->ensureSpace(6);
            $y0 = $this->GetY();
            $this->SetFont('Roboto_Condensed-Bold', '', 9.5);
            $this->SetTextColor(...$this->cMuted);
            $this->MultiCell($labelW, 5, $this->safe($k), 0, 'L');
            $y1 = $this->GetY();
            $this->SetXY($this->lMargin + $labelW, $y0);
            $this->SetFont('Roboto_Condensed-Regular', '', 9.5);
            $this->SetTextColor(...$this->cInk);
            $this->MultiCell($bodyW, 5, $this->safe((string)$v), 0, 'L');
            $y2 = $this->GetY();
            $this->SetY(max($y1, $y2));
        }
        $this->Ln(2);
    }

    public function calloutBox(string $title, string $body, array $subs = []): void
    {
        $body = strtr($body, $subs);
        $w = $this->GetPageWidth() - $this->lMargin - $this->rMargin;
        $x = $this->lMargin; $y = $this->GetY();
        $this->ensureSpace(20);
        $y = $this->GetY();
        // measure body
        $this->SetFont('Roboto_Condensed-Regular', '', 9.5);
        $lines = $this->wrap($body, $w - 6);
        $h = 7 + 5 * count($lines) + 2;
        $this->SetFillColor(...$this->cCalloutBg);
        $this->SetDrawColor(...$this->cCalloutBd);
        $this->Rect($x, $y, $w, $h, 'DF');
        $this->SetTextColor(...$this->cInk);
        $this->SetFont('Roboto_Condensed-Bold', '', 10);
        $this->SetXY($x + 3, $y + 2);
        $this->Cell($w - 6, 5, $this->safe($title), 0, 1, 'L');
        $this->SetFont('Roboto_Condensed-Regular', '', 9.5);
        $this->SetX($x + 3);
        $this->MultiCell($w - 6, 5, $this->safe($body));
        $this->SetY($y + $h + 2);
    }

    /** Numbered "Click:" step. Numbered list with tinted step number. */
    public function step(int $n, string $click, string $explain = ''): void
    {
        $this->ensureSpace(10);
        $this->SetFont('Roboto_Condensed-Bold', '', 10);
        $this->SetTextColor(...$this->cAccent);
        $this->Cell(8, 5.5, $this->safe($n . '.'), 0, 0, 'R');
        $this->SetTextColor(...$this->cInk);
        $w = $this->GetPageWidth() - $this->rMargin - $this->GetX();
        $this->SetX($this->GetX() + 1);
        $this->SetFont('Roboto_Condensed-Bold', '', 10);
        $this->MultiCell($w - 1, 5.5, $this->safe($click));
        if ($explain !== '') {
            $this->SetX($this->lMargin + 9);
            $this->SetFont('Roboto_Condensed-Regular', '', 9.5);
            $this->SetTextColor(...$this->cMuted);
            $this->MultiCell($w - 2, 5, $this->safe($explain));
        }
        $this->Ln(0.5);
    }

    /* ── Utils ─────────────────────────────────────────────────────────── */

    private function ensureSpace(float $mm): void
    {
        if ($this->GetY() + $mm > $this->GetPageHeight() - 20) $this->AddPage();
    }

    private function wrap(string $s, float $w): array
    {
        $words = preg_split('/\s+/', $s);
        $lines = []; $cur = '';
        foreach ($words as $wd) {
            $trial = $cur === '' ? $wd : $cur . ' ' . $wd;
            if ($this->GetStringWidth($this->safe($trial)) > $w) {
                if ($cur !== '') $lines[] = $cur;
                $cur = $wd;
            } else $cur = $trial;
        }
        if ($cur !== '') $lines[] = $cur;
        return $lines ?: [$s];
    }

    private function safe($s): string
    {
        $s = (string)$s;
        $s = strtr($s, [
            "\u{2013}" => '-', "\u{2014}" => '-',
            "\u{2018}" => "'", "\u{2019}" => "'",
            "\u{201C}" => '"', "\u{201D}" => '"',
            "\u{2026}" => '...', "\u{2212}" => '-',
        ]);
        return @iconv('UTF-8', 'ISO-8859-1//TRANSLIT//IGNORE', $s) ?: $s;
    }

    /* ═══════════════════════════════════════════════════════════════════
     * BUILD — assembles all sections from the tender data.
     * ═════════════════════════════════════════════════════════════════ */

    public static function build(array $ctx): string
    {
        if (!defined('FPDF_FONTPATH')) define('FPDF_FONTPATH', ROOT . '/fonts/fpdf/');
        if (!class_exists('FPDF')) require ROOT . '/core/FPDF.php';

        $pdf = new self('P', 'mm', 'A4');
        $pdf->AliasNbPages();
        $t         = $ctx['tender']    ?? [];
        $spec      = $ctx['spec']      ?? [];
        $equipment = $ctx['equipment'] ?? [];
        $items     = $ctx['items']     ?? [];
        $version   = $ctx['version']   ?? null;
        $sections  = $ctx['sections']  ?? [];
        $lines     = $ctx['lines']     ?? [];
        $overheads = $ctx['overheads'] ?? [];

        $pdf->setMeta('Services Walkthrough Manual', (string)($t['tender_no'] ?? '') . ' · ' . (string)($t['buyer_name'] ?? ''));
        $pdf->setTenderIdRef((int)($t['c0d'] ?? 0));
        $pdf->coverPage($t, $spec, $items, $version, $overheads);

        $sectionTitles = [
            'About this tender',
            'Details tab — every field',
            'Prep tab — required documents',
            'Items tab (a) — Services bid-time answers',
            'Items tab (b) — Buyer\'s item list',
            'Pricing tab — versions, lines, overheads',
            'Submission tab — dispatch + proof',
            'Follow up · Opening · Negotiations tabs',
            'Awarded tab — mark WON',
            'Contract signing tab — terms + chain',
            'Convert to project — the button',
            'Post-conversion handoff (FM → Exec → PM)',
            'Operational setup (PM · PC · FS)',
            'Ongoing operations',
            'Field reference — enum values',
        ];
        $pdf->toc($sectionTitles);

        $pdf->AddPage(); $pdf->h1('1 · About this tender', 'context — why we\'re using this sample');
        $pdf->p("This walkthrough uses tender {$t['tender_no']} — the persistent Vodacom Tower PM sample. It is a Services tender because a client (Vodacom Tanzania) is asking us to run a Preventive Maintenance programme across 50 BTS sites for 24 months. Services tenders differ from Goods (buying and delivering physical products) and Works (building infrastructure) in three important ways:");
        $pdf->bullets([
            'The buyer\'s "item list" is a mix of visits, retainers, spare packs and SLA coverage — not physical products.',
            'The bid must answer the CRM Services Checklist (10 structured questions on the Items tab) — sites, equipment, PM cadence, callouts, SLA, spare policy, invoicing, pricing model, and the SOP for adhoc part replacements.',
            'On WON → convert, those 10 answers pre-populate a DRAFT service_contract so PC never re-types what CRM already collected during the bid.',
        ]);
        $pdf->calloutBox('Why this matters', 'On a Goods tender you can win without knowing which supplier will fulfil it. On a Services tender you cannot — the bid IS the contract. Every callout SLA, every spare-policy rule, every emergency multiplier you quote becomes contractual on Won.');

        $pdf->AddPage(); $pdf->h1('2 · Details tab', 'every field explained · what the form saves to the DB');
        $pdf->p('The Details tab is the tender header — most fields were captured on Add tender. Fields with an [Edit] button can be modified inline by CRM / Admin / Executive.');
        $pdf->fieldTable([
            'Source'             => 'PRIVATE_CLIENT (a private RFP from Vodacom) — NEST_PUBLIC would be a Tanzania PPRA public tender ingested via the NeST feed.',
            'Source ref / URL'   => 'Client-side reference and portal link — carried into the contract for cross-reference.',
            'Buyer'              => 'Selected from customers dropdown when adding — auto-copies branch name into buyer_name.',
            'Project type'       => 'GOODS / SERVICES / WORKS / CONSULTANCY — LOCKED after creation. Type drives the Items tab UI and the post-Won convert path.',
            'Industry'           => 'Telecom / Construction / Marine / etc — informational only.',
            'Region + District'  => 'Where the work will be delivered — auto-flows into c_projects on convert.',
            'Received / Deadline'=> 'Received = the day CRM logged the tender · Deadline = submission-close date (drives the submission-countdown chip).',
            'Description'        => 'Free-form scope summary — appears at the top of every internal report + pricing PDF subtitle.',
            'Submission method'  => 'Sealed envelope · e-Procurement portal · email · courier — captured now so the Submission tab knows what to prompt for.',
            'Estimated value'    => 'Ballpark from CRM (informational). Actual bid amount is the SELL total from the pricing tab.',
        ]);
        $pdf->calloutBox('For this sample', 'Vodacom sample fields already populated. To edit any field, click [Edit details] at the top-right of the Details tab, change the value, then Save.');

        $pdf->AddPage(); $pdf->h1('3 · Prep tab — required documents', 'checklist of what must be in the submission envelope');
        $pdf->p('The Prep tab is where CRM ticks which document types the buyer requires for this specific tender. Statutory docs (TIN, VRN, tax clearance, TRA compliance) are pre-marked with a star.');
        $pdf->h3('Buttons'); $pdf->bullets([
            '[Apply statutory defaults] — quick-tick all statutory items in one click. Recommended as the first action on every new tender.',
            '[Save requirements] — persists the ticked set. Must be clicked after every change.',
            '[Clear all] — untick everything (rarely needed).',
        ]);
        $pdf->h3('Why this matters'); $pdf->p('The count on the Prep tab appears as a badge next to "Prep" on the tabs bar so the CRM inbox at a glance shows which tenders still need document assembly.');

        // ── Section 4: Items tab (a) service spec
        $pdf->AddPage(); $pdf->h1('4 · Items tab (a) — Services bid-time answers', '10 CRM-checklist questions · fills the DRAFT service_contract on Won');
        $pdf->p('This is the Services-only card at the top of the Items tab. It appears ONLY when project_type = services — Goods / Works / Consultancy tenders skip it entirely.');
        $pdf->calloutBox('Editor gate', 'Only Administrator and CRM can save these answers. All other roles (Projects Coordinator, Project Manager, Executive, Accountant, Finance Manager) see the card read-only for context.');
        $pdf->h3('The 10 questions + Vodacom sample answers');
        $pdf->fieldTable([
            '1 · Sites'               => ($spec['no_of_sites'] ?? '—') . ' sites  ·  ' . ($spec['sites_notes'] ?? '—'),
            '2 · Equipment breakdown' => self::eqSummary($equipment),
            '3 · PM cadence + payment'=> ($spec['pm_cadence'] ?? '—') . '  ·  paid ' . ($spec['pm_payment_structure'] ?? '—'),
            '4 · Callouts'            => ((int)($spec['has_callouts'] ?? 0) === 1 ? 'included' : 'excluded')
                                          . '  ·  ' . ($spec['callout_payment_structure'] ?? '—')
                                          . (isset($spec['callout_fee'])          ? '  ·  fee ' . number_format((float)$spec['callout_fee']) . ' TZS'      : '')
                                          . (isset($spec['emergency_multiplier']) ? '  ·  emergency x' . number_format((float)$spec['emergency_multiplier'], 2) : '')
                                          . (isset($spec['free_callouts_per_month']) ? '  ·  ' . (int)$spec['free_callouts_per_month'] . ' free/mo' : ''),
            '5 · SLA hours'           => 'EMR ' . ($spec['sla_emergency_hrs'] ?? '—') . 'h  ·  URG ' . ($spec['sla_urgent_hrs'] ?? '—') . 'h  ·  ROU ' . ($spec['sla_routine_hrs'] ?? '—') . 'h',
            '6 · Spare policy'        => ($spec['spare_policy'] ?? '—') . '  ·  ' . ($spec['spare_policy_notes'] ?? ''),
            '7 · Service pack'        => ((int)($spec['client_supplied_pack'] ?? 0) === 1 ? 'client-supplied' : 'company-supplied'),
            '8 · Invoicing'           => ($spec['invoicing_cadence'] ?? '—') . '  ·  terms ' . ($spec['payment_terms_days'] ?? '—') . 'd',
            '9 · Pricing model'       => 'labour ' . number_format((float)($spec['labour_rate_per_hr']  ?? 0)) . '/hr'
                                          . '  ·  mileage ' . number_format((float)($spec['mileage_rate_per_km'] ?? 0)) . '/km'
                                          . '  ·  parts markup ' . number_format((float)($spec['parts_markup_pct'] ?? 0), 2) . '%',
            '10 · Adhoc part SOP'     => ((int)($spec['part_replacement_sop_agreed'] ?? 0) === 1 ? 'agreed with client' : 'not yet agreed'),
        ]);
        $pdf->h3('How to save'); $pdf->step(1, 'Fill / edit any answers on the card.', 'You can save partial — untouched fields keep their existing values.');
        $pdf->step(2, 'For equipment: click [Add row] to insert a Type / Size / Qty row.', 'e.g. GEN · 60-100 kVA · 20. Empty rows are dropped on save.');
        $pdf->step(3, 'Click [Save answers] at the top-right of the card.', 'A toast confirms save. The answers persist to tender_service_spec.');

        // ── Section 5: Items tab (b) buyer's list
        $pdf->AddPage(); $pdf->h1('5 · Items tab (b) — Buyer\'s item list', 'the actual lines the buyer will see on the priced BOQ');
        $pdf->p('Below the Services card is the buyer\'s item list — the lines that will appear on the pricing BOQ and, ultimately, on the client-facing invoice. For Services tenders the default line_kind is SERVICE (visits / retainers / SLA) but you can also add PRODUCT lines (spare packs), MILESTONE lines, or LOT lines.');
        $pdf->h3('Vodacom sample items · ' . count($items) . ' lines');
        foreach ($items as $it) {
            $pdf->bullet(sprintf('%s  ·  %s  ·  qty %s %s',
                (string)($it['item_ref']    ?? '—'),
                (string)($it['description'] ?? '—'),
                number_format((float)($it['qty'] ?? 0)),
                (string)($it['uom']         ?? '')
            ));
        }
        $pdf->Ln(1);
        $pdf->h3('Buttons'); $pdf->bullets([
            '[Add line] — inline row with description / qty / UOM / kind. Save each line individually.',
            '[Bulk paste] — paste a table from Excel or the tender PDF; each row becomes a line.',
            'Pencil icon — edit the row inline.',
            'X icon — remove the row (soft-delete).',
        ]);

        // ── Section 6: Pricing tab
        $pdf->AddPage(); $pdf->h1('6 · Pricing tab', 'versions · sections · lines · overhead pool');
        $pdf->p('The Pricing tab is where the tender is priced. A tender can have many versions — each version is a full snapshot of the priced BOQ. Versions have three statuses: DRAFT (editable), SUBMITTED (locked, awaiting PM → FM → Exec approval), APPROVED (becomes the bid amount).');
        $pdf->h3('Version header fields');
        $pdf->fieldTable([
            'Version label'      => 'Free-form name (e.g. "v1 · initial pricing pass").',
            'Currency'           => 'TZS by default. Locked at header save.',
            'VAT %'              => 'e.g. 18.00 — added to client-facing prices where applicable.',
            'Contingency %'      => 'Buffer added to internal margin math.',
            'Prelims (fixed)'    => 'Fixed preliminaries — appears as a client-visible "Preliminaries" line on the client BOQ.',
            'Default markup %'   => 'Applied when a new line is added or when [Seed from items] is clicked.',
            'Pricing mode'       => 'PER_ITEM (default — line-by-line markup) or LUMPSUM (single flat number).',
        ]);
        $pdf->h3('Sections and lines');
        $pdf->p('Lines live inside sections so the client BOQ can be structured (e.g. "Preventive maintenance" / "Callouts" / "Materials"). Click [Add section] to create one. Inside each section, click [Add line] or use [Seed from items] to auto-import the buyer\'s item list as pricing lines.');
        if ($version) {
            $pdf->calloutBox('Vodacom sample version', 'v{v} · {s} · direct cost {c} TZS · overhead {oh} TZS · sell {p} TZS · margin {m}%', [
                '{v}' => (string)$version['version_no'],
                '{s}' => (string)$version['status'],
                '{c}' => number_format((float)$version['sum_cost'] - (float)$version['sum_overhead']),
                '{oh}'=> number_format((float)$version['sum_overhead']),
                '{p}' => number_format((float)$version['sum_price']),
                '{m}' => number_format((float)$version['margin_pct'], 1),
            ]);
        }

        $pdf->h2('The overhead pool');
        $pdf->p('Overheads are costs that don\'t belong to any single BOQ line but must be recovered before you can quote a margin. On a Services bid, overheads dominate: the FS crew retainer, vehicles, tools, admin allocation etc. Each overhead line has a display_mode that decides where it surfaces to the buyer:');
        $pdf->fieldTable([
            'DISTRIBUTED'   => 'Amount is spread pro-rata across item lines by line_price. The buyer sees a higher unit price on every line but no separate overhead entry.',
            'PRELIMS_LINE'  => 'Amount shows on the client BOQ as its own line under "Preliminaries" — buyer can see it.',
            'INTERNAL_ONLY' => 'Amount only affects internal margin math. Never shown to the buyer.',
        ]);
        if (!empty($overheads)) {
            $pdf->h3('Vodacom sample overhead pool');
            foreach ($overheads as $oh) {
                $pdf->bullet(sprintf('%s  ·  %s  ·  %s  ·  %s TZS',
                    (string)$oh['category'],
                    (string)$oh['label'],
                    (string)$oh['display_mode'],
                    number_format((float)$oh['amount'])
                ));
            }
            $pdf->Ln(1);
            $pdf->calloutBox('Why we added these', 'A Services PM contract needs Manpower (FS techs), Equipment (FS vehicles + toolbox), Transport (fuel/mileage float), OTHER (adhoc consumables) and Admin (PM + NOC coverage). Without these the sell price would only recover the direct visit/retainer costs and the bid would lose money on execution.');
        }

        $pdf->h2('Submit the version for approval');
        $pdf->step(1, 'Click [Submit for approval] at the top of the version.', 'Version status flips DRAFT → SUBMITTED. Version is now locked (uneditable) until approved / declined.');
        $pdf->step(2, 'Chain PRICING_APPROVAL runs: PM → FM → EXEC.', 'Each step gets an inbox item at FinChains/myQueue.');
        $pdf->step(3, 'On Exec APPROVED → click [Apply] on the version.', 'This copies the approved SELL total back to tender_tracker.submission_amount so the Submission tab is pre-filled.');

        // ── Section 7: Submission
        $pdf->AddPage(); $pdf->h1('7 · Submission tab', 'submission_method + submission_amount + proof attachments');
        $pdf->p('Once pricing is APPROVED and applied, the Submission tab captures the moment the bid is submitted.');
        $pdf->fieldTable([
            'Submission method'    => 'How the bid was dispatched — Portal, Sealed envelope, Courier, Email.',
            'Submission amount'    => 'Auto-filled from the applied pricing version. Editable if the buyer demanded a last-minute change.',
            'Submission date/time' => 'Timestamped on save.',
            'Proof attachments'    => 'Upload delivery receipts, portal screenshots, courier PODs. Preview inline in an iframe modal.',
        ]);
        $pdf->step(1, 'Fill Submission method + Amount + Date.', 'Click [Save submission].');
        $pdf->step(2, 'Click [Upload proof] and pick the file.', 'Kind = DELIVERY_RECEIPT / PORTAL_SCREENSHOT / COURIER_POD / OTHER.');

        // ── Section 8-10 grouped
        $pdf->AddPage(); $pdf->h1('8 · Follow up · Opening · Negotiations tabs', 'lightly-used tracking tabs between submission and result');
        $pdf->bullets([
            'Follow up — log every contact with the buyer (call, email, portal message) between submission and the opening. Free-form.',
            'Opening — capture other bidders when the tender is opened (their name, bid amount, ranking). Populates the "Awarded" tab with competitors.',
            'Negotiations — record any post-opening negotiation rounds (e.g. best-and-final-offer, price challenges). Free-form.',
        ]);
        $pdf->calloutBox('Skip on the sample', 'The Vodacom sample doesn\'t need these — you can jump straight from Submission to Awarded.');

        $pdf->AddPage(); $pdf->h1('9 · Awarded tab — mark WON', 'the trigger for everything downstream');
        $pdf->p('The Awarded tab is where CRM records the tender outcome. This is the single most important click in the tender flow — flipping the tender to WON is what unlocks Contract signing, then convert to project, then all downstream ops.');
        $pdf->step(1, 'Click [Record result] on the Awarded tab.',              'A form appears — fill the fields below.');
        $pdf->step(2, 'Choose Result status.',                                  'WON / LOST / NO_AWARD / WITHDRAWN. This walkthrough assumes WON.');
        $pdf->step(3, 'Choose Reason category.',                                'For WON: PRICE / RELATIONSHIP / TECHNICAL / TIMING / OTHER. This feeds the win-rate report.');
        $pdf->step(4, 'Fill Winning bidder + Winning amount.',                  'For a WON tender, this is "US" and the amount matches your bid.');
        $pdf->step(5, 'Free-form Reason + Lessons.',                            'Post-bid retrospective — feeds the CRM knowledge base.');
        $pdf->step(6, 'Click [Save result].',                                   'tender_tracker.tender_status = WON. Contract-signing and Convert-to-project become available.');

        // ── Section 10: Contract signing
        $pdf->AddPage(); $pdf->h1('10 · Contract signing tab', 'terms + Executive approval + signing + receipts');
        $pdf->p('Contract signing is the mandatory step between WON and Convert. It captures the final signed contract terms and routes them through Finance Manager and Executive for verification. Only on Exec APPROVED can CRM proceed to Convert.');
        $pdf->h3('Contract terms fields');
        $pdf->fieldTable([
            'Contract value'   => 'The signed value (may differ from your bid — buyer may have negotiated).',
            'Currency'         => 'TZS default.',
            'Payment terms'    => 'Days from invoice (30 / 45 / 60). Matches the spec answer #8.',
            'Retention %'      => 'Amount held back until warranty expiry (typical: 5-10%).',
            'Warranty months'  => 'Post-completion warranty period.',
            'Start date'       => 'Contract effective date.',
            'End date'         => 'Contract expiry — for a 24-month PM programme, start + 24 months.',
            'Milestones JSON'  => 'For milestone-billed contracts, define percentage milestones and their triggers.',
        ]);
        $pdf->h3('Chain: TENDER_CONTRACT_APPROVAL (2 steps)');
        $pdf->step(1, 'CRM clicks [Submit contract for approval].',                     'Status: chain IN_PROGRESS at step 1.');
        $pdf->step(2, 'FINANCE MANAGER sees it in FinChains/myQueue → [Approve].',       'Same click assigns 3 seats: ACCOUNTANT + PROCUREMENT + EXECUTIVE. FM picks a specific user for each. The Exec seat determines who signs off at step 2.');
        $pdf->step(3, 'EXECUTIVE (the one FM assigned) sees the item → [Approve].',      'Same click assigns 1 seat: PROJECT_MANAGER. Chain is terminal on Exec APPROVED.');
        $pdf->step(4, 'On terminal APPROVED — terms are LOCKED.',                        'Further edits require an Executive unlock with a mandatory reason (audited).');
        $pdf->calloutBox('Why seats are assigned during the chain',
            'By the time the contract is Exec-approved, the four seats (Accountant, Procurement, Executive, Project Manager) are already bound to the tender. On convert, those seats become the project team automatically — no separate "who owns this?" meeting is needed.');
        $pdf->h3('Record signing');
        $pdf->step(1, 'Click [Record contract signing].',                                'Enter signed date + client signatory name.');
        $pdf->step(2, 'Attach the scanned signed contract.',                             'Stored on tender_contract_attachments.');
        $pdf->step(3, 'Record any signing-time receipts (down-payment, deposit).',       'Optional but recommended for milestone contracts.');

        // ── Section 11: Convert
        $pdf->AddPage(); $pdf->h1('11 · Convert to project', 'the moment tender becomes project — services-specific side effects');
        $pdf->p('With the contract Exec-approved and signed, CRM can now convert the tender into a live project.');
        $pdf->step(1, 'Open the Awarded tab (or Contract signing tab).',            'Click [Convert to project].');
        $pdf->step(2, 'Confirm the dialog.',                                        'The system creates a c_projects row, links tender_tracker.linked_project_id, and pre-assigns the 4 seats from the contract chain.');
        $pdf->h3('What gets created (behind the click)');
        $pdf->bullets([
            'c_projects row · project_no = P-YYYY-NNNN · origin = TENDER_BDM or TENDER_PUBLIC · contract_value from the signed terms.',
            'c_projects_role_changes rows · PM, Accountant, Procurement seats logged as the project\'s first assignments.',
            'ProjectStarter seeds the per-project-type starter checklist for the assigned PM.',
            '[Services only] service_contracts DRAFT row · pre-populated from tender_service_spec via TenderServiceSpec::toContractPayload().',
        ]);
        $pdf->calloutBox('Services-only side effect', 'Because this is a services tender, a DRAFT service_contract is created automatically. It inherits spare_policy, invoice_cadence, payment_terms_days, callout_fee, emergency_multiplier, free_callouts_per_month, labour_rate_per_hr, mileage_rate_per_km, parts_markup_pct and both notes fields from the bid-time answers. PC never re-types.');

        // ── Section 12: FM → Exec → PM handoff (this IS the contract chain, but framed post-convert)
        $pdf->AddPage(); $pdf->h1('12 · Post-conversion handoff (FM · Exec · PM)', 'the same 4 seats now become the project team');
        $pdf->p('The handoff described in the CRM narrative is the TENDER_CONTRACT_APPROVAL chain in section 10 — its purpose is exactly to assign the project team before the tender becomes a project. Once convert has run, the following inboxes light up:');
        $pdf->h3('Finance Manager · already actioned in section 10');
        $pdf->p('FM\'s work is done at contract approval — Accountant + Procurement + Executive were assigned there. FM keeps read access to the new project via Projects/view/{pid} for cash-flow oversight.');
        $pdf->h3('Executive · already actioned in section 10');
        $pdf->p('Exec\'s work is done at contract approval — Project Manager was assigned there. Exec now sees the new project on Executive Dashboard.');
        $pdf->h3('Project Manager (assigned by Exec)');
        $pdf->step(1, 'PM opens Projects/view/{pid} (the new project).',              'Sees the DRAFT service_contract pre-populated with the spec answers.');
        $pdf->step(2, 'PM opens ServiceContracts/view/{cid} for the DRAFT contract.',  'Reviews the pre-filled fields — adjusts anything the signed contract changed vs the bid.');
        $pdf->step(3, 'PM sets the PM cadence schedule.',                              'Click [Generate schedule] once sites and assets are added (see section 13). This creates sp_pm_duedate rows.');
        $pdf->step(4, 'PM completes any starter-checklist tasks.',                     'ProjectStarter seeded them on convert — items like "confirm signed contract on file", "onboard FS lead".');

        // ── Section 13: PC / FS operational
        $pdf->AddPage(); $pdf->h1('13 · Operational setup (PC · FS)', 'from DRAFT service_contract to first PM visit');
        $pdf->h2('Projects Coordinator (PC) — the operational owner');
        $pdf->p('PC is the day-to-day owner of the project. From the DRAFT service_contract, PC does the four things that make the contract go LIVE:');
        $pdf->h3('a · Add the sites');
        $pdf->step(1, 'Open ServiceContracts/view/{cid} → Sites tab.',                          'The spec said no_of_sites = ' . ($spec['no_of_sites'] ?? '—') . '. PC adds each site here.');
        $pdf->step(2, 'For each site: name, site code, physical address, region, GPS.',           'Bulk import supported for the 50-site Vodacom fleet.');
        $pdf->h3('b · Register the assets on each site');
        $pdf->step(1, 'On each site, click [Add asset].',                                          'The spec\'s equipment breakdown (GEN / AC / UPS) tells PC roughly what to expect.');
        $pdf->step(2, 'For each asset: type, make/model, serial no, capacity, install date.',      'Serial numbers matter — spare requests reference them.');
        $pdf->h3('c · Configure the SLAs');
        $pdf->step(1, 'Open the SLA tab.',                                                          'The bid spec captured EMR / URG / ROU hours — those pre-populate but PC can adjust per-site.');
        $pdf->step(2, 'Confirm each SLA row.',                                                      'Callout dispatch prioritises against these numbers.');
        $pdf->h3('d · Generate the PM schedule + Activate');
        $pdf->step(1, 'Click [Generate schedule].',                                                  'Creates sp_pm_duedate rows for every asset at the PM cadence from the spec.');
        $pdf->step(2, 'Click [Activate contract].',                                                  'Requires >= 1 site + >= 1 asset. Status DRAFT → ACTIVE. Callouts and PMs are now billable.');

        $pdf->h2('Field Supervisor (FS) — on-ground execution');
        $pdf->p('FS is the tech who actually visits the sites. Their inbox is driven by the sp_pm_duedate rows PC generated and by dispatched callouts.');
        $pdf->bullets([
            'FS opens SpVisits/plan to see upcoming PM visits by site + date.',
            'On arrival, FS creates a visit report — records readings, replaces the spare-pack items, uploads photos.',
            'For adhoc issues found during the visit → FS raises a spare request or a callout follow-up.',
            'For emergency callouts → FS is dispatched via SpCallouts and must respond within the SLA hours.',
        ]);
        $pdf->calloutBox('PC or FS? Who does what',
            'PC owns the plan and the paperwork — sites, assets, schedules, contract activation. FS owns the execution — visits, reports, callout responses. On smaller programmes PC often plays both roles.');

        // ── Section 14: Ongoing ops
        $pdf->AddPage(); $pdf->h1('14 · Ongoing operations', 'once the contract is ACTIVE');
        $pdf->bullets([
            'Monthly retainer invoicing — Accountant runs the monthly invoice (uses invoicing_cadence + monthly_retainer from the contract).',
            'Callout tracking — free_callouts_per_month quota is decremented per callout. Overage is billed at callout_fee, emergency at fee x emergency_multiplier.',
            'Spare requests — FS raises → PC/PM approves → Procurement issues LPO to supplier (if COMPANY spare_policy) or draws from client stock (if CLIENT spare_policy).',
            'PM visit closure — sp_pm_duedate updated on completion. Missing a PM within the SLA window is flagged on the PM Manager dashboard.',
            'Contract renewal — 60 days before end_date, PM Manager gets a renewal alert on Projects/view/{pid}.',
        ]);

        // ── Section 15: Field reference
        $pdf->AddPage(); $pdf->h1('15 · Field reference — enum values', 'copy-paste ready');
        $pdf->h3('pm_cadence');                 $pdf->p('MONTHLY · QUARTERLY · SEMI_ANNUAL · ANNUAL · ADHOC');
        $pdf->h3('pm_payment_structure');       $pdf->p('MONTHLY_RETAINER · PER_EVENT · BUNDLED_WITH_CALLOUTS');
        $pdf->h3('callout_payment_structure');  $pdf->p('PER_CALLOUT_FEE · T_AND_M · BUNDLED · FREE_QUOTA_PLUS_FEE');
        $pdf->h3('spare_policy');               $pdf->p('COMPANY · CLIENT · CONSIGNMENT · FIXED_PACK');
        $pdf->h3('invoicing_cadence');          $pdf->p('MONTHLY_RETAINER · PER_EVENT · MILESTONE · CONSUMPTION');
        $pdf->h3('overhead category');          $pdf->p('MANPOWER · EQUIPMENT · TRANSPORT · DISMANTLE_ASSEMBLE · SITE_SETUP · ADMIN_OVERHEAD · CUSTOMS_DUTY · FREIGHT_INSURANCE · CLEARING_HANDLING · OTHER');
        $pdf->h3('overhead display_mode');      $pdf->p('PRELIMS_LINE (client-visible) · DISTRIBUTED (baked into unit prices) · INTERNAL_ONLY (never shown to buyer)');
        $pdf->h3('tender_status');              $pdf->p('OPEN · SUBMITTED · WON · LOST · NO_AWARD · WITHDRAWN');
        $pdf->h3('service_contract.status');    $pdf->p('DRAFT · ACTIVE · SUSPENDED · EXPIRED · TERMINATED');

        $pdf->Ln(4);
        $pdf->SetFont('Roboto_Condensed-Italic', '', 9);
        $pdf->SetTextColor(...$pdf->cMuted);
        $pdf->MultiCell(0, 5, $pdf->safe('End of manual · Slice 8S-10 · This document is regenerated live every time you click [Download walkthrough manual] on Tenders/view/' . ($t['c0d'] ?? 'N') . ' so numbers always reflect the current tender state.'));

        return $pdf->Output('S');
    }

    private static function eqSummary(array $equipment): string
    {
        if (empty($equipment)) return '—';
        $bits = [];
        foreach ($equipment as $r) {
            $bits[] = trim(sprintf('%s %s x%d', (string)($r['type'] ?? ''), (string)($r['size'] ?? ''), (int)($r['count'] ?? 0)));
        }
        return implode('  ·  ', $bits);
    }
}
