<?php

declare(strict_types=1);

namespace Drupal\native_observability_dashboard\Service;

use Drupal\native_observability_dashboard\ValueObject\TimeRange;

/**
 * Builds the technical insights section for the dashboard.
 */
final readonly class TechnicalInsightsSectionBuilder {

  /**
   * Creates a new technical insights section builder instance.
   *
   * @param DashboardAvailabilityResolver $availabilityResolver
   *   The dashboard availability resolver.
   * @param TraceMetricsReader $traceMetricsReader
   *   The trace metrics reader service.
   */
  public function __construct(
    private DashboardAvailabilityResolver $availabilityResolver,
    private TraceMetricsReader $traceMetricsReader,
  ) {
  }

  /**
   * Builds the technical insights section payload.
   *
   * @param \Drupal\native_observability_dashboard\ValueObject\TimeRange|null $range
   *   The selected dashboard time range.
   *
   * @return array
   *   The technical insights section payload.
   */
  public function build(?TimeRange $range = NULL): array {
    $range ??= TimeRange::createDefault();

    if (!$this->availabilityResolver->hasTraceSupport()) {
      return [
        'enabled' => FALSE,
        'title' => 'Technical Insights',
        'reason' => 'The trace storage layer is not currently available.',
      ];
    }

    $slowRows = $this->traceMetricsReader->getSlowEndpointDataset($range);
    $ajaxRows = $this->traceMetricsReader->getAjaxEndpointDataset($range);

    $slowRows = $this->enrichDatasetWithSpanBreakdown($slowRows);
    $ajaxRows = $this->enrichDatasetWithSpanBreakdown($ajaxRows);

    return [
      'enabled' => TRUE,
      'title' => 'Technical Insights',
      'subtitle' => 'Operational analysis of captured requests for the selected master range.',
      'time_range' => $range->getLabel(),
      'datasets' => [
        'slow_endpoints' => [
          'key' => 'slow_endpoints',
          'label' => 'Slow Endpoints',
          'description' => 'Review the slowest internal Drupal routes captured inside the active master range.',
          'search_placeholder' => 'Search by path or route',
          'search_keys' => ['path', 'route_name'],
          'export_key' => 'slow-endpoints',
          'default_sort' => [
            'key' => 'avg_duration',
            'direction' => 'desc',
          ],
          'flush_help' => 'Flush will be enabled after a dedicated aggregated dataset is introduced. This safe batch does not delete raw trace records.',
          'empty_message' => 'No slow endpoints were recorded for the selected time range.',
          'columns' => [
            ['key' => 'path', 'label' => 'Path'],
            ['key' => 'route_name', 'label' => 'Route'],
            ['key' => 'request_count', 'label' => 'Calls'],
            ['key' => 'avg_duration', 'label' => 'Avg'],
            ['key' => 'error_count', 'label' => 'Errors'],
          ],
          'rows' => $slowRows,
        ],
        'ajax_endpoints' => [
          'key' => 'ajax_endpoints',
          'label' => 'AJAX Endpoints',
          'description' => 'Inspect AJAX-heavy endpoints inside the currently selected dashboard range.',
          'search_placeholder' => 'Search by path or route',
          'search_keys' => ['path', 'route_name'],
          'export_key' => 'ajax-endpoints',
          'default_sort' => [
            'key' => 'request_count',
            'direction' => 'desc',
          ],
          'flush_help' => 'Flush will be enabled after a dedicated aggregated dataset is introduced. This safe batch does not delete raw trace records.',
          'empty_message' => 'No AJAX endpoints were recorded for the selected time range.',
          'columns' => [
            ['key' => 'path', 'label' => 'Path'],
            ['key' => 'route_name', 'label' => 'Route'],
            ['key' => 'request_count', 'label' => 'Calls'],
            ['key' => 'avg_duration', 'label' => 'Avg'],
            ['key' => 'error_count', 'label' => 'Errors'],
          ],
          'rows' => $ajaxRows,
        ],
        'external_requests' => [
          'key' => 'external_requests',
          'label' => 'External Requests',
          'description' => 'Inspect outbound HTTP calls captured through Guzzle and HTTP client span tracing.',
          'search_placeholder' => 'Search by target or method',
          'search_keys' => ['target', 'method'],
          'export_key' => 'external-requests',
          'default_sort' => [
            'key' => 'request_count',
            'direction' => 'desc',
          ],
          'flush_help' => 'Flush will be enabled after a dedicated aggregated dataset is introduced. This safe batch does not delete raw span records.',
          'empty_message' => 'No external requests were captured for the selected time range.',
          'columns' => [
            ['key' => 'target', 'label' => 'Target'],
            ['key' => 'method', 'label' => 'Method'],
            ['key' => 'request_count', 'label' => 'Calls'],
            ['key' => 'avg_duration', 'label' => 'Avg'],
            ['key' => 'error_count', 'label' => 'Errors'],
          ],
          'rows' => $this->traceMetricsReader->getExternalRequestDataset($range),
        ],
      ],
    ];
  }

  /**
   * Enriches dataset rows with per-trace span breakdown in one batch query.
   *
   * Collects all request_ids from trace samples in the dataset, loads their
   * span breakdowns in one query, and adds span breakdown entries to each
   * trace sample's detail summary for display in the modal meta section.
   *
   * @param array $rows
   *   Endpoint dataset rows as returned by TraceMetricsReader.
   *
   * @return array
   *   The same rows with span breakdown injected into each trace sample detail.
   */
  private function enrichDatasetWithSpanBreakdown(array $rows): array {
    $requestIds = [];
    foreach ($rows as $row) {
      foreach ($row['detail']['table_rows'] ?? [] as $traceRow) {
        $requestId = (string) ($traceRow['detail']['request_id'] ?? '');
        if ($requestId !== '') {
          $requestIds[] = $requestId;
        }
      }
    }

    if ($requestIds === []) {
      return $rows;
    }

    $breakdowns = $this->traceMetricsReader->getSpanBreakdownBatch(array_unique($requestIds));

    if ($breakdowns === []) {
      return $rows;
    }

    foreach ($rows as &$row) {
      if (!isset($row['detail']['table_rows'])) {
        continue;
      }

      foreach ($row['detail']['table_rows'] as &$traceRow) {
        $requestId = (string) ($traceRow['detail']['request_id'] ?? '');
        if ($requestId === '' || !isset($breakdowns[$requestId])) {
          continue;
        }

        $traceRow['detail']['span_breakdown'] = $breakdowns[$requestId];

        foreach ($breakdowns[$requestId] as $spanRow) {
          $label = ($spanRow['label'] ?? $spanRow['category']) . ' (span)';
          $traceRow['detail']['summary'][$label] = number_format((float) ($spanRow['avg_duration_ms'] ?? 0), 2) . ' ms';
        }
      }
      unset($traceRow);
    }
    unset($row);

    return $rows;
  }

}
