<?php

declare(strict_types=1);

namespace PpPhp\Core;

/**
 * Resolves where a project lives on disk.
 *
 * Projects are stored under projects/{ownerUsername}/{slug}. Historically every
 * path was built from the *current* user's name, which meant only the creator
 * could open a project — an administrator (or the owner) opening someone else's
 * project resolved to a folder that doesn't exist.
 *
 * This locator maps a slug to the username that actually owns it, so the central
 * path builders (DB::Init, ModuleState, the build engine) resolve the correct
 * directory regardless of who is signed in. Access control is enforced separately
 * by each controller (own project, or an administrator) BEFORE these paths are
 * used — this class only answers "where is it on disk", never "may you touch it".
 *
 * Slugs are globally unique (enforced at creation), so exactly one owner folder
 * holds any given slug. Resolution is filesystem-based (no DB dependency, so it
 * is safe to call while a project connection is active) and cached per request.
 */
final class ProjectLocator
{
    /** @var array<string,string> slug => owner username */
    private static array $cache = [];

    /** The username whose folder holds this project's slug. */
    public static function ownerUsername(string $slug): string
    {
        $slug = strtolower(trim($slug));

        if (isset(self::$cache[$slug])) {
            return self::$cache[$slug];
        }

        $current = self::currentUsername();

        // Only well-formed slugs are looked up on disk; anything else falls back
        // to the current user (also the correct answer for a project being created).
        if ($slug === '' || !preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $slug)) {
            return self::$cache[$slug] = $current;
        }

        $base = constant('BASE_PATH') . '/projects';
        $candidates = glob("{$base}/*/{$slug}", GLOB_ONLYDIR) ?: [];

        // The REAL project folder is the one carrying its connection config. This
        // is authoritative: a stray/empty "{owner}/{slug}" folder (e.g. left by an
        // earlier resume-state write under the wrong owner) must never win over the
        // creator's actual project directory.
        foreach ($candidates as $dir) {
            if (is_file($dir . '/config/database.php')) {
                return self::$cache[$slug] = basename(dirname($dir));
            }
        }

        // No config yet (a brand-new project mid-creation): prefer the signed-in
        // user's own folder, else any candidate, else the current user.
        if ($current !== '' && is_dir("{$base}/{$current}/{$slug}")) {
            return self::$cache[$slug] = $current;
        }
        if ($candidates !== []) {
            return self::$cache[$slug] = basename(dirname($candidates[0]));
        }
        return self::$cache[$slug] = $current;
    }

    /** Absolute path to a project's directory, resolved to its real owner. */
    public static function dir(string $slug): string
    {
        return constant('BASE_PATH') . '/projects/' . self::ownerUsername($slug) . '/' . strtolower(trim($slug));
    }

    /** Forget cached lookups (e.g. after a project is created, moved or deleted). */
    public static function forget(?string $slug = null): void
    {
        if ($slug === null) {
            self::$cache = [];
        } else {
            unset(self::$cache[strtolower(trim($slug))]);
        }
    }

    private static function currentUsername(): string
    {
        $name = class_exists(Security::class)
            ? Security::CurrentUsername()
            : (string) ($_SESSION['user']['username'] ?? '');
        return $name !== '' ? $name : 'default';
    }
}
