<?php

namespace App\Services\Forecasting;

use App\Services\Forecasting\Contracts\ForecastFeature;
use App\Services\Forecasting\Models\RidgeRegressionModel;
use Carbon\CarbonInterface;

/**
 * Phase 6 honesty rule: the reasoning text only references features
 * the model actually used, ranked by how much each contributed to
 * this week's prediction.
 *
 * Contribution is the standardised (z-score × β) for each feature —
 * the same number the ridge model summed to produce the prediction.
 * That makes the explanation literally what the model did, not a
 * narrative invented post-hoc.
 */
final class ReasoningGenerator
{
    /** @var array<string, string> */
    private const array PHRASES = [
        'delta_ulsp_lag_0' => "last week's pump price move",
        'delta_ulsp_lag_1' => 'the pump price move two weeks ago',
        'delta_ulsp_lag_3' => 'the pump price move four weeks ago',
        'delta_ulsd_lag_0' => "last week's diesel move",
        'ulsp_minus_ma8' => "the gap between this week's pump price and its 8-week average",
        'week_of_year_sin' => 'the seasonal pattern',
        'week_of_year_cos' => 'the seasonal pattern',
        'is_pre_bank_holiday' => 'an upcoming bank holiday',
    ];

    /**
     * @param  array<int, ForecastFeature>  $features
     */
    public function generate(
        RidgeRegressionModel $model,
        WeeklyPrediction $prediction,
        array $features,
        CarbonInterface $targetMonday,
        int $confidence,
        bool $flaggedDutyChange,
        ?float $trailingHitRate,
    ): string {
        if ($confidence < 40) {
            return 'Not enough signal in the historical pattern to call this week — staying silent.';
        }

        $coeffs = $model->coefficients() ?? [];
        $features_meta = $coeffs['features'] ?? [];

        $contributions = [];
        foreach ($features as $f) {
            $name = $f->name();
            $meta = $features_meta[$name] ?? null;
            if ($meta === null) {
                continue;
            }
            $value = $f->valueFor($targetMonday);
            if ($value === null) {
                continue;
            }
            $z = ($value - $meta['mean']) / ($meta['std_dev'] ?: 1.0);
            $contributions[$name] = $z * $meta['beta_standardised'];
        }

        $headline = $this->headline($prediction);
        $driver = $this->dominantFeatureSentence($contributions);
        $duty = $flaggedDutyChange
            ? ' Recent fuel duty change may skew accuracy for the next several weeks.'
            : '';
        $accuracy = $trailingHitRate !== null
            ? sprintf(' Last 13 weeks: %d%% hit rate.', (int) round($trailingHitRate * 100))
            : '';

        return $headline.' '.$driver.$duty.$accuracy;
    }

    private function headline(WeeklyPrediction $prediction): string
    {
        $absP = round(abs($prediction->magnitudePence) / 100, 1);

        return match ($prediction->direction) {
            'rising' => sprintf('Model expects pump prices to rise by ~%sp/L next week.', number_format($absP, 1)),
            'falling' => sprintf('Model expects pump prices to fall by ~%sp/L next week.', number_format($absP, 1)),
            default => 'Pump prices are likely flat next week.',
        };
    }

    /** @param array<string, float> $contributions */
    private function dominantFeatureSentence(array $contributions): string
    {
        if ($contributions === []) {
            return 'Drawn from the full feature set with no single dominant signal.';
        }

        uasort($contributions, fn (float $a, float $b): int => abs($b) <=> abs($a));
        $topName = array_key_first($contributions);
        $phrase = self::PHRASES[$topName] ?? $topName;

        return sprintf('Driver: %s.', $phrase);
    }
}
