<?php

declare(strict_types=1);

/*
|--------------------------------------------------------------------------
| RUTE28 WEB HARDCODE MIGRATOR
|--------------------------------------------------------------------------
| Upload ke public_html, gunakan sebentar, lalu hapus.
|--------------------------------------------------------------------------
*/

const MIGRATOR_ACCESS_KEY = 'PA4W68fRqJSJZmuFNhfSnyrG';
const MIGRATOR_SESSION_NAME = 'RUTE28MIGRATORSESSID';

error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
@set_time_limit(0);
@ini_set('memory_limit', '512M');

session_name(MIGRATOR_SESSION_NAME);
session_start();

header('Content-Type: text/html; charset=UTF-8');
header('X-Robots-Tag: noindex, nofollow, noarchive');
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Pragma: no-cache');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: same-origin');

function hmEscape($value): string
{
    return htmlspecialchars(
        (string) $value,
        ENT_QUOTES | ENT_SUBSTITUTE,
        'UTF-8'
    );
}

function hmNormalizePath(string $path): string
{
    $path = str_replace('\\', '/', trim($path));

    $real = realpath($path);

    if ($real !== false) {
        $path = str_replace('\\', '/', $real);
    }

    return rtrim($path, '/');
}

function hmStartsWithPath(string $path, string $root): bool
{
    $path = hmNormalizePath($path);
    $root = hmNormalizePath($root);

    return
        $path === $root ||
        str_starts_with(
            $path . '/',
            $root . '/'
        );
}

function hmCsrf(): string
{
    if (
        empty($_SESSION['hm_csrf']) ||
        !is_string($_SESSION['hm_csrf'])
    ) {
        $_SESSION['hm_csrf'] =
            bin2hex(random_bytes(32));
    }

    return $_SESSION['hm_csrf'];
}

function hmVerifyCsrf(): void
{
    $posted = (string) (
        $_POST['csrf_token'] ?? ''
    );

    $stored = (string) (
        $_SESSION['hm_csrf'] ?? ''
    );

    if (
        $posted === '' ||
        $stored === '' ||
        !hash_equals($stored, $posted)
    ) {
        throw new RuntimeException(
            'Token keamanan tidak valid. Muat ulang halaman.'
        );
    }
}

function hmFlash(
    string $type,
    string $message
): void {
    $_SESSION['hm_flash'] = [
        'type' => $type,
        'message' => $message,
    ];
}

function hmPullFlash(): ?array
{
    $flash = $_SESSION['hm_flash'] ?? null;
    unset($_SESSION['hm_flash']);

    return is_array($flash)
        ? $flash
        : null;
}

function hmRedirect(string $location = ''): never
{
    $target = basename(__FILE__);

    if ($location !== '') {
        $target .= '?' . $location;
    }

    header('Location: ' . $target, true, 303);
    exit;
}

function hmIsTextFile(string $path): bool
{
    $handle = @fopen($path, 'rb');

    if (!$handle) {
        return false;
    }

    $sample = fread($handle, 8192);
    fclose($handle);

    return
        is_string($sample) &&
        strpos($sample, "\0") === false;
}

function hmRelativePath(
    string $root,
    string $path
): string {
    $root = rtrim(
        hmNormalizePath($root),
        '/'
    ) . '/';

    $path = hmNormalizePath($path);

    return str_starts_with($path, $root)
        ? substr($path, strlen($root))
        : basename($path);
}

function hmShouldSkip(
    string $relative
): bool {
    $relative = '/' .
        trim(
            str_replace('\\', '/', $relative),
            '/'
        ) .
        '/';

    $skip = [
        '/.git/',
        '/.svn/',
        '/.idea/',
        '/.vscode/',
        '/vendor/',
        '/node_modules/',
        '/storage/logs/',
        '/logs/',
        '/cache/',
        '/tmp/',
        '/temp/',
        '/sessions/',
        '/uploads/',
        '/backup/',
        '/backups/',
        '/.migration-backup-',
    ];

    foreach ($skip as $needle) {
        if (str_contains($relative, $needle)) {
            return true;
        }
    }

    return false;
}

