<?php

declare(strict_types=1);

namespace Drupal\native_observability_report\Service;

use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
 * Builds printable report documents from prepared preview payloads.
 */
final class ObservabilityReportDocumentBuilder {

  use StringTranslationTrait;

  /**
   * Builds a normalized download filename.
   */
  public function buildFilename(array $preview): string {
    $title = preg_replace('/[^a-z0-9]+/i', '-', (string) ($preview['report_title'] ?? 'native-observability-report'));
    $title = trim((string) $title, '-');

    return ($title !== '' ? strtolower($title) : 'native-observability-report') . '.html';
  }

  /**
   * Builds a printable HTML document from the preview payload.
   */
  public function buildHtmlDocument(array $preview, bool $forPdf): string {
    $labels = $preview['narrative_labels'] ?? [];
    $title = htmlspecialchars((string) ($preview['report_title'] ?? (string) $this->t('Native Observability Report')));
    $range = htmlspecialchars((string) ($preview['range_label'] ?? ''));
    $environment = htmlspecialchars((string) ($preview['environment'] ?? ''));
    $detail = htmlspecialchars((string) ($preview['detail_level'] ?? ''));
    $appendixSections = $this->buildAppendixSections($preview, $labels);

    $pages = [];

    $pages[] = $this->pageDescriptor(
      'report-cover',
      (string) ($labels['document_cover_title'] ?? (string) $this->t('Cover')),
      'hero',
      $this->buildCoverPageHtml($title, $environment, $range, $detail, $preview)
    );

    foreach ($this->buildPreludePageDescriptors($preview, $labels) as $descriptor) {
      $pages[] = $descriptor;
    }

    foreach (($preview['sections'] ?? []) as $sectionKey => $section) {
      $sectionTitle = (string) ($section['title'] ?? (string) $this->t('Section'));
      $pages[] = $this->pageDescriptor(
        'section-summary-' . $this->slugify($sectionKey ?: $sectionTitle),
        $sectionTitle,
        'report-section',
        $this->buildSectionSummaryInnerHtml($section, $labels)
      );

      foreach (($section['charts'] ?? []) as $index => $chart) {
        $chartId = (string) ($chart['chart_id'] ?? ('export-chart-' . $index));
        $pageTitle = sprintf('%s — %s', $sectionTitle, (string) ($chart['title'] ?? (string) $this->t('Chart')));
        $pages[] = $this->pageDescriptor(
          'section-chart-' . $this->slugify($sectionKey . '-' . $chartId),
          $pageTitle,
          'chart-page',
          $this->buildChartPageInnerHtml($chart, $chartId, $pageTitle, $labels, $forPdf)
        );
      }

      foreach (($section['tables'] ?? []) as $index => $table) {
        $tableTitle = sprintf('%s — %s', $sectionTitle, (string) ($table['title'] ?? (string) $this->t('Evidence table')));
        $pages[] = $this->pageDescriptor(
          'section-table-' . $this->slugify($sectionKey . '-' . $index),
          $tableTitle,
          'table-page',
          $this->buildSingleTableInnerHtml($table, $tableTitle, $labels, FALSE)
        );
      }
    }

    if (!empty($preview['cross_section_insights'])) {
      $pages[] = $this->pageDescriptor(
        'report-cross-section-insights',
        (string) ($labels['cross_section_insights_title'] ?? (string) $this->t('Cross-section insights')),
        'narrative-page',
        $this->buildNarrativeSectionInnerHtml(
          (string) ($labels['cross_section_insights_title'] ?? (string) $this->t('Cross-section insights')),
          (string) ($labels['cross_section_insights_lead'] ?? ''),
          $preview['cross_section_insights']
        )
      );
    }

    if (!empty($preview['priority_actions'])) {
      $pages[] = $this->pageDescriptor(
        'report-priority-actions',
        (string) ($labels['priority_actions_title'] ?? (string) $this->t('Priority actions')),
        'narrative-page',
        $this->buildNarrativeSectionInnerHtml(
          (string) ($labels['priority_actions_title'] ?? (string) $this->t('Priority actions')),
          (string) ($labels['priority_actions_lead'] ?? ''),
          $preview['priority_actions']
        )
      );
    }

    if (!empty($preview['report_readiness'])) {
      $pages[] = $this->pageDescriptor(
        'report-readiness',
        (string) ($labels['report_readiness_title'] ?? (string) $this->t('Report readiness')),
        'cards-page',
        $this->buildCardSectionInnerHtml(
          (string) ($labels['report_readiness_title'] ?? (string) $this->t('Report readiness')),
          '',
          $preview['report_readiness']
        )
      );
    }

    if (!empty($preview['appendix_cards'])) {
      $pages[] = $this->pageDescriptor(
        'report-appendix-snapshot',
        (string) ($labels['appendix_title'] ?? (string) $this->t('Report appendix snapshot')),
        'cards-page',
        $this->buildCardSectionInnerHtml(
          (string) ($labels['appendix_title'] ?? (string) $this->t('Report appendix snapshot')),
          (string) ($labels['appendix_lead'] ?? ''),
          $preview['appendix_cards']
        )
      );
    }

    if (!empty($preview['impact'])) {
      $pages[] = $this->pageDescriptor(
        'report-overhead',
        (string) ($labels['overhead_title'] ?? (string) $this->t('Observability overhead')),
        'narrative-page',
        $this->buildOverheadInnerHtml($preview, $labels)
      );
    }

    if ($appendixSections !== []) {
      $pages[] = $this->pageDescriptor(
        'report-technical-appendices',
        (string) ($labels['document_appendices_title'] ?? (string) $this->t('Technical appendices')),
        'appendix-intro-page',
        $this->buildAppendixOverviewInnerHtml($appendixSections, $labels)
      );

      foreach ($appendixSections as $index => $appendix) {
        $pages[] = $this->pageDescriptor(
          'appendix-' . $this->slugify((string) ($appendix['key'] ?? $index)),
          (string) $appendix['title'],
          'appendix-page table-page',
          $this->buildSingleTableInnerHtml($appendix['table'], (string) $appendix['title'], $labels, TRUE)
        );
      }
    }

    array_splice($pages, 1, 0, [[
      'id' => 'report-contents',
      'title' => (string) ($labels['document_contents_title'] ?? (string) $this->t('Document contents')),
      'class' => 'contents-page',
      'inner' => '',
    ],
    ]);

    foreach ($pages as $index => &$page) {
      $page['page'] = $index + 1;
    }
    unset($page);

    $pages[1]['inner'] = $this->buildContentsInnerHtml($pages, $labels);

    $body = [];
    foreach ($pages as $page) {
      $body[] = $this->wrapPageHtml($page['id'], $page['inner'], (int) $page['page'], (string) $page['class'], (string) $page['title'], $labels);
    }

    $footer = htmlspecialchars((string) ($preview['footer_text'] ?? 'Generated by Native Observability Report'));
    $scripts = $this->buildChartRuntime($preview['chart_groups'] ?? []);

    return '<!doctype html><html><head><meta charset="utf-8"><title>' . $title . '</title><style>' . $this->buildStyles($forPdf) . '</style></head><body><main>' . implode('', $body) . '</main><footer class="document-footer">' . $footer . '</footer>' . $scripts . '</body></html>';
  }

