<?php

namespace App\Support;

use Illuminate\Support\Str;

/**
 * Renders the assessment visuals (score gauge + radar "money map") as PNG images.
 * dompdf reliably embeds raster PNGs, whereas inline <svg> is rendered as plain text,
 * so we draw with GD and hand back base64 data URIs.
 */
class ReportChart
{
    private const NAVY = [28, 43, 95];      // #1c2b5f
    private const WISE = [88, 185, 230];    // #58b9e6
    private const WISE_DK = [39, 135, 184]; // #2787b8
    private const INK = [40, 54, 106];      // #28366a
    private const SS = 3;                   // supersample factor for crisp downscaling

    public static function available(): bool
    {
        return extension_loaded('gd') && function_exists('imagepng');
    }

    private static function font(bool $bold = false): ?string
    {
        if (! function_exists('imagettftext')) {
            return null;
        }
        $path = base_path('vendor/dompdf/dompdf/lib/fonts/DejaVuSans'.($bold ? '-Bold' : '').'.ttf');

        return is_file($path) ? $path : null;
    }

    private static function dataUri($im): string
    {
        ob_start();
        imagepng($im);
        $png = ob_get_clean();
        imagedestroy($im);

        return 'data:image/png;base64,'.base64_encode($png);
    }

    private static function text($im, ?string $font, float $size, float $x, float $y, $color, string $text, string $anchor = 'middle'): void
    {
        if ($font) {
            $bb = imagettfbbox($size, 0, $font, $text);
            $w = $bb[2] - $bb[0];
            $h = $bb[1] - $bb[7];
            $tx = $anchor === 'middle' ? $x - $w / 2 : ($anchor === 'end' ? $x - $w : $x);
            imagettftext($im, $size, 0, (int) round($tx), (int) round($y + $h / 2), $color, $font, $text);
        } else {
            $gd = 5;
            $w = imagefontwidth($gd) * strlen($text);
            $tx = $anchor === 'middle' ? $x - $w / 2 : ($anchor === 'end' ? $x - $w : $x);
            imagestring($im, $gd, (int) round($tx), (int) round($y - imagefontheight($gd) / 2), $text, $color);
        }
    }

    /** Donut gauge with a centred percent. */
    public static function gauge(int $percent): ?string
    {
        if (! self::available()) {
            return null;
        }

        $percent = max(0, min(100, $percent));
        $s = self::SS;
        $size = 150 * $s;
        $im = imagecreatetruecolor($size, $size);
        imagealphablending($im, true);
        $white = imagecolorallocate($im, 255, 255, 255);
        imagefilledrectangle($im, 0, 0, $size, $size, $white);

        $cx = (int) ($size / 2);
        $cy = (int) ($size / 2);
        $outer = 64 * $s;
        $thick = 15 * $s;
        $track = imagecolorallocate($im, 238, 241, 249);
        $accent = imagecolorallocate($im, ...self::WISE);
        $navy = imagecolorallocate($im, ...self::NAVY);

        imagefilledellipse($im, $cx, $cy, $outer * 2, $outer * 2, $track);
        if ($percent > 0) {
            imagefilledarc($im, $cx, $cy, $outer * 2, $outer * 2, -90, (int) round(-90 + 3.6 * $percent), $accent, IMG_ARC_PIE);
        }
        imagefilledellipse($im, $cx, $cy, ($outer - $thick) * 2, ($outer - $thick) * 2, $white);

        self::text($im, self::font(true), 30 * $s, $cx, $cy, $navy, $percent.'%');

        return self::dataUri($im);
    }

