<?php
declare(strict_types=1);

require_once __DIR__ . '/bootstrap.php';
require_once dirname(__DIR__) . '/pdf-sanitize/pdf_sanitize.php';

/** Log every PandaDoc API request/response to php/log/pandadoc_api.log for debugging. */
function pandadoc_api_log(string $step, string $method, string $path, $requestBody, int $httpCode, $responseData, bool $ok, ?string $curlError = null): void {
    $logDir = dirname(__DIR__, 1) . '/log';
    if (!is_dir($logDir)) @mkdir($logDir, 0777, true);
    $logFile = $logDir . '/pandadoc_api.log';
    $reqStr = is_string($requestBody) ? $requestBody : json_encode($requestBody, JSON_UNESCAPED_SLASHES);
    $resStr = is_string($responseData) ? $responseData : json_encode($responseData, JSON_UNESCAPED_SLASHES);
    if (strlen($reqStr) > 4000) $reqStr = substr($reqStr, 0, 4000) . '...[truncated]';
    if (strlen($resStr) > 4000) $resStr = substr($resStr, 0, 4000) . '...[truncated]';
    $line = '[' . date('c') . "] step=$step method=$method path=$path http=$httpCode ok=" . ($ok ? 'yes' : 'NO') . "\n";
    $line .= "  request: $reqStr\n";
    $line .= "  response: $resStr\n";
    if ($curlError !== null && $curlError !== '') $line .= "  curl_error: $curlError\n";
    @file_put_contents($logFile, $line . "\n", FILE_APPEND);
}

function pandadoc_api_key(): string {
    $k = pd_env('PANDADOC_API_KEY', '');
    if ($k !== '') return $k;
    // Backward-compat: allow using pandadoc-quote-builder config if present.
    $qb = dirname(__DIR__, 1) . '/../pandadoc-quote-builder/config.php';
    if (is_file($qb)) {
        require_once $qb;
        if (defined('PANDADOC_API_KEY') && (string)PANDADOC_API_KEY !== '') return (string)PANDADOC_API_KEY;
    }
    return '';
}

function pandadoc_api_base(): string {
    $b = pd_env('PANDADOC_API_BASE', '');
    if ($b !== '') return rtrim($b, '/');
    if (defined('PANDADOC_API_BASE') && (string)PANDADOC_API_BASE !== '') return rtrim((string)PANDADOC_API_BASE, '/');
    return 'https://api.pandadoc.com/public/v1';
}

if (!function_exists('pd_backoff_us')) {
    function pd_backoff_us(int $attempt): int {
        // attempt: 0,1,2,... -> 200ms, 500ms, 1000ms, 2000ms (cap)
        $map = [200000, 500000, 1000000, 2000000];
        return $map[min($attempt, count($map) - 1)];
    }
}

function pandadoc_http(string $method, string $path, ?array $jsonBody = null, ?string $logStep = null): array {
    $apiKey = pandadoc_api_key();
    if ($apiKey === '') {
        pd_json(500, ['success' => false, 'error' => 'Set PANDADOC_API_KEY (env var or pandadoc-quote-builder/config.php)']);
    }
    $url = pandadoc_api_base() . $path;

    $headers = [
        'Authorization: API-Key ' . $apiKey,
        'Accept: application/json',
    ];
    $payload = null;
    if ($jsonBody !== null) {
        $payload = json_encode($jsonBody, JSON_UNESCAPED_SLASHES);
        $headers[] = 'Content-Type: application/json';
    }

    $attempts = 4;
    for ($i = 0; $i < $attempts; $i++) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 60,
        ]);
        $resp = curl_exec($ch);
        $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err = curl_error($ch);
        curl_close($ch);

        $data = json_decode((string)$resp, true);
        if (!is_array($data)) $data = ['raw' => substr((string)$resp, 0, 2000)];

        $isRetryableHttp = in_array($code, [429, 500, 502, 503, 504], true);
        if ($err || ($code && $isRetryableHttp)) {
            if ($i < ($attempts - 1)) {
                usleep(pd_backoff_us($i));
                continue;
            }
            if ($logStep !== null) {
                pandadoc_api_log($logStep, $method, $path, $jsonBody, $code, $data, false, $err ?: null);
            }
            return ['ok' => false, 'code' => $code, 'error' => $err ?: null, 'data' => $data];
        }

        $ok = ($code >= 200 && $code < 300);
        if ($logStep !== null) {
            pandadoc_api_log($logStep, $method, $path, $jsonBody, $code, $data, $ok, null);
        }
        return ['ok' => $ok, 'code' => $code, 'error' => null, 'data' => $data];
    }

    if ($logStep !== null) {
        pandadoc_api_log($logStep, $method, $path, $jsonBody, 0, null, false, 'Unexpected retry loop exit');
    }
    return ['ok' => false, 'code' => 0, 'error' => 'Unexpected retry loop exit', 'data' => null];
}

