<?php

namespace App\Http\Controllers\NcdAnalysis\Concerns;

use App\Models\Patients;
use App\Models\PtConfig;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use Symfony\Component\Process\Process;

trait HandlesNcdAnalysisFlow
{
    public function runNcdAnalysis(Request $request)
    {
        $phpBinary = $this->resolvePhpCliBinary();
        $existingStatus = $this->refreshNcdAnalysisStatus();
        if (($existingStatus['state'] ?? null) === 'running') {
            $startedAt = (string) ($existingStatus['started_at'] ?? '');
            $scope = (string) ($existingStatus['scope'] ?? 'selected scope');
            return redirect()
                ->back()
                ->with('message', 'NCD analysis is already running in background for ' . $scope . ($startedAt !== '' ? ' (started ' . $startedAt . ').' : '.'));
        }

        $endDateInput = trim((string) $request->input('end_date', $request->query('end_date', '')));
        $endDateTs = $this->parseDateValue($endDateInput);
        if ($endDateTs === null) {
            return redirect()
                ->back()
                ->withInput()
                ->with('message', 'Please select LTFU cut date before running NCD analysis.');
        }
        $observeDate = date('Y-m-d', $endDateTs);
        $runtimeConfigPath = $this->createNcdRuntimeConfig($observeDate);
        if ($runtimeConfigPath === null) {
            return redirect()
                ->back()
                ->withInput()
                ->with('message', 'Unable to prepare runtime NCD config for the selected LTFU cut date. Please check storage permissions.');
        }

        $clinicOptions = $this->buildNcdClinicOptions();
        $defaultClinic = $this->defaultNcdDatabaseName();
        $requestedClinic = (string) $request->input('clinic', $request->query('clinic', $defaultClinic));
        if (strtoupper($requestedClinic) === 'ALL') {
            $requestedClinic = 'overall';
        }
        $clinicConnection = array_key_exists($requestedClinic, $clinicOptions) ? $requestedClinic : $defaultClinic;

        $runDatabases = [];
        $scopeLabel = '';
        if ($clinicConnection === 'overall') {
            $ncdConfig = $this->loadNcdConfig();
            $runDatabases = array_values(array_filter(array_map(static function ($db) {
                return trim((string) $db);
            }, (array) ($ncdConfig['databases'] ?? []))));
            if (empty($runDatabases)) {
                $runDatabases = array_values(array_filter($this->allowedNcdDatabases(), static function ($db) {
                    $label = strtoupper(trim((string) $db));
                    return $label !== '' && $label !== 'ALL' && $label !== 'OVERALL';
                }));
            }
            $scopeLabel = 'ALL clinics';
        } else {
            $runDatabases = [$clinicConnection];
            $scopeLabel = 'clinic ' . $clinicConnection;
        }
        $runDatabases = array_values(array_unique($runDatabases));

        $commandParts = [$phpBinary, 'artisan', 'ncd:analyze', '--config=' . $runtimeConfigPath, '--timeout=0'];
        if (!empty($runDatabases)) {
            $commandParts[] = '--databases=' . implode(',', $runDatabases);
        }
        $analysisCommand = implode(' ', array_map('escapeshellarg', $commandParts));

        $logPath = $this->ncdAnalysisLogPath();
        $logDir = dirname($logPath);
        if (!is_dir($logDir)) {
            @mkdir($logDir, 0775, true);
        }

        try {
            $runSuffix = bin2hex(random_bytes(4));
        } catch (\Throwable $exception) {
            $runSuffix = substr(md5((string) microtime(true)), 0, 8);
        }
        $runId = date('Ymd_His') . '_' . $runSuffix;
        $inner = 'echo "[NCD_RUN:' . $runId . '] START" >> ' . escapeshellarg($logPath)
            . '; ' . $analysisCommand . ' >> ' . escapeshellarg($logPath) . ' 2>&1'
            . '; rc=$?; echo "[NCD_RUN:' . $runId . '] EXIT:$rc" >> ' . escapeshellarg($logPath);
        $shellCommand = 'cd ' . escapeshellarg(base_path())
            . ' && nohup bash -lc ' . escapeshellarg($inner)
            . ' >/dev/null 2>&1 & echo $!';

        $launch = new Process(['bash', '-lc', $shellCommand], base_path());
        $launch->setTimeout(15);
        $launch->setIdleTimeout(15);
        $launch->run();

        if (!$launch->isSuccessful()) {
            Log::error('Failed to start background NCD analysis from dashboard', [
                'user_id' => Auth::id(),
                'scope' => $scopeLabel,
                'command' => $analysisCommand,
                'stderr' => trim($launch->getErrorOutput()),
                'stdout' => trim($launch->getOutput()),
            ]);
            return redirect()
                ->back()
                ->with('message', 'Failed to start background NCD analysis. Please check server logs.');
        }

        $pidText = trim($launch->getOutput());
        $pid = ctype_digit($pidText) ? (int) $pidText : 0;
        if ($pid <= 0) {
            Log::error('Background NCD analysis started without valid PID', [
                'user_id' => Auth::id(),
                'scope' => $scopeLabel,
                'stdout' => $pidText,
            ]);
            return redirect()
                ->back()
                ->with('message', 'Background NCD analysis started but PID could not be verified.');
        }

        $status = [
            'state' => 'running',
            'running' => true,
            'pid' => $pid,
            'run_id' => $runId,
            'scope' => $scopeLabel,
            'requested_clinic' => $clinicConnection,
            'databases' => $runDatabases,
            'started_at' => now()->toDateTimeString(),
            'finished_at' => null,
            'exit_code' => null,
            'log_file' => $logPath,
            'command' => $analysisCommand,
            'requested_by' => Auth::id(),
        ];
        $this->writeNcdAnalysisStatus($status);

        Log::info('Background NCD analysis started from dashboard', [
            'user_id' => Auth::id(),
            'scope' => $scopeLabel,
            'pid' => $pid,
            'run_id' => $runId,
            'databases' => $runDatabases,
        ]);

        $redirectQuery = array_filter(array_merge($request->query(), [
            'clinic' => $clinicConnection,
            'end_date' => $observeDate,
        ]), static function ($value) {
            return $value !== null && $value !== '';
        });

        return redirect()
            ->route('ncd_analysis.dashboard', $redirectQuery)
            ->with('message', 'NCD analysis started in background for ' . $scopeLabel . '. You can continue using the page; refresh in a few minutes to see updated data.');
    }

