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

use function Termwind\{ask, render};

require 'vendor/autoload.php';

render(<<<'HTML'
    <div>
        <div class="space-y-1">
            <div class="py-1 px-3 bg-amber-600 text-amber-50">
                <strong>Filament v5</strong> Upgrade
            </div>

            <div>
                Welcome to the <strong>Filament v5</strong> upgrade process!

                <br />

                This script will attempt to handle breaking changes for you, and will guide you through any manual steps that may be required.

                <br />

                If you have any questions, please reach out to us on <a href="https://filamentphp.com/discord" class="underline font-bold">Discord</a> or <a href="https://github.com/filamentphp/filament/discussions/new?category=q-a" class="underline font-bold">GitHub</a>.
            </div>

            <div>
                To begin, please ensure that you are using a version control system such as Git.

                <br />

                We will make changes directly to your files, and you will need to be able to revert them if something goes wrong.

                <br />

                <strong>Please commit any changes you have made to your project before continuing.</strong>
            </div>
        </div>

        <br />
    </div>
HTML);

require __DIR__ . '/../src/check-compatibility.php';

$directories = $argv[1] ?? ask(<<<HTML
    <div>
        Please provide a comma-separated list of directories containing Filament code to upgrade (e.g. <strong>app</strong>, <strong>app-modules</strong>, <strong>src</strong>).

        <br />

        <span class="bg-amber-600 text-amber-50 mr-1">
             You can skip this if you have a normal Laravel app structure:
        </span>
    </div>
    HTML) ?: 'app';

render(<<<HTML
    <p class="bg-green-600 text-green-50">
        Starting upgrade...
    </p>
HTML);

$rectorScriptPath = implode(DIRECTORY_SEPARATOR, ['vendor', 'bin', 'rector']);

foreach (explode(',', $directories) as $directory) {
    $directory = trim($directory);
    $directory = trim($directory, DIRECTORY_SEPARATOR);

    render(<<<HTML
        <p>
            Start processing <strong>/{$directory}</strong> to fix code affected by breaking changes.
        </p>
        HTML);

    exec("{$rectorScriptPath} process {$directory} --config vendor/filament/upgrade/src/rector.php --clear-cache");

    render(<<<HTML
        <p class="pt-2">
            Finished processing <strong>/{$directory}</strong>.
        </p>
        HTML);
}

$requireCommands = [];

foreach (json_decode(file_get_contents('composer.json'), true)['require'] as $package => $version) {
    if ($package === 'filament/upgrade') {
        continue;
    }

    if (! str_starts_with($package, 'filament/')) {
        continue;
    }

    if ($package === 'filament/spatie-laravel-translatable-plugin') {
        $requireCommands[] = "composer remove filament/spatie-laravel-translatable-plugin -W --no-update";
        $requireCommands[] = "composer require lara-zeus/spatie-translatable -W --no-update";

        continue;
    }

    $isWindows = str_starts_with(php_uname(), 'Windows');

    if ($package === 'filament/blueprint') {
        $requireCommands[] = $isWindows
            ? "composer require {$package}:\"~2.0\" -W --no-update"
            : "composer require {$package}:\"^2.0\" -W --no-update";

        continue;
    }

    if (! $isWindows) {
        $requireCommands[] = "composer require {$package}:\"^5.0\" -W --no-update";
    } else {
        $requireCommands[] = "composer require {$package}:\"~5.0\" -W --no-update";
    }
}

// If the project already depends on Livewire, suggest bumping it to v4 as part of the upgrade.
try {
    $composer = json_decode(file_get_contents('composer.json'), true) ?: [];
    $require = $composer['require'] ?? [];
    $requireDev = $composer['require-dev'] ?? [];

    $hasLivewire = array_key_exists('livewire/livewire', $require) || array_key_exists('livewire/livewire', $requireDev);
    if ($hasLivewire) {
        $isWindows = str_starts_with(php_uname(), 'Windows');
        $constraint = $isWindows ? '~4.0' : '^4.0';
        $requireCommands[] = "composer require livewire/livewire:\"{$constraint}\" -W --no-update";
    }
} catch (Throwable $exception) {
    // ignore detection errors
}