  /**
   * Creates a page descriptor.
   */
  private function pageDescriptor(string $id, string $title, string $class, string $inner): array {
    return [
      'id' => $id,
      'title' => $title,
      'class' => $class,
      'inner' => $inner,
    ];
  }

  /**
   * Builds the cover page body.
   */
  private function buildCoverPageHtml(string $title, string $environment, string $range, string $detail, array $preview): string {
    $html = [];
    $html[] = '<h1>' . $title . '</h1>';
    $html[] = '<p class="meta">Environment: ' . $environment . ' · Range: ' . $range . ' · Detail: ' . $detail . '</p>';
    if (!empty($preview['organization_name'])) {
      $html[] = '<p class="org">' . htmlspecialchars((string) $preview['organization_name']) . '</p>';
    }
    if (!empty($preview['header_text'])) {
      $html[] = '<p class="lead">' . htmlspecialchars((string) $preview['header_text']) . '</p>';
    }
    return implode('', $html);
  }

  /**
   * Builds prelude page descriptors.
   */
  private function buildPreludePageDescriptors(array $preview, array $labels): array {
    $blocks = [];

    if (!empty($preview['privacy_summary'])) {
      $blocks[] = $this->pageDescriptor(
        'report-privacy-summary',
        (string) ($labels['privacy_summary_title'] ?? (string) $this->t('Privacy and scope summary')),
        'cards-page',
        $this->buildCardSectionInnerHtml((string) ($labels['privacy_summary_title'] ?? (string) $this->t('Privacy and scope summary')), '', $preview['privacy_summary'])
      );
    }

    if (!empty($preview['executive_kpis'])) {
      $blocks[] = $this->pageDescriptor(
        'report-executive-kpis',
        (string) ($labels['executive_kpis_title'] ?? (string) $this->t('Executive KPI snapshot')),
        'cards-page',
        $this->buildCardSectionInnerHtml((string) ($labels['executive_kpis_title'] ?? (string) $this->t('Executive KPI snapshot')), '', $preview['executive_kpis'])
      );
    }

    if (!empty($preview['executive_summary'])) {
      $blocks[] = $this->pageDescriptor(
        'report-executive-summary',
        (string) ($labels['executive_summary_title'] ?? (string) $this->t('Executive summary')),
        'narrative-page',
        $this->buildNarrativeSectionInnerHtml((string) ($labels['executive_summary_title'] ?? (string) $this->t('Executive summary')), (string) ($labels['executive_summary_lead'] ?? ''), $preview['executive_summary'])
      );
    }

    return $blocks;
  }

