<?php

declare(strict_types=1);

namespace Drupal\Tests\visitors\Unit\Plugin\views\field;

use Drupal\Tests\UnitTestCase;
use Drupal\views\Plugin\views\query\Sql;
use Drupal\views\ResultRow;
use Drupal\visitors\Plugin\views\field\NumberRange;
use Symfony\Component\DependencyInjection\ContainerBuilder;

/**
 * Unit tests for NumberRange views field plugin.
 *
 * @group visitors
 * @coversDefaultClass \Drupal\visitors\Plugin\views\field\NumberRange
 */
class NumberRangeTest extends UnitTestCase {

  /**
   * The field plugin instance.
   *
   * @var \Drupal\visitors\Plugin\views\field\NumberRange
   */
  protected NumberRange $field;

  /**
   * The mock query.
   *
   * @var \Drupal\views\Plugin\views\query\Sql|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $query;

  /**
   * The mock config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $configFactory;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $container = new ContainerBuilder();

    $string_translation = $this->getStringTranslationStub();
    $container->set('string_translation', $string_translation);

    // Mock the module handler service.
    $module_handler = $this->createMock('Drupal\Core\Extension\ModuleHandlerInterface');
    $container->set('module_handler', $module_handler);

    // Mock the config factory service.
    $this->configFactory = $this->createMock('Drupal\Core\Config\ConfigFactoryInterface');
    $container->set('config.factory', $this->configFactory);

    // Mock the views data service.
    $views_data = $this->createMock('Drupal\views\ViewsData');
    $container->set('views.data', $views_data);

    \Drupal::setContainer($container);

    $configuration = [
      'table' => 'visitors_visit',
      'field' => 'total_page_views',
    ];
    $plugin_id = 'visitors_number_range';
    $plugin_definition = [
      'group' => 'Visitors',
      'title' => 'Number Range',
      'title short' => 'Number Range',
    ];
    $this->field = NumberRange::create($container, $configuration, $plugin_id, $plugin_definition);

    // Mock the query.
    $this->query = $this->createMock(Sql::class);
    $this->field->query = $this->query;
    $this->field->tableAlias = 'vv';
    $this->field->realField = 'total_page_views';

    // Set default options.
    $this->field->options = [
      'pluralize' => FALSE,
      'singular_label' => '1 item',
      'plural_label' => '@count items',
      'ranges' => "0\n1\n2\n3\n4\n5\n6-7\n8-10\n11-14\n15-20\n21+",
      'index' => TRUE,
      'time_format' => FALSE,
      'id' => 'visitors_number_range',
      'admin_label' => 'Number Range',
      'element_label_colon' => FALSE,
      'exclude' => FALSE,
      'element_type' => FALSE,
      'element_class' => FALSE,
      'element_label_type' => FALSE,
      'element_label_class' => FALSE,
      'element_wrapper_type' => FALSE,
      'element_wrapper_class' => FALSE,
      'empty' => FALSE,
      'hide_empty' => FALSE,
      'empty_zero' => FALSE,
      'hide_alter_empty' => FALSE,
      'element_default_classes' => FALSE,
      'alter' => [
        'alter_text' => FALSE,
        'text' => '',
        'make_link' => FALSE,
        'preserve_tags' => FALSE,
        'strip_tags' => FALSE,
        'trim_whitespace' => FALSE,
        'path' => '',
        'absolute' => FALSE,
        'replace_spaces' => FALSE,
        'external' => FALSE,
        'path_case' => FALSE,
        'link_class' => '',
        'alt' => '',
        'rel' => '',
        'prefix' => '',
        'suffix' => '',
        'target' => '',
        'max_length' => FALSE,
        'word_boundary' => FALSE,
        'ellipsis' => FALSE,
        'trim' => FALSE,
        'html' => FALSE,
        'nl2br' => FALSE,
        'more_link' => FALSE,
        'more_link_text' => '',
        'more_link_path' => '',
      ],
    ];
  }

  /**
   * Tests the query method.
   *
   * @covers ::query
   */
  public function testQuery(): void {
    $this->field->options['ranges'] = "0\n1\n2\n3\n4\n5\n6-7\n8-10\n11-14\n15-20\n21+";

    $this->query->expects($this->once())
      ->method('addField')
      ->with(
        NULL,
        "CASE WHEN vv.total_page_views = 0 THEN 0 WHEN vv.total_page_views = 1 THEN 1 WHEN vv.total_page_views = 2 THEN 2 WHEN vv.total_page_views = 3 THEN 3 WHEN vv.total_page_views = 4 THEN 4 WHEN vv.total_page_views = 5 THEN 5 WHEN vv.total_page_views BETWEEN 6 AND 7 THEN 6 WHEN vv.total_page_views BETWEEN 8 AND 10 THEN 7 WHEN vv.total_page_views BETWEEN 11 AND 14 THEN 8 WHEN vv.total_page_views BETWEEN 15 AND 20 THEN 9 WHEN vv.total_page_views >= 21 THEN 10 ELSE 12 END",
        'vv_total_page_views__range'
      )
      ->willReturn('vv_total_page_views__range');

    $this->field->query();

    $this->assertEquals('vv_total_page_views__range', $this->field->field_alias);
  }