function hmBuildMap(array $input): array
{
    $oldHome = rtrim(
        str_replace(
            '\\',
            '/',
            trim(
                (string) $input['old_home']
            )
        ),
        '/'
    );

    $newHome = rtrim(
        str_replace(
            '\\',
            '/',
            trim(
                (string) $input['new_home']
            )
        ),
        '/'
    );

    $oldDomain = strtolower(
        trim(
            (string) $input['old_domain']
        )
    );

    $newDomain = strtolower(
        trim(
            (string) $input['new_domain']
        )
    );

    $oldBackoffice = strtolower(
        trim(
            (string) $input['old_backoffice']
        )
    );

    $newBackoffice = strtolower(
        trim(
            (string) $input['new_backoffice']
        )
    );

    $oldBackofficePath = trim(
        (string) (
            $input['old_backoffice_path'] ?? ''
        )
    );

    if ($oldBackofficePath === '') {
        $oldBackofficePath =
            $oldHome . '/' . $oldBackoffice;
    }

    $newBackofficePath = trim(
        (string) (
            $input['new_backoffice_path'] ?? ''
        )
    );

    if ($newBackofficePath === '') {
        $newBackofficePath =
            $newHome . '/' . $newBackoffice;
    }

    $map = [
        'https://' . $oldBackoffice =>
            'https://' . $newBackoffice,

        'http://' . $oldBackoffice =>
            'http://' . $newBackoffice,

        'https://www.' . $oldDomain =>
            'https://www.' . $newDomain,

        'http://www.' . $oldDomain =>
            'http://www.' . $newDomain,

        'https://' . $oldDomain =>
            'https://' . $newDomain,

        'http://' . $oldDomain =>
            'http://' . $newDomain,

        rtrim($oldBackofficePath, '/') =>
            rtrim($newBackofficePath, '/'),

        $oldHome . '/public_html' =>
            $newHome . '/public_html',

        $oldHome => $newHome,

        $oldBackoffice =>
            $newBackoffice,

        $oldDomain =>
            $newDomain,
    ];

    $map = array_filter(
        $map,
        static fn(
            string $to,
            string $from
        ): bool =>
            $from !== '' &&
            $to !== '' &&
            $from !== $to,
        ARRAY_FILTER_USE_BOTH
    );

    uksort(
        $map,
        static fn(
            string $first,
            string $second
        ): int =>
            strlen($second) <=>
            strlen($first)
    );

    return $map;
}

function hmScan(
    string $scanRoot,
    array $map
): array {
    $allowedExtensions = [
        'php',
        'env',
        'ini',
        'htaccess',
        'json',
        'js',
        'css',
        'txt',
        'xml',
        'yaml',
        'yml',
    ];

    $scriptPath =
        hmNormalizePath(__FILE__);

    $changes = [];
    $fileCount = 0;
    $matchedCount = 0;

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

    foreach ($iterator as $fileInfo) {
        if (
            !$fileInfo->isFile() ||
            $fileInfo->isLink()
        ) {
            continue;
        }

        $fileCount++;

        if ($fileCount > 30000) {
            throw new RuntimeException(
                'Scan dihentikan karena melebihi 30.000 file. Gunakan root scan yang lebih spesifik.'
            );
        }

        $path = hmNormalizePath(
            $fileInfo->getPathname()
        );

        if ($path === $scriptPath) {
            continue;
        }

        $relative =
            hmRelativePath(
                $scanRoot,
                $path
            );

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

        if ($fileInfo->getSize() > 3 * 1024 * 1024) {
            continue;
        }

        $extension = strtolower(
            pathinfo(
                $path,
                PATHINFO_EXTENSION
            )
        );

        $basename = strtolower(
            basename($path)
        );

        if (
            !in_array(
                $extension,
                $allowedExtensions,
                true
            ) &&
            !in_array(
                $basename,
                [
                    '.env',
                    '.htaccess',
                    'robots.txt',
                ],
                true
            )
        ) {
            continue;
        }

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

        $original =
            @file_get_contents($path);

        if (!is_string($original)) {
            continue;
        }

        $updated = $original;
        $matched = [];
        $occurrences = 0;

        foreach ($map as $from => $to) {
            if (
                $from === '' ||
                !str_contains(
                    $updated,
                    $from
                )
            ) {
                continue;
            }

            $count = substr_count(
                $updated,
                $from
            );

            $updated = str_replace(
                $from,
                $to,
                $updated
            );

            $matched[] = [
                'from' => $from,
                'to' => $to,
                'count' => $count,
            ];

            $occurrences += $count;
        }

        if ($updated === $original) {
            continue;
        }

        $matchedCount += $occurrences;

        $changes[] = [
            'path' => $path,
            'relative' => $relative,
            'original' => $original,
            'updated' => $updated,
            'matched' => $matched,
            'occurrences' => $occurrences,
            'permissions' =>
                fileperms($path) & 0777,
        ];
    }

    return [
        'changes' => $changes,
        'files_scanned' => $fileCount,
        'occurrences' => $matchedCount,
    ];
}