function pandadoc_get_document_status(string $documentId): string {
    $res = pandadoc_http('GET', '/documents/' . rawurlencode($documentId));
    if (!$res['ok']) return '';
    $data = $res['data'];
    return is_array($data) ? (string)($data['status'] ?? '') : '';
}

function pandadoc_wait_for_document_status(string $documentId, array $allowed, int $timeoutSeconds = 30): string {
    $start = time();
    do {
        $st = pandadoc_get_document_status($documentId);
        if ($st !== '' && in_array($st, $allowed, true)) return $st;
        usleep(400 * 1000);
    } while ((time() - $start) < $timeoutSeconds);
    return pandadoc_get_document_status($documentId);
}

/**
 * PATCH /documents/{id} — update image blocks, fields, tokens, etc.
 *
 * @return array{ok: bool, code: int, error: ?string, data: mixed}
 */
function pandadoc_update_document(string $documentId, array $body): array {
    return pandadoc_http('PATCH', '/documents/' . rawurlencode($documentId), $body, 'update_document');
}

/**
 * Populate template image blocks from public URLs (one URL per block).
 * Block names must match the Image block names in the PandaDoc template (e.g. "Image 2").
 *
 * @param list<array{name: string, url: string}> $blocks
 * @return array{ok: bool, reason?: string, applied?: int, http?: int, data?: mixed}
 */
function pandadoc_apply_image_blocks(string $documentId, array $blocks): array {
    $images = [];
    foreach ($blocks as $block) {
        if (!is_array($block)) {
            continue;
        }
        $name = trim((string)($block['name'] ?? ''));
        $url = trim((string)($block['url'] ?? ''));
        if ($name === '' || $url === '') {
            continue;
        }
        $images[] = ['name' => $name, 'urls' => [$url]];
    }
    if (empty($images)) {
        return ['ok' => false, 'reason' => 'no_images'];
    }
    $res = pandadoc_update_document($documentId, ['images' => $images]);
    if (!empty($res['ok'])) {
        return ['ok' => true, 'applied' => count($images), 'http' => $res['code'] ?? 0, 'data' => $res['data'] ?? null];
    }
    return [
        'ok' => false,
        'reason' => 'image_update_failed',
        'http' => $res['code'] ?? 0,
        'data' => $res['data'] ?? null,
    ];
}

function pandadoc_create_from_template(string $templateUuid, string $name, array $recipients, array $fields = [], array $pricingTables = [], array $images = []): array {
    $body = [
        'name' => $name,
        'template_uuid' => $templateUuid,
        'recipients' => $recipients,
    ];
    if (!empty($fields)) {
        // Populate PandaDoc fields (for actual template fields)…
        $body['fields'] = $fields;

        // …and also populate custom variables (tokens) using the same values.
        // This lets your Template 1 custom variables like [Bill to Name] work
        // without changing the rest of the integration.
        $tokens = [];
        foreach ($fields as $nameKey => $cfg) {
            if (!is_array($cfg)) continue;
            if (!array_key_exists('value', $cfg)) continue;
            $tokens[] = [
                'name'  => (string)$nameKey,
                'value' => $cfg['value'],
            ];
        }
        if (!empty($tokens)) {
            $body['tokens'] = $tokens;
        }
    }
    if (!empty($pricingTables)) {
        $body['pricing_tables'] = $pricingTables;
    }
    if (!empty($images)) {
        $body['images'] = $images;
    }
    $res = pandadoc_http('POST', '/documents', $body, 'create_document');
    if (!$res['ok']) {
        return ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
    }
    $id = (string)($res['data']['id'] ?? '');
    return $id !== '' ? ['success' => true, 'id' => $id, 'status' => $res['data']['status'] ?? null] : ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
}