    private function resolvePhpCliBinary(): string
    {
        $candidates = [
            '/usr/bin/php',
            '/usr/local/bin/php',
            PHP_BINARY,
            'php',
        ];
        foreach ($candidates as $candidate) {
            if ($candidate === null || $candidate === '') {
                continue;
            }
            if ($candidate === 'php') {
                return $candidate;
            }
            if (is_executable($candidate)) {
                return $candidate;
            }
        }
        return 'php';
    }

    public function ncdDashboard(Request $request)
    {
        $clinicLeader = Auth::user()->name ?? 'Clinic Leader Dr.';
        $clinicOptions = $this->buildNcdClinicOptions();
        $defaultClinic = $this->defaultNcdDatabaseName();
        $requestedClinic = (string) $request->input('clinic', $defaultClinic);
        if (strtoupper($requestedClinic) === 'ALL') {
            $requestedClinic = 'overall';
        }
        $clinicConnection = array_key_exists($requestedClinic, $clinicOptions) ? $requestedClinic : $defaultClinic;
        $clinicWarning = null;

        $ncdConfig = $this->loadNcdConfig();
        $filters = [
            'start_date' => $request->input('start_date'),
            'end_date' => $request->input('end_date'),
            'gender' => $request->input('gender'),
            'age_band' => $request->input('age_band'),
            'timeframe' => $request->input('timeframe'),
            'trend_granularity' => $request->input('trend_granularity'),
            'trend_year' => $request->input('trend_year'),
        ];
        $ncdAnalytics = $this->loadNcdAnalytics($clinicConnection, $filters, $ncdConfig);
        $ncdAnalysisStatus = $this->refreshNcdAnalysisStatus();

        return view('ncd_dashboard', compact('clinicLeader', 'clinicOptions', 'clinicConnection', 'clinicWarning', 'ncdAnalytics', 'ncdConfig', 'ncdAnalysisStatus'));
    }

