<?php

declare(strict_types=1);

namespace PpPhp\Core\Deploy;

/**
 * Per-connection record of what has already been uploaded, so
 * "Upload changed files only" can skip untouched files.
 *
 * Stored as JSON at storage/deploy/{slug}/{connectionId}.json:
 *   { "path/rel.php": {"hash":"md5…","size":1234,"time":1720000000}, … }
 *
 * The manifest is keyed by connection id, so the same project can be
 * mirrored to several servers independently.
 */
class FileManifest
{
    private string $file;

    /** @var array<string,array{hash:string,size:int,time:int}> */
    private array $entries = [];

    public function __construct(string $slug, string $connectionId)
    {
        $dir = self::baseDir() . '/' . self::sanitise($slug);
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }
        $this->file = $dir . '/' . self::sanitise($connectionId ?: 'default') . '.json';

        if (is_file($this->file)) {
            $decoded = json_decode((string) file_get_contents($this->file), true);
            if (is_array($decoded)) {
                $this->entries = $decoded;
            }
        }
    }

    /** True when the file is new or its hash differs from the last upload. */
    public function hasChanged(string $relPath, string $hash): bool
    {
        return ($this->entries[$relPath]['hash'] ?? null) !== $hash;
    }

    /** Record a successful upload. */
    public function record(string $relPath, string $hash, int $size): void
    {
        $this->entries[$relPath] = ['hash' => $hash, 'size' => $size, 'time' => time()];
    }

    /** Forget a file (e.g. after a remote delete). */
    public function forget(string $relPath): void
    {
        unset($this->entries[$relPath]);
    }

    /** Relative paths known to the manifest. @return string[] */
    public function knownPaths(): array
    {
        return array_keys($this->entries);
    }

    public function isEmpty(): bool
    {
        return $this->entries === [];
    }

    /** Persist to disk. */
    public function save(): void
    {
        @file_put_contents(
            $this->file,
            json_encode($this->entries, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
        );
    }

    /** Wipe the manifest — forces a full re-upload next time. */
    public function reset(): void
    {
        $this->entries = [];
        if (is_file($this->file)) {
            @unlink($this->file);
        }
    }

    private static function baseDir(): string
    {
        $base = defined('BASE_PATH') ? constant('BASE_PATH') : dirname(__DIR__, 2);
        return $base . '/storage/deploy';
    }

    private static function sanitise(string $s): string
    {
        $s = preg_replace('/[^A-Za-z0-9_\-]/', '_', $s) ?? '';
        return $s !== '' ? $s : 'x';
    }
}