  /**
   * Tests the query method with empty ranges.
   *
   * @covers ::query
   */
  public function testQueryWithEmptyRanges(): void {
    $this->field->options['ranges'] = '';
    $this->field->options['group_type'] = '';

    // When ranges is empty, the method should call
    // addField with an empty CASE statement.
    $this->query->expects($this->once())
      ->method('addField')
      ->with(
        'vv',
        'total_page_views',
      )
      ->willReturn('vv_total_page_views');

    $this->field->query();
  }

  /**
   * Tests the query method with whitespace-only ranges.
   *
   * @covers ::query
   */
  public function testQueryWithWhitespaceOnlyRanges(): void {
    $this->field->options['ranges'] = "  \n  \n  ";
    $this->field->options['group_type'] = '';

    // When ranges contains only whitespace, the method should call
    // addField with an empty CASE statement.
    $this->query->expects($this->once())
      ->method('addField')
      ->with(
        'vv',
        'total_page_views',
      )
      ->willReturn('vv_total_page_views');

    $this->field->query();
  }

  /**
   * Tests the render method.
   *
   * @covers ::render
   */
  public function testRender(): void {
    $this->field->options['ranges'] = "0\n1\n2\n3\n4\n5\n6-7\n8-10\n11-14\n15-20\n21+";

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views__range = '2';

    $this->field->field_alias = 'vv_total_page_views__range';

    $result = $this->field->render($result_row);

    $this->assertEquals('2', $result);
  }

  /**
   * Tests the render method with time format enabled.
   *
   * @covers ::render
   * @covers ::formatTimeRange
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   * @covers ::formatSeconds
   */
  public function testRenderWithTimeFormat(): void {
    $this->field->options['time_format'] = TRUE;
    $this->field->options['ranges'] = "60\n3600\n86400";

    $test_cases = [
      ['input' => '60', 'expected' => '1 minute'],
      ['input' => '3600', 'expected' => '1 hour'],
      ['input' => '86400', 'expected' => '1 day'],
    ];

    foreach ($test_cases as $test_case) {
      $result_row = $this->createMock(ResultRow::class);
      $result_row->vv_total_page_views_range = $test_case['input'];

      $this->field->field_alias = 'vv_total_page_views_range';

      $result = $this->field->render($result_row);

      $this->assertEquals($test_case['expected'], $result);
    }
  }

  /**
   * Tests the render method with pluralization enabled.
   *
   * @covers ::render
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   */
  public function testRenderWithPluralization(): void {
    $this->field->options['pluralize'] = TRUE;
    $this->field->options['singular_label'] = '1 page view';
    $this->field->options['plural_label'] = '@count page views';

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views_range = '5';

    $this->field->field_alias = 'vv_total_page_views_range';

    $result = $this->field->render($result_row);

    $this->assertEquals('5 page views', $result);
  }

  /**
   * Tests the render method with singular value.
   *
   * @covers ::render
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   */
  public function testRenderWithSingularValue(): void {
    $this->field->options['pluralize'] = TRUE;
    $this->field->options['singular_label'] = '1 page view';
    $this->field->options['plural_label'] = '@count page views';

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views_range = '1';

    $this->field->field_alias = 'vv_total_page_views_range';

    $result = $this->field->render($result_row);

    $this->assertEquals('1 page view', $result);
  }

  /**
   * Tests the render method with time range format.
   *
   * @covers ::render
   * @covers ::formatTimeRange
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   * @covers ::formatSeconds
   */
  public function testRenderWithTimeRangeFormat(): void {
    $this->field->options['time_format'] = TRUE;
    $this->field->options['ranges'] = "60-120\n300-600\n1800-3600";

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views_range = '60-120';

    $this->field->field_alias = 'vv_total_page_views_range';

    $result = $this->field->render($result_row);

    $this->assertEquals('1-2 minutes', $result);
  }

  /**
   * Tests the render method with time plus format.
   *
   * @covers ::formatTimeRange
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   */
  public function testRenderWithTimePlusFormat(): void {
    $this->field->options['time_format'] = TRUE;
    $this->field->options['ranges'] = "3600+\n86400+";

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views_range = '3600+';

    $this->field->field_alias = 'vv_total_page_views_range';

    $result = $this->field->render($result_row);

    $this->assertEquals('1+ hour', $result);
  }