    private function ncdAnalysisStatusPath(): string
    {
        return storage_path('app/ncd_analysis_status.json');
    }

    private function ncdAnalysisLogPath(): string
    {
        return storage_path('logs/ncd_analysis_background.log');
    }

    private function readNcdAnalysisStatus(): array
    {
        $path = $this->ncdAnalysisStatusPath();
        if (!file_exists($path)) {
            return [];
        }
        $raw = @file_get_contents($path);
        if ($raw === false || trim($raw) === '') {
            return [];
        }
        $decoded = json_decode($raw, true);
        return is_array($decoded) ? $decoded : [];
    }

    private function writeNcdAnalysisStatus(array $status): void
    {
        $path = $this->ncdAnalysisStatusPath();
        $dir = dirname($path);
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }
        @file_put_contents($path, json_encode($status, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
    }

    private function resolveNcdRunExitCode(string $runId, string $logPath): ?int
    {
        if ($runId === '' || !file_exists($logPath)) {
            return null;
        }
        $content = @file_get_contents($logPath);
        if ($content === false || $content === '') {
            return null;
        }
        $pattern = '/\\[NCD_RUN:' . preg_quote($runId, '/') . '\\] EXIT:(\\d+)/';
        if (!preg_match_all($pattern, $content, $matches) || empty($matches[1])) {
            return null;
        }
        $last = end($matches[1]);
        return is_string($last) && ctype_digit($last) ? (int) $last : null;
    }

    private function isProcessRunning(?int $pid): bool
    {
        if ($pid === null || $pid <= 0) {
            return false;
        }
        $check = new Process(['bash', '-lc', 'ps -p ' . (int) $pid . ' -o pid=']);
        $check->setTimeout(5);
        $check->run();
        return trim($check->getOutput()) !== '';
    }

    private function refreshNcdAnalysisStatus(): array
    {
        $status = $this->readNcdAnalysisStatus();
        if (empty($status)) {
            return ['state' => 'idle'];
        }

        $state = (string) ($status['state'] ?? '');
        if ($state !== 'running') {
            return $status;
        }

        $pid = isset($status['pid']) ? (int) $status['pid'] : 0;
        if ($this->isProcessRunning($pid)) {
            return $status;
        }

        $runId = (string) ($status['run_id'] ?? '');
        $logFile = (string) ($status['log_file'] ?? $this->ncdAnalysisLogPath());
        $exitCode = $this->resolveNcdRunExitCode($runId, $logFile);

        $status['running'] = false;
        $status['exit_code'] = $exitCode;
        $status['finished_at'] = now()->toDateTimeString();
        $status['state'] = $exitCode === 0 ? 'completed' : 'failed';
        $this->writeNcdAnalysisStatus($status);

        return $status;
    }

    public function updateNcdConfig(Request $request)
    {
        $configPath = base_path('config.yaml');
        if (!file_exists($configPath)) {
            return redirect()
                ->back()
                ->with('message', 'Unable to update NCD settings because config.yaml was not found.');
        }

        $config = $this->loadNcdConfig();
        $config['date_range'] = $config['date_range'] ?? [];
        $config['thresholds'] = $config['thresholds'] ?? [];
        $config['privacy'] = $config['privacy'] ?? [];

        $allowedDbs = $this->allowedNcdDatabases();
        $rawDbs = $request->input('databases', []);
        $rawDbs = is_array($rawDbs) ? $rawDbs : [$rawDbs];
        $selectedDbs = array_values(array_intersect($rawDbs, $allowedDbs));
        if (!empty($selectedDbs)) {
            $config['databases'] = $selectedDbs;
        }

        $startDate = $request->input('start_date');
        if (!empty($startDate)) {
            $config['date_range']['start_date'] = $startDate;
        }
        $endDate = $request->input('end_date');
        if (!empty($endDate)) {
            $config['date_range']['end_date'] = $endDate;
        }

        $activeDays = filter_var($request->input('active_days'), FILTER_VALIDATE_INT);
        if ($activeDays !== false && $activeDays > 0) {
            $config['thresholds']['active_days'] = $activeDays;
        }
        $ltfuDays = filter_var($request->input('ltfu_days'), FILTER_VALIDATE_INT);
        if ($ltfuDays !== false && $ltfuDays > 0) {
            $config['thresholds']['ltfu_days'] = $ltfuDays;
        }
        $hba1cLookback = filter_var($request->input('hba1c_lookback_months'), FILTER_VALIDATE_INT);
        if ($hba1cLookback !== false && $hba1cLookback > 0) {
            $config['thresholds']['hba1c_lookback_months'] = $hba1cLookback;
        }
        $kidneyLookback = filter_var($request->input('kidney_lookback_months'), FILTER_VALIDATE_INT);
        if ($kidneyLookback !== false && $kidneyLookback > 0) {
            $config['thresholds']['kidney_lookback_months'] = $kidneyLookback;
        }
        $cvdLookback = filter_var($request->input('cvd_risk_lookback_months'), FILTER_VALIDATE_INT);
        if ($cvdLookback !== false && $cvdLookback > 0) {
            $config['thresholds']['cvd_risk_lookback_months'] = $cvdLookback;
        }

        $config['privacy']['mask_ids'] = $request->boolean('mask_ids');

        $yamlAvailable = class_exists('\\Symfony\\Component\\Yaml\\Yaml');
        $yamlExtAvailable = function_exists('yaml_parse_file') && function_exists('yaml_emit');

        if ($yamlAvailable) {
            $config = \Symfony\Component\Yaml\Yaml::parseFile($configPath);
            if (!is_array($config)) {
                $config = [];
            }
            $config['date_range'] = $config['date_range'] ?? [];
            $config['thresholds'] = $config['thresholds'] ?? [];
            $config['privacy'] = $config['privacy'] ?? [];
            if (!empty($selectedDbs)) {
                $config['databases'] = $selectedDbs;
            }
            if (!empty($startDate)) {
                $config['date_range']['start_date'] = $startDate;
            }
            if (!empty($endDate)) {
                $config['date_range']['end_date'] = $endDate;
            }
            if ($activeDays !== false && $activeDays > 0) {
                $config['thresholds']['active_days'] = $activeDays;
            }
            if ($ltfuDays !== false && $ltfuDays > 0) {
                $config['thresholds']['ltfu_days'] = $ltfuDays;
            }
            if ($hba1cLookback !== false && $hba1cLookback > 0) {
                $config['thresholds']['hba1c_lookback_months'] = $hba1cLookback;
            }
            if ($kidneyLookback !== false && $kidneyLookback > 0) {
                $config['thresholds']['kidney_lookback_months'] = $kidneyLookback;
            }
            if ($cvdLookback !== false && $cvdLookback > 0) {
                $config['thresholds']['cvd_risk_lookback_months'] = $cvdLookback;
            }
            $config['privacy']['mask_ids'] = $request->boolean('mask_ids');
            file_put_contents($configPath, \Symfony\Component\Yaml\Yaml::dump($config, 4, 2));
        } elseif ($yamlExtAvailable) {
            $config = yaml_parse_file($configPath);
            if (!is_array($config)) {
                $config = [];
            }
            $config['date_range'] = $config['date_range'] ?? [];
            $config['thresholds'] = $config['thresholds'] ?? [];
            $config['privacy'] = $config['privacy'] ?? [];
            if (!empty($selectedDbs)) {
                $config['databases'] = $selectedDbs;
            }
            if (!empty($startDate)) {
                $config['date_range']['start_date'] = $startDate;
            }
            if (!empty($endDate)) {
                $config['date_range']['end_date'] = $endDate;
            }
            if ($activeDays !== false && $activeDays > 0) {
                $config['thresholds']['active_days'] = $activeDays;
            }
            if ($ltfuDays !== false && $ltfuDays > 0) {
                $config['thresholds']['ltfu_days'] = $ltfuDays;
            }
            if ($hba1cLookback !== false && $hba1cLookback > 0) {
                $config['thresholds']['hba1c_lookback_months'] = $hba1cLookback;
            }
            if ($kidneyLookback !== false && $kidneyLookback > 0) {
                $config['thresholds']['kidney_lookback_months'] = $kidneyLookback;
            }
            if ($cvdLookback !== false && $cvdLookback > 0) {
                $config['thresholds']['cvd_risk_lookback_months'] = $cvdLookback;
            }
            $config['privacy']['mask_ids'] = $request->boolean('mask_ids');
            file_put_contents($configPath, yaml_emit($config));
        } else {
            $updated = $this->updateNcdConfigFile($configPath, [
                'databases' => $selectedDbs,
                'date_range' => [
                    'start_date' => $startDate,
                    'end_date' => $endDate,
                ],
                'thresholds' => [
                    'active_days' => $activeDays,
                    'ltfu_days' => $ltfuDays,
                    'hba1c_lookback_months' => $hba1cLookback,
                    'kidney_lookback_months' => $kidneyLookback,
                    'cvd_risk_lookback_months' => $cvdLookback,
                ],
                'privacy' => [
                    'mask_ids' => $request->boolean('mask_ids'),
                ],
            ]);

            if (!$updated) {
                return redirect()
                    ->back()
                    ->with('message', 'Unable to update NCD settings. Please check file permissions.');
            }
        }

        $clinic = $request->input('clinic', 'ALL');
        return redirect()
            ->route('ncd_analysis.dashboard', ['clinic' => $clinic])
            ->with('message', 'NCD settings saved. Run analysis to refresh outputs.');
    }

    private function createNcdRuntimeConfig(string $endDate): ?string
    {
        $sourcePath = base_path('config.yaml');
        if (!file_exists($sourcePath)) {
            return null;
        }

        $runtimeDir = storage_path('app/ncd_runtime');
        if (!is_dir($runtimeDir) && !@mkdir($runtimeDir, 0775, true) && !is_dir($runtimeDir)) {
            return null;
        }
        $runtimeConfigPath = $runtimeDir . DIRECTORY_SEPARATOR . 'config_runtime.yaml';
        $sourceContent = @file_get_contents($sourcePath);
        if ($sourceContent === false) {
            return null;
        }
        if (@file_put_contents($runtimeConfigPath, $sourceContent) === false) {
            return null;
        }

        $currentConfig = $this->loadNcdConfig();
        $maskIds = (bool) ($currentConfig['privacy']['mask_ids'] ?? false);

        $updated = $this->updateNcdConfigFile($runtimeConfigPath, [
            'databases' => [],
            'date_range' => [
                'end_date' => $endDate,
            ],
            'thresholds' => [],
            'privacy' => [
                'mask_ids' => $maskIds,
            ],
        ]);

        return $updated ? $runtimeConfigPath : null;
    }

    private function defaultNcdDatabaseName(): string
    {
        $ncdConfig = $this->loadNcdConfig();
        $configuredDatabases = array_values(array_filter(array_map(static function ($db) {
            return trim((string) $db);
        }, (array) ($ncdConfig['databases'] ?? []))));
        if (!empty($configuredDatabases)) {
            return $configuredDatabases[0];
        }

        $defaultConnection = (string) config('database.default', 'mysql');
        $defaultDatabase = config("database.connections.{$defaultConnection}.database");
        $value = trim((string) ($defaultDatabase ?? $defaultConnection));
        return $value !== '' ? $value : 'mam';
    }

    private function allowedNcdDatabases(): array
    {
        $databases = [
            $this->defaultNcdDatabaseName(),
            'MAM_A',
            'MAM_B',
            'MAM_C1',
            'MAM_SPT',
            'MAM_SDG',
            'MAM_TL',
            'MAM_TBZY',
        ];
        return array_values(array_unique(array_filter($databases)));
    }

    private function buildNcdClinicOptions(): array
    {
        $ncdConfig = $this->loadNcdConfig();
        $configuredDatabases = array_values(array_filter(array_map(static function ($db) {
            return trim((string) $db);
        }, (array) ($ncdConfig['databases'] ?? []))));
        $defaultDatabase = $this->defaultNcdDatabaseName();
        $candidates = array_values(array_unique(array_merge(
            [$defaultDatabase],
            $configuredDatabases,
            $this->allowedNcdDatabases()
        )));
        $options = [];
        foreach ($candidates as $database) {
            $database = trim((string) $database);
            if ($database === '' || strtolower($database) === 'overall' || strtoupper($database) === 'ALL') {
                continue;
            }
            $options[$database] = strtoupper($database);
        }

        if (is_dir(base_path('outputs' . DIRECTORY_SEPARATOR . 'overall'))) {
            $options['overall'] = 'ALL';
        }

        return $options;
    }

    private function loadNcdConfig(): array
    {
        $configPath = base_path('config.yaml');
        if (!file_exists($configPath)) {
            return [];
        }

        if (class_exists('\\Symfony\\Component\\Yaml\\Yaml')) {
            try {
                $config = \Symfony\Component\Yaml\Yaml::parseFile($configPath);
            } catch (\Symfony\Component\Yaml\Exception\ParseException $exception) {
                Log::warning('Unable to read config.yaml for NCD settings: ' . $exception->getMessage());
                return [];
            }
            return is_array($config) ? $config : [];
        }

        if (function_exists('yaml_parse_file')) {
            $config = yaml_parse_file($configPath);
            return is_array($config) ? $config : [];
        }

        return $this->parseNcdConfigFallback($configPath);
    }

    private function parseNcdConfigFallback(string $path): array
    {
        $config = [
            'databases' => [],
            'date_range' => [],
            'thresholds' => [],
            'privacy' => [],
        ];

        $lines = file($path, FILE_IGNORE_NEW_LINES);
        if ($lines === false) {
            return $config;
        }

        $section = null;
        foreach ($lines as $line) {
            $trimmed = trim($line);
            if ($trimmed === '' || strpos($trimmed, '#') === 0) {
                continue;
            }

            if (preg_match('/^([A-Za-z0-9_]+):\s*$/', $line, $match)) {
                $section = $match[1];
                continue;
            }

            if ($section === 'databases' && preg_match('/^\s*-\s*(.+)$/', $line, $match)) {
                $config['databases'][] = $this->stripYamlValue($match[1]);
                continue;
            }

            if (in_array($section, ['date_range', 'thresholds', 'privacy'], true)
                && preg_match('/^\s*([A-Za-z0-9_]+):\s*(.+)$/', $line, $match)) {
                $key = $match[1];
                $value = $this->stripYamlValue($match[2]);
                if ($section === 'privacy' && $key === 'mask_ids') {
                    $value = in_array(strtolower($value), ['true', 'yes', '1'], true);
                }
                $config[$section][$key] = $value;
            }
        }

        return $config;
    }

    private function stripYamlValue(string $value): string
    {
        $value = preg_replace('/\s+#.*$/', '', $value ?? '');
        $value = trim($value);
        return trim($value, " \t\n\r\0\x0B'\"");
    }

    private function updateNcdConfigFile(string $path, array $updates): bool
    {
        $lines = file($path, FILE_IGNORE_NEW_LINES);
        if ($lines === false) {
            return false;
        }

        if (!empty($updates['databases'])) {
            $lines = $this->replaceSectionBlock($lines, 'databases', $this->renderDatabasesBlock($updates['databases']));
        }

        if (!empty($updates['date_range']['start_date'])) {
            $lines = $this->replaceSectionScalar(
                $lines,
                'date_range',
                'start_date',
                $this->formatYamlString($updates['date_range']['start_date'])
            );
        }
        if (!empty($updates['date_range']['end_date'])) {
            $lines = $this->replaceSectionScalar(
                $lines,
                'date_range',
                'end_date',
                $this->formatYamlString($updates['date_range']['end_date'])
            );
        }

        $thresholdKeys = ['active_days', 'ltfu_days', 'hba1c_lookback_months', 'kidney_lookback_months', 'cvd_risk_lookback_months'];
        foreach ($thresholdKeys as $key) {
            $value = $updates['thresholds'][$key] ?? null;
            if ($value !== null && $value !== false && $value > 0) {
                $lines = $this->replaceSectionScalar($lines, 'thresholds', $key, (string) $value);
            }
        }

        $maskIds = $updates['privacy']['mask_ids'] ?? false;
        $lines = $this->replaceSectionScalar($lines, 'privacy', 'mask_ids', $maskIds ? 'true' : 'false');

        $content = implode(PHP_EOL, $lines);
        if ($content !== '' && substr($content, -strlen(PHP_EOL)) !== PHP_EOL) {
            $content .= PHP_EOL;
        }

        return file_put_contents($path, $content) !== false;
    }

    private function renderDatabasesBlock(array $databases): array
    {
        $block = ['databases:'];
        foreach ($databases as $database) {
            $block[] = '- ' . $database;
        }
        return $block;
    }

    private function replaceSectionBlock(array $lines, string $section, array $block): array
    {
        $bounds = $this->findSectionBounds($lines, $section);
        if ($bounds === null) {
            return array_merge($lines, [''], $block);
        }

        [$start, $end] = $bounds;
        return array_merge(array_slice($lines, 0, $start), $block, array_slice($lines, $end));
    }

    private function replaceSectionScalar(array $lines, string $section, string $key, string $value): array
    {
        $bounds = $this->findSectionBounds($lines, $section);
        if ($bounds === null) {
            return array_merge($lines, [''], [$section . ':', '  ' . $key . ': ' . $value]);
        }

        [$start, $end] = $bounds;
        for ($idx = $start + 1; $idx < $end; $idx++) {
            if (preg_match('/^\s*' . preg_quote($key, '/') . '\s*:/', $lines[$idx])) {
                $lines[$idx] = '  ' . $key . ': ' . $value;
                return $lines;
            }
        }

        array_splice($lines, $start + 1, 0, '  ' . $key . ': ' . $value);
        return $lines;
    }

    private function findSectionBounds(array $lines, string $section): ?array
    {
        $total = count($lines);
        $start = null;
        for ($idx = 0; $idx < $total; $idx++) {
            if (preg_match('/^' . preg_quote($section, '/') . ':\s*$/', $lines[$idx])) {
                $start = $idx;
                break;
            }
        }

        if ($start === null) {
            return null;
        }

        $end = $total;
        for ($idx = $start + 1; $idx < $total; $idx++) {
            if (preg_match('/^[A-Za-z0-9_]+:\s*/', $lines[$idx])) {
                $end = $idx;
                break;
            }
        }

        return [$start, $end];
    }

    private function formatYamlString(string $value): string
    {
        $value = trim($value);
        if ($value === '') {
            return "''";
        }
        if (preg_match('/^["\'].*["\']$/', $value)) {
            return $value;
        }
        return "'" . str_replace("'", "''", $value) . "'";
    }
}