  /**
   * Wraps page content with deterministic page chrome.
   */
  private function wrapPageHtml(string $id, string $inner, int $pageNumber, string $class, string $title, array $labels): string {
    $pageLabel = htmlspecialchars((string) ($labels['document_page_label'] ?? 'Page'));
    return '<section class="document-page ' . htmlspecialchars($class) . '" id="' . htmlspecialchars($id) . '"><div class="page-shell"><div class="page-body">' . $inner . '</div><div class="page-footer"><span class="page-footer-title">' . htmlspecialchars($title) . '</span><span class="page-footer-number">' . $pageLabel . ' ' . $pageNumber . '</span></div></div></section>';
  }

  /**
   * Builds the contents page body.
   */
  private function buildContentsInnerHtml(array $pages, array $labels): string {
    $html = [];
    $html[] = '<h2>' . htmlspecialchars((string) ($labels['document_contents_title'] ?? (string) $this->t('Document contents'))) . '</h2>';
    $html[] = '<p class="section-lead">' . htmlspecialchars((string) ($labels['document_contents_lead'] ?? 'This structured index summarises the printable report blocks and the pages where each block starts.')) . '</p>';
    $html[] = '<div class="print-notice">' . htmlspecialchars((string) ($labels['document_print_notice'] ?? 'Interactive charts are rendered in printable HTML. Use your browser print dialog to save the document as PDF when needed.')) . '</div>';
    $html[] = '<h3>' . htmlspecialchars((string) ($labels['document_page_map_title'] ?? 'Page map')) . '</h3>';
    $html[] = '<ol class="page-map">';
    foreach ($pages as $page) {
      if ($page['id'] === 'report-contents') {
        continue;
      }
      $pageAnchor = '#' . htmlspecialchars((string) $page['id']);
      $pageLabel = htmlspecialchars((string) ($labels['document_page_label'] ?? 'Page'));
      $html[] = '<li><a class="map-label" href="' . $pageAnchor . '">' . htmlspecialchars((string) $page['title']) . '</a><a class="map-page" href="' . $pageAnchor . '">' . $pageLabel . ' ' . (int) $page['page'] . '</a></li>';
    }
    $html[] = '</ol>';
    return implode('', $html);
  }

  /**
   * Builds an appendix overview page body.
   */
  private function buildAppendixOverviewInnerHtml(array $appendixSections, array $labels): string {
    $html = [];
    $html[] = '<h2>' . htmlspecialchars((string) ($labels['document_appendices_title'] ?? (string) $this->t('Technical appendices'))) . '</h2>';
    $html[] = '<p class="section-lead">' . htmlspecialchars((string) ($labels['document_appendices_lead'] ?? 'These appendices consolidate route evidence, span-derived technical traces, cache runtime samples, and database records into deterministic printable pages.')) . '</p>';
    $html[] = '<h3>' . htmlspecialchars((string) ($labels['document_appendix_index_title'] ?? 'Appendix inventory')) . '</h3>';
    $html[] = '<ul class="section-list">';
    foreach ($appendixSections as $appendix) {
      $description = (string) ($appendix['description'] ?? '');
      $appendixKey = (string) ($appendix['key'] ?? '');
      $appendixAnchor = '#appendix-' . htmlspecialchars($this->slugify($appendixKey));
      $html[] = '<li><a href="' . $appendixAnchor . '"><strong>' . htmlspecialchars((string) $appendix['title']) . '</strong></a>' . ($description !== '' ? ': ' . htmlspecialchars($description) : '') . '</li>';
    }
    $html[] = '</ul>';
    return implode('', $html);
  }

