<?php

declare(strict_types=1);

namespace ItccaAllievi;

if (!defined('ABSPATH')) {
    exit;
}

final class Dashboard
{
    /**
     * Palette ciclica usata per gli animali (uno per ognuno dei 12 segni,
     * mantiene lo stesso colore indipendentemente dall'elemento). Per
     * "Elemento / El Sx / El Dx" si usano invece i colori canonici di
     * Fields::element_color() così Acqua è sempre azzurro, ecc.
     *
     * @var array<string, string>
     */
    private const ANIMAL_PALETTE = [
        'Topo'     => '#6b7280',
        'Bue'      => '#92400e',
        'Tigre'    => '#f59e0b',
        'Coniglio' => '#d1bcaf',
        'Drago'    => '#dc2626',
        'Serpente' => '#16a34a',
        'Cavallo'  => '#7c2d12',
        'Capra'    => '#94a3b8',
        'Scimmia'  => '#a16207',
        'Gallo'    => '#ea580c',
        'Cane'     => '#365314',
        'Maiale'   => '#be185d',
    ];

    public static function register(): void
    {
        add_action('admin_enqueue_scripts', [self::class, 'enqueue_assets']);
        add_action('wp_ajax_itcca_geocode_batch_start', [self::class, 'ajax_batch_start']);
    }