function hmPreviewHash(
    string $scanRoot,
    array $map,
    array $changes
): string {
    $parts = [
        hmNormalizePath($scanRoot),
        json_encode(
            $map,
            JSON_UNESCAPED_SLASHES
        ),
    ];

    foreach ($changes as $change) {
        $parts[] =
            $change['path'] .
            ':' .
            hash(
                'sha256',
                $change['original']
            ) .
            ':' .
            hash(
                'sha256',
                $change['updated']
            );
    }

    return hash(
        'sha256',
        implode("\n", $parts)
    );
}

function hmApply(
    string $scanRoot,
    array $map,
    array $scan,
    string $expectedHash
): array {
    $actualHash = hmPreviewHash(
        $scanRoot,
        $map,
        $scan['changes']
    );

    if (
        $expectedHash === '' ||
        !hash_equals(
            $expectedHash,
            $actualHash
        )
    ) {
        throw new RuntimeException(
            'Isi file berubah setelah preview. Jalankan Preview ulang sebelum Terapkan.'
        );
    }

    $backupRoot =
        $scanRoot .
        '/.migration-backup-' .
        date('Ymd-His');

    if (
        !mkdir(
            $backupRoot . '/files',
            0750,
            true
        )
    ) {
        throw new RuntimeException(
            'Folder backup tidak dapat dibuat: ' .
            $backupRoot
        );
    }

    $manifest = [];
    $updatedFiles = 0;

    foreach ($scan['changes'] as $change) {
        $backupFile =
            $backupRoot .
            '/files/' .
            $change['relative'];

        $backupDirectory =
            dirname($backupFile);

        if (!is_dir($backupDirectory)) {
            if (
                !mkdir(
                    $backupDirectory,
                    0750,
                    true
                ) &&
                !is_dir($backupDirectory)
            ) {
                throw new RuntimeException(
                    'Gagal membuat folder backup: ' .
                    $backupDirectory
                );
            }
        }

        if (
            !copy(
                $change['path'],
                $backupFile
            )
        ) {
            throw new RuntimeException(
                'Gagal backup file: ' .
                $change['path']
            );
        }

        $manifest[] = [
            'original' => $change['path'],
            'backup' => $backupFile,
            'relative' =>
                $change['relative'],
            'permissions' =>
                $change['permissions'],
        ];
    }

    file_put_contents(
        $backupRoot . '/manifest.json',
        json_encode(
            $manifest,
            JSON_PRETTY_PRINT |
            JSON_UNESCAPED_SLASHES
        ),
        LOCK_EX
    );

    file_put_contents(
        $backupRoot .
        '/replacement-map.json',
        json_encode(
            $map,
            JSON_PRETTY_PRINT |
            JSON_UNESCAPED_SLASHES
        ),
        LOCK_EX
    );

    foreach ($scan['changes'] as $change) {
        $temporary =
            $change['path'] .
            '.migration-tmp-' .
            bin2hex(random_bytes(4));

        if (
            file_put_contents(
                $temporary,
                $change['updated'],
                LOCK_EX
            ) === false
        ) {
            throw new RuntimeException(
                'Gagal menulis file temporary: ' .
                $change['path']
            );
        }

        @chmod(
            $temporary,
            $change['permissions']
        );

        if (
            !rename(
                $temporary,
                $change['path']
            )
        ) {
            @unlink($temporary);

            throw new RuntimeException(
                'Gagal mengganti file: ' .
                $change['path']
            );
        }

        $updatedFiles++;
    }

    file_put_contents(
        $backupRoot . '/report.json',
        json_encode(
            [
                'created_at' =>
                    date(DATE_ATOM),
                'scan_root' =>
                    $scanRoot,
                'updated_files' =>
                    $updatedFiles,
                'occurrences' =>
                    $scan['occurrences'],
            ],
            JSON_PRETTY_PRINT |
            JSON_UNESCAPED_SLASHES
        ),
        LOCK_EX
    );

    return [
        'backup_root' => $backupRoot,
        'updated_files' => $updatedFiles,
        'occurrences' =>
            $scan['occurrences'],
    ];
}