  /**
   * Builds the main summary page body for a section.
   */
  private function buildSectionSummaryInnerHtml(array $section, array $labels): string {
    $html = [];
    $html[] = '<h2 class="page-title">' . htmlspecialchars((string) ($section['title'] ?? (string) $this->t('Section'))) . '</h2>';
    $html[] = '<p class="status">Status: ' . htmlspecialchars((string) ($section['status_label'] ?? $section['status'] ?? '')) . ' · Score: ' . htmlspecialchars((string) ($section['score'] ?? '')) . '/100</p>';

    foreach (['introduction', 'importance', 'message', 'recommendation'] as $field) {
      if (!empty($section[$field])) {
        $html[] = '<p class="summary-copy">' . nl2br(htmlspecialchars((string) $section[$field])) . '</p>';
      }
    }

    if (!empty($section['summary_cards'])) {
      $html[] = '<h3>' . htmlspecialchars((string) ($labels['section_summary_cards_title'] ?? 'Section KPI summary')) . '</h3>';
      $html[] = '<div class="cards">';
      foreach ($section['summary_cards'] as $card) {
        $html[] = '<article class="card"><div class="label">' . htmlspecialchars((string) ($card['label'] ?? $card['title'] ?? '')) . '</div><div class="value">' . htmlspecialchars((string) ($card['value'] ?? '')) . '</div>';
        if (!empty($card['description'])) {
          $html[] = '<div class="description">' . htmlspecialchars((string) $card['description']) . '</div>';
        }
        $html[] = '</article>';
      }
      $html[] = '</div>';
    }

    if (!empty($section['chart_explanations'])) {
      $html[] = '<h3>' . htmlspecialchars((string) ($labels['section_chart_explanations_title'] ?? 'Chart explanations')) . '</h3>';
      $html[] = $this->buildStringListHtml($section['chart_explanations']);
    }

    if (!empty($section['interpretation'])) {
      $html[] = '<h3>' . htmlspecialchars((string) ($labels['section_interpretation_title'] ?? 'Metric interpretation guide')) . '</h3>';
      $html[] = $this->buildStringListHtml($section['interpretation']);
    }

    return implode('', $html);
  }

  /**
   * Builds an individual chart page body.
   */
  private function buildChartPageInnerHtml(array $chart, string $chartId, string $pageTitle, array $labels, bool $forPdf): string {
    $html = [];
    $html[] = '<h2 class="page-title">' . htmlspecialchars($pageTitle) . '</h2>';
    if (!empty($chart['description'])) {
      $html[] = '<p class="page-subtitle">' . htmlspecialchars((string) $chart['description']) . '</p>';
    }
    $html[] = '<article class="chart-card">';
    $html[] = '<div class="chart-surface" data-no-export-chart-id="' . htmlspecialchars($chartId) . '"></div>';
    $html[] = '<div class="chart-panels">';
    $html[] = '<div class="chart-panel"><h4>' . htmlspecialchars((string) ($labels['chart_reading_guide_title'] ?? 'What this chart shows')) . '</h4>' . $this->buildStringListHtml($chart['reading_guide'] ?? []) . '</div>';
    $html[] = '<div class="chart-panel"><h4>' . htmlspecialchars((string) ($labels['chart_interpretation_title'] ?? 'How to interpret it')) . '</h4>' . $this->buildStringListHtml($chart['interpretation'] ?? []) . '</div>';
    $html[] = '</div></article>';
    return implode('', $html);
  }

  /**
   * Builds a narrative block section body.
   */
  private function buildNarrativeSectionInnerHtml(string $title, string $lead, array $items): string {
    $html = [];
    $html[] = '<h2>' . htmlspecialchars($title) . '</h2>';
    if ($lead !== '') {
      $html[] = '<p class="section-lead">' . htmlspecialchars($lead) . '</p>';
    }
    $html[] = '<ul class="narrative-list">';
    foreach ($items as $item) {
      if (is_array($item)) {
        $titleValue = (string) ($item['title'] ?? '');
        $description = (string) ($item['description'] ?? $item['message'] ?? '');
        $html[] = '<li><strong>' . htmlspecialchars($titleValue) . '</strong>' . ($description !== '' ? ': ' . htmlspecialchars($description) : '') . '</li>';
      }
      else {
        $html[] = '<li>' . htmlspecialchars((string) $item) . '</li>';
      }
    }
    $html[] = '</ul>';
    return implode('', $html);
  }

