<?php

declare(strict_types=1);

namespace PpPhp\Core\Migrate;

/**
 * An immutable description of ONE migration: what to move and where to.
 *
 * The plan is pure data — it performs no I/O and holds no live connection. It is
 * created from the console form, persisted inside the job folder, and read back by
 * every phase so the whole pipeline agrees on the same scope and destination even
 * across separate requests. The destination password is kept out of the plan on
 * disk (it lives, encrypted, in PlatformSettings) so a job folder is never a
 * secret leak.
 */
final class MigrationPlan
{
    /**
     * @param array{protocol:string,host:string,port:int,user:string,basePath:string,panel:string,newBaseUrl:string} $destination
     * @param bool          $platformDb    move the central platform database
     * @param list<string>  $projectIds    "owner/slug" ids to move
     * @param list<int>     $managedDbIds  managed-database ids to move
     * @param list<string>  $fileSetKeys   non-project file sets (uploads/storage/output/customfiles)
     */
    public function __construct(
        public readonly array $destination,
        public readonly bool $platformDb,
        public readonly array $projectIds,
        public readonly array $managedDbIds,
        public readonly array $fileSetKeys,
    ) {}

    /**
     * Build a plan from the posted console form, filtered against the live
     * inventory so a tampered request can never name a project or database that
     * does not exist on this install.
     *
     * @param array<string,mixed> $in    $_POST
     * @param array<string,mixed> $inv   MigrationInventory::summary()
     */
    public static function fromInput(array $in, array $inv): self
    {
        // Scope — every id is intersected with what actually exists.
        $validProjects = array_map(static fn ($p) => (string) $p['id'], $inv['projects'] ?? []);
        $validDbs      = array_map(static fn ($d) => (int) $d['id'], $inv['managed'] ?? []);
        $validSets     = array_map(static fn ($f) => (string) $f['key'], $inv['fileSets'] ?? []);

        $projectIds = array_values(array_intersect(
            array_map('strval', (array) ($in['projects'] ?? [])),
            $validProjects
        ));
        $managedDbIds = array_values(array_intersect(
            array_map('intval', (array) ($in['db'] ?? [])),
            $validDbs
        ));
        $fileSetKeys = array_values(array_intersect(
            array_map('strval', (array) ($in['files'] ?? [])),
            $validSets
        ));

        $protocol = in_array(strtolower((string) ($in['protocol'] ?? 'sftp')), ['sftp', 'ftp', 'ftps'], true)
            ? strtolower((string) $in['protocol']) : 'sftp';

        $destination = [
            'protocol'   => $protocol,
            'host'       => trim((string) ($in['host'] ?? '')),
            'port'       => (int) ($in['port'] ?? 0),
            'user'       => trim((string) ($in['user'] ?? '')),
            'basePath'   => trim((string) ($in['basePath'] ?? '')),
            'panel'      => trim((string) ($in['panel'] ?? '')),
            'newBaseUrl' => rtrim(trim((string) ($in['newBaseUrl'] ?? '')), '/'),
        ];

        return new self(
            $destination,
            // The platform database is selected by default (it is the spine of the
            // install) but the operator can untick it for a files-only snapshot.
            !empty($in['platformdb']),
            $projectIds,
            $managedDbIds,
            $fileSetKeys,
        );
    }

    /** @return array<string,mixed> for persistence in the job folder */
    public function toArray(): array
    {
        return [
            'destination'  => $this->destination,
            'platformDb'   => $this->platformDb,
            'projectIds'   => $this->projectIds,
            'managedDbIds' => $this->managedDbIds,
            'fileSetKeys'  => $this->fileSetKeys,
        ];
    }

    /** @param array<string,mixed> $a */
    public static function fromArray(array $a): self
    {
        return new self(
            (array) ($a['destination'] ?? []),
            (bool) ($a['platformDb'] ?? true),
            array_map('strval', (array) ($a['projectIds'] ?? [])),
            array_map('intval', (array) ($a['managedDbIds'] ?? [])),
            array_map('strval', (array) ($a['fileSetKeys'] ?? [])),
        );
    }

    /** A short human sentence describing the scope, for the job header. */
    public function scopeSummary(): string
    {
        $parts = [];
        if ($this->platformDb)       { $parts[] = 'platform database'; }
        if ($this->projectIds)       { $parts[] = count($this->projectIds) . ' project(s)'; }
        if ($this->managedDbIds)     { $parts[] = count($this->managedDbIds) . ' user database(s)'; }
        if ($this->fileSetKeys)      { $parts[] = count($this->fileSetKeys) . ' file set(s)'; }
        return $parts ? implode(', ', $parts) : 'nothing selected';
    }
}