    /**
     * Radar chart from [label => percent]. Returns null when fewer than 3 axes
     * (a radar needs at least a triangle).
     *
     * @param  array<string,int|float>  $items
     */
    public static function radar(array $items): ?string
    {
        if (! self::available() || count($items) < 3) {
            return null;
        }

        $items = array_slice($items, 0, 8, true);
        $n = count($items);
        $s = self::SS;
        $w = 320 * $s;  // landscape: extra width for side labels
        $h = 250 * $s;
        $im = imagecreatetruecolor($w, $h);
        imagealphablending($im, true);
        $white = imagecolorallocate($im, 255, 255, 255);
        imagefilledrectangle($im, 0, 0, $w, $h, $white);

        $cx = (int) ($w / 2);
        $cy = (int) ($h / 2);
        $r = 72 * $s;
        $grid = imagecolorallocate($im, 220, 225, 242);
        $ink = imagecolorallocate($im, ...self::INK);
        $navy = imagecolorallocate($im, ...self::NAVY);
        $line = imagecolorallocate($im, ...self::WISE_DK);
        $fill = imagecolorallocatealpha($im, self::WISE[0], self::WISE[1], self::WISE[2], 82);
        $font = self::font(false);
        $pad = 4 * $s;
        $start = -M_PI / 2;
        $step = 2 * M_PI / $n;

        foreach ([0.25, 0.5, 0.75, 1.0] as $f) {
            $pts = [];
            for ($k = 0; $k < $n; $k++) {
                $a = $start + $k * $step;
                $pts[] = $cx + $r * $f * cos($a);
                $pts[] = $cy + $r * $f * sin($a);
            }
            self::outline($im, $pts, $grid, 1);
        }

        $i = 0;
        foreach ($items as $label => $pct) {
            $a = $start + $i * $step;
            imageline($im, (int) $cx, (int) $cy, (int) ($cx + $r * cos($a)), (int) ($cy + $r * sin($a)), $grid);
            $lx = $cx + ($r + 12 * $s) * cos($a);
            $ly = $cy + ($r + 12 * $s) * sin($a);
            $text = Str::limit((string) $label, 22, '');
            $fontSize = 9 * $s;
            $horizontal = abs(cos($a)) < 0.25;
            $rightSide = cos($a) > 0;

            // Position the label text and clamp it fully inside the canvas.
            if ($font) {
                $bb = imagettfbbox($fontSize, 0, $font, $text);
                $tw = $bb[2] - $bb[0];
                $th = $bb[1] - $bb[7];
                $tx = $horizontal ? $lx - $tw / 2 : ($rightSide ? $lx : $lx - $tw);
                $tx = max($pad, min($tx, $w - $tw - $pad));
                $ty = max($th, min($ly + $th / 2, $h - $pad));
                imagettftext($im, $fontSize, 0, (int) round($tx), (int) round($ty), $ink, $font, $text);
            } else {
                $tw = imagefontwidth(4) * strlen($text);
                $tx = $horizontal ? $lx - $tw / 2 : ($rightSide ? $lx : $lx - $tw);
                $tx = max($pad, min($tx, $w - $tw - $pad));
                imagestring($im, 4, (int) round($tx), (int) round($ly - imagefontheight(4) / 2), $text, $ink);
            }
            $i++;
        }

        $dpts = [];
        $points = [];
        $i = 0;
        foreach ($items as $pct) {
            $a = $start + $i * $step;
            $rr = $r * max(0, min(100, (float) $pct)) / 100;
            $x = $cx + $rr * cos($a);
            $y = $cy + $rr * sin($a);
            $dpts[] = $x;
            $dpts[] = $y;
            $points[] = [$x, $y, $a, (int) round((float) $pct)];
            $i++;
        }

        self::filled($im, $dpts, $fill);
        self::outline($im, $dpts, $line, 2.5 * $s);
        foreach ($points as [$x, $y, $a, $pct]) {
            imagefilledellipse($im, (int) $x, (int) $y, 6 * $s, 6 * $s, $navy);
            // Score % nudged toward the centre so it sits on the fill, not over the axis label.
            self::text($im, self::font(true), 8 * $s, $x - 13 * $s * cos($a), $y - 13 * $s * sin($a), $navy, $pct.'%', 'middle');
        }

        return self::dataUri($im);
    }

    private static function filled($im, array $pts, $color): void
    {
        $pts = array_map('intval', $pts);
        if (PHP_VERSION_ID >= 80100) {
            imagefilledpolygon($im, $pts, $color);
        } else {
            imagefilledpolygon($im, $pts, count($pts) / 2, $color);
        }
    }

    private static function outline($im, array $pts, $color, float $thickness = 1): void
    {
        $pts = array_map('intval', $pts);
        imagesetthickness($im, max(1, (int) round($thickness)));
        if (PHP_VERSION_ID >= 80100) {
            imagepolygon($im, $pts, $color);
        } else {
            imagepolygon($im, $pts, count($pts) / 2, $color);
        }
        imagesetthickness($im, 1);
    }
}