function pandadoc_create_section_from_template(string $documentId, string $templateUuid, string $name, array $recipients, string $mergeFieldScope = 'document'): array {
    $path = '/documents/' . rawurlencode($documentId) . '/sections/uploads?merge_field_scope=' . rawurlencode($mergeFieldScope);
    $body = [
        'name' => $name,
        'template_uuid' => $templateUuid,
        'recipients' => $recipients,
    ];
    $res = pandadoc_http('POST', $path, $body, 'add_section_from_template');
    if (!$res['ok']) return ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
    $uuid = (string)($res['data']['uuid'] ?? '');
    return $uuid !== '' ? ['success' => true, 'upload_id' => $uuid, 'status' => $res['data']['status'] ?? null] : ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
}

function pandadoc_create_section_from_url(string $documentId, string $url, string $name, string $mergeFieldScope = 'document'): array {
    $path = '/documents/' . rawurlencode($documentId) . '/sections/uploads?merge_field_scope=' . rawurlencode($mergeFieldScope);
    $body = [
        'name' => $name,
        'url' => $url,
        // If your Drive PDF has form fields you want PandaDoc to parse, set parse_form_fields=true.
        'parse_form_fields' => false,
    ];
    $res = pandadoc_http('POST', $path, $body, 'add_section_from_url');
    if (!$res['ok']) return ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
    $uuid = (string)($res['data']['uuid'] ?? '');
    return $uuid !== '' ? ['success' => true, 'upload_id' => $uuid, 'status' => $res['data']['status'] ?? null] : ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
}

function pandadoc_get_section_upload_status(string $documentId, string $uploadId): array {
    $path = '/documents/' . rawurlencode($documentId) . '/sections/uploads/' . rawurlencode($uploadId);
    $res = pandadoc_http('GET', $path);
    if (!$res['ok']) return ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
    return ['success' => true, 'data' => $res['data']];
}

function pandadoc_wait_section_processed(string $documentId, string $uploadId, int $timeoutSeconds = 60): array {
    $start = time();
    do {
        $st = pandadoc_get_section_upload_status($documentId, $uploadId);
        if (!$st['success']) return $st;
        $status = (string)($st['data']['status'] ?? '');
        if ($status === 'document_sections_upload.PROCESSED') return $st;
        if ($status === 'document_sections_upload.ERROR') return ['success' => false, 'error' => $st['data'], 'http' => 409];
        usleep(500 * 1000);
    } while ((time() - $start) < $timeoutSeconds);
    return pandadoc_get_section_upload_status($documentId, $uploadId);
}

function pandadoc_send_document(string $documentId, string $subject, string $message, bool $silent = false): array {
    $path = '/documents/' . rawurlencode($documentId) . '/send';
    $body = [
        'subject' => $subject,
        'message' => $message,
        'silent' => $silent,
    ];
    $res = pandadoc_http('POST', $path, $body, 'send_document');
    if (!$res['ok']) return ['success' => false, 'error' => $res['data'], 'http' => $res['code']];
    return ['success' => true, 'data' => $res['data']];
}

/**
 * Download document as PDF via GET /documents/{id}/download (not /download-protected).
 * Use this for the standard export; /download-protected is the digitally sealed variant.
 * Whether a signature-certificate *page* appears can still depend on completion type and workspace settings.
 */
function pandadoc_download_document_pdf(string $documentId): array {
    $apiKey = pandadoc_api_key();
    if ($apiKey === '') {
        return ['success' => false, 'http' => 0, 'error' => 'Set PANDADOC_API_KEY', 'pdf' => null];
    }
    $path = '/documents/' . rawurlencode($documentId) . '/download';
    $url = pandadoc_api_base() . $path;
    $headers = [
        'Authorization: API-Key ' . $apiKey,
        'Accept: application/pdf',
    ];
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_HTTPGET => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 120,
    ]);
    $resp = curl_exec($ch);
    $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $ctype = (string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    $err = curl_error($ch);
    curl_close($ch);

    if ($err !== '') {
        pandadoc_api_log('download_pdf', 'GET', $path, null, $code, ['curl_error' => $err], false, $err);
        return ['success' => false, 'http' => $code, 'error' => $err, 'pdf' => null];
    }

    $raw = is_string($resp) ? $resp : '';
    if ($code < 200 || $code >= 300) {
        $decoded = json_decode($raw, true);
        pandadoc_api_log('download_pdf', 'GET', $path, null, $code, is_array($decoded) ? $decoded : ['body' => substr($raw, 0, 500)], false, null);
        return ['success' => false, 'http' => $code, 'error' => is_array($decoded) ? $decoded : substr($raw, 0, 300), 'pdf' => null];
    }

    if ($raw === '' || (stripos($ctype, 'pdf') === false && substr($raw, 0, 4) !== '%PDF')) {
        pandadoc_api_log('download_pdf', 'GET', $path, null, $code, ['bytes' => strlen($raw), 'content_type' => $ctype], false, null);
        return ['success' => false, 'http' => $code, 'error' => 'Response was not a PDF', 'pdf' => null];
    }

    pandadoc_api_log('download_pdf', 'GET', $path, null, $code, ['bytes' => strlen($raw), 'content_type' => $ctype], true, null);
    return ['success' => true, 'http' => $code, 'error' => null, 'pdf' => $raw, 'content_type' => $ctype];
}