$requireCommandsHtml = implode("</strong><br /><strong>", $requireCommands);

// Suggest commands for third-party Filament plugins that have v5-compatible releases
$thirdPartyCommands = [];

try {
    // Prefer shared cache generated by check-compatibility.php
    $shared = $GLOBALS['FILAMENT_UPGRADE_PACKAGIST'] ?? null;

    // Fallback detection if cache is not available
    if ($shared === null || empty($shared['plugins'])) {
        $composer = json_decode(file_get_contents('composer.json'), true);
        $deps = $composer['require'] ?? [];
        $allPackages = array_keys($deps);

        $plugins = array_filter($allPackages, function ($plugin) {
            if (str_starts_with($plugin, 'filament/')) {
                return false;
            }

            try {
                $composerPath = "vendor/{$plugin}/composer.json";
                if (! file_exists($composerPath)) {
                    return false;
                }

                $composerContent = file_get_contents($composerPath);
                $composer = json_decode($composerContent, true);
                if (! $composer || ! is_array($composer)) {
                    return false;
                }

                $requires = $composer['require'] ?? [];
                foreach ($requires as $key => $value) {
                    if (str_starts_with($key, 'filament/')) {
                        return true;
                    }
                }
            } catch (Throwable $exception) {}

            return false;
        });

        // Create a minimal shared structure to continue without extra HTTP (none yet cached though)
        $shared = [
            'plugins' => array_values($plugins),
            'compatibility' => [],
            'versions' => [],
        ];
    }

    $plugins = $shared['plugins'] ?? [];
    $isWindows = str_starts_with(php_uname(), 'Windows');

    foreach ($plugins as $plugin) {
        // Try to reuse computed compatibility first
        $compatibility = $shared['compatibility'][$plugin] ?? null; // ['version' => string, 'isPrerelease' => bool]

        // If not computed, try to evaluate using cached versions arrays (no HTTP)
        if ($compatibility === null) {
            $stableVersions = $shared['versions'][$plugin]['stable'] ?? [];
            foreach ($stableVersions as $checkingVersion) {
                $requires = $checkingVersion['require'] ?? [];
                foreach ($requires as $dep => $constraint) {
                    if (!str_starts_with($dep, 'filament/')) {
                        continue;
                    }
                    if (preg_match("/\^\s*5(?:\.|$)|~\s*5(?:\.|$)|>=\s*5(?:\.|$)/", (string) $constraint)) {
                        $compatibility = [
                            'version' => (string) ($checkingVersion['version'] ?? ''),
                            'isPrerelease' => false,
                        ];
                        break;
                    }
                }
            }

            if ($compatibility === null) {
                $devVersions = $shared['versions'][$plugin]['dev'] ?? [];

                foreach ($devVersions as $checkingVersion) {
                    $requires = $checkingVersion['require'] ?? [];

                    foreach ($requires as $dep => $constraint) {
                        if (!str_starts_with($dep, 'filament/')) {
                            continue;
                        }

                        if (preg_match("/\^\s*5(?:\.|$)|~\s*5(?:\.|$)|>=\s*5(?:\.|$)/", (string) $constraint)) {
                            $compatibility = [
                                'version' => (string) ($checkingVersion['version'] ?? ''),
                                'isPrerelease' => true,
                            ];
                            break;
                        }
                    }
                }
            }
        }

        // As a last resort, if we still don't have compatibility and there are no cached versions, do HTTP queries
        if ($compatibility === null && empty($shared['versions'][$plugin])) {
            try {
                $url = "https://repo.packagist.org/p2/{$plugin}.json";
                $json = @file_get_contents($url);

                if ($json) {
                    $data = json_decode($json, true);
                    $versions = $data['packages'][$plugin] ?? [];
                    $shared['versions'][$plugin]['stable'] = $versions;

                    foreach ($versions as $checkingVersion) {
                        $requires = $checkingVersion['require'] ?? [];

                        foreach ($requires as $dep => $constraint) {
                            if (!str_starts_with($dep, 'filament/')) {
                                continue;
                            }

                            if (preg_match("/\^\s*5(?:\.|$)|~\s*5(?:\.|$)|>=\s*5(?:\.|$)/", (string) $constraint)) {
                                $compatibility = [
                                    'version' => (string) ($checkingVersion['version'] ?? ''),
                                    'isPrerelease' => false,
                                ];

                                break;
                            }
                        }
                    }
                }

                if ($compatibility === null) {
                    $devUrl = "https://repo.packagist.org/p2/{$plugin}~dev.json";
                    $devJson = @file_get_contents($devUrl);

                    if ($devJson) {
                        $devData = json_decode($devJson, true);
                        $devVersions = $devData['packages'][$plugin] ?? [];
                        $shared['versions'][$plugin]['dev'] = $devVersions;

                        foreach ($devVersions as $checkingVersion) {
                            $requires = $checkingVersion['require'] ?? [];

                            foreach ($requires as $dep => $constraint) {
                                if (!str_starts_with($dep, 'filament/')) {
                                    continue;
                                }

                                if (preg_match("/\^\s*5(?:\.|$)|~\s*5(?:\.|$)|>=\s*5(?:\.|$)/", (string) $constraint)) {
                                    $compatibility = [
                                        'version' => (string) ($checkingVersion['version'] ?? ''),
                                        'isPrerelease' => true,
                                    ];

                                    break;
                                }
                            }
                        }
                    }
                }
            } catch (Throwable $exception) {
                // ignore
            }
        }

        if ($compatibility !== null) {
            // Persist back for other consumers during this run
            $GLOBALS['FILAMENT_UPGRADE_PACKAGIST']['compatibility'][$plugin] = $compatibility;

            $version = ltrim($compatibility['version'], 'v');
            $isPrerelease = $compatibility['isPrerelease'];

            if ($version === 'unknown') {
                // Skip suggesting install command for plugins with unknown version (e.g., private/non-Packagist)
                continue;
            }

            $constraint = null;
            if (! $isPrerelease) {
                // Prefer using major.minor for pre-v1 packages to avoid ^0.0 or ~0.0
                if (preg_match('/^(\d+)\.(\d+)/', $version, $m)) {
                    $major = (int) $m[1];
                    $minor = (int) $m[2];

                    if ($major === 0) {
                        // For 0.x.y, constrain to the minor series, e.g. ^0.12 or ~0.12
                        $constraint = $isWindows ? "~0.{$minor}" : "^0.{$minor}";
                    } else {
                        // For 1.x and above, stick to major series, e.g. ^5.0 or ~5.0
                        $constraint = $isWindows ? "~{$major}.0" : "^{$major}.0";
                    }
                } elseif (preg_match('/^(\d+)/', $version, $m)) {
                    // Fallback: if only a major could be parsed
                    $major = (int) $m[1];
                    $constraint = $isWindows ? "~{$major}.0" : "^{$major}.0";
                }
            }

            if ($constraint === null) {
                $constraint = $version;
            }

            $thirdPartyCommands[] = "composer require {$plugin}:\"{$constraint}\" -W --no-update";
        }
    }
} catch (Throwable $exception) {
    // If anything goes wrong, we just won't print third-party suggestions
}

$thirdPartyCommandsHtml = $thirdPartyCommands ? '<br />' . implode("</strong><br /><strong>", $thirdPartyCommands) : '';

render(<<<HTML
    <div>
        <span class="bg-green-600 text-green-50">
            Now you're ready to update your Composer dependencies!
        </span>

        <br /> <br />

        First require new versions of Filament packages:

        <br />

        <strong>{$requireCommandsHtml}{$thirdPartyCommandsHtml}</strong>

        <br /> <br />

        And then run:

        <br />

        <strong>composer update</strong>

        <br /> <br />

        If you have any questions, please reach out to us on <a href="https://filamentphp.com/discord" class="underline font-bold">Discord</a> or <a href="https://github.com/filamentphp/filament/discussions/new?category=q-a" class="underline font-bold">GitHub</a>.
    </div>
HTML);
