#!/usr/bin/env php
<?php

declare(strict_types=1);

use InvoiceShelf\Modules\Bundle\BundleBuilder;
use InvoiceShelf\Modules\Bundle\ComposerProcess;
use InvoiceShelf\Modules\Manifest\ManifestValidator;
use InvoiceShelf\Modules\Manifest\SigningKeyPairGenerator;

$autoloadPath = $GLOBALS['_composer_autoload_path'] ?? dirname(__DIR__).'/vendor/autoload.php';
if (! is_string($autoloadPath) || ! is_file($autoloadPath)) {
    fwrite(STDERR, "Cannot locate Composer's autoloader.\n");
    exit(70);
}

require $autoloadPath;

$positional = [];
$options = [];
foreach (array_slice($argv, 1) as $argument) {
    if (preg_match('/^--([a-z-]+)(?:=(.*))?$/', $argument, $matches) === 1) {
        $options[$matches[1]] = $matches[2] ?? true;
    } else {
        $positional[] = $argument;
    }
}
[$command, $target] = array_pad($positional, 2, null);

if (! in_array($command, ['validate-module', 'validate-package', 'validate-release', 'canonicalize-release', 'generate-keypair', 'bundle'], true) || ! is_string($target)
    || ($command === 'bundle' && ! is_string($options['host-lock'] ?? null))) {
    fwrite(STDERR, "Usage: invoiceshelf-module <validate-module|validate-package|validate-release|canonicalize-release> <path>\n");
    fwrite(STDERR, "       invoiceshelf-module generate-keypair <key-id>\n");
    fwrite(STDERR, "       invoiceshelf-module bundle <module-dir> --host-lock=<host composer.lock> [--update] [--composer=<binary>]\n");
    exit(64);
}

if ($command === 'bundle') {
    try {
        $composer = is_string($options['composer'] ?? null) ? $options['composer'] : null;
        $report = (new BundleBuilder(ComposerProcess::locate($composer)))->build($target, $options['host-lock'], ($options['update'] ?? false) === true);
    } catch (Throwable $exception) {
        fwrite(STDERR, "Bundle failed: {$exception->getMessage()}\n");
        exit(1);
    }

    fwrite(STDOUT, ($report->lockWritten ? 'Resolved and wrote bundle.lock. ' : 'Installed from bundle.lock. ')
        .($report->manifest->packages === [] ? "bundle/ ships nothing: the host provides every requirement.\n" : "bundle/ ships:\n"));
    foreach ($report->manifest->packages as $name => $package) {
        fwrite(STDOUT, "  {$name} {$package['version']} (".implode(' or ', $package['license']).")\n");
    }
    if ($report->manifest->hostPackages !== []) {
        fwrite(STDOUT, 'Relies on the host for: '.implode(', ', array_keys($report->manifest->hostPackages))."\n");
    }
    foreach ($report->warnings as $warning) {
        fwrite(STDERR, "Warning: {$warning}\n");
    }
    exit(0);
}

try {
    if ($command === 'generate-keypair') {
        $keypair = SigningKeyPairGenerator::generate($target);
        fwrite(STDOUT, json_encode($keypair, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR)."\n");
        fwrite(STDERR, "Store secret_key_b64 only as MODULE_SIGNING_SECRET_KEY_B64 in a protected GitHub environment; add public_key_b64 under key_id to website and InvoiceShelf public-key configuration. Do not commit either output.\n");
        exit(0);
    }

    if ($command === 'validate-package') {
        ManifestValidator::package($target);
        fwrite(STDOUT, "Valid {$command}: {$target}\n");
        exit(0);
    }

    $contents = $target === '-' ? stream_get_contents(STDIN) : file_get_contents($target);
    if ($contents === false) {
        throw new RuntimeException("Cannot read {$target}.");
    }
    $manifest = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
    if (! is_array($manifest) || array_is_list($manifest)) {
        throw new RuntimeException('Manifest root must be a JSON object.');
    }

    if ($command === 'validate-module') {
        ManifestValidator::module($manifest);
    } elseif ($command === 'validate-release') {
        ManifestValidator::release($manifest);
    } else {
        fwrite(STDOUT, ManifestValidator::canonicalRelease($manifest));
        exit(0);
    }

    fwrite(STDOUT, "Valid {$command} manifest: {$target}\n");
} catch (Throwable $exception) {
    fwrite(STDERR, "Invalid manifest: {$exception->getMessage()}\n");
    exit(1);
}
