<?php
/**
 * Front Matter Parser
 *
 * Parses markdown files with YAML front matter sections.
 *
 * PHP version 8.2+
 *
 * @category Markdown
 * @package  IronKanban\Markdown
 * @author   Keith Solomon <keith@keithsolomon.net>
 * @license  Unlicense https://unlicense.org
 * @link     https://git.keithsolomon.net/keith/IronKanban
 */

declare( strict_types=1 );

namespace IronKanban\Markdown;

use RuntimeException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Yaml;

/**
 * FrontMatterParser parses markdown files with YAML front matter
 *
 * @category Markdown
 * @package  IronKanban\Markdown
 * @author   Keith Solomon <keith@keithsolomon.net>
 * @license  Unlicense https://unlicense.org
 * @link     https://git.keithsolomon.net/keith/IronKanban
 */
class FrontMatterParser {
    /**
     * Parse a markdown file and extract front matter
     *
     * @param string $filePath Path to the markdown file.
     *
     * @return FrontMatterDocument The parsed document.
     *
     * @throws RuntimeException If file cannot be read.
     */
    public function parseFile( string $filePath ): FrontMatterDocument {
        if (!is_file($filePath)) {
            throw new RuntimeException("Markdown file not found: {$filePath}");
        }

        $contents = file_get_contents($filePath);

        if ($contents === false) {
            throw new RuntimeException("Unable to read markdown file: {$filePath}");
        }

        return $this->parseString($contents);
    }

    /**
     * Parse a markdown string and extract front matter
     *
     * @param string $contents The markdown content to parse.
     *
     * @return FrontMatterDocument The parsed document.
     *
     * @throws RuntimeException If YAML parsing fails.
     */
    public function parseString( string $contents ): FrontMatterDocument {
        $contents = str_replace(["\r\n", "\r"], "\n", $contents);

        if (!str_starts_with($contents, "---\n")) {
            return new FrontMatterDocument([], ltrim($contents, "\n"));
        }

        $closingPos = strpos($contents, "\n---\n", 4);

        if ($closingPos === false) {
            return new FrontMatterDocument([], ltrim($contents, "\n"));
        }

        $yamlStart = 4;
        $yamlLength = $closingPos - $yamlStart;
        $yamlText = substr($contents, $yamlStart, $yamlLength);
        $bodyStart = $closingPos + 5;
        $body = substr($contents, $bodyStart);

        if ($body === false) {
            $body = '';
        }

        try {
            $meta = Yaml::parse($yamlText);
        } catch ( ParseException $e ) {
            throw new RuntimeException(
                'Failed to parse YAML front matter: ' . $e->getMessage(),
                0,
                $e
            );
        }

        if (!is_array($meta)) {
            $meta = [];
        }

        return new FrontMatterDocument($meta, ltrim($body, "\n"));
    }

    /**
     * Dump a document to markdown string with front matter
     *
     * @param FrontMatterDocument $document The document to dump.
     *
     * @return string The markdown content with front matter.
     */
    public function dump( FrontMatterDocument $document ): string {
        $yaml = Yaml::dump(
            $document->meta,
            4,
            2,
            Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK
        );

        $body = str_replace(["\r\n", "\r"], "\n", $document->body);
        $body = ltrim($body, "\n");

        return "---\n{$yaml}---\n\n{$body}";
    }
}