  /**
   * Builds a card-based section body.
   */
  private function buildCardSectionInnerHtml(string $title, string $lead, array $items): string {
    $html = [];
    $html[] = '<h2>' . htmlspecialchars($title) . '</h2>';
    if ($lead !== '') {
      $html[] = '<p class="section-lead">' . htmlspecialchars($lead) . '</p>';
    }
    $html[] = '<div class="cards">';
    foreach ($items as $item) {
      $html[] = '<article class="card">';
      $html[] = '<div class="label">' . htmlspecialchars((string) ($item['title'] ?? '')) . '</div>';
      if (isset($item['value'])) {
        $html[] = '<div class="value">' . htmlspecialchars((string) $item['value']) . '</div>';
      }
      if (isset($item['message']) && (string) $item['message'] !== '') {
        $html[] = '<div class="description">' . htmlspecialchars((string) $item['message']) . '</div>';
      }
      if (isset($item['description']) && (string) $item['description'] !== '') {
        $html[] = '<div class="description">' . htmlspecialchars((string) $item['description']) . '</div>';
      }
      $html[] = '</article>';
    }
    $html[] = '</div>';
    return implode('', $html);
  }

  /**
   * Builds the observability overhead block body.
   */
  private function buildOverheadInnerHtml(array $preview, array $labels): string {
    $html = [];
    $html[] = '<h2>' . htmlspecialchars((string) ($labels['overhead_title'] ?? (string) $this->t('Observability overhead'))) . '</h2>';
    $html[] = '<p>' . htmlspecialchars(strtr((string) ($labels['overhead_summary_template'] ?? 'Estimated overhead: @percent% | Impact level: @level'), [
      '@percent' => (string) ($preview['impact']['estimated_overhead_percent'] ?? 0),
      '@level' => (string) ($preview['impact']['impact_level'] ?? ''),
    ])) . '</p>';
    if (!empty($preview['impact']['contributors'])) {
      $html[] = '<h3>' . htmlspecialchars((string) ($labels['overhead_contributors_title'] ?? 'Main contributors')) . '</h3>' . $this->buildStringListHtml($preview['impact']['contributors']);
    }
    return implode('', $html);
  }

  /**
   * Builds a single table page body.
   */
  private function buildSingleTableInnerHtml(array $table, string $pageTitle, array $labels, bool $isAppendix): string {
    $headers = $this->normalizeTableHeaders($table['headers'] ?? []);
    $rows = $this->normalizeTableRows($table['rows'] ?? [], $headers);
    $html = [];
    $html[] = '<h2 class="page-title">' . htmlspecialchars($pageTitle) . '</h2>';
    if (!empty($table['description'])) {
      $html[] = '<p class="page-subtitle">' . htmlspecialchars((string) $table['description']) . '</p>';
    }

    if ($rows === []) {
      $html[] = '<p class="empty-message">' . htmlspecialchars((string) ($table['empty_message'] ?? $labels['table_empty_message'] ?? 'No rows are currently available for this dataset.')) . '</p>';
    }
    else {
      $html[] = '<div class="table-wrap"><table class="section-table"><thead><tr>';
      foreach ($headers as $header) {
        $html[] = '<th>' . htmlspecialchars($header['label']) . '</th>';
      }
      $html[] = '</tr></thead><tbody>';
      foreach ($rows as $row) {
        $html[] = '<tr>';
        foreach ($row as $cell) {
          $html[] = '<td>' . $this->formatTableCell($cell) . '</td>';
        }
        $html[] = '</tr>';
      }
      $html[] = '</tbody></table></div>';
    }

    if ($isAppendix && !empty($table['source_note'])) {
      $html[] = '<p class="appendix-source-note">' . htmlspecialchars((string) $table['source_note']) . '</p>';
    }

    return implode('', $html);
  }

  /**
   * Normalizes table headers.
   */
  private function normalizeTableHeaders(array $headers): array {
    $normalized = [];
    foreach ($headers as $index => $header) {
      if (is_array($header)) {
        $normalized[] = [
          'key' => (string) ($header['key'] ?? $index),
          'label' => (string) ($header['label'] ?? $header['key'] ?? ('Column ' . ($index + 1))),
        ];
      }
      else {
        $normalized[] = [
          'key' => (string) $index,
          'label' => (string) $header,
        ];
      }
    }

    return $normalized;
  }

