<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop_node_processor\Unit\Plugin\FlowDropNodeProcessor;

use Drupal\flowdrop\DTO\ParameterBag;
use Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor\MarkdownExtractor;
use PHPUnit\Framework\TestCase;

/**
 * Tests the MarkdownExtractor node processor.
 *
 * @coversDefaultClass \Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor\MarkdownExtractor
 * @group flowdrop_node_processor
 */
class MarkdownExtractorTest extends TestCase {

  /**
   * Creates a MarkdownExtractor processor instance.
   *
   * @return \Drupal\flowdrop_node_processor\Plugin\FlowDropNodeProcessor\MarkdownExtractor
   *   The processor instance.
   */
  private function createProcessor(): MarkdownExtractor {
    return new MarkdownExtractor([], 'markdown_extractor', ['id' => 'markdown_extractor']);
  }

  /**
   * Tests that the raw output equals the original input.
   *
   * @covers ::process
   */
  public function testRawOutputIsOriginalInput(): void {
    $processor = $this->createProcessor();
    $input = "# Title\n\nSome body text.";
    $result = $processor->process(new ParameterBag(['text' => $input]));

    $this->assertSame($input, $result['raw']);
  }

  /**
   * Tests H1 title extraction.
   *
   * @covers ::process
   */
  public function testTitleExtraction(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "# My Title\n\nSome body text.",
    ]));

    $this->assertSame('My Title', $result['title']);
  }

  /**
   * Tests that title is empty when no H1 is present.
   *
   * @covers ::process
   */
  public function testTitleEmptyWhenNoH1(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "## Subtitle\n\nJust body text.",
    ]));

    $this->assertSame('', $result['title']);
  }

  /**
   * Tests body extraction strips the first H1.
   *
   * @covers ::process
   */
  public function testBodyStripsFirstH1(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "# Title\n\nBody paragraph.",
    ]));

    $this->assertStringNotContainsString('# Title', $result['body']);
    $this->assertStringContainsString('Body paragraph.', $result['body']);
  }

  /**
   * Tests body extraction when there is no H1.
   *
   * @covers ::process
   */
  public function testBodyWithoutH1(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "Just body text with no heading.",
    ]));

    $this->assertSame('Just body text with no heading.', $result['body']);
  }

  /**
   * Tests YAML front matter extraction.
   *
   * @covers ::process
   */
  public function testYamlFrontMatterExtraction(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "---\ntitle: My Page\nauthor: Alice\n---\n# Content\n\nBody.",
    ]));

    $this->assertIsArray($result['front_matter']);
    $this->assertSame('My Page', $result['front_matter']['title']);
    $this->assertSame('Alice', $result['front_matter']['author']);
  }

  /**
   * Tests JSON front matter extraction.
   *
   * @covers ::process
   */
  public function testJsonFrontMatterExtraction(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "---\n{\"title\": \"JSON Front\", \"tags\": [\"a\", \"b\"]}\n---\n# Content",
    ]));

    $this->assertIsArray($result['front_matter']);
    $this->assertSame('JSON Front', $result['front_matter']['title']);
    $this->assertSame(['a', 'b'], $result['front_matter']['tags']);
  }

  /**
   * Tests that front matter block is excluded from body.
   *
   * @covers ::process
   */
  public function testFrontMatterRemovedFromBody(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "---\ntitle: Test\n---\n# Heading\n\nBody content.",
    ]));

    $this->assertStringNotContainsString('title: Test', $result['body']);
    $this->assertStringContainsString('Body content.', $result['body']);
  }

  /**
   * Tests empty front matter when none is present.
   *
   * @covers ::process
   */
  public function testNoFrontMatterReturnsEmptyArray(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "# Title\n\nBody.",
    ]));

    $this->assertSame([], $result['front_matter']);
  }

  /**
   * Tests section extraction by ## headings.
   *
   * @covers ::process
   */
  public function testSectionExtraction(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "# Title\n\n## Section One\n\nContent one.\n\n## Section Two\n\nContent two.",
    ]));

    $this->assertCount(2, $result['sections']);
    $this->assertSame('Section One', $result['sections'][0]['heading']);
    $this->assertSame(2, $result['sections'][0]['level']);
    $this->assertStringContainsString('Content one.', $result['sections'][0]['content']);
    $this->assertSame('Section Two', $result['sections'][1]['heading']);
  }

  /**
   * Tests that preamble text before first heading is returned as a section.
   *
   * @covers ::process
   */
  public function testPreambleSectionHasEmptyHeading(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "# Title\n\nIntroductory text.\n\n## First Section\n\nSection content.",
    ]));

    $this->assertSame('', $result['sections'][0]['heading']);
    $this->assertSame(0, $result['sections'][0]['level']);
    $this->assertStringContainsString('Introductory text.', $result['sections'][0]['content']);
  }

  /**
   * Tests plain text strips markdown syntax.
   *
   * @covers ::process
   */
  public function testPlainTextStripsMarkdown(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "# Heading\n\n**Bold** and *italic* with `inline code`.",
    ]));

    $this->assertStringNotContainsString('# ', $result['plain_text']);
    $this->assertStringNotContainsString('**', $result['plain_text']);
    $this->assertStringNotContainsString('`', $result['plain_text']);
    $this->assertStringContainsString('Bold', $result['plain_text']);
    $this->assertStringContainsString('italic', $result['plain_text']);
  }

  /**
   * Tests that empty input returns safe default values.
   *
   * @covers ::process
   */
  public function testEmptyInput(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag(['text' => '']));

    $this->assertSame('', $result['title']);
    $this->assertSame('', $result['body']);
    $this->assertSame([], $result['front_matter']);
    $this->assertSame([], $result['sections']);
    $this->assertSame('', $result['plain_text']);
    $this->assertSame('', $result['raw']);
  }

  /**
   * Tests that invalid YAML front matter returns empty array.
   *
   * @covers ::process
   */
  public function testInvalidFrontMatterReturnsEmptyArray(): void {
    $processor = $this->createProcessor();
    // Front matter block present but content is not valid YAML or JSON.
    $result = $processor->process(new ParameterBag([
      'text' => "---\n: invalid: yaml: :\n---\n# Title",
    ]));

    $this->assertIsArray($result['front_matter']);
  }

  /**
   * Tests that links are stripped in plain_text output.
   *
   * @covers ::process
   */
  public function testPlainTextStripsLinks(): void {
    $processor = $this->createProcessor();
    $result = $processor->process(new ParameterBag([
      'text' => "Visit [Example](https://example.com) for more.",
    ]));

    $this->assertStringNotContainsString('](', $result['plain_text']);
    $this->assertStringContainsString('Example', $result['plain_text']);
  }

}