function hmFindBackups(
    string $scanRoot
): array {
    $directories = glob(
        rtrim($scanRoot, '/') .
        '/.migration-backup-*',
        GLOB_ONLYDIR
    );

    if (!is_array($directories)) {
        return [];
    }

    rsort($directories);

    return array_values(
        array_filter(
            $directories,
            static fn(string $directory): bool =>
                is_file(
                    $directory .
                    '/manifest.json'
                )
        )
    );
}

function hmRestore(
    string $scanRoot,
    string $backupRoot
): int {
    $scanRoot = hmNormalizePath(
        $scanRoot
    );

    $backupRoot = hmNormalizePath(
        $backupRoot
    );

    if (
        !hmStartsWithPath(
            $backupRoot,
            $scanRoot
        ) ||
        !str_contains(
            basename($backupRoot),
            '.migration-backup-'
        )
    ) {
        throw new RuntimeException(
            'Lokasi backup tidak valid.'
        );
    }

    $manifestPath =
        $backupRoot .
        '/manifest.json';

    if (!is_file($manifestPath)) {
        throw new RuntimeException(
            'Manifest backup tidak ditemukan.'
        );
    }

    $manifest = json_decode(
        (string) file_get_contents(
            $manifestPath
        ),
        true
    );

    if (!is_array($manifest)) {
        throw new RuntimeException(
            'Manifest backup tidak valid.'
        );
    }

    $restored = 0;

    foreach ($manifest as $row) {
        $original = hmNormalizePath(
            (string) (
                $row['original'] ?? ''
            )
        );

        $backup = hmNormalizePath(
            (string) (
                $row['backup'] ?? ''
            )
        );

        if (
            !hmStartsWithPath(
                $original,
                $scanRoot
            ) ||
            !hmStartsWithPath(
                $backup,
                $backupRoot
            ) ||
            !is_file($backup)
        ) {
            continue;
        }

        $directory = dirname($original);

        if (!is_dir($directory)) {
            mkdir(
                $directory,
                0755,
                true
            );
        }

        if (!copy($backup, $original)) {
            throw new RuntimeException(
                'Gagal restore file: ' .
                $original
            );
        }

        if (
            isset($row['permissions'])
        ) {
            @chmod(
                $original,
                (int) $row[
                    'permissions'
                ]
            );
        }

        $restored++;
    }

    return $restored;
}

/*
|--------------------------------------------------------------------------
| Login
|--------------------------------------------------------------------------
*/

$clientIp = (string) (
    $_SERVER['REMOTE_ADDR'] ?? ''
);

if (
    isset($_POST['action']) &&
    $_POST['action'] === 'login'
) {
    $key = trim(
        (string) (
            $_POST['access_key'] ?? ''
        )
    );

    if (
        !hash_equals(
            MIGRATOR_ACCESS_KEY,
            $key
        )
    ) {
        sleep(2);
        hmFlash(
            'error',
            'Access key salah.'
        );
        hmRedirect();
    }

    $_SESSION['hm_logged_in'] = true;
    $_SESSION['hm_ip'] = $clientIp;

    hmFlash(
        'success',
        'Login berhasil.'
    );

    hmRedirect();
}