  /**
   * Tests the render method with various time units.
   *
   * @covers ::render
   * @covers ::formatTimeRange
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   */
  public function testRenderWithVariousTimeUnits(): void {
    $this->field->options['time_format'] = TRUE;

    $test_cases = [
      ['input' => '0', 'expected' => '0 seconds'],
      ['input' => '1', 'expected' => '1 second'],
      ['input' => '30', 'expected' => '30 seconds'],
      ['input' => '60', 'expected' => '1 minute'],
      ['input' => '90', 'expected' => '1.5 minutes'],
      ['input' => '3600', 'expected' => '1 hour'],
      ['input' => '7200', 'expected' => '2 hours'],
      ['input' => '86400', 'expected' => '1 day'],
      ['input' => '172800', 'expected' => '2 days'],
      ['input' => '604800', 'expected' => '1 week'],
      ['input' => '1209600', 'expected' => '2 weeks'],
      ['input' => '2592000', 'expected' => '1 month'],
      ['input' => '5184000', 'expected' => '2 months'],
      ['input' => '31536000', 'expected' => '1 year'],
      ['input' => '63072000', 'expected' => '2 years'],
    ];

    foreach ($test_cases as $test_case) {
      $result_row = $this->createMock(ResultRow::class);
      $result_row->vv_total_page_views_range = $test_case['input'];

      $this->field->field_alias = 'vv_total_page_views_range';

      $result = $this->field->render($result_row);

      $this->assertEquals($test_case['expected'], $result);
    }
  }

  /**
   * Tests the render method with range labels.
   *
   * @covers ::render
   * @covers ::getRangeLabel
   */
  public function testRenderWithRangeLabels(): void {
    $this->field->options['ranges'] = "0\n1-2\n3-5\n6-10\n11+";

    $test_cases = [
      ['index' => 0, 'expected' => '0'],
      ['index' => 1, 'expected' => '1-2'],
      ['index' => 2, 'expected' => '3-5'],
      ['index' => 3, 'expected' => '6-10'],
      ['index' => 4, 'expected' => '11+'],
    ];

    foreach ($test_cases as $test_case) {
      $result_row = $this->createMock(ResultRow::class);
      $result_row->vv_total_page_views__range = (string) $test_case['index'];

      $this->field->field_alias = 'vv_total_page_views__range';

      $result = $this->field->render($result_row);

      $this->assertEquals($test_case['expected'], $result);
    }
  }

  /**
   * Tests the render method with empty ranges.
   *
   * @covers ::render
   */
  public function testRenderWithEmptyRanges(): void {
    $this->field->options['ranges'] = '';

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views__range = '0';

    $this->field->field_alias = 'vv_total_page_views__range';

    $result = $this->field->render($result_row);

    $this->assertEquals('0', $result);
  }

  /**
   * Tests the render method with whitespace-only ranges.
   *
   * @covers ::render
   */
  public function testRenderWithWhitespaceOnlyRanges(): void {
    $this->field->options['ranges'] = "  \n  \n  ";

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views__range = '0';

    $this->field->field_alias = 'vv_total_page_views__range';

    $result = $this->field->render($result_row);

    $this->assertEquals('0', $result);
  }

  /**
   * Tests the render method with non-existent index.
   *
   * @covers ::render
   * @covers ::getRangeLabel
   */
  public function testRenderWithNonExistentIndex(): void {
    $this->field->options['ranges'] = "0\n1\n2";

    $result_row = $this->createMock(ResultRow::class);
    $result_row->vv_total_page_views__range = '999';

    $this->field->field_alias = 'vv_total_page_views__range';

    $result = $this->field->render($result_row);

    $this->assertEquals('999', $result);
  }

  /**
   * Tests time range format where start and end are less than 1 unit apart.
   *
   * @covers ::render
   * @covers ::formatTimeRange
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   */
  public function testRenderWithTimeRangeFormatLessThanOneUnitApart(): void {
    $this->field->options['time_format'] = TRUE;
    $this->field->options['ranges'] = "0-60\n0-3600\n0-86400";

    $test_cases = [
      ['index' => 0, 'expected' => '1 minute'],
      ['index' => 1, 'expected' => '1 hour'],
      ['index' => 2, 'expected' => '1 day'],
    ];

    foreach ($test_cases as $test_case) {
      $result_row = $this->createMock(ResultRow::class);
      $result_row->vv_total_page_views__range = (string) $test_case['index'];

      $this->field->field_alias = 'vv_total_page_views__range';

      $result = $this->field->render($result_row);

      $this->assertEquals($test_case['expected'], $result);
    }
  }