    public static function enqueue_assets(string $hook): void
    {
        if (strpos($hook, 'itcca-allievi-dashboard') === false) {
            return;
        }
        // Leaflet (mappa) — CDN ufficiale.
        wp_enqueue_style(
            'leaflet',
            'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css',
            [],
            '1.9.4'
        );
        wp_enqueue_script(
            'leaflet',
            'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js',
            [],
            '1.9.4',
            true
        );
        wp_enqueue_script(
            'itcca-dashboard-map',
            ITCCA_URL . 'assets/js/dashboard-map.js',
            ['leaflet'],
            ITCCA_VERSION,
            true
        );
        $view = self::current_view();
        wp_localize_script('itcca-dashboard-map', 'itccaMap', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('itcca_geocode_batch'),
            'view'    => $view,
            'sedi'    => self::map_sedi_payload(),
            // La mappa mostra sempre entrambi i gruppi (attivi + inattivi)
            // su layer separati: la view filtra solo KPI/torte/contatori.
            'allievi' => self::map_allievi_payload('all'),
            'strings' => [
                'no_data'         => __('Nessuna posizione disponibile.', 'itcca-allievi'),
                'geocoding'       => __('Geocodifica in corso…', 'itcca-allievi'),
                'done'            => __('Completata.', 'itcca-allievi'),
                'sedi'            => __('Sedi attive', 'itcca-allievi'),
                'sedi_aband'      => __('Sedi abbandonate', 'itcca-allievi'),
                'allievi_active'  => __('Allievi attivi', 'itcca-allievi'),
                'allievi_inactive'=> __('Allievi inattivi', 'itcca-allievi'),
                'abandoned'       => __('abbandonata', 'itcca-allievi'),
                'inactive'        => __('inattivo', 'itcca-allievi'),
            ],
        ]);
    }

    /**
     * @return array<int, array{name:string, address:string, lat:float, lng:float, abandoned:bool}>
     */
    private static function map_sedi_payload(): array
    {
        $out = [];
        foreach (Sedi::all() as $s) {
            if ($s['lat'] === null || $s['lng'] === null) continue;
            $out[] = [
                'name'      => $s['name'],
                'address'   => $s['address'],
                'lat'       => (float) $s['lat'],
                'lng'       => (float) $s['lng'],
                'abandoned' => !empty($s['abandoned']),
            ];
        }
        return $out;
    }

    /**
     * @return array<int, array{name:string, address:string, lat:float, lng:float, active:bool}>
     */
    private static function map_allievi_payload(string $view = 'all'): array
    {
        global $wpdb;
        $sql = $wpdb->prepare(
            "SELECT u.ID, u.display_name,
                    MAX(CASE WHEN m.meta_key = %s THEN m.meta_value END) AS cognome,
                    MAX(CASE WHEN m.meta_key = %s THEN m.meta_value END) AS nome,
                    MAX(CASE WHEN m.meta_key = %s THEN m.meta_value END) AS comune,
                    MAX(CASE WHEN m.meta_key = %s THEN m.meta_value END) AS indirizzo,
                    MAX(CASE WHEN m.meta_key = %s THEN m.meta_value END) AS lat,
                    MAX(CASE WHEN m.meta_key = %s THEN m.meta_value END) AS lng,
                    MAX(CASE WHEN m.meta_key = %s THEN m.meta_value END) AS active_flag
             FROM {$wpdb->users} u
             INNER JOIN {$wpdb->usermeta} m ON m.user_id = u.ID
             WHERE m.meta_key IN (%s,%s,%s,%s,%s,%s,%s)
             GROUP BY u.ID
             HAVING lat IS NOT NULL AND lat <> '' AND lng IS NOT NULL AND lng <> ''",
            ITCCA_META_PREFIX . 'cognome',
            ITCCA_META_PREFIX . 'nome',
            ITCCA_META_PREFIX . 'comune',
            ITCCA_META_PREFIX . 'indirizzo',
            ITCCA_META_PREFIX . Geocoder::META_LAT,
            ITCCA_META_PREFIX . Geocoder::META_LNG,
            ITCCA_META_PREFIX . 'active',
            ITCCA_META_PREFIX . 'cognome',
            ITCCA_META_PREFIX . 'nome',
            ITCCA_META_PREFIX . 'comune',
            ITCCA_META_PREFIX . 'indirizzo',
            ITCCA_META_PREFIX . Geocoder::META_LAT,
            ITCCA_META_PREFIX . Geocoder::META_LNG,
            ITCCA_META_PREFIX . 'active'
        );
        $rows = $wpdb->get_results($sql, ARRAY_A) ?: [];
        $out = [];
        foreach ($rows as $r) {
            $active_flag = (string) ($r['active_flag'] ?? '');
            $is_active = $active_flag === '' || $active_flag === '1';
            if ($view === 'active' && !$is_active) continue;
            if ($view === 'inactive' && $is_active) continue;
            $name = trim(((string) $r['cognome']) . ' ' . ((string) $r['nome']));
            if ($name === '') $name = (string) $r['display_name'];
            $out[] = [
                'name'    => $name,
                'address' => trim(((string) $r['indirizzo']) . ' — ' . ((string) $r['comune']), ' —'),
                'lat'     => (float) $r['lat'],
                'lng'     => (float) $r['lng'],
                'active'  => $is_active,
            ];
        }
        return $out;
    }

    public static function ajax_batch_start(): void
    {
        if (!current_user_can('edit_users')) {
            wp_send_json_error(['msg' => 'forbidden'], 403);
        }
        check_ajax_referer('itcca_geocode_batch', 'nonce');
        $pending = Geocoder::pending_user_ids();
        wp_send_json_success(['total' => count($pending)]);
    }

    public static function render_page(): void
    {
        if (!current_user_can('edit_users')) {
            wp_die(__('Permessi insufficienti.', 'itcca-allievi'));
        }

        $view = self::current_view();
        $counts = self::count_by_active();
        $ids = self::user_ids_for_view($view);
        $total = count($ids);

        $counts_animale = self::tally_meta($ids, 'animale');
        $counts_elem    = self::tally_meta($ids, 'elem');
        $counts_el_sx   = self::tally_meta($ids, 'el_sx');
        $counts_el_dx   = self::tally_meta($ids, 'el_dx');

        echo '<div class="wrap itcca-dashboard">';
        echo '<h1>' . esc_html__('Dashboard Allievi ITCCA', 'itcca-allievi') . '</h1>';
        self::render_notice();
        self::render_view_switcher($view, $counts);

        $label = match ($view) {
            'inactive' => __('Distribuzione su %d allievi inattivi.', 'itcca-allievi'),
            'all'      => __('Distribuzione su %d allievi totali (attivi + inattivi).', 'itcca-allievi'),
            default    => __('Distribuzione su %d allievi attivi.', 'itcca-allievi'),
        };
        echo '<p class="description">' . sprintf(esc_html($label), $total) . '</p>';

        if ($total === 0) {
            echo '<p>' . esc_html__('Nessun allievo corrispondente alla vista selezionata.', 'itcca-allievi') . '</p></div>';
            return;
        }

        $kpi = self::compute_age_kpi($ids);
        $births = self::compute_births_per_month($ids);
        self::render_kpi_section($kpi, $births);

        echo '<div class="itcca-dashboard-grid">';
        self::render_chart_card(
            __('Animale (Zodiaco cinese)', 'itcca-allievi'),
            $counts_animale,
            'animale'
        );
        self::render_chart_card(
            __('Elemento', 'itcca-allievi'),
            $counts_elem,
            'element'
        );
        self::render_chart_card(
            __('Elemento sinistro', 'itcca-allievi'),
            $counts_el_sx,
            'element'
        );
        self::render_chart_card(
            __('Elemento destro', 'itcca-allievi'),
            $counts_el_dx,
            'element'
        );
        echo '</div>'; // .itcca-dashboard-grid

        self::render_map_section($view);

        echo '</div>'; // .wrap
    }

    private static function render_notice(): void
    {
        $notice = sanitize_key((string) ($_GET['itcca_notice'] ?? ''));
        if ($notice === 'geocode_retry') {
            $n = max(0, (int) ($_GET['cleared'] ?? 0));
            printf(
                '<div class="notice notice-success is-dismissible"><p>%s</p></div>',
                esc_html(sprintf(
                    /* translators: %d numero di utenti reimmessi in coda */
                    _n(
                        '%d utente rimesso in coda per la geocodifica.',
                        '%d utenti rimessi in coda per la geocodifica.',
                        $n,
                        'itcca-allievi'
                    ),
                    $n
                ))
            );
        }
    }

    public static function current_view(): string
    {
        $v = sanitize_key((string) ($_GET['view'] ?? 'active'));
        return in_array($v, ['active', 'inactive', 'all'], true) ? $v : 'active';
    }

    /**
     * @return array<int, int>
     */
    private static function user_ids_for_view(string $view): array
    {
        $args = ['role' => ITCCA_ROLE, 'fields' => ['ID']];
        if ($view === 'active') {
            $args['meta_query'] = [[
                'relation' => 'OR',
                ['key' => ITCCA_META_PREFIX . 'active', 'value' => '1', 'compare' => '='],
                ['key' => ITCCA_META_PREFIX . 'active', 'compare' => 'NOT EXISTS'],
            ]];
        } elseif ($view === 'inactive') {
            $args['meta_query'] = [[
                'key' => ITCCA_META_PREFIX . 'active', 'value' => '0', 'compare' => '=',
            ]];
        }
        $users = get_users($args);
        return array_map(static fn ($u) => (int) $u->ID, $users);
    }

    /**
     * Conteggio degli allievi pending alla geocodifica, separati per stato.
     *
     * @return array{active:int, inactive:int}
     */
    private static function pending_split_by_active(): array
    {
        $pending = array_map('intval', Geocoder::pending_user_ids());
        $out = ['active' => 0, 'inactive' => 0];
        if (empty($pending)) return $out;
        // Prime user meta cache: una query, evita N letture singole.
        update_meta_cache('user', $pending);
        foreach ($pending as $uid) {
            $flag = (string) get_user_meta($uid, ITCCA_META_PREFIX . 'active', true);
            if ($flag === '0') $out['inactive']++;
            else $out['active']++;
        }
        return $out;
    }

    /**
     * @return array{active:int, inactive:int, all:int}
     */
    private static function count_by_active(): array
    {
        $all = (int) (new \WP_User_Query([
            'role' => ITCCA_ROLE, 'count_total' => true, 'fields' => 'ID', 'number' => 1,
        ]))->get_total();
        $inactive = (int) (new \WP_User_Query([
            'role'        => ITCCA_ROLE,
            'count_total' => true,
            'fields'      => 'ID',
            'number'      => 1,
            'meta_query'  => [
                ['key' => ITCCA_META_PREFIX . 'active', 'value' => '0', 'compare' => '='],
            ],
        ]))->get_total();
        return [
            'active'   => $all - $inactive,
            'inactive' => $inactive,
            'all'      => $all,
        ];
    }

    /**
     * @param array{active:int, inactive:int, all:int} $counts
     */
    private static function render_view_switcher(string $current, array $counts): void
    {
        $base = admin_url('admin.php?page=itcca-allievi-dashboard');
        $items = [
            'active'   => [__('Attivi', 'itcca-allievi'),   $counts['active']],
            'inactive' => [__('Inattivi', 'itcca-allievi'), $counts['inactive']],
            'all'      => [__('Tutti', 'itcca-allievi'),    $counts['all']],
        ];
        echo '<ul class="subsubsub itcca-dashboard-views">';
        $links = [];
        foreach ($items as $view => $info) {
            $href = $view === 'active' ? $base : add_query_arg('view', $view, $base);
            $class = $current === $view ? ' class="current"' : '';
            $links[] = sprintf(
                '<li><a href="%s"%s>%s <span class="count">(%d)</span></a></li>',
                esc_url($href),
                $class,
                esc_html((string) $info[0]),
                (int) $info[1]
            );
        }
        echo implode(' | ', $links);
        echo '</ul><br class="clear" />';
    }

    private static function render_map_section(string $view = 'active'): void
    {
        $pending_total = count(Geocoder::pending_user_ids());
        $pending_split = self::pending_split_by_active();
        $allievi_payload = self::map_allievi_payload('all');
        $geocoded_active = 0;
        $geocoded_inactive = 0;
        foreach ($allievi_payload as $a) {
            if (!empty($a['active'])) $geocoded_active++;
            else $geocoded_inactive++;
        }
        $sedi_count = count(self::map_sedi_payload());
        $failed = Geocoder::failed_counts();

        echo '<div class="itcca-map-card">';
        echo '<h2>' . esc_html__('Distribuzione geografica', 'itcca-allievi') . '</h2>';
        echo '<p class="description">'
            . sprintf(
                esc_html__('%1$d sedi · %2$d allievi attivi e %3$d inattivi posizionati. %4$d ancora da geocodificare (%5$d attivi + %6$d inattivi).', 'itcca-allievi'),
                $sedi_count,
                $geocoded_active,
                $geocoded_inactive,
                $pending_total,
                $pending_split['active'],
                $pending_split['inactive']
            ) . '</p>';

        if ($failed['transient'] > 0 || $failed['permanent'] > 0) {
            echo '<p class="description"><strong>';
            printf(
                esc_html__('Geocodifiche fallite: %1$d transitorie (es. errori di rete/SSL), %2$d permanenti (indirizzo non trovato).', 'itcca-allievi'),
                $failed['transient'],
                $failed['permanent']
            );
            echo '</strong></p>';
        }

        echo '<div class="itcca-map-toolbar">';
        echo '<button type="button" class="button button-secondary" id="itcca-geocode-batch-start"';
        if ($pending_total === 0) echo ' disabled';
        echo '>' . esc_html__('Geocodifica indirizzi mancanti', 'itcca-allievi') . '</button>';

        if ($failed['transient'] > 0) {
            $retry_url = wp_nonce_url(
                admin_url('admin-post.php?action=itcca_geocode_retry_failed&mode=transient'),
                'itcca_geocode_retry_failed'
            );
            echo ' <a href="' . esc_url($retry_url) . '" class="button button-secondary">'
                . esc_html__('Riprova falliti transitori', 'itcca-allievi') . '</a>';
        }
        if ($failed['permanent'] > 0 || $failed['transient'] > 0) {
            $retry_all_url = wp_nonce_url(
                admin_url('admin-post.php?action=itcca_geocode_retry_failed&mode=all'),
                'itcca_geocode_retry_failed'
            );
            echo ' <a href="' . esc_url($retry_all_url) . '" class="button">'
                . esc_html__('Riprova tutti i falliti', 'itcca-allievi') . '</a>';
        }

        echo '<span id="itcca-geocode-progress" class="description" style="margin-left:1rem"></span>';
        echo '</div>';

        echo '<div id="itcca-map" style="height:480px;border-radius:6px;margin-top:.75rem"></div>';

        echo '<p class="description" style="margin-top:.5rem">'
            . esc_html__('Mappa © OpenStreetMap contributors. Posizioni ricavate via Nominatim, geocodifica limitata a 1 chiamata/sec.', 'itcca-allievi')
            . '</p>';
        echo '</div>';
    }

    /**
     * @param array<int> $user_ids
     * @return array{
     *   count:int,
     *   avg:?float,
     *   min:?array{age:int,user_id:int,name:string},
     *   max:?array{age:int,user_id:int,name:string},
     * }
     */
    private static function compute_age_kpi(array $user_ids): array
    {
        if (empty($user_ids)) {
            return ['count' => 0, 'avg' => null, 'min' => null, 'max' => null];
        }
        global $wpdb;
        $placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
        $key = ITCCA_META_PREFIX . 'nascita_data';
        $sql = $wpdb->prepare(
            "SELECT user_id, meta_value FROM {$wpdb->usermeta}
             WHERE meta_key = %s AND user_id IN ($placeholders) AND meta_value <> ''",
            array_merge([$key], $user_ids)
        );
        $rows = $wpdb->get_results($sql, ARRAY_A) ?: [];

        $today = new \DateTimeImmutable('today');
        $ages = [];
        $min = null;
        $max = null;
        foreach ($rows as $r) {
            try {
                $d = new \DateTimeImmutable((string) $r['meta_value']);
            } catch (\Exception) {
                continue;
            }
            if ($d > $today) continue;
            $age = (int) $today->diff($d)->y;
            if ($age < 0 || $age > 130) continue;
            $uid = (int) $r['user_id'];
            $ages[$uid] = $age;
            if ($min === null || $age < $min['age']) $min = ['age' => $age, 'user_id' => $uid, 'name' => ''];
            if ($max === null || $age > $max['age']) $max = ['age' => $age, 'user_id' => $uid, 'name' => ''];
        }
        if (empty($ages)) {
            return ['count' => 0, 'avg' => null, 'min' => null, 'max' => null];
        }
        if ($min !== null) $min['name'] = self::display_name((int) $min['user_id']);
        if ($max !== null) $max['name'] = self::display_name((int) $max['user_id']);

        return [
            'count' => count($ages),
            'avg'   => array_sum($ages) / count($ages),
            'min'   => $min,
            'max'   => $max,
        ];
    }

    /**
     * @param array<int> $user_ids
     * @return array<int, int>  index 1..12 → count
     */
    private static function compute_births_per_month(array $user_ids): array
    {
        $out = array_fill(1, 12, 0);
        if (empty($user_ids)) return $out;
        global $wpdb;
        $placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
        $key = ITCCA_META_PREFIX . 'nascita_data';
        $sql = $wpdb->prepare(
            "SELECT meta_value FROM {$wpdb->usermeta}
             WHERE meta_key = %s AND user_id IN ($placeholders) AND meta_value <> ''",
            array_merge([$key], $user_ids)
        );
        $rows = $wpdb->get_col($sql) ?: [];
        foreach ($rows as $v) {
            try {
                $d = new \DateTimeImmutable((string) $v);
            } catch (\Exception) {
                continue;
            }
            $m = (int) $d->format('n');
            if ($m >= 1 && $m <= 12) $out[$m]++;
        }
        return $out;
    }

    private static function display_name(int $user_id): string
    {
        $cognome = (string) get_user_meta($user_id, ITCCA_META_PREFIX . 'cognome', true);
        $nome    = (string) get_user_meta($user_id, ITCCA_META_PREFIX . 'nome', true);
        $full = trim($cognome . ' ' . $nome);
        if ($full !== '') return $full;
        $u = get_user_by('id', $user_id);
        return $u instanceof \WP_User ? (string) $u->display_name : '';
    }

    /**
     * @param array{count:int, avg:?float, min:?array, max:?array} $kpi
     * @param array<int, int> $births
     */
    private static function render_kpi_section(array $kpi, array $births): void
    {
        echo '<div class="itcca-kpi-grid">';

        // Card età media
        echo '<div class="itcca-kpi-card">';
        echo '<div class="itcca-kpi-label">' . esc_html__('Età media', 'itcca-allievi') . '</div>';
        if ($kpi['avg'] !== null) {
            echo '<div class="itcca-kpi-value">' . esc_html(number_format($kpi['avg'], 1, ',', '')) . '</div>';
            echo '<div class="itcca-kpi-note">' . sprintf(
                esc_html__('su %d allievi con data di nascita', 'itcca-allievi'),
                (int) $kpi['count']
            ) . '</div>';
        } else {
            echo '<div class="itcca-kpi-value itcca-kpi-value--muted">—</div>';
            echo '<div class="itcca-kpi-note">' . esc_html__('nessuna data di nascita disponibile', 'itcca-allievi') . '</div>';
        }
        echo '</div>';

        // Card età minima
        echo '<div class="itcca-kpi-card itcca-kpi-card--accent-blue">';
        echo '<div class="itcca-kpi-label">' . esc_html__('Più giovane', 'itcca-allievi') . '</div>';
        if (!empty($kpi['min'])) {
            $edit = get_edit_user_link((int) $kpi['min']['user_id']);
            echo '<div class="itcca-kpi-value">' . (int) $kpi['min']['age'] . '<span class="itcca-kpi-unit">' . esc_html__('anni', 'itcca-allievi') . '</span></div>';
            echo '<div class="itcca-kpi-note"><a href="' . esc_url($edit) . '">' . esc_html($kpi['min']['name']) . '</a></div>';
        } else {
            echo '<div class="itcca-kpi-value itcca-kpi-value--muted">—</div>';
        }
        echo '</div>';

        // Card età massima
        echo '<div class="itcca-kpi-card itcca-kpi-card--accent-red">';
        echo '<div class="itcca-kpi-label">' . esc_html__('Più anziano', 'itcca-allievi') . '</div>';
        if (!empty($kpi['max'])) {
            $edit = get_edit_user_link((int) $kpi['max']['user_id']);
            echo '<div class="itcca-kpi-value">' . (int) $kpi['max']['age'] . '<span class="itcca-kpi-unit">' . esc_html__('anni', 'itcca-allievi') . '</span></div>';
            echo '<div class="itcca-kpi-note"><a href="' . esc_url($edit) . '">' . esc_html($kpi['max']['name']) . '</a></div>';
        } else {
            echo '<div class="itcca-kpi-value itcca-kpi-value--muted">—</div>';
        }
        echo '</div>';

        // Card grafico nascite per mese
        echo '<div class="itcca-kpi-card itcca-kpi-card--chart">';
        echo '<div class="itcca-kpi-label">' . esc_html__('Nascite per mese', 'itcca-allievi') . '</div>';
        echo self::render_births_bar_chart($births);
        echo '</div>';

        echo '</div>'; // .itcca-kpi-grid
    }

    /**
     * @param array<int, int> $births
     */
    private static function render_births_bar_chart(array $births): string
    {
        $months = ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'];
        $max = max(1, max($births));
        $w = 280; $h = 110;
        $bar_w = $w / 12;
        $top_pad = 16; $bottom_pad = 18;
        $svg = '<svg class="itcca-bars" viewBox="0 0 ' . $w . ' ' . $h . '" preserveAspectRatio="none" role="img" aria-label="' . esc_attr__('Nascite per mese', 'itcca-allievi') . '">';
        for ($m = 1; $m <= 12; $m++) {
            $count = (int) $births[$m];
            $bh = $count > 0 ? max(2, ($count / $max) * ($h - $top_pad - $bottom_pad)) : 0;
            $x = ($m - 1) * $bar_w + 1.5;
            $bw = max(1, $bar_w - 3);
            $y = $h - $bottom_pad - $bh;
            $svg .= sprintf(
                '<rect x="%.2f" y="%.2f" width="%.2f" height="%.2f" rx="2" fill="#2271b1"><title>%s: %d</title></rect>',
                $x,
                $y,
                $bw,
                $bh,
                esc_html($months[$m - 1]),
                $count
            );
            if ($count > 0) {
                $svg .= sprintf(
                    '<text x="%.2f" y="%.2f" text-anchor="middle" font-size="8" fill="#1d2327">%d</text>',
                    $x + $bw / 2,
                    $y - 2,
                    $count
                );
            }
            $svg .= sprintf(
                '<text x="%.2f" y="%.2f" text-anchor="middle" font-size="9" fill="#50575e">%s</text>',
                $x + $bw / 2,
                $h - 4,
                esc_html($months[$m - 1])
            );
        }
        $svg .= '</svg>';
        return $svg;
    }

    /**
     * @param array<int> $user_ids
     * @return array<string, int>
     */
    private static function tally_meta(array $user_ids, string $meta_suffix): array
    {
        if (empty($user_ids)) return [];
        global $wpdb;
        $key = ITCCA_META_PREFIX . $meta_suffix;
        $placeholders = implode(',', array_fill(0, count($user_ids), '%d'));
        $params = array_merge([$key], $user_ids);
        $sql = $wpdb->prepare(
            "SELECT meta_value, COUNT(*) AS n
             FROM {$wpdb->usermeta}
             WHERE meta_key = %s AND user_id IN ($placeholders) AND meta_value <> ''
             GROUP BY meta_value",
            $params
        );
        $rows = $wpdb->get_results($sql, ARRAY_A) ?: [];
        $out = [];
        foreach ($rows as $r) {
            $val = trim((string) $r['meta_value']);
            if ($val === '') continue;
            $out[$val] = (int) $r['n'];
        }
        arsort($out);
        return $out;
    }

    /**
     * @param array<string, int> $counts
     * @param 'animale'|'element' $palette
     */
    private static function render_chart_card(string $title, array $counts, string $palette): void
    {
        $total = array_sum($counts);

        echo '<div class="itcca-chart-card">';
        echo '<h2>' . esc_html($title) . '</h2>';

        if ($total === 0) {
            echo '<p class="description">'
                . esc_html__('Nessun dato per questo campo.', 'itcca-allievi')
                . '</p></div>';
            return;
        }

        $slices = self::build_slices($counts, $total, $palette);

        echo '<div class="itcca-chart-row">';
        echo self::render_pie_svg($slices);
        echo '<ol class="itcca-chart-legend">';
        foreach ($slices as $slice) {
            printf(
                '<li><span class="itcca-swatch" style="background:%s"></span>'
                . '<span class="itcca-chart-legend__label">%s</span>'
                . '<span class="itcca-chart-legend__value">%d (%s%%)</span></li>',
                esc_attr($slice['color']),
                esc_html($slice['label']),
                $slice['count'],
                esc_html(number_format($slice['pct'], 1, ',', ''))
            );
        }
        echo '</ol>';
        echo '</div></div>';
    }

    /**
     * @param array<string, int> $counts
     * @param 'animale'|'element' $palette
     * @return array<int, array{label:string, count:int, pct:float, color:string}>
     */
    private static function build_slices(array $counts, int $total, string $palette): array
    {
        $slices = [];
        $fallback_palette = ['#6366f1', '#0ea5e9', '#14b8a6', '#84cc16', '#eab308', '#f97316', '#ec4899', '#a855f7'];
        $i = 0;
        foreach ($counts as $value => $count) {
            $color = '';
            if ($palette === 'element') {
                $color = Fields::element_color($value);
            } elseif ($palette === 'animale') {
                $animal = explode(' di ', $value)[0] ?? '';
                $color = self::ANIMAL_PALETTE[$animal] ?? '';
            }
            if ($color === '') {
                $color = $fallback_palette[$i % count($fallback_palette)];
            }
            $i++;
            $slices[] = [
                'label' => $value,
                'count' => $count,
                'pct'   => $total > 0 ? ($count / $total) * 100 : 0.0,
                'color' => $color,
            ];
        }
        return $slices;
    }

    /**
     * @param array<int, array{label:string, count:int, pct:float, color:string}> $slices
     */
    private static function render_pie_svg(array $slices): string
    {
        $size = 240;
        $r = 110;
        $cx = $size / 2;
        $cy = $size / 2;

        // Caso speciale: singola fetta = cerchio pieno (gli archi a 360° sono degeneri).
        if (count($slices) === 1) {
            return sprintf(
                '<svg class="itcca-pie" width="%d" height="%d" viewBox="0 0 %d %d" role="img" aria-label="%s">'
                . '<circle cx="%d" cy="%d" r="%d" fill="%s" />'
                . '</svg>',
                $size,
                $size,
                $size,
                $size,
                esc_attr__('Grafico a torta', 'itcca-allievi'),
                $cx,
                $cy,
                $r,
                esc_attr($slices[0]['color'])
            );
        }

        $paths = '';
        $angle_start = -M_PI / 2; // partiamo in alto (12 in punto)
        $total = array_sum(array_column($slices, 'count'));
        foreach ($slices as $slice) {
            $portion = $total > 0 ? $slice['count'] / $total : 0;
            $angle_end = $angle_start + ($portion * 2 * M_PI);
            $x1 = $cx + $r * cos($angle_start);
            $y1 = $cy + $r * sin($angle_start);
            $x2 = $cx + $r * cos($angle_end);
            $y2 = $cy + $r * sin($angle_end);
            $large_arc = $portion > 0.5 ? 1 : 0;
            $d = sprintf(
                'M%.3f,%.3f L%.3f,%.3f A%d,%d 0 %d 1 %.3f,%.3f Z',
                $cx,
                $cy,
                $x1,
                $y1,
                $r,
                $r,
                $large_arc,
                $x2,
                $y2
            );
            $paths .= sprintf(
                '<path d="%s" fill="%s"><title>%s — %d (%s%%)</title></path>',
                esc_attr($d),
                esc_attr($slice['color']),
                esc_html($slice['label']),
                $slice['count'],
                esc_html(number_format($slice['pct'], 1, ',', ''))
            );
            $angle_start = $angle_end;
        }
        return sprintf(
            '<svg class="itcca-pie" width="%d" height="%d" viewBox="0 0 %d %d" role="img" aria-label="%s">%s</svg>',
            $size,
            $size,
            $size,
            $size,
            esc_attr__('Grafico a torta', 'itcca-allievi'),
            $paths
        );
    }
}