$loggedIn =
    !empty(
        $_SESSION['hm_logged_in']
    ) &&
    isset($_SESSION['hm_ip']) &&
    hash_equals(
        (string) $_SESSION['hm_ip'],
        $clientIp
    );

if (
    isset($_GET['logout']) &&
    $_GET['logout'] === '1'
) {
    $_SESSION = [];
    session_destroy();
    hmRedirect();
}

$flash = hmPullFlash();

if (!$loggedIn) {
    ?>
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>RUTE28 Migrator Login</title>
<style>
*{box-sizing:border-box}
body{
    margin:0;
    min-height:100vh;
    display:grid;
    place-items:center;
    padding:18px;
    color:#fff;
    background:#07111f;
    font:14px Arial,sans-serif
}
.box{
    width:min(420px,100%);
    padding:22px;
    border:1px solid #29537d;
    border-radius:14px;
    background:#0c2038
}
h1{margin:0;color:#ffd24d}
p{color:#a9bdd3;line-height:1.6}
input,button{
    width:100%;
    min-height:43px;
    margin-top:10px;
    border-radius:9px;
    font:inherit
}
input{
    padding:0 12px;
    border:1px solid #35658f;
    color:#fff;
    background:#071525
}
button{
    border:1px solid #ffe382;
    color:#2b1600;
    background:linear-gradient(#ffe987,#f3ab16);
    font-weight:900;
    cursor:pointer
}
.alert{
    margin-top:12px;
    padding:10px;
    border-radius:8px;
    background:#5b1218;
    color:#ffd4d4
}
</style>
</head>
<body>
<form class="box" method="post">
<h1>RUTE28 Migrator</h1>
<p>
Masukkan access key dari README. Halaman terkunci
ke IP browser yang berhasil login.
</p>

<?php if (is_array($flash)): ?>
<div class="alert">
<?= hmEscape($flash['message'] ?? ''); ?>
</div>
<?php endif; ?>

<input
    type="password"
    name="access_key"
    placeholder="Access key"
    autocomplete="off"
    required
>
<input
    type="hidden"
    name="action"
    value="login"
>
<button type="submit">MASUK</button>
</form>
</body>
</html>
    <?php
    exit;
}

/*
|--------------------------------------------------------------------------
| Input default
|--------------------------------------------------------------------------
*/

$documentRoot = hmNormalizePath(
    (string) (
        $_SERVER['DOCUMENT_ROOT'] ??
        __DIR__
    )
);

$detectedHome = dirname(
    $documentRoot
);

if (
    basename($documentRoot) !==
    'public_html'
) {
    $detectedHome = dirname(__DIR__);
}

$defaults = [
    'scan_root' => $detectedHome,
    'old_home' => '/home/megan28',
    'new_home' => $detectedHome,
    'old_domain' => 'megan28.shop',
    'new_domain' => 'rute28.xyz',
    'old_backoffice' =>
        'backoffice.megan28.shop',
    'new_backoffice' =>
        'backoffice.rute28.xyz',
    'old_backoffice_path' =>
        '/home/megan28/backoffice.megan28.shop',
    'new_backoffice_path' =>
        $detectedHome .
        '/backoffice.rute28.xyz',
];

$form = $defaults;

foreach (
    array_keys($defaults)
    as $field
) {
    if (isset($_POST[$field])) {
        $form[$field] = trim(
            (string) $_POST[$field]
        );
    } elseif (
        isset($_SESSION['hm_form'][$field])
    ) {
        $form[$field] =
            (string) $_SESSION[
                'hm_form'
            ][$field];
    }
}

$preview = null;
$previewHash = '';
$pageError = '';

try {
    if (
        $_SERVER['REQUEST_METHOD'] ===
        'POST'
    ) {
        hmVerifyCsrf();

        $action = (string) (
            $_POST['action'] ?? ''
        );

        if (
            in_array(
                $action,
                ['preview', 'apply'],
                true
            )
        ) {
            $scanRoot = hmNormalizePath(
                $form['scan_root']
            );

            $newHome = hmNormalizePath(
                $form['new_home']
            );

            if (!is_dir($scanRoot)) {
                throw new RuntimeException(
                    'Root scan tidak ditemukan: ' .
                    $scanRoot
                );
            }

            if (
                !hmStartsWithPath(
                    $scanRoot,
                    $detectedHome
                ) &&
                !hmStartsWithPath(
                    $detectedHome,
                    $scanRoot
                )
            ) {
                throw new RuntimeException(
                    'Root scan harus berada di dalam akun hosting yang terdeteksi.'
                );
            }

            if (
                !hmStartsWithPath(
                    $newHome,
                    $detectedHome
                ) &&
                $newHome !==
                    $detectedHome
            ) {
                throw new RuntimeException(
                    'New home tidak cocok dengan home hosting yang terdeteksi.'
                );
            }

            $_SESSION['hm_form'] = $form;

            $map = hmBuildMap($form);

            if (empty($map)) {
                throw new RuntimeException(
                    'Tidak ada mapping yang dapat diterapkan.'
                );
            }

            $preview = hmScan(
                $scanRoot,
                $map
            );

            $previewHash = hmPreviewHash(
                $scanRoot,
                $map,
                $preview['changes']
            );

            if ($action === 'apply') {
                $confirm = strtoupper(
                    trim(
                        (string) (
                            $_POST['confirm_apply'] ??
                            ''
                        )
                    )
                );

                if ($confirm !== 'TERAPKAN') {
                    throw new RuntimeException(
                        'Ketik TERAPKAN pada kotak konfirmasi.'
                    );
                }

                $postedHash = (string) (
                    $_POST['preview_hash'] ??
                    ''
                );

                $result = hmApply(
                    $scanRoot,
                    $map,
                    $preview,
                    $postedHash
                );

                hmFlash(
                    'success',
                    'Berhasil mengubah ' .
                    $result['updated_files'] .
                    ' file dengan ' .
                    $result['occurrences'] .
                    ' replacement. Backup: ' .
                    $result['backup_root']
                );

                hmRedirect();
            }
        }

        if ($action === 'restore') {
            $scanRoot = hmNormalizePath(
                $form['scan_root']
            );

            $backupRoot = (string) (
                $_POST['backup_root'] ?? ''
            );

            $confirm = strtoupper(
                trim(
                    (string) (
                        $_POST['confirm_restore'] ??
                        ''
                    )
                )
            );

            if ($confirm !== 'RESTORE') {
                throw new RuntimeException(
                    'Ketik RESTORE untuk mengembalikan backup.'
                );
            }

            $restored = hmRestore(
                $scanRoot,
                $backupRoot
            );

            hmFlash(
                'success',
                'Restore selesai. ' .
                $restored .
                ' file dikembalikan.'
            );

            hmRedirect();
        }
    }
} catch (Throwable $exception) {
    $pageError =
        $exception->getMessage();
}

$backups = hmFindBackups(
    hmNormalizePath(
        $form['scan_root']
    )
);

?>
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>RUTE28 Web Hardcode Migrator</title>
<style>
*{box-sizing:border-box}
body{
    margin:0;
    color:#e8f1fb;
    background:#07111f;
    font:14px/1.55 Arial,sans-serif
}
.wrap{
    width:min(1180px,100%);
    margin:auto;
    padding:18px
}
h1{margin:0;color:#ffd24d}
h2{margin:0 0 12px}
.lead{color:#9fb4ca}
.panel{
    margin-top:14px;
    padding:15px;
    border:1px solid #28517b;
    border-radius:12px;
    background:#0c2038
}
.alert{
    margin-top:14px;
    padding:12px;
    border-radius:9px
}
.success{
    color:#a6f2ca;
    border:1px solid #1c7651;
    background:#073d2b
}
.error{
    color:#ffd0d0;
    border:1px solid #8f2c35;
    background:#531319
}
.grid{
    display:grid;
    grid-template-columns:
        repeat(2,minmax(0,1fr));
    gap:11px
}
label{
    display:block;
    color:#91d6ff;
    font-size:12px;
    font-weight:700
}
input{
    width:100%;
    min-height:41px;
    margin-top:5px;
    padding:0 11px;
    border:1px solid #35658f;
    border-radius:8px;
    color:#fff;
    background:#071525;
    font:inherit
}
.actions{
    display:flex;
    flex-wrap:wrap;
    gap:8px;
    margin-top:14px
}
button,.button{
    display:inline-flex;
    min-height:40px;
    align-items:center;
    justify-content:center;
    padding:0 14px;
    border:1px solid #ffe382;
    border-radius:8px;
    color:#2b1600;
    background:linear-gradient(#ffe987,#f3ab16);
    font:inherit;
    font-weight:900;
    text-decoration:none;
    cursor:pointer
}
.secondary{
    color:#e3effc;
    border-color:#35658f;
    background:#12385c
}
.danger{
    color:#fff;
    border-color:#e9656d;
    background:#8b1e27
}
.table-wrap{
    overflow:auto;
    border:1px solid #244b70;
    border-radius:9px
}
table{
    width:100%;
    min-width:820px;
    border-collapse:collapse
}
th,td{
    padding:9px;
    border-bottom:1px solid #24415f;
    text-align:left;
    vertical-align:top
}
th{
    color:#91d6ff;
    background:#082653
}
code{
    color:#ffd24d;
    font-family:Consolas,monospace;
    overflow-wrap:anywhere
}
small{color:#829ab3}
.badge{
    display:inline-flex;
    padding:3px 7px;
    border-radius:999px;
    color:#ffd97b;
    background:#5f4308;
    font-size:11px;
    font-weight:700
}
.top{
    display:flex;
    align-items:flex-start;
    justify-content:space-between;
    gap:10px
}
@media(max-width:760px){
    .grid{grid-template-columns:1fr}
    .wrap{padding:10px}
    .top{display:block}
}
</style>
</head>
<body>
<main class="wrap">
<div class="top">
<div>
<h1>RUTE28 Web Hardcode Migrator</h1>
<p class="lead">
Tidak memerlukan Terminal. Jalankan Preview dahulu,
periksa hasilnya, lalu Terapkan.
</p>
</div>
<a class="button secondary" href="?logout=1">
KELUAR
</a>
</div>

<?php if (is_array($flash)): ?>
<div class="alert <?= hmEscape(
    ($flash['type'] ?? '') ===
        'success'
        ? 'success'
        : 'error'
); ?>">
<?= hmEscape(
    $flash['message'] ?? ''
); ?>
</div>
<?php endif; ?>

<?php if ($pageError !== ''): ?>
<div class="alert error">
<?= hmEscape($pageError); ?>
</div>
<?php endif; ?>

<section class="panel">
<h2>Konfigurasi Migrasi</h2>

<form method="post">
<input
    type="hidden"
    name="csrf_token"
    value="<?= hmEscape(hmCsrf()); ?>"
>

<div class="grid">
<label>
Root yang dipindai
<input
    name="scan_root"
    value="<?= hmEscape(
        $form['scan_root']
    ); ?>"
    required
>
</label>

<label>
Home host baru
<input
    name="new_home"
    value="<?= hmEscape(
        $form['new_home']
    ); ?>"
    required
>
</label>

<label>
Home host lama
<input
    name="old_home"
    value="<?= hmEscape(
        $form['old_home']
    ); ?>"
    required
>
</label>

<label>
Domain lama
<input
    name="old_domain"
    value="<?= hmEscape(
        $form['old_domain']
    ); ?>"
    required
>
</label>

<label>
Domain baru
<input
    name="new_domain"
    value="<?= hmEscape(
        $form['new_domain']
    ); ?>"
    required
>
</label>

<label>
Subdomain backoffice lama
<input
    name="old_backoffice"
    value="<?= hmEscape(
        $form['old_backoffice']
    ); ?>"
    required