  /**
   * Tests time range format where start and end are exactly 1 unit apart.
   *
   * @covers ::render
   * @covers ::formatTimeRange
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   */
  public function testRenderWithTimeRangeFormatExactlyOneUnitApart(): void {
    $this->field->options['time_format'] = TRUE;
    $this->field->options['ranges'] = "-60\n60-120\n3600-7200\n86400-172800";

    $test_cases = [
      ['index' => 0, 'expected' => '1 minute'],
      ['index' => 1, 'expected' => '1-2 minutes'],
      ['index' => 2, 'expected' => '1-2 hours'],
      ['index' => 3, 'expected' => '1-2 days'],
    ];

    foreach ($test_cases as $test_case) {
      $result_row = $this->createMock(ResultRow::class);
      $result_row->vv_total_page_views__range = (string) $test_case['index'];

      $this->field->field_alias = 'vv_total_page_views__range';

      $result = $this->field->render($result_row);

      $this->assertEquals($test_case['expected'], $result);
    }
  }

  /**
   * Tests time range format where start and end are more than 1 unit apart.
   *
   * @covers ::render
   * @covers ::formatTimeRange
   * @covers ::makePluralWith
   * @covers ::makePlural
   * @covers ::makeSingle
   */
  public function testRenderWithTimeRangeFormatMoreThanOneUnitApart(): void {
    $this->field->options['time_format'] = TRUE;
    $this->field->options['ranges'] = "60-180\n3600-10800\n86400-259200";

    $test_cases = [
      ['index' => 0, 'expected' => '1-3 minutes'],
      ['index' => 1, 'expected' => '1-3 hours'],
      ['index' => 2, 'expected' => '1-3 days'],
    ];

    foreach ($test_cases as $test_case) {
      $result_row = $this->createMock(ResultRow::class);
      $result_row->vv_total_page_views__range = (string) $test_case['index'];

      $this->field->field_alias = 'vv_total_page_views__range';

      $result = $this->field->render($result_row);

      $this->assertEquals($test_case['expected'], $result);
    }
  }

  /**
   * Tests the formatSeconds method with automatic unit detection.
   *
   * @covers ::formatSeconds
   */
  public function testFormatSecondsWithAutomaticUnitDetection(): void {
    $test_cases = [
      ['seconds' => 0, 'expected_value' => 0.0, 'expected_unit' => NULL],
      ['seconds' => 1, 'expected_value' => 1.0, 'expected_unit' => 'second'],
      ['seconds' => 30, 'expected_value' => 30.0, 'expected_unit' => 'second'],
      ['seconds' => 59, 'expected_value' => 59.0, 'expected_unit' => 'second'],
      ['seconds' => 60, 'expected_value' => 1.0, 'expected_unit' => 'minute'],
      ['seconds' => 90, 'expected_value' => 1.5, 'expected_unit' => 'minute'],
      ['seconds' => 3599, 'expected_value' => 59.983333333333334, 'expected_unit' => 'minute'],
      ['seconds' => 3600, 'expected_value' => 1.0, 'expected_unit' => 'hour'],
      ['seconds' => 7200, 'expected_value' => 2.0, 'expected_unit' => 'hour'],
      ['seconds' => 86399, 'expected_value' => 23.999722222222222, 'expected_unit' => 'hour'],
      ['seconds' => 86400, 'expected_value' => 1.0, 'expected_unit' => 'day'],
      ['seconds' => 172800, 'expected_value' => 2.0, 'expected_unit' => 'day'],
      ['seconds' => 604799, 'expected_value' => 6.999988425925926, 'expected_unit' => 'day'],
      ['seconds' => 604800, 'expected_value' => 1.0, 'expected_unit' => 'week'],
      ['seconds' => 1209600, 'expected_value' => 2.0, 'expected_unit' => 'week'],
      ['seconds' => 2591999, 'expected_value' => 4.285712632275132, 'expected_unit' => 'week'],
      ['seconds' => 2592000, 'expected_value' => 1.0, 'expected_unit' => 'month'],
      ['seconds' => 5184000, 'expected_value' => 2.0, 'expected_unit' => 'month'],
      ['seconds' => 31535999, 'expected_value' => 12.166666666666666, 'expected_unit' => 'month'],
      ['seconds' => 31536000, 'expected_value' => 1.0, 'expected_unit' => 'year'],
      ['seconds' => 63072000, 'expected_value' => 2.0, 'expected_unit' => 'year'],
    ];

    foreach ($test_cases as $test_case) {
      $unit = NULL;
      $result = $this->callProtectedMethod('formatSeconds', [$test_case['seconds'], &$unit]);

      if (is_float($result)) {
        $this->assertEqualsWithDelta($test_case['expected_value'], $result, 0.000001,
          "Failed for {$test_case['seconds']} seconds");
      }
      else {
        $this->assertEquals($test_case['expected_value'], $result,
          "Failed for {$test_case['seconds']} seconds");
      }
      $this->assertEquals($test_case['expected_unit'], $unit,
        "Unit mismatch for {$test_case['seconds']} seconds");
    }
  }