  /**
   * Normalizes table rows.
   */
  private function normalizeTableRows(array $rows, array $headers): array {
    $normalized = [];
    foreach ($rows as $row) {
      $line = [];
      if (is_array($row)) {
        $keys = array_keys($row);
        $isSequential = $keys === range(0, count($keys) - 1);
        foreach ($headers as $position => $header) {
          $line[] = $isSequential ? ($row[$position] ?? '') : ($row[$header['key']] ?? '');
        }
      }
      else {
        $line[] = $row;
      }
      $normalized[] = $line;
    }

    return $normalized;
  }

  /**
   * Formats a table cell.
   */
  private function formatTableCell(mixed $cell): string {
    if (is_array($cell)) {
      $values = [];
      foreach ($cell as $key => $value) {
        if (is_scalar($value) || $value === NULL) {
          $values[] = is_string($key) ? ($key . ': ' . (string) $value) : (string) $value;
        }
      }
      return htmlspecialchars(implode(' · ', $values));
    }

    return htmlspecialchars((string) $cell);
  }

  /**
   * Builds a string list.
   */
  private function buildStringListHtml(array $items): string {
    if ($items === []) {
      return '<p class="empty-message">No guidance is currently available.</p>';
    }

    $html = ['<ul class="section-list">'];
    foreach ($items as $item) {
      $html[] = '<li>' . htmlspecialchars((string) $item) . '</li>';
    }
    $html[] = '</ul>';

    return implode('', $html);
  }

  /**
   * Builds appendix sections from all section evidence tables.
   */
  private function buildAppendixSections(array $preview, array $labels): array {
    $appendices = [];

    foreach (($preview['sections'] ?? []) as $sectionKey => $section) {
      $sectionTitle = (string) ($section['title'] ?? (string) $this->t('Section'));
      foreach (($section['tables'] ?? []) as $index => $table) {
        $title = $sectionTitle . ' — ' . (string) ($table['title'] ?? (string) $this->t('Evidence table'));
        $sourceNote = $this->detectAppendixSourceNote($title, $labels);
        $table['source_note'] = $sourceNote;
        $appendices[] = [
          'key' => $sectionKey . '-' . $index,
          'title' => $title,
          'description' => $sourceNote,
          'table' => $table,
        ];
      }
    }

    return $appendices;
  }

  /**
   * Detects an appendix source note for the given title.
   */
  private function detectAppendixSourceNote(string $title, array $labels): string {
    $titleLower = strtolower($title);
    if (str_contains($titleLower, 'external request')) {
      return (string) ($labels['document_appendix_source_spans'] ?? 'This appendix is derived from outbound HTTP span evidence captured by the observability pipeline.');
    }
    if (str_contains($titleLower, 'runtime') || str_contains($titleLower, 'cache layer')) {
      return (string) ($labels['document_appendix_source_cache'] ?? 'This appendix is derived from cache runtime metadata and synthetic request summary spans.');
    }
    if (str_contains($titleLower, 'slow query') || str_contains($titleLower, 'db') || str_contains($titleLower, 'database')) {
      return (string) ($labels['document_appendix_source_database'] ?? 'This appendix is derived from database observability evidence and stored slow-query records.');
    }
    return (string) ($labels['document_appendix_source_routes'] ?? 'This appendix is derived from route-level evidence captured during the selected report range.');
  }