/**
 * Default directory for completed PDFs when PANDADOC_COMPLETED_PDF_DIR is not set.
 * Prefers …/php/pdf-sanitize/upload when pdf-sanitize sits next to pandadoc_integration;
 * else …/<site>/pdf-sanitize/upload when pdf-sanitize is at site root (sibling of php/).
 * Override with env PANDADOC_COMPLETED_PDF_DIR (absolute path recommended on cPanel).
 */
function pandadoc_completed_pdf_upload_dir(): string {
    $phpRoot = dirname(__DIR__);
    $phpPdfSanitize = $phpRoot . DIRECTORY_SEPARATOR . 'pdf-sanitize';
    $sitePdfSanitize = dirname($phpRoot) . DIRECTORY_SEPARATOR . 'pdf-sanitize';
    if (is_dir($phpPdfSanitize)) {
        return $phpPdfSanitize . DIRECTORY_SEPARATOR . 'upload';
    }
    if (is_dir($sitePdfSanitize)) {
        return $sitePdfSanitize . DIRECTORY_SEPARATOR . 'upload';
    }
    return $phpPdfSanitize . DIRECTORY_SEPARATOR . 'upload';
}

/**
 * Fetch PDF via pandadoc_download_document_pdf() and write to disk.
 *
 * @return array{success: bool, path: ?string, bytes: int, error: mixed}
 */
function pandadoc_store_document_pdf(string $documentId, ?int $shopifyOrderId = null): array {
    $dl = pandadoc_download_document_pdf($documentId);
    if (!$dl['success'] || !is_string($dl['pdf'] ?? null) || $dl['pdf'] === '') {
        return ['success' => false, 'path' => null, 'bytes' => 0, 'error' => $dl['error'] ?? 'Download failed', 'http' => $dl['http'] ?? 0];
    }
    $pdf = $dl['pdf'];

    $base = trim((string)pd_env('PANDADOC_COMPLETED_PDF_DIR', ''));
    if ($base === '') {
        $base = pandadoc_completed_pdf_upload_dir();
    }
    $base = rtrim($base, DIRECTORY_SEPARATOR . '/');
    if (!is_dir($base) && !@mkdir($base, 0777, true)) {
        return ['success' => false, 'path' => null, 'bytes' => 0, 'error' => 'Cannot create directory: ' . $base];
    }

    $safeDoc = preg_replace('/[^a-zA-Z0-9_-]+/', '_', $documentId) ?: 'doc';
    $orderPart = $shopifyOrderId !== null && $shopifyOrderId > 0 ? ('order_' . $shopifyOrderId . '_') : '';
    $file = $base . DIRECTORY_SEPARATOR . $orderPart . $safeDoc . '_' . gmdate('Ymd_His') . '.pdf';
    $written = @file_put_contents($file, $pdf);
    if ($written === false) {
        return ['success' => false, 'path' => null, 'bytes' => 0, 'error' => 'Failed to write file'];
    }

    return ['success' => true, 'path' => $file, 'bytes' => (int)$written, 'error' => null, 'http' => $dl['http']];
}

/**
 * Store completed PDF and (optionally) write a sanitized copy without certificate pages.
 *
 * @return array{
 *   success: bool,
 *   original_path: ?string,
 *   original_bytes: int,
 *   clean_path: ?string,
 *   removed_pages?: list<int>,
 *   total_pages?: int,
 *   error?: mixed
 * }
 */