  /**
   * Tests the formatSeconds method with pre-specified units.
   *
   * @covers ::formatSeconds
   */
  public function testFormatSecondsWithPreSpecifiedUnits(): void {
    $test_cases = [
      ['seconds' => 120, 'unit' => 'second', 'expected_value' => 120.0],
      ['seconds' => 120, 'unit' => 'minute', 'expected_value' => 2.0],
      ['seconds' => 120, 'unit' => 'hour', 'expected_value' => 0.03333333333333333],
      ['seconds' => 120, 'unit' => 'day', 'expected_value' => 0.001388888888888889],
      ['seconds' => 120, 'unit' => 'week', 'expected_value' => 0.0001984126984126984],
      ['seconds' => 120, 'unit' => 'month', 'expected_value' => 0.0000462962962962963],
      ['seconds' => 120, 'unit' => 'year', 'expected_value' => 0.00000380517503805175],
      ['seconds' => 3600, 'unit' => 'second', 'expected_value' => 3600.0],
      ['seconds' => 3600, 'unit' => 'minute', 'expected_value' => 60.0],
      ['seconds' => 3600, 'unit' => 'hour', 'expected_value' => 1.0],
      ['seconds' => 86400, 'unit' => 'second', 'expected_value' => 86400.0],
      ['seconds' => 86400, 'unit' => 'minute', 'expected_value' => 1440.0],
      ['seconds' => 86400, 'unit' => 'hour', 'expected_value' => 24.0],
      ['seconds' => 86400, 'unit' => 'day', 'expected_value' => 1.0],
    ];

    foreach ($test_cases as $test_case) {
      $unit = $test_case['unit'];
      $result = $this->callProtectedMethod('formatSeconds', [$test_case['seconds'], &$unit]);

      if (is_float($result)) {
        $this->assertEqualsWithDelta($test_case['expected_value'], $result, 0.000001,
          "Failed for {$test_case['seconds']} seconds with unit '{$test_case['unit']}'");
      }
      else {
        $this->assertEquals($test_case['expected_value'], $result,
          "Failed for {$test_case['seconds']} seconds with unit '{$test_case['unit']}'");
      }
      $this->assertEquals($test_case['unit'], $unit,
        "Unit should remain unchanged for {$test_case['seconds']} seconds with unit '{$test_case['unit']}'");
    }
  }

  /**
   * Tests the formatSeconds method with unrecognized pre-specified units.
   *
   * @covers ::formatSeconds
   */
  public function testFormatSecondsWithUnrecognizedPreSpecifiedUnits(): void {
    $test_cases = [
      ['seconds' => 120, 'unit' => 'unknown_unit'],
      ['seconds' => 120, 'unit' => 'decade'],
      ['seconds' => 120, 'unit' => 'century'],
      ['seconds' => 120, 'unit' => 'millennium'],
    ];

    foreach ($test_cases as $test_case) {
      $unit = $test_case['unit'];
      $result = $this->callProtectedMethod('formatSeconds', [$test_case['seconds'], &$unit]);

      // Should fall back to automatic detection.
      $this->assertEquals(2.0, $result,
        "Failed for {$test_case['seconds']} seconds with unrecognized unit '{$test_case['unit']}'");
      $this->assertEquals('minute', $unit,
        "Unit should be automatically detected for {$test_case['seconds']} seconds with unrecognized unit '{$test_case['unit']}'");
    }
  }