  /**
   * Builds the inline styles for printable documents.
   */
  private function buildStyles(bool $forPdf): string {
    $background = $forPdf ? '#ffffff' : '#f8fafc';
    return '@page{size:A4 portrait;margin:12mm 10mm 14mm 10mm}body{font-family:Arial,sans-serif;color:#1f2937;margin:0;background:' . $background . ';font-size:12px;line-height:1.42}main{max-width:190mm;margin:0 auto;padding:0}h1,h2,h3,h4{color:#111827;margin:0 0 10px 0}section{background:#fff;border:1px solid #e5e7eb;border-radius:14px;page-break-inside:avoid;break-inside:avoid}.document-page{min-height:268mm;page-break-before:always;break-before:page;display:block}.hero{page-break-before:auto;break-before:auto}.page-shell{min-height:262mm;display:flex;flex-direction:column;justify-content:space-between;padding:10mm 10mm 8mm 10mm;box-sizing:border-box}.page-body{flex:1 1 auto}.page-footer{display:flex;justify-content:space-between;align-items:center;border-top:1px solid #e5e7eb;padding-top:6px;margin-top:10px;font-size:11px;color:#6b7280}.page-footer-title{max-width:70%}.page-footer-number{white-space:nowrap}.meta,.org,.status,.chart-note,.section-lead,.print-notice,.page-subtitle,.appendix-source-note{color:#4b5563}.lead{font-size:15px;line-height:1.6}.cards{display:flex;flex-wrap:wrap;gap:10px}.card{border:1px solid #e5e7eb;border-radius:12px;padding:10px;min-width:180px;flex:1 1 210px;background:#f9fafb}.card .label{font-size:11px;text-transform:uppercase;color:#6b7280}.card .value{font-size:20px;font-weight:700;margin-top:5px}.card .description{font-size:12px;line-height:1.45;color:#4b5563;margin-top:7px}.page-map{list-style:none;padding:0;margin:0}.page-map li{display:flex;justify-content:space-between;gap:18px;padding:7px 0;border-bottom:1px dashed #d1d5db}.map-label{font-weight:600;color:#111827;text-decoration:none}.map-label:hover,.map-label:focus{text-decoration:underline}.map-page{white-space:nowrap;color:#6b7280;text-decoration:none}.map-page:hover,.map-page:focus{text-decoration:underline}.section-list a{color:#111827;text-decoration:none}.section-list a:hover,.section-list a:focus{text-decoration:underline}.chart-card{border:1px solid #e5e7eb;border-radius:12px;background:#fbfcfe;padding:12px}.chart-surface{height:118mm;border:1px solid #e5e7eb;border-radius:10px;background:#fff}.chart-panels{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:10px}.chart-panel{border:1px solid #e5e7eb;border-radius:10px;background:#fff;padding:10px}.chart-panel ul{margin:0;padding-left:18px}.table-wrap{overflow:hidden}.section-table{width:100%;border-collapse:collapse;margin-top:8px;table-layout:fixed}.section-table th,.section-table td{border:1px solid #d1d5db;padding:6px 8px;text-align:left;vertical-align:top;font-size:11px;line-height:1.35;word-break:break-word;overflow-wrap:anywhere}.section-table th{background:#f3f4f6;font-weight:700}.empty-message{color:#6b7280}.narrative-list{margin:0;padding-left:18px}.narrative-list li{margin-bottom:8px}.print-notice{border:1px dashed #cbd5e1;padding:8px 10px;border-radius:10px;background:#f8fafc;margin:10px 0}.section-list{margin:10px 0 0 0;padding-left:18px}.summary-copy{margin-bottom:10px}.page-title{margin-bottom:6px}.page-subtitle{margin-top:-2px;margin-bottom:10px}.appendix-source-note{font-size:11px;margin-top:8px}.appendix-page .page-title::before{content:"Appendix · ";color:#6b7280}.table-page .page-title::before{content:"Evidence · ";color:#6b7280}.chart-page .page-title::before{content:"Chart · ";color:#6b7280}.contents-page .page-title::before,.appendix-intro-page .page-title::before,.report-section .page-title::before{content:""}.document-footer{text-align:center;color:#6b7280;font-size:11px;padding:5mm 0 7mm}';
  }

