<?php
header('Content-Type: application/json');

// Define the project root. Go up two directories from the current `api` directory.
$projectRoot = dirname(__DIR__, 2);

// Get the requested path from the query parameters
$relativePath = isset($_GET['path']) ? $_GET['path'] : '';

// Basic security check: prevent directory traversal
if (strpos($relativePath, '..') !== false) {
    http_response_code(400);
    echo json_encode(['error' => 'Invalid path specified.']);
    exit;
}

$fullPath = realpath($projectRoot . '/' . $relativePath);

// Ensure the path is within the project directory
if (!$fullPath || strpos($fullPath, realpath($projectRoot)) !== 0) {
    http_response_code(400);
    echo json_encode(['error' => 'Access denied to the specified path.']);
    exit;
}

if (!file_exists($fullPath)) {
    http_response_code(404);
    echo json_encode(['error' => 'File or directory not found.']);
    exit;
}

$details = [
    'path' => $relativePath,
    'type' => is_dir($fullPath) ? 'directory' : 'file',
];

if (is_dir($fullPath)) {
    $details['size'] = getDirectorySize($fullPath);
    $details['file_count'] = countFiles($fullPath);
} else {
    $details['size'] = filesize($fullPath);
    if (pathinfo($fullPath, PATHINFO_EXTENSION) === 'php') {
        $details['code_points'] = getPhpCodePoints($fullPath);
    }
}

echo json_encode($details);

/**
 * Recursively calculates the size of a directory.
 *
 * @param string $path The path to the directory.
 * @return int The size in bytes.
 */
function getDirectorySize(string $path): int
{
    $totalSize = 0;
    $items = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS));
    foreach ($items as $item) {
        if ($item->isReadable()) {
            $totalSize += $item->getSize();
        }
    }
    return $totalSize;
}

/**
 * Recursively counts the number of files in a directory.
 *
 * @param string $path The path to the directory.
 * @return int The total number of files.
 */
function countFiles(string $path): int
{
    $fileCount = 0;
    $items = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS));
    foreach ($items as $item) {
        if ($item->isFile() && $item->isReadable()) {
            $fileCount++;
        }
    }
    return $fileCount;
}

/**
 * Extracts class, trait, interface, and function names from a PHP file.
 *
 * @param string $filePath The path to the PHP file.
 * @return array An array of code points found.
 */
function getPhpCodePoints(string $filePath): array
{
    $codePoints = [
        'classes' => [],
        'functions' => [],
        'traits' => [],
        'interfaces' => [],
    ];
    $source = file_get_contents($filePath);
    $tokens = token_get_all($source);
    $count = count($tokens);

    for ($i = 0; $i < $count; $i++) {
        // Check for class, interface, trait
        if (in_array($tokens[$i][0], [T_CLASS, T_INTERFACE, T_TRAIT])) {
            $type = $tokens[$i][1]; // 'class', 'interface', 'trait'
            // Find the name of the class/interface/trait
            for ($j = $i + 1; $j < $count; $j++) {
                if ($tokens[$j][0] === T_STRING) {
                    $name = $tokens[$j][1];
                    switch ($type) {
                        case 'class':
                            $codePoints['classes'][] = $name;
                            break;
                        case 'interface':
                            $codePoints['interfaces'][] = $name;
                            break;
                        case 'trait':
                            $codePoints['traits'][] = $name;
                            break;
                    }
                    $i = $j; // Continue scanning from here
                    break;
                }
            }
        }
        // Check for function
        elseif ($tokens[$i][0] === T_FUNCTION) {
            // Find the name of the function
            for ($j = $i + 1; $j < $count; $j++) {
                if ($tokens[$j][0] === T_STRING) {
                    $codePoints['functions'][] = $tokens[$j][1];
                    $i = $j; // Continue scanning from here
                    break;
                }
                // Handle anonymous functions
                if ($tokens[$j] === '(') {
                    break;
                }
            }
        }
    }
    return $codePoints;
}