  /**
   * Tests the formatSeconds method with edge case values.
   *
   * @covers ::formatSeconds
   */
  public function testFormatSecondsWithEdgeCaseValues(): void {
    $test_cases = [
      ['seconds' => 0, 'expected_value' => 0.0, 'expected_unit' => NULL],
      ['seconds' => 1, 'expected_value' => 1.0, 'expected_unit' => 'second'],
      ['seconds' => 59, 'expected_value' => 59.0, 'expected_unit' => 'second'],
      ['seconds' => 60, 'expected_value' => 1.0, 'expected_unit' => 'minute'],
      ['seconds' => 3599, 'expected_value' => 59.983333333333334, 'expected_unit' => 'minute'],
      ['seconds' => 3600, 'expected_value' => 1.0, 'expected_unit' => 'hour'],
      ['seconds' => 86399, 'expected_value' => 23.999722222222222, 'expected_unit' => 'hour'],
      ['seconds' => 86400, 'expected_value' => 1.0, 'expected_unit' => 'day'],
      ['seconds' => 604799, 'expected_value' => 6.999988425925926, 'expected_unit' => 'day'],
      ['seconds' => 604800, 'expected_value' => 1.0, 'expected_unit' => 'week'],
      ['seconds' => 2591999, 'expected_value' => 4.285712632275132, 'expected_unit' => 'week'],
      ['seconds' => 2592000, 'expected_value' => 1.0, 'expected_unit' => 'month'],
      ['seconds' => 31535999, 'expected_value' => 12.166666666666666, 'expected_unit' => 'month'],
      ['seconds' => 31536000, 'expected_value' => 1.0, 'expected_unit' => 'year'],
    ];

    foreach ($test_cases as $test_case) {
      $unit = NULL;
      $result = $this->callProtectedMethod('formatSeconds', [$test_case['seconds'], &$unit]);

      if (is_float($result)) {
        $this->assertEqualsWithDelta($test_case['expected_value'], $result, 0.000001,
          "Failed for {$test_case['seconds']} seconds (edge case)");
      }
      else {
        $this->assertEquals($test_case['expected_value'], $result,
          "Failed for {$test_case['seconds']} seconds (edge case)");
      }
      $this->assertEquals($test_case['expected_unit'], $unit,
        "Unit mismatch for {$test_case['seconds']} seconds (edge case)");
    }
  }

  /**
   * Tests the formatSeconds method with large values.
   *
   * @covers ::formatSeconds
   */
  public function testFormatSecondsWithLargeValues(): void {
    $test_cases = [
      ['seconds' => 31536000, 'expected_value' => 1.0, 'expected_unit' => 'year'],
      ['seconds' => 63072000, 'expected_value' => 2.0, 'expected_unit' => 'year'],
      ['seconds' => 94608000, 'expected_value' => 3.0, 'expected_unit' => 'year'],
      ['seconds' => 126144000, 'expected_value' => 4.0, 'expected_unit' => 'year'],
      ['seconds' => 157680000, 'expected_value' => 5.0, 'expected_unit' => 'year'],
    ];

    foreach ($test_cases as $test_case) {
      $unit = NULL;
      $result = $this->callProtectedMethod('formatSeconds', [$test_case['seconds'], &$unit]);

      if (is_float($result)) {
        $this->assertEqualsWithDelta($test_case['expected_value'], $result, 0.000001,
          "Failed for {$test_case['seconds']} seconds (large value)");
      }
      else {
        $this->assertEquals($test_case['expected_value'], $result,
          "Failed for {$test_case['seconds']} seconds (large value)");
      }
      $this->assertEquals($test_case['expected_unit'], $unit,
        "Unit mismatch for {$test_case['seconds']} seconds (large value)");
    }
  }

  /**
   * Tests the formatSeconds method with fractional results.
   *
   * @covers ::formatSeconds
   */
  public function testFormatSecondsWithFractionalResults(): void {
    $test_cases = [
      ['seconds' => 30, 'unit' => 'minute', 'expected_value' => 0.5],
      ['seconds' => 90, 'unit' => 'minute', 'expected_value' => 1.5],
      ['seconds' => 1800, 'unit' => 'hour', 'expected_value' => 0.5],
      ['seconds' => 2700, 'unit' => 'hour', 'expected_value' => 0.75],
      ['seconds' => 43200, 'unit' => 'day', 'expected_value' => 0.5],
      ['seconds' => 64800, 'unit' => 'day', 'expected_value' => 0.75],
      ['seconds' => 302400, 'unit' => 'week', 'expected_value' => 0.5],
      ['seconds' => 453600, 'unit' => 'week', 'expected_value' => 0.75],
      ['seconds' => 1296000, 'unit' => 'month', 'expected_value' => 0.5],
      ['seconds' => 1944000, 'unit' => 'month', 'expected_value' => 0.75],
      ['seconds' => 15768000, 'unit' => 'year', 'expected_value' => 0.5],
      ['seconds' => 23652000, 'unit' => 'year', 'expected_value' => 0.75],
    ];

    foreach ($test_cases as $test_case) {
      $unit = $test_case['unit'];
      $result = $this->callProtectedMethod('formatSeconds', [$test_case['seconds'], &$unit]);

      if (is_float($result)) {
        $this->assertEqualsWithDelta($test_case['expected_value'], $result, 0.000001,
          "Failed for {$test_case['seconds']} seconds with unit '{$test_case['unit']}' (fractional)");
      }
      else {
        $this->assertEquals($test_case['expected_value'], $result,
          "Failed for {$test_case['seconds']} seconds with unit '{$test_case['unit']}' (fractional)");
      }
      $this->assertEquals($test_case['unit'], $unit,
        "Unit should remain unchanged for {$test_case['seconds']} seconds with unit '{$test_case['unit']}' (fractional)");
    }
  }