>
</label>

<label>
Subdomain backoffice baru
<input
    name="new_backoffice"
    value="<?= hmEscape(
        $form['new_backoffice']
    ); ?>"
    required
>
</label>

<label>
Path backoffice lama
<input
    name="old_backoffice_path"
    value="<?= hmEscape(
        $form[
            'old_backoffice_path'
        ]
    ); ?>"
>
</label>

<label>
Path backoffice baru
<input
    name="new_backoffice_path"
    value="<?= hmEscape(
        $form[
            'new_backoffice_path'
        ]
    ); ?>"
>
</label>
</div>

<div class="actions">
<button
    type="submit"
    name="action"
    value="preview"
>
PREVIEW PERUBAHAN
</button>
</div>
</form>
</section>

<?php if (is_array($preview)): ?>
<section class="panel">
<h2>Hasil Preview</h2>

<p>
<span class="badge">
<?= count(
    $preview['changes']
); ?> file
</span>

<span class="badge">
<?= (int) $preview[
    'occurrences'
]; ?> replacement
</span>

<span class="badge">
<?= (int) $preview[
    'files_scanned'
]; ?> file dipindai
</span>
</p>

<?php if (empty($preview['changes'])): ?>
<p>
Tidak ada hardcode lama yang ditemukan.
</p>
<?php else: ?>