function pandadoc_store_document_pdf_clean(string $documentId, ?int $shopifyOrderId = null): array {
    $stored = pandadoc_store_document_pdf($documentId, $shopifyOrderId);
    if (empty($stored['success']) || empty($stored['path'])) {
        return [
            'success' => false,
            'original_path' => $stored['path'] ?? null,
            'original_bytes' => (int)($stored['bytes'] ?? 0),
            'clean_path' => null,
            'error' => $stored['error'] ?? 'store_failed',
            'http' => $stored['http'] ?? 0,
        ];
    }

    $doClean = strtolower(trim((string)pd_env('PANDADOC_REMOVE_CERTIFICATE', '1')));
    if (in_array($doClean, ['0', 'false', 'no', 'off'], true)) {
        return [
            'success' => true,
            'original_path' => (string)$stored['path'],
            'original_bytes' => (int)$stored['bytes'],
            'clean_path' => (string)$stored['path'],
        ];
    }

    $in = (string)$stored['path'];
    $out = preg_replace('/\.pdf$/i', '', $in) . '_clean.pdf';
    if (!is_string($out) || trim($out) === '') {
        $out = $in . '_clean.pdf';
    }

    $cleaned = pd_pdf_remove_certificate_pages($in, $out);
    if (!$cleaned['success'] || empty($cleaned['clean_path'])) {
        pd_log('PandaDoc PDF sanitize failed; keeping original', [
            'document_id' => $documentId,
            'order_id' => $shopifyOrderId,
            'original' => $in,
            'error' => $cleaned['error'] ?? 'unknown',
            'removed_pages' => $cleaned['removed_pages'] ?? [],
        ]);
        return [
            'success' => true,
            'original_path' => $in,
            'original_bytes' => (int)$stored['bytes'],
            'clean_path' => $in,
            'sanitize_failed' => true,
            'sanitize_error' => $cleaned['error'] ?? null,
        ];
    }

    return [
        'success' => true,
        'original_path' => $in,
        'original_bytes' => (int)$stored['bytes'],
        'clean_path' => (string)$cleaned['clean_path'],
        'removed_pages' => $cleaned['removed_pages'],
        'total_pages' => $cleaned['total_pages'],
    ];
}

/* =====================================================================
 * Product Catalog (v2) + Quote builder (v1) helpers.
 *
 * Goal: show a product image inside the quote table via API. The only
 * supported way is to (1) create/update a catalog item that holds the image,
 * then (2) reference that catalog item from a quote line item. PandaDoc then
 * renders the catalog image automatically.
 *
 * NOTE: the catalog endpoints live under /public/v2 while documents/quotes are
 * /public/v1, so these helpers build absolute URLs via pandadoc_api_host().
 * ===================================================================== */

/** Host without the /public/vN suffix, e.g. https://api.pandadoc.com */
function pandadoc_api_host(): string {
    $h = preg_replace('#/public/v\d+/?$#', '', pandadoc_api_base());
    return rtrim((string)($h ?: 'https://api.pandadoc.com'), '/');
}

/**
 * Shared sender (auth + retry + logging) that accepts an absolute URL, so it
 * works for both v1 and v2 endpoints. Mirrors pandadoc_http() behaviour.
 *
 * @return array{ok: bool, code: int, error: ?string, data: mixed}
 */
function pandadoc_request(string $method, string $url, ?array $jsonBody, ?string $logStep, string $logPath, array $query = []): array {
    $apiKey = pandadoc_api_key();
    if ($apiKey === '') {
        return ['ok' => false, 'code' => 0, 'error' => 'Set PANDADOC_API_KEY', 'data' => null];
    }
    if (!empty($query)) {
        $url .= (strpos($url, '?') === false ? '?' : '&') . http_build_query($query);
    }

    $headers = [
        'Authorization: API-Key ' . $apiKey,
        'Accept: application/json',
    ];
    $payload = null;
    if ($jsonBody !== null) {
        $payload = json_encode($jsonBody, JSON_UNESCAPED_SLASHES);
        $headers[] = 'Content-Type: application/json';
    }

    $attempts = 4;
    for ($i = 0; $i < $attempts; $i++) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_POSTFIELDS => $payload,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 60,
        ]);
        $resp = curl_exec($ch);
        $code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $err = curl_error($ch);
        curl_close($ch);

        $data = json_decode((string)$resp, true);
        if (!is_array($data)) $data = ['raw' => substr((string)$resp, 0, 2000)];

        $isRetryableHttp = in_array($code, [429, 500, 502, 503, 504], true);
        if ($err || ($code && $isRetryableHttp)) {
            if ($i < ($attempts - 1)) {
                usleep(pd_backoff_us($i));
                continue;
            }
            if ($logStep !== null) pandadoc_api_log($logStep, $method, $logPath, $jsonBody, $code, $data, false, $err ?: null);
            return ['ok' => false, 'code' => $code, 'error' => $err ?: null, 'data' => $data];
        }

        $ok = ($code >= 200 && $code < 300);
        if ($logStep !== null) pandadoc_api_log($logStep, $method, $logPath, $jsonBody, $code, $data, $ok, null);
        return ['ok' => $ok, 'code' => $code, 'error' => null, 'data' => $data];
    }

    if ($logStep !== null) pandadoc_api_log($logStep, $method, $logPath, $jsonBody, 0, null, false, 'Unexpected retry loop exit');
    return ['ok' => false, 'code' => 0, 'error' => 'Unexpected retry loop exit', 'data' => null];
}