  /**
   * Tests the buildOptionsForm method.
   *
   * @covers ::buildOptionsForm
   */
  public function testBuildOptionsForm(): void {
    // Set up the view context required by parent::buildOptionsForm.
    $this->field->view = $this->createMock('Drupal\views\ViewExecutable');
    $this->field->view->display_handler = $this->createMock('Drupal\views\Plugin\views\display\DisplayPluginBase');
    $this->field->view->display_handler->expects($this->once())
      ->method('getFieldLabels')
      ->willReturn([]);
    $this->field->view->display_handler->expects($this->once())
      ->method('getHandlers')
      ->with('argument')
      ->willReturn([]);

    // Mock views settings.
    $views_settings = $this->createMock('Drupal\Core\Config\ImmutableConfig');
    $views_settings->expects($this->any())
      ->method('get')
      ->with('field_rewrite_elements')
      ->willReturn([]);

    $this->configFactory->expects($this->any())
      ->method('get')
      ->with('views.settings')
      ->willReturn($views_settings);

    $form = [];
    $form_state = $this->createMock('Drupal\Core\Form\FormStateInterface');

    $this->field->buildOptionsForm($form, $form_state);

    // Test that all expected form elements are present.
    $this->assertArrayHasKey('pluralize', $form);
    $this->assertArrayHasKey('singular_label', $form);
    $this->assertArrayHasKey('plural_label', $form);
    $this->assertArrayHasKey('ranges', $form);
    $this->assertArrayHasKey('time_format', $form);

    // Test pluralize checkbox.
    $this->assertEquals('checkbox', $form['pluralize']['#type']);
    $this->assertEquals('Enable pluralization', $form['pluralize']['#title']);
    $this->assertFalse($form['pluralize']['#default_value']);

    // Test singular label textfield.
    $this->assertEquals('textfield', $form['singular_label']['#type']);
    $this->assertEquals('Singular label', $form['singular_label']['#title']);
    $this->assertEquals('1 item', $form['singular_label']['#default_value']);
    $this->assertArrayHasKey('#states', $form['singular_label']);

    // Test plural label textfield.
    $this->assertEquals('textfield', $form['plural_label']['#type']);
    $this->assertEquals('Plural label', $form['plural_label']['#title']);
    $this->assertEquals('@count items', $form['plural_label']['#default_value']);
    $this->assertArrayHasKey('#states', $form['plural_label']);

    // Test ranges textarea.
    $this->assertEquals('textarea', $form['ranges']['#type']);
    $this->assertEquals('Number ranges', $form['ranges']['#title']);
    $this->assertStringContainsString('Enter one range per line', (string) $form['ranges']['#description']);
    $this->assertStringContainsString('Examples:', (string) $form['ranges']['#description']);
    $this->assertStringContainsString('<code>0</code>', (string) $form['ranges']['#description']);
    $this->assertStringContainsString('<code>6-7</code>', (string) $form['ranges']['#description']);
    $this->assertStringContainsString('<code>21+</code>', (string) $form['ranges']['#description']);
    $this->assertEquals("0\n1\n2\n3\n4\n5\n6-7\n8-10\n11-14\n15-20\n21+", $form['ranges']['#default_value']);

    // Test time format checkbox.
    $this->assertEquals('checkbox', $form['time_format']['#type']);
    $this->assertEquals('Format ranges as time (e.g., 60s, 1h, 1d)', $form['time_format']['#title']);
    $this->assertFalse($form['time_format']['#default_value']);
  }