<div class="table-wrap">
<table>
<thead>
<tr>
<th>File</th>
<th>Jumlah</th>
<th>Mapping</th>
</tr>
</thead>
<tbody>
<?php foreach (
    $preview['changes']
    as $change
): ?>
<tr>
<td>
<code>
<?= hmEscape(
    $change['relative']
); ?>
</code>
</td>
<td>
<?= (int) $change[
    'occurrences'
]; ?>
</td>
<td>
<?php foreach (
    $change['matched']
    as $matched
): ?>
<div>
<small>
<?= (int) $matched['count']; ?>×
</small>
<code>
<?= hmEscape(
    $matched['from']
); ?>
</code>
→
<code>
<?= hmEscape(
    $matched['to']
); ?>
</code>
</div>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>

<form method="post" style="margin-top:14px">
<input
    type="hidden"
    name="csrf_token"
    value="<?= hmEscape(hmCsrf()); ?>"
>
<?php foreach (
    $form as $field => $value
): ?>
<input
    type="hidden"
    name="<?= hmEscape($field); ?>"
    value="<?= hmEscape($value); ?>"
>
<?php endforeach; ?>

<input
    type="hidden"
    name="preview_hash"
    value="<?= hmEscape(
        $previewHash
    ); ?>"
>

<label>
Ketik <strong>TERAPKAN</strong>
<input
    name="confirm_apply"
    autocomplete="off"
    required
