<?php

declare(strict_types=1);

namespace PpPhp\Core\Projects;

use PpPhp\Core\DatabaseConnection;

/** Builds a new editable project directory from an existing project. */
final class ProjectCloneService
{
    public function clone(string $source, string $destination, array $sourceConnection, array $options = []): array
    {
        if (!is_dir($source)) throw new \RuntimeException('The source project directory was not found.');
        if (file_exists($destination)) throw new \RuntimeException('The destination project directory already exists.');
        $parent = dirname($destination);
        if (!is_dir($parent) && !mkdir($parent, 0755, true) && !is_dir($parent)) throw new \RuntimeException('Could not create the project owner directory.');
        $temporary = $parent . '/.duplicate-' . bin2hex(random_bytes(10));
        if (!mkdir($temporary, 0700, true)) throw new \RuntimeException('Could not prepare the duplicate project.');

        $includeCustom = $this->toBool($options['include_custom'] ?? true);
        $connectionMode = (string)($options['connection_mode'] ?? 'share');
        $connection = DatabaseConnection::normalize($sourceConnection);
        $warnings = [];
        try {
            $configTarget = $temporary . '/config';
            mkdir($configTarget, 0700, true);
            foreach (glob(rtrim($source, '/') . '/config/*.json') ?: [] as $file) {
                if (!copy($file, $configTarget . '/' . basename($file))) throw new \RuntimeException('Could not duplicate ' . basename($file) . '.');
            }
            if ($includeCustom && is_dir($source . '/custom')) $this->copyDirectory($source . '/custom', $temporary . '/custom');

            if ($connectionMode === 'sqlite') {
                $connection = DatabaseConnection::normalize(['type' => 'sqlite', 'path' => 'database/app.sqlite']);
            } elseif ($connection['type'] === 'sqlite') {
                $sourceSqlite = DatabaseConnection::resolveSqlitePath($connection['path'], $source);
                $connection['path'] = 'database/app.sqlite';
                $connection['dbname'] = 'database/app.sqlite';
                if ($connectionMode === 'sqlite_copy' && $sourceSqlite !== ':memory:' && is_file($sourceSqlite)) {
                    mkdir($temporary . '/database', 0700, true);
                    if (!copy($sourceSqlite, $temporary . '/database/app.sqlite')) throw new \RuntimeException('Could not copy the SQLite database file.');
                } else {
                    $warnings[] = 'A new empty SQLite database will be created for the duplicate.';
                }
            } elseif ($connectionMode !== 'share') {
                throw new \InvalidArgumentException('Choose either the shared source connection or a portable SQLite database.');
            }

            if (!rename($temporary, $destination)) throw new \RuntimeException('Could not publish the duplicate project atomically.');
            @chmod($destination, 0755);
            return ['connection' => $connection, 'warnings' => $warnings];
        } catch (\Throwable $e) {
            $this->removeDirectory($temporary);
            throw $e;
        }
    }

    public function rollback(string $destination): void { $this->removeDirectory($destination); }

    private function copyDirectory(string $source, string $destination): void
    {
        if (!is_dir($destination) && !mkdir($destination, 0700, true) && !is_dir($destination)) throw new \RuntimeException('Could not create a custom-files directory.');
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
        foreach ($iterator as $item) {
            if ($item->isLink()) continue;
            $relative = substr($item->getPathname(), strlen(rtrim($source, '/')) + 1);
            $target = $destination . '/' . $relative;
            if ($item->isDir()) {
                if (!is_dir($target) && !mkdir($target, 0700, true) && !is_dir($target)) throw new \RuntimeException('Could not duplicate a custom-files folder.');
            } elseif (!copy($item->getPathname(), $target)) {
                throw new \RuntimeException('Could not duplicate custom file ' . $relative . '.');
            }
        }
    }

    private function removeDirectory(string $dir): void
    {
        if (!is_dir($dir)) return;
        $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST);
        foreach ($iterator as $item) $item->isDir() ? @rmdir($item->getPathname()) : @unlink($item->getPathname());
        @rmdir($dir);
    }

    private function toBool(mixed $value): bool
    {
        return is_bool($value) ? $value : in_array(strtolower(trim((string)$value)), ['1', 'true', 'yes', 'on'], true);
    }
}