  /**
   * Tests the buildOptionsForm method with custom options.
   *
   * @covers ::buildOptionsForm
   */
  public function testBuildOptionsFormWithCustomOptions(): void {
    // Set up the view context required by parent::buildOptionsForm.
    $this->field->view = $this->createMock('Drupal\views\ViewExecutable');
    $this->field->view->display_handler = $this->createMock('Drupal\views\Plugin\views\display\DisplayPluginBase');
    $this->field->view->display_handler->expects($this->once())
      ->method('getFieldLabels')
      ->willReturn([]);
    $this->field->view->display_handler->expects($this->once())
      ->method('getHandlers')
      ->with('argument')
      ->willReturn([]);

    // Mock views settings.
    $views_settings = $this->createMock('Drupal\Core\Config\ImmutableConfig');
    $views_settings->expects($this->any())
      ->method('get')
      ->with('field_rewrite_elements')
      ->willReturn([]);

    $this->configFactory->expects($this->any())
      ->method('get')
      ->with('views.settings')
      ->willReturn($views_settings);

    // Set custom options.
    $this->field->options['pluralize'] = TRUE;
    $this->field->options['singular_label'] = '1 page view';
    $this->field->options['plural_label'] = '@count page views';
    $this->field->options['ranges'] = "0\n1-5\n6-10\n11+";
    $this->field->options['time_format'] = TRUE;

    $form = [];
    $form_state = $this->createMock('Drupal\Core\Form\FormStateInterface');

    $this->field->buildOptionsForm($form, $form_state);

    // Test that custom values are used as defaults.
    $this->assertTrue($form['pluralize']['#default_value']);
    $this->assertEquals('1 page view', $form['singular_label']['#default_value']);
    $this->assertEquals('@count page views', $form['plural_label']['#default_value']);
    $this->assertEquals("0\n1-5\n6-10\n11+", $form['ranges']['#default_value']);
    $this->assertTrue($form['time_format']['#default_value']);
  }

  /**
   * Tests the buildOptionsForm method form element states.
   *
   * @covers ::buildOptionsForm
   */
  public function testBuildOptionsFormElementStates(): void {
    // Set up the view context required by parent::buildOptionsForm.
    $this->field->view = $this->createMock('Drupal\views\ViewExecutable');
    $this->field->view->display_handler = $this->createMock('Drupal\views\Plugin\views\display\DisplayPluginBase');
    $this->field->view->display_handler->expects($this->once())
      ->method('getFieldLabels')
      ->willReturn([]);
    $this->field->view->display_handler->expects($this->once())
      ->method('getHandlers')
      ->with('argument')
      ->willReturn([]);

    // Mock views settings.
    $views_settings = $this->createMock('Drupal\Core\Config\ImmutableConfig');
    $views_settings->expects($this->any())
      ->method('get')
      ->with('field_rewrite_elements')
      ->willReturn([]);

    $this->configFactory->expects($this->any())
      ->method('get')
      ->with('views.settings')
      ->willReturn($views_settings);

    $form = [];
    $form_state = $this->createMock('Drupal\Core\Form\FormStateInterface');

    $this->field->buildOptionsForm($form, $form_state);

    // Test that singular and plural label fields have conditional visibility.
    $this->assertArrayHasKey('#states', $form['singular_label']);
    $this->assertArrayHasKey('#states', $form['plural_label']);

    // Test singular label states.
    $singular_states = $form['singular_label']['#states'];
    $this->assertArrayHasKey('visible', $singular_states);
    $this->assertArrayHasKey(':input[name="options[pluralize]"]', $singular_states['visible']);
    $this->assertEquals(['checked' => TRUE], $singular_states['visible'][':input[name="options[pluralize]"]']);

    // Test plural label states.
    $plural_states = $form['plural_label']['#states'];
    $this->assertArrayHasKey('visible', $plural_states);
    $this->assertArrayHasKey(':input[name="options[pluralize]"]', $plural_states['visible']);
    $this->assertEquals(['checked' => TRUE], $plural_states['visible'][':input[name="options[pluralize]"]']);
  }

  /**
   * Tests the defineOptions method.
   *
   * @covers ::defineOptions
   */
  public function testDefineOptions(): void {
    // Set up the view context required by defineOptions.
    $this->field->view = $this->createMock('Drupal\views\ViewExecutable');

    $options = $this->callProtectedMethod('defineOptions');

    $this->assertArrayHasKey('pluralize', $options);
    $this->assertArrayHasKey('singular_label', $options);
    $this->assertArrayHasKey('plural_label', $options);
    $this->assertArrayHasKey('ranges', $options);
    $this->assertArrayHasKey('time_format', $options);

    // Test default values.
    $this->assertFalse($options['pluralize']['default']);
    $this->assertEquals('1 item', $options['singular_label']['default']);
    $this->assertEquals('@count items', $options['plural_label']['default']);
    $this->assertEquals("0\n1\n2\n3\n4\n5\n6-7\n8-10\n11-14\n15-20\n21+", $options['ranges']['default']);
    $this->assertFalse($options['time_format']['default']);
  }

  /**
   * Helper method to call protected methods for testing.
   *
   * @param string $methodName
   *   The name of the protected method to call.
   * @param array $arguments
   *   The arguments to pass to the method.
   *
   * @return mixed
   *   The result of the method call.
   */
  private function callProtectedMethod(string $methodName, array $arguments = []) {
    $reflection = new \ReflectionClass($this->field);
    $method = $reflection->getMethod($methodName);
    $method->setAccessible(TRUE);
    return $method->invokeArgs($this->field, $arguments);
  }

}
