<?php

/**
 * Builds the ZIP that gets uploaded to cPanel, for a first install or an update.
 *
 *   php install/empaquetar.php [version]
 *
 * The package carries the compiled front end and the PHP dependencies, because
 * shared hosting rarely offers Node or Composer. It never carries the site's own
 * state: no .env, no database file, no uploaded plans.
 */
$root = dirname(__DIR__);
$version = $argv[1] ?? null;

$manifestPath = $root.'/install/release.json';
$manifest = json_decode((string) file_get_contents($manifestPath), true);

if ($version !== null) {
    $manifest['version'] = $version;
    file_put_contents($manifestPath, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
}

$version = $manifest['version'];
$output = $root.'/install/lotea-'.$version.'.zip';

/** Directories and files that make up a release. */
$include = [
    'app', 'bootstrap', 'config', 'database', 'public', 'resources', 'routes',
    'storage', 'vendor', 'install', 'artisan', 'composer.json', 'composer.lock',
];

/** Never shipped: the host's own state, build tooling and version control. */
$excluded = static function (string $relative): bool {
    $rules = [
        '.git', '.github', 'node_modules', 'tests', '.env',
        'database/database.sqlite',
        'bootstrap/cache/',
        'storage/app/public/', 'storage/app/private/', 'storage/app/backups/',
        'storage/app/updates/', 'storage/app/installed.json',
        'storage/framework/cache/', 'storage/framework/sessions/',
        'storage/framework/views/', 'storage/framework/testing/',
        'storage/logs/',
    ];

    foreach ($rules as $rule) {
        if ($relative === rtrim($rule, '/') || str_starts_with($relative, $rule)) {
            return true;
        }
    }

    // Previously built packages must not travel inside the new one.
    if (str_starts_with($relative, 'install/') && str_ends_with($relative, '.zip')) {
        return true;
    }

    return str_ends_with($relative, '.DS_Store');
};

@unlink($output);
$zip = new ZipArchive;

if ($zip->open($output, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
    fwrite(STDERR, "No se pudo crear {$output}\n");
    exit(1);
}

$files = 0;

foreach ($include as $item) {
    $path = $root.'/'.$item;

    if (is_file($path)) {
        $zip->addFile($path, $item);
        $files++;

        continue;
    }

    if (! is_dir($path)) {
        continue;
    }

    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST,
    );

    foreach ($iterator as $file) {
        $relative = substr($file->getPathname(), strlen($root) + 1);

        if ($excluded($relative)) {
            continue;
        }

        if ($file->isDir()) {
            $zip->addEmptyDir($relative);

            continue;
        }

        $zip->addFile($file->getPathname(), $relative);
        $files++;
    }
}

// A fresh .env the installer completes. The key is random per package and the
// installer replaces it again on install, so no two sites share one.
$env = str_replace(
    'APP_KEY=',
    'APP_KEY=base64:'.base64_encode(random_bytes(32)),
    (string) file_get_contents($root.'/install/env.production'),
);
$zip->addFromString('.env', $env);
$zip->addFromString('install/release.json', (string) json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

foreach ([
    'bootstrap/cache', 'storage/app/public', 'storage/app/private',
    'storage/framework/cache/data', 'storage/framework/sessions',
    'storage/framework/views', 'storage/logs',
] as $directory) {
    $zip->addEmptyDir($directory);
    $zip->addFromString($directory.'/.gitignore', "*\n!.gitignore\n");
}

$zip->close();

printf(
    "Paquete listo: %s\n%d archivos · %.1f MB\n",
    $output,
    $files,
    filesize($output) / 1048576,
);