  /**
   * Builds the standalone chart runtime for printable HTML.
   */
  private function buildChartRuntime(array $chartGroups): string {
    if ($chartGroups === []) {
      return '';
    }

    $json = json_encode($chartGroups, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
    $echartsRuntime = $this->loadEchartsRuntime();

    return '<script>window.__nativeObservabilityChartsReady=false;' . $echartsRuntime . '</script><script>(function(){const charts=' . $json . ';function buildTimeseriesOption(settings){return {animation:true,tooltip:{trigger:"axis"},legend:{top:0,left:"center",data:(settings.series||[]).map((serie)=>serie.name||"")},grid:{left:56,right:72,top:56,bottom:40},xAxis:{type:"category",boundaryGap:false,data:settings.labels||[]},yAxis:settings.y_axes||[{type:"value",name:"",position:"left"}],series:(settings.series||[]).map((serie)=>({name:serie.name||"",type:serie.type||"line",smooth:serie.smooth!==false,symbol:serie.symbol||"none",yAxisIndex:Number(serie.y_axis_index||0),areaStyle:serie.area?{}:undefined,data:serie.data||[]}))};}function buildScatterOption(settings){const points=settings.points||[];return {animation:true,tooltip:{trigger:"item"},grid:{left:56,right:32,top:32,bottom:48},xAxis:{type:"value",name:"Request Time (ms)"},yAxis:{type:"value",name:"DB Time (ms)"},series:[{type:"scatter",symbolSize:function(value){return Math.max(10,Math.min(42,Number(value[2]||10)));},data:points.map((point)=>({value:[Number(point.request_duration_ms||0),Number(point.db_time_ms||0),Number(point.request_count||0)],meta:point}))}]};}function buildPieOption(settings){return {animation:true,tooltip:{trigger:"item"},legend:{top:0,left:"center"},series:[{name:settings.series_name||"",type:"pie",radius:["42%","68%"],center:["50%","58%"],avoidLabelOverlap:true,label:{formatter:"{b}: {d}%"},data:settings.data||[]}]};}function buildGaugeOption(settings){return {animation:true,series:[{name:settings.series_name||"",type:"gauge",min:0,max:Number(settings.max||100),detail:{valueAnimation:true,formatter:function(value){return Math.round(value)+"/"+Number(settings.max||100);}},data:[{value:Number(settings.value||0),name:settings.series_name||""}]}]};}function buildRadarOption(settings){return {animation:true,tooltip:{},radar:{indicator:settings.indicators||[]},series:[{name:settings.series_name||"",type:"radar",data:[{value:settings.values||[],name:settings.series_name||""}]}]};}function buildCategoryBarOption(settings){return {animation:true,tooltip:{trigger:"axis"},legend:{top:0,left:"center"},grid:{left:56,right:32,top:56,bottom:48},xAxis:{type:"category",data:settings.categories||[],axisLabel:{interval:0,rotate:18}},yAxis:{type:"value"},series:(settings.series||[]).map((serie)=>({name:serie.name||"",type:"bar",data:serie.data||[]}))};}function buildHorizontalBarOption(settings){return {animation:true,tooltip:{trigger:"axis"},legend:{top:0,left:"center"},grid:{left:160,right:32,top:56,bottom:24},xAxis:{type:"value"},yAxis:{type:"category",data:settings.categories||[]},series:(settings.series||[]).map((serie)=>({name:serie.name||"",type:"bar",data:serie.data||[]}))};}function resolve(renderer){switch(renderer){case "scatter":return buildScatterOption;case "pie":return buildPieOption;case "gauge":return buildGaugeOption;case "radar":return buildRadarOption;case "category_bar":return buildCategoryBarOption;case "horizontal_bar":return buildHorizontalBarOption;default:return buildTimeseriesOption;}}function initChart(el,settings){if(!settings||typeof echarts==="undefined"){return Promise.resolve();}const builder=resolve(settings.renderer||"timeseries");const chart=echarts.init(el);chart.setOption(builder(settings));window.addEventListener("resize",function(){chart.resize();});return new Promise(function(resolvePromise){requestAnimationFrame(function(){chart.resize();resolvePromise();});});}function boot(){const jobs=[];document.querySelectorAll("[data-no-export-chart-id]").forEach(function(el){const id=el.getAttribute("data-no-export-chart-id");if(!id||!charts[id]){return;}jobs.push(initChart(el,charts[id]));});Promise.all(jobs).finally(function(){window.__nativeObservabilityChartsReady=true;});}if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",boot);}else{boot();}})();</script>';
  }

  /**
   * Loads the Apache ECharts runtime inline when available.
   */
  private function loadEchartsRuntime(): string {
    $paths = [
      DRUPAL_ROOT . '/libraries/echarts/dist/echarts.min.js',
      DRUPAL_ROOT . '/libraries/echarts/dist/echarts.js',
    ];

    foreach ($paths as $path) {
      if (is_readable($path)) {
        return (string) file_get_contents($path);
      }
    }

    return 'console.warn("Apache ECharts runtime was not found in /libraries/echarts.");';
  }

  /**
   * Builds a slug-like identifier.
   */
  private function slugify(string $value): string {
    $value = strtolower(trim($value));
    $value = preg_replace('/[^a-z0-9]+/', '-', $value) ?: 'item';
    return trim((string) $value, '-');
  }

}
