<?php
/**
 * Paths Helper
 *
 * Manages path construction and validation for projects and tasks.
 *
 * PHP version 8.2+
 *
 * @category Support
 * @package  IronKanban\Support
 * @author   Keith Solomon <keith@keithsolomon.net>
 * @license  Unlicense https://unlicense.org
 * @link     https://git.keithsolomon.net/keith/IronKanban
 */

declare(strict_types=1);

namespace IronKanban\Support;

use RuntimeException;

/**
 * Paths manages path construction and validation
 *
 * @category Support
 * @package  IronKanban\Support
 * @author   Keith Solomon <keith@keithsolomon.net>
 * @license  Unlicense https://unlicense.org
 * @link     https://git.keithsolomon.net/keith/IronKanban
 */
class Paths {
    /**
     * Constructor for Paths
     *
     * @param string $projectRoot The root directory of the project
     *
     * @throws RuntimeException If the project root is invalid
     */
    public function __construct( private string $projectRoot ) {
        $realRoot = realpath($projectRoot);

        if ($realRoot === false || !is_dir($realRoot)) {
            throw new RuntimeException("Invalid project root: {$projectRoot}");
        }

        $this->projectRoot = $realRoot;
    }

    /**
     * Get the project root directory
     *
     * @return string The project root path
     */
    public function getProjectRoot(): string {
        return $this->projectRoot;
    }

    /**
     * Get the path for a specific project
     *
     * @param string $projectId The project identifier
     *
     * @return string The project path
     *
     * @throws RuntimeException If project not found
     */
    public function getProjectPath(string $projectId): string {
        $safeProjectId = $this->_sanitizeSegment($projectId);
        $path = $this->projectRoot . DIRECTORY_SEPARATOR . $safeProjectId;

        if (!is_dir($path)) {
            throw new RuntimeException("Project not found: {$projectId}");
        }

        return $path;
    }

    /**
     * Get the tasks directory path for a project
     *
     * @param string $projectId The project identifier
     *
     * @return string The tasks directory path
     *
     * @throws RuntimeException If tasks directory not found
     */
    public function getTasksPath(string $projectId): string {
        $path = $this->getProjectPath($projectId) . DIRECTORY_SEPARATOR . 'tasks';

        if (!is_dir($path)) {
            throw new RuntimeException(
                "Tasks directory not found for project: {$projectId}"
            );
        }

        return $path;
    }

    /**
     * Get the file path for a specific task
     *
     * @param string $projectId The project identifier
     * @param string $taskId    The task identifier
     *
     * @return string The task file path
     */
    public function getTaskFilePath(string $projectId, string $taskId): string {
        $tasksPath = $this->getTasksPath($projectId);
        $safeTaskId = $this->_sanitizeSegment($taskId);

        return $tasksPath . DIRECTORY_SEPARATOR . $safeTaskId . '.md';
    }

    /**
     * Ensure tasks directory exists
     *
     * @param string $projectId The project identifier
     *
     * @return string The tasks directory path
     *
     * @throws RuntimeException If directory cannot be created
     */
    public function ensureTasksPath(string $projectId): string {
        $path = $this->getProjectPath($projectId) . DIRECTORY_SEPARATOR . 'tasks';

        if (!is_dir($path) && !mkdir($path, 0775, true) && !is_dir($path)) {
            throw new RuntimeException("Unable to create tasks directory: {$path}");
        }

        return $path;
    }

    /**
     * Sanitize a path segment
     *
     * @param string $value The path segment to sanitize
     *
     * @return string The sanitized segment
     *
     * @throws RuntimeException If segment is invalid
     */
    private function _sanitizeSegment(string $value): string {
        $value = trim($value);

        if ($value === '') {
            throw new RuntimeException('Path segment cannot be empty.');
        }

        if (!preg_match('/^[a-zA-Z0-9._-]+$/', $value)) {
            throw new RuntimeException("Invalid path segment: {$value}");
        }

        return $value;
    }
}