/** Document Details (v1) — includes the quote structure once a quote block exists. */
function pandadoc_get_document_details(string $documentId): array {
    return pandadoc_http('GET', '/documents/' . rawurlencode($documentId) . '/details', null, 'document_details');
}

/**
 * Best-effort extraction of quotes from a Document Details payload.
 * Tip: inspect php/log/pandadoc_api.log (step=document_details) on first run to
 * confirm the exact keys for your account if this returns empty.
 *
 * @return list<array{quote_id: string, name: string, sections: list<array{id: string, name: string}>}>
 */
function pandadoc_extract_quotes_from_details(array $details): array {
    $out = [];
    // Quotes live under pricing.quotes[] in Document Details. Keep fallbacks
    // in case the payload is wrapped or the shape changes.
    $quotes = $details['pricing']['quotes']
        ?? $details['quotes']
        ?? ($details['data']['pricing']['quotes'] ?? ($details['data']['quotes'] ?? []));
    if (!is_array($quotes)) return $out;
    foreach ($quotes as $q) {
        if (!is_array($q)) continue;
        $qid = (string)($q['id'] ?? $q['uuid'] ?? '');
        if ($qid === '') continue;
        $sections = [];
        $qsecs = $q['sections'] ?? [];
        if (is_array($qsecs)) {
            foreach ($qsecs as $s) {
                if (!is_array($s)) continue;
                $sid = (string)($s['id'] ?? '');
                if ($sid !== '') $sections[] = ['id' => $sid, 'name' => (string)($s['name'] ?? '')];
            }
        }
        $out[] = ['quote_id' => $qid, 'name' => (string)($q['name'] ?? ''), 'sections' => $sections];
    }
    return $out;
}

/**
 * Pick a quote by name (case-insensitive) from extracted quotes; falls back to
 * the first quote when no name is given or no name match is found.
 *
 * @param list<array{quote_id: string, name: string, sections: array}> $quotes
 */
function pandadoc_pick_quote(array $quotes, string $name = ''): ?array {
    if (empty($quotes)) return null;
    $name = trim($name);
    if ($name !== '') {
        foreach ($quotes as $q) {
            if (strcasecmp(trim((string)($q['name'] ?? '')), $name) === 0) return $q;
        }
    }
    return $quotes[0];
}

/**
 * Replace a quote's sections (PUT /documents/{id}/quotes/{quote_id}).
 * Any section/item not present in $sections is removed, so pass the full set.
 *
 * @param array $summary  Quote-level summary adjustments, e.g.
 *                        ['taxes' => ['Tax' => ['type'=>'percent','value'=>19]],
 *                         'fees'  => ['Shipping' => ['type'=>'flat','value'=>9.99]]]
 */
function pandadoc_update_quote(string $documentId, string $quoteId, array $sections, array $summary = [], string $currency = ''): array {
    $body = ['sections' => $sections];
    if ($currency !== '') $body['currency'] = $currency;
    if (!empty($summary)) $body['summary'] = $summary;
    $url = pandadoc_api_base() . '/documents/' . rawurlencode($documentId) . '/quotes/' . rawurlencode($quoteId);
    return pandadoc_request('PUT', $url, $body, 'quote_update', '/documents/{id}/quotes/{quote_id}');
}

require_once __DIR__ . '/pandadoc_signing.php';