>
</label>

<div class="actions">
<button
    class="danger"
    type="submit"
    name="action"
    value="apply"
>
TERAPKAN DAN BUAT BACKUP
</button>
</div>
</form>
<?php endif; ?>
</section>
<?php endif; ?>

<section class="panel">
<h2>Backup dan Rollback</h2>

<?php if (empty($backups)): ?>
<p>
Belum ada backup migrasi.
</p>
<?php else: ?>

<?php foreach ($backups as $backup): ?>
<form method="post" style="
    margin-top:10px;
    padding:12px;
    border:1px solid #28517b;
    border-radius:9px;
">
<input
    type="hidden"
    name="csrf_token"
    value="<?= hmEscape(hmCsrf()); ?>"
>
<?php foreach (
    $form as $field => $value
): ?>
<input
    type="hidden"
    name="<?= hmEscape($field); ?>"
    value="<?= hmEscape($value); ?>"
>
<?php endforeach; ?>

<input
    type="hidden"
    name="backup_root"
    value="<?= hmEscape($backup); ?>"
>

<code><?= hmEscape($backup); ?></code>

<label style="margin-top:10px">
Ketik <strong>RESTORE</strong>
<input
    name="confirm_restore"
    autocomplete="off"
    required
>
</label>

<div class="actions">
<button
    class="secondary"
    type="submit"
    name="action"
    value="restore"
>
ROLLBACK BACKUP INI
</button>
</div>
</form>
<?php endforeach; ?>
<?php endif; ?>
</section>

<section class="panel">
<h2>Setelah Selesai</h2>
<p>
Periksa ulang konfigurasi database dan API karena password,
username database, serta token API tidak diubah oleh migrator.
</p>
<p>
Setelah website normal, hapus file ini dari server.
</p>
</section>
</main>
</body>
</html>
