<?php

namespace Calio\Widgets;

use Elementor\Controls_Manager;
use Elementor\Group_Control_Border;
use Elementor\Group_Control_Box_Shadow;
use Elementor\Group_Control_Typography;
use Elementor\Repeater;

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

/**
 * Renders lightweight canvas charts without external chart dependencies.
 */
class FancyChartWidget extends BaseWidget
{
    public function get_name(): string
    {
        return 'calio-fancy-chart';
    }

    public function get_title(): string
    {
        return esc_html__('Fancy Chart', 'calio-addon');
    }

    public function get_icon(): string
    {
        return 'eicon-skill-bar';
    }

    public function get_style_depends(): array
    {
        return $this->resolve_widget_style_depends('calio-widget-fancy-chart');
    }

    public function get_script_depends(): array
    {
        return $this->resolve_widget_script_depends('calio-widget-fancy-chart');
    }

    /**
     * Registers chart controls.
     */
    protected function register_controls(): void
    {
        $this->start_controls_section('content_section', ['label' => esc_html__('Chart', 'calio-addon')]);
        $this->add_control('title', ['label' => esc_html__('Title', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Growth Snapshot', 'calio-addon'), 'label_block' => true]);
        $this->add_control('subtitle', ['label' => esc_html__('Subtitle', 'calio-addon'), 'type' => Controls_Manager::TEXTAREA, 'default' => esc_html__('Compare recent performance with a clean visual summary.', 'calio-addon')]);
        $this->add_control('preset_pack', ['label' => esc_html__('Preset Pack', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'analytics', 'options' => ['analytics' => esc_html__('Analytics', 'calio-addon'), 'finance' => esc_html__('Finance', 'calio-addon'), 'product' => esc_html__('Product', 'calio-addon'), 'saas' => esc_html__('SaaS', 'calio-addon'), 'custom' => esc_html__('Custom', 'calio-addon')]]);
        $this->add_control('show_preset_badge', ['label' => esc_html__('Show Preset Badge', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('preset_badge_text', ['label' => esc_html__('Preset Badge Text', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => '', 'placeholder' => esc_html__('Leave empty to use the preset label.', 'calio-addon'), 'label_block' => true, 'condition' => ['show_preset_badge' => 'yes']]);
        $this->add_control('chart_engine', ['label' => esc_html__('Engine', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'custom', 'options' => ['custom' => esc_html__('Custom Canvas', 'calio-addon'), 'chartjs' => esc_html__('Chart.js', 'calio-addon')], 'description' => esc_html__('Custom stays default. Chart.js is bundled locally and kept GPL-compatible for WordPress.org distribution.', 'calio-addon')]);
        $this->add_control('chart_type', ['label' => esc_html__('Chart Type', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'bar', 'options' => ['bar' => esc_html__('Bar', 'calio-addon'), 'line' => esc_html__('Line', 'calio-addon'), 'area' => esc_html__('Area', 'calio-addon'), 'mixed' => esc_html__('Mixed', 'calio-addon'), 'pie' => esc_html__('Pie', 'calio-addon'), 'donut' => esc_html__('Donut', 'calio-addon'), 'radar' => esc_html__('Radar', 'calio-addon'), 'polar' => esc_html__('Polar Area', 'calio-addon')]]);
        $this->add_control('skin', ['label' => esc_html__('Skin', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'soft', 'options' => ['soft' => esc_html__('Soft', 'calio-addon'), 'outline' => esc_html__('Outline', 'calio-addon'), 'contrast' => esc_html__('Contrast', 'calio-addon'), 'glass' => esc_html__('Glass', 'calio-addon')]]);
        $this->add_control('legend_position', ['label' => esc_html__('Legend Position', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'bottom', 'options' => ['top' => esc_html__('Top', 'calio-addon'), 'bottom' => esc_html__('Bottom', 'calio-addon'), 'right' => esc_html__('Right', 'calio-addon'), 'hidden' => esc_html__('Hidden', 'calio-addon')]]);
        $this->add_control('chart_height', ['label' => esc_html__('Chart Height', 'calio-addon'), 'type' => Controls_Manager::SLIDER, 'size_units' => ['px'], 'range' => ['px' => ['min' => 220, 'max' => 640]], 'default' => ['unit' => 'px', 'size' => 360], 'selectors' => ['{{WRAPPER}} .calio-fancy-chart-canvas-wrap' => 'height: {{SIZE}}{{UNIT}};']]);
        $this->add_control('show_grid', ['label' => esc_html__('Show Grid', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('show_axes', ['label' => esc_html__('Show Axes', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('show_points', ['label' => esc_html__('Show Points', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['chart_type' => ['line', 'area', 'radar', 'mixed']]]);
        $this->add_control('show_values', ['label' => esc_html__('Show Values', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '']);
        $this->add_control('show_tooltip', ['label' => esc_html__('Show Tooltip', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('hover_emphasis', ['label' => esc_html__('Hover Emphasis', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'soft', 'options' => ['soft' => esc_html__('Soft', 'calio-addon'), 'strong' => esc_html__('Strong', 'calio-addon')]]);
        $this->add_control('enable_animation', ['label' => esc_html__('Animate On Load', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('animation_duration', ['label' => esc_html__('Animation Duration', 'calio-addon'), 'type' => Controls_Manager::SLIDER, 'range' => ['ms' => ['min' => 200, 'max' => 2200]], 'size_units' => ['ms'], 'default' => ['unit' => 'ms', 'size' => 900], 'condition' => ['enable_animation' => 'yes']]);
        $this->end_controls_section();

        $this->start_controls_section('data_section', ['label' => esc_html__('Data', 'calio-addon')]);
        $this->add_control('data_source', ['label' => esc_html__('Data Source', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'manual', 'options' => ['manual' => esc_html__('Manual Series', 'calio-addon'), 'sample' => esc_html__('Sample Preset', 'calio-addon'), 'csv' => esc_html__('CSV Import', 'calio-addon'), 'json' => esc_html__('JSON Import', 'calio-addon')]]);
        $this->add_control('labels_csv', ['label' => esc_html__('Labels', 'calio-addon'), 'type' => Controls_Manager::TEXTAREA, 'default' => 'Jan, Feb, Mar, Apr, May, Jun', 'description' => esc_html__('Comma-separated labels.', 'calio-addon'), 'condition' => ['data_source' => 'manual']]);
        $repeater = new Repeater();
        $repeater->add_control('label', ['label' => esc_html__('Series Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Revenue', 'calio-addon'), 'label_block' => true]);
        $repeater->add_control('values', ['label' => esc_html__('Values', 'calio-addon'), 'type' => Controls_Manager::TEXTAREA, 'default' => '18, 24, 21, 28, 34, 39', 'description' => esc_html__('Comma-separated values.', 'calio-addon')]);
        $repeater->add_control('render_as', ['label' => esc_html__('Series Style', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'auto', 'options' => ['auto' => esc_html__('Auto', 'calio-addon'), 'bar' => esc_html__('Bar', 'calio-addon'), 'line' => esc_html__('Line', 'calio-addon')]]);
        $repeater->add_control('stroke', ['label' => esc_html__('Line / Fill Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#2563eb']);
        $repeater->add_control('fill', ['label' => esc_html__('Soft Fill Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => 'rgba(37, 99, 235, 0.18)']);
        $repeater->add_control('point', ['label' => esc_html__('Point Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#ffffff']);
        $this->add_control('datasets', ['label' => esc_html__('Series', 'calio-addon'), 'type' => Controls_Manager::REPEATER, 'fields' => $repeater->get_controls(), 'default' => $this->get_default_datasets(), 'title_field' => '{{{ label }}}', 'condition' => ['data_source' => 'manual']]);
        $this->add_control('sample_preset', ['label' => esc_html__('Sample Preset', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'analytics_growth', 'options' => ['analytics_growth' => esc_html__('Analytics Growth', 'calio-addon'), 'finance_forecast' => esc_html__('Finance Forecast', 'calio-addon'), 'product_engagement' => esc_html__('Product Engagement', 'calio-addon'), 'saas_pipeline' => esc_html__('SaaS Pipeline', 'calio-addon')], 'condition' => ['data_source' => 'sample']]);
        $this->add_control('sample_preset_notice', [
            'type' => Controls_Manager::RAW_HTML,
            'raw' => wp_kses_post(
                '<div class="elementor-panel-alert elementor-panel-alert-info">' .
                esc_html__('Use a preset to populate the chart quickly, then switch back to Manual, CSV or JSON if you want to replace the sample data with your own source.', 'calio-addon') .
                '</div>'
            ),
            'condition' => ['data_source' => 'sample'],
            'content_classes' => 'elementor-descriptor',
        ]);
        $this->add_control('csv_input', ['label' => esc_html__('CSV Data', 'calio-addon'), 'type' => Controls_Manager::TEXTAREA, 'rows' => 8, 'default' => "Label, Revenue, Pipeline\nJan, 18, 12\nFeb, 24, 18\nMar, 21, 16\nApr, 28, 21\nMay, 34, 27\nJun, 39, 31", 'description' => esc_html__('First column becomes labels. Remaining columns become datasets. Empty numeric cells are treated as 0.', 'calio-addon'), 'condition' => ['data_source' => 'csv']]);
        $this->add_control('csv_delimiter', ['label' => esc_html__('Delimiter', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'comma', 'options' => ['comma' => esc_html__('Comma', 'calio-addon'), 'semicolon' => esc_html__('Semicolon', 'calio-addon'), 'tab' => esc_html__('Tab', 'calio-addon')], 'condition' => ['data_source' => 'csv']]);
        $this->add_control('csv_skip_top_rows', ['label' => esc_html__('Skip Top Rows', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 0, 'min' => 0, 'condition' => ['data_source' => 'csv'], 'description' => esc_html__('Ignore extra report rows before header detection begins.', 'calio-addon')]);
        $this->add_control('csv_has_header', ['label' => esc_html__('First Row Has Series Labels', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['data_source' => 'csv']]);
        $this->add_control('csv_start_data_row', ['label' => esc_html__('Start Data Row', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 1, 'min' => 1, 'condition' => ['data_source' => 'csv'], 'description' => esc_html__('1-based row index to start importing from after skip rows and optional header handling.', 'calio-addon')]);
        $this->add_control('csv_row_limit', ['label' => esc_html__('Row Limit', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 0, 'min' => 0, 'condition' => ['data_source' => 'csv'], 'description' => esc_html__('Leave 0 to import all available rows.', 'calio-addon')]);
        $this->add_control('csv_stop_at_empty_row', ['label' => esc_html__('Stop At Empty Row', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '', 'condition' => ['data_source' => 'csv'], 'description' => esc_html__('Useful for pasted report blocks where a blank row marks the end of the data table.', 'calio-addon')]);
        $this->add_control('csv_first_column_labels', ['label' => esc_html__('First Column Contains Labels', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['data_source' => 'csv']]);
        $this->add_control('csv_label_column', ['label' => esc_html__('Label Column', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 1, 'min' => 1, 'condition' => ['data_source' => 'csv', 'csv_first_column_labels' => 'yes'], 'description' => esc_html__('1-based column index used for chart labels.', 'calio-addon')]);
        $this->add_control('csv_dataset_start_column', ['label' => esc_html__('Dataset Start Column', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 2, 'min' => 1, 'condition' => ['data_source' => 'csv'], 'description' => esc_html__('1-based column index where series values start.', 'calio-addon')]);
        $this->add_control('csv_dataset_end_column', ['label' => esc_html__('Dataset End Column', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 0, 'min' => 0, 'condition' => ['data_source' => 'csv'], 'description' => esc_html__('Leave 0 to use all remaining columns.', 'calio-addon')]);
        $this->add_control('csv_series_names', ['label' => esc_html__('Series Names Override', 'calio-addon'), 'type' => Controls_Manager::TEXTAREA, 'default' => '', 'condition' => ['data_source' => 'csv'], 'description' => esc_html__('Optional comma-separated names. Used when you want custom series labels instead of header cells.', 'calio-addon')]);
        $this->add_control('csv_series_style', ['label' => esc_html__('CSV Series Style', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'auto', 'options' => ['auto' => esc_html__('Auto', 'calio-addon'), 'bar' => esc_html__('Bar', 'calio-addon'), 'line' => esc_html__('Line', 'calio-addon')], 'condition' => ['data_source' => 'csv']]);
        $this->add_control('json_input', ['label' => esc_html__('JSON Data', 'calio-addon'), 'type' => Controls_Manager::TEXTAREA, 'rows' => 12, 'default' => "{\n  \"labels\": [\"Jan\", \"Feb\", \"Mar\", \"Apr\"],\n  \"datasets\": [\n    {\n      \"label\": \"Revenue\",\n      \"values\": [18, 24, 21, 28]\n    },\n    {\n      \"label\": \"Pipeline\",\n      \"values\": [12, 18, 16, 21],\n      \"renderAs\": \"line\"\n    }\n  ]\n}", 'description' => esc_html__('Expected shape: labels array plus datasets array with label and values. Optional keys: renderAs, stroke, fill, point.', 'calio-addon'), 'condition' => ['data_source' => 'json']]);
        $this->add_control('json_help_notice', [
            'type' => Controls_Manager::RAW_HTML,
            'raw' => wp_kses_post(
                '<div class="elementor-panel-alert elementor-panel-alert-info">' .
                '<strong>' . esc_html__('JSON Mapping Tips', 'calio-addon') . '</strong><br>' .
                esc_html__('Use dot paths such as report.labels or payload.chart.datasets. Default format expects labels + datasets at the root.', 'calio-addon') . '<br><br>' .
                '<strong>' . esc_html__('Root example', 'calio-addon') . '</strong><br><code>{ "labels": ["Jan"], "datasets": [{ "label": "Revenue", "values": [18] }] }</code><br><br>' .
                '<strong>' . esc_html__('Nested example', 'calio-addon') . '</strong><br><code>{ "report": { "chart": { "labels": ["Jan"], "series": [{ "name": "Revenue", "points": [18] }] } } }</code></div>'
            ),
            'condition' => ['data_source' => 'json'],
            'content_classes' => 'elementor-descriptor',
        ]);
        $this->add_control('json_dataset_path', ['label' => esc_html__('Dataset Array Key', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'datasets', 'condition' => ['data_source' => 'json'], 'description' => esc_html__('Root key that contains the dataset array. Leave default for the standard format.', 'calio-addon')]);
        $this->add_control('json_labels_path', ['label' => esc_html__('Labels Key', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'labels', 'condition' => ['data_source' => 'json'], 'description' => esc_html__('Root key used for the labels array.', 'calio-addon')]);
        $this->add_control('json_dataset_label_key', ['label' => esc_html__('Dataset Label Key', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'label', 'condition' => ['data_source' => 'json'], 'description' => esc_html__('Field name used for each dataset label.', 'calio-addon')]);
        $this->add_control('json_dataset_values_key', ['label' => esc_html__('Dataset Values Key', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'values', 'condition' => ['data_source' => 'json'], 'description' => esc_html__('Field name used for each dataset values array.', 'calio-addon')]);
        $this->add_control('json_dataset_render_key', ['label' => esc_html__('Dataset Render Key', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'renderAs', 'condition' => ['data_source' => 'json'], 'description' => esc_html__('Optional field name for per-series style such as bar or line.', 'calio-addon')]);
        $this->add_control('json_series_style_fallback', ['label' => esc_html__('JSON Series Style Fallback', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'auto', 'options' => ['auto' => esc_html__('Auto', 'calio-addon'), 'bar' => esc_html__('Bar', 'calio-addon'), 'line' => esc_html__('Line', 'calio-addon')], 'condition' => ['data_source' => 'json']]);
        $this->add_control('json_nested_example_notice', [
            'type' => Controls_Manager::RAW_HTML,
            'raw' => wp_kses_post(
                '<div class="elementor-panel-alert elementor-panel-alert-warning">' .
                '<strong>' . esc_html__('Nested Mapping Example', 'calio-addon') . '</strong><br>' .
                esc_html__('For the nested example above, use:', 'calio-addon') .
                '<br><code>' . esc_html__('Labels Key: report.chart.labels', 'calio-addon') . '</code>' .
                '<br><code>' . esc_html__('Dataset Array Key: report.chart.series', 'calio-addon') . '</code>' .
                '<br><code>' . esc_html__('Dataset Label Key: name', 'calio-addon') . '</code>' .
                '<br><code>' . esc_html__('Dataset Values Key: points', 'calio-addon') . '</code></div>'
            ),
            'condition' => ['data_source' => 'json'],
            'content_classes' => 'elementor-descriptor',
        ]);
        $this->add_control('empty_message', ['label' => esc_html__('Empty Message', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Add labels and at least one data series to display the chart.', 'calio-addon')]);
        $this->end_controls_section();

        $this->start_controls_section('annotation_section', ['label' => esc_html__('Annotations', 'calio-addon')]);
        $this->add_control('show_summary_strip', ['label' => esc_html__('Show Summary Strip', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('summary_label', ['label' => esc_html__('Summary Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Delta', 'calio-addon'), 'condition' => ['show_summary_strip' => 'yes']]);
        $this->add_control('summary_value', ['label' => esc_html__('Summary Value', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => '+14.2%', 'condition' => ['show_summary_strip' => 'yes']]);
        $this->add_control('summary_note', ['label' => esc_html__('Summary Note', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('vs previous period', 'calio-addon'), 'condition' => ['show_summary_strip' => 'yes']]);
        $this->add_control('summary_trend', ['label' => esc_html__('Summary Trend', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'up', 'options' => ['up' => esc_html__('Up', 'calio-addon'), 'down' => esc_html__('Down', 'calio-addon'), 'neutral' => esc_html__('Neutral', 'calio-addon')], 'condition' => ['show_summary_strip' => 'yes']]);
        $this->add_control('show_kpi_cards', ['label' => esc_html__('Show KPI Cards', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('kpi_columns', ['label' => esc_html__('KPI Columns', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => '3', 'options' => ['2' => '2', '3' => '3', '4' => '4'], 'condition' => ['show_kpi_cards' => 'yes']]);
        $kpi_repeater = new Repeater();
        $kpi_repeater->add_control('label', ['label' => esc_html__('Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Conversion', 'calio-addon'), 'label_block' => true]);
        $kpi_repeater->add_control('value', ['label' => esc_html__('Value', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => '4.8%']);
        $kpi_repeater->add_control('note', ['label' => esc_html__('Note', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('From last 30 days', 'calio-addon')]);
        $kpi_repeater->add_control('trend', ['label' => esc_html__('Trend', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'up', 'options' => ['up' => esc_html__('Up', 'calio-addon'), 'down' => esc_html__('Down', 'calio-addon'), 'neutral' => esc_html__('Neutral', 'calio-addon')]]);
        $this->add_control('kpi_cards', ['label' => esc_html__('KPI Cards', 'calio-addon'), 'type' => Controls_Manager::REPEATER, 'fields' => $kpi_repeater->get_controls(), 'default' => $this->get_default_kpis(), 'title_field' => '{{{ label }}}', 'condition' => ['show_kpi_cards' => 'yes']]);
        $this->add_control('show_target_line', ['label' => esc_html__('Show Target Line', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('target_value', ['label' => esc_html__('Target Value', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 30, 'condition' => ['show_target_line' => 'yes']]);
        $this->add_control('target_label', ['label' => esc_html__('Target Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Target', 'calio-addon'), 'condition' => ['show_target_line' => 'yes']]);
        $this->add_control('show_range_highlight', ['label' => esc_html__('Show Range Highlight', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => '']);
        $this->add_control('range_start_label', ['label' => esc_html__('Range Start Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'Mar', 'condition' => ['show_range_highlight' => 'yes']]);
        $this->add_control('range_end_label', ['label' => esc_html__('Range End Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'May', 'condition' => ['show_range_highlight' => 'yes']]);
        $this->add_control('range_label', ['label' => esc_html__('Range Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Campaign Window', 'calio-addon'), 'condition' => ['show_range_highlight' => 'yes']]);
        $this->add_control('show_event_markers', ['label' => esc_html__('Show Event Markers', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $marker_repeater = new Repeater();
        $marker_repeater->add_control('label_match', ['label' => esc_html__('Target Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => 'Apr']);
        $marker_repeater->add_control('title', ['label' => esc_html__('Marker Title', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Launch', 'calio-addon'), 'label_block' => true]);
        $marker_repeater->add_control('note', ['label' => esc_html__('Marker Note', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Campaign goes live', 'calio-addon')]);
        $marker_repeater->add_control('tone', ['label' => esc_html__('Tone', 'calio-addon'), 'type' => Controls_Manager::SELECT, 'default' => 'accent', 'options' => ['accent' => esc_html__('Accent', 'calio-addon'), 'success' => esc_html__('Success', 'calio-addon'), 'warning' => esc_html__('Warning', 'calio-addon')]]);
        $this->add_control('event_markers', ['label' => esc_html__('Markers', 'calio-addon'), 'type' => Controls_Manager::REPEATER, 'fields' => $marker_repeater->get_controls(), 'default' => $this->get_default_markers(), 'title_field' => '{{{ title }}}', 'condition' => ['show_event_markers' => 'yes']]);
        $this->end_controls_section();

        $this->start_controls_section('utility_section', ['label' => esc_html__('Utilities', 'calio-addon')]);
        $this->add_control('show_export', ['label' => esc_html__('Show Export Button', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('export_label', ['label' => esc_html__('Export Label', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Download PNG', 'calio-addon'), 'condition' => ['show_export' => 'yes']]);
        $this->add_control('show_data_preview', ['label' => esc_html__('Show Data Preview', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('preview_title', ['label' => esc_html__('Preview Title', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Parsed Preview', 'calio-addon'), 'condition' => ['show_data_preview' => 'yes']]);
        $this->add_control('preview_rows', ['label' => esc_html__('Preview Rows', 'calio-addon'), 'type' => Controls_Manager::NUMBER, 'default' => 4, 'min' => 1, 'max' => 12, 'condition' => ['show_data_preview' => 'yes']]);
        $this->add_control('show_data_table', ['label' => esc_html__('Show Data Table', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes']);
        $this->add_control('table_title', ['label' => esc_html__('Table Title', 'calio-addon'), 'type' => Controls_Manager::TEXT, 'default' => esc_html__('Chart Data', 'calio-addon'), 'condition' => ['show_data_table' => 'yes']]);
        $this->add_control('table_collapsed', ['label' => esc_html__('Collapse Table Initially', 'calio-addon'), 'type' => Controls_Manager::SWITCHER, 'return_value' => 'yes', 'default' => 'yes', 'condition' => ['show_data_table' => 'yes']]);
        $this->end_controls_section();

        $this->start_controls_section('style_card', ['label' => esc_html__('Card', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_style_heading('fancy_chart_card_spacing_heading', esc_html__('Shape & Spacing', 'calio-addon'));
        $this->add_dimensions_control('card_padding', esc_html__('Padding', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart', 'padding');
        $this->add_dimensions_control('card_margin', esc_html__('Margin', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart', 'margin');
        $this->add_dimensions_control('card_radius', esc_html__('Radius', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart', 'border-radius', ['size_units' => ['px', '%']]);
        $this->add_style_heading('fancy_chart_card_surface_heading', esc_html__('Surface States', 'calio-addon'));
        $this->start_controls_tabs('fancy_chart_card_tabs');
        $this->start_controls_tab('fancy_chart_card_tab_normal', ['label' => esc_html__('Normal', 'calio-addon')]);
        $this->add_background_type_control('fancy_chart_card_bg_group', '{{WRAPPER}} .calio-fancy-chart');
        $this->add_border_paint_controls('fancy_chart_card_border_normal', esc_html__('Border Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart');
        $this->end_controls_tab();
        $this->start_controls_tab('fancy_chart_card_tab_hover', ['label' => esc_html__('Hover', 'calio-addon')]);
        $this->add_background_type_control('fancy_chart_card_bg_hover_group', '{{WRAPPER}} .calio-fancy-chart:hover');
        $this->add_border_paint_controls('fancy_chart_card_border_hover', esc_html__('Border Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart:hover');
        $this->end_controls_tab();
        $this->start_controls_tab('fancy_chart_card_tab_active', ['label' => esc_html__('Active', 'calio-addon')]);
        $this->add_background_type_control('fancy_chart_card_bg_active_group', '{{WRAPPER}} .calio-fancy-chart:focus-within');
        $this->add_border_paint_controls('fancy_chart_card_border_active', esc_html__('Border Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart:focus-within');
        $this->end_controls_tab();
        $this->end_controls_tabs();
        $this->add_style_heading('fancy_chart_card_outline_heading', esc_html__('Border Type & Shadow', 'calio-addon'));
        $this->add_group_control(Group_Control_Border::get_type(), ['name' => 'card_border', 'selector' => '{{WRAPPER}} .calio-fancy-chart']);
        $this->add_group_control(Group_Control_Box_Shadow::get_type(), ['name' => 'card_shadow', 'selector' => '{{WRAPPER}} .calio-fancy-chart']);
        $this->end_controls_section();

        $this->start_controls_section('style_header', ['label' => esc_html__('Header', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'title_typography', 'selector' => '{{WRAPPER}} .calio-fancy-chart-title']);
        $this->add_text_paint_controls('fancy_chart_title_text', esc_html__('Title Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-title');
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'subtitle_typography', 'selector' => '{{WRAPPER}} .calio-fancy-chart-subtitle']);
        $this->add_text_paint_controls('fancy_chart_subtitle_text', esc_html__('Subtitle Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-subtitle');
        $this->add_dimensions_control('header_margin', esc_html__('Header Margin', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-header', 'margin');
        $this->end_controls_section();

        $this->start_controls_section('style_summary', ['label' => esc_html__('Summary Strip', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'summary_typography', 'selector' => '{{WRAPPER}} .calio-fancy-chart-summary']);
        $this->add_text_paint_controls('fancy_chart_summary_text', esc_html__('Text Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-summary');
        $this->add_background_type_control('fancy_chart_summary_bg_group', '{{WRAPPER}} .calio-fancy-chart-summary');
        $this->add_text_paint_controls('fancy_chart_summary_value_text', esc_html__('Value Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-summary-value');
        $this->add_dimensions_control('summary_padding', esc_html__('Padding', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-summary', 'padding');
        $this->add_dimensions_control('summary_radius', esc_html__('Radius', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-summary', 'border-radius', ['size_units' => ['px', '%']]);
        $this->end_controls_section();

        $this->start_controls_section('style_kpi', ['label' => esc_html__('KPI Cards', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_control('kpi_gap', ['label' => esc_html__('Gap', 'calio-addon'), 'type' => Controls_Manager::SLIDER, 'range' => ['px' => ['min' => 0, 'max' => 32]], 'selectors' => ['{{WRAPPER}} .calio-fancy-chart-kpis' => 'gap: {{SIZE}}{{UNIT}};']]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'kpi_typography', 'selector' => '{{WRAPPER}} .calio-fancy-chart-kpi']);
        $this->add_text_paint_controls('fancy_chart_kpi_text', esc_html__('Text Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-kpi');
        $this->add_background_type_control('fancy_chart_kpi_bg_group', '{{WRAPPER}} .calio-fancy-chart-kpi');
        $this->add_text_paint_controls('fancy_chart_kpi_value_text', esc_html__('Value Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-kpi-value');
        $this->add_dimensions_control('kpi_padding', esc_html__('Padding', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-kpi', 'padding');
        $this->add_dimensions_control('kpi_radius', esc_html__('Radius', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-kpi', 'border-radius', ['size_units' => ['px', '%']]);
        $this->add_group_control(Group_Control_Border::get_type(), ['name' => 'kpi_border', 'selector' => '{{WRAPPER}} .calio-fancy-chart-kpi']);
        $this->add_group_control(Group_Control_Box_Shadow::get_type(), ['name' => 'kpi_shadow', 'selector' => '{{WRAPPER}} .calio-fancy-chart-kpi']);
        $this->end_controls_section();

        $this->start_controls_section('style_legend', ['label' => esc_html__('Legend', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'legend_typography', 'selector' => '{{WRAPPER}} .calio-fancy-chart-legend-item']);
        $this->add_text_paint_controls('fancy_chart_legend_text', esc_html__('Text Color', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-legend-item');
        $this->add_background_type_control('fancy_chart_legend_bg_group', '{{WRAPPER}} .calio-fancy-chart-legend-item');
        $this->add_dimensions_control('legend_padding', esc_html__('Item Padding', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-legend-item', 'padding');
        $this->add_dimensions_control('legend_radius', esc_html__('Item Radius', 'calio-addon'), '{{WRAPPER}} .calio-fancy-chart-legend-item', 'border-radius', ['size_units' => ['px', '%']]);
        $this->add_control('legend_gap', ['label' => esc_html__('Gap', 'calio-addon'), 'type' => Controls_Manager::SLIDER, 'range' => ['px' => ['min' => 0, 'max' => 32]], 'selectors' => ['{{WRAPPER}} .calio-fancy-chart-legend' => 'gap: {{SIZE}}{{UNIT}};']]);
        $this->end_controls_section();

        $this->start_controls_section('style_chart', ['label' => esc_html__('Chart Surface', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_control('grid_color', ['label' => esc_html__('Grid Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => 'rgba(148, 163, 184, 0.18)']);
        $this->add_control('axis_color', ['label' => esc_html__('Axis Stroke Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => 'rgba(148, 163, 184, 0.55)']);
        $this->add_control('value_color', ['label' => esc_html__('Value Label Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#0f172a']);
        $this->add_control('target_line_color', ['label' => esc_html__('Target Line Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#dc2626']);
        $this->add_control('target_label_color', ['label' => esc_html__('Target Label Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#dc2626']);
        $this->add_control('range_fill_color', ['label' => esc_html__('Range Fill Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => 'rgba(37, 99, 235, 0.08)']);
        $this->add_control('range_label_color', ['label' => esc_html__('Range Label Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#2563eb']);
        $this->add_control('marker_line_color', ['label' => esc_html__('Marker Line Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#0f172a']);
        $this->add_control('marker_label_color', ['label' => esc_html__('Marker Label Color', 'calio-addon'), 'type' => Controls_Manager::COLOR, 'default' => '#0f172a']);
        $this->add_control('canvas_inner_padding', ['label' => esc_html__('Inner Padding', 'calio-addon'), 'type' => Controls_Manager::SLIDER, 'range' => ['px' => ['min' => 0, 'max' => 48]], 'default' => ['size' => 12, 'unit' => 'px']]);
        $this->end_controls_section();

        $this->start_controls_section('style_utilities', ['label' => esc_html__('Utilities', 'calio-addon'), 'tab' => Controls_Manager::TAB_STYLE]);
        $this->add_group_control(Group_Control_Typography::get_type(), ['name' => 'utility_typography', 'selector' => '{{WRAPPER}} .calio-fancy-chart-export, {{WRAPPER}} .calio-fancy-chart-table summary, {{WRAPPER}} .calio-fancy-chart-table']);
        $this->add_text_paint_controls('fancy_chart_utility_text', esc_html__('Text Color', 'calio-addon'), ['{{WRAPPER}} .calio-fancy-chart-export', '{{WRAPPER}} .calio-fancy-chart-table', '{{WRAPPER}} .calio-fancy-chart-table summary', '{{WRAPPER}} .calio-fancy-chart-preview']);
        $this->add_background_type_control('fancy_chart_utility_bg_group', ['{{WRAPPER}} .calio-fancy-chart-export', '{{WRAPPER}} .calio-fancy-chart-table', '{{WRAPPER}} .calio-fancy-chart-preview']);
        $this->add_dimensions_control('utility_radius', esc_html__('Radius', 'calio-addon'), ['{{WRAPPER}} .calio-fancy-chart-export', '{{WRAPPER}} .calio-fancy-chart-table', '{{WRAPPER}} .calio-fancy-chart-preview'], 'border-radius', ['size_units' => ['px', '%']]);
        $this->end_controls_section();
    }

    /**
     * Outputs the chart shell and data payload.
     */
    protected function render(): void
    {
        $settings = $this->get_settings_for_display();
        $chart_data = $this->build_chart_payload($settings);

        if (empty($chart_data['labels']) || empty($chart_data['datasets'])) {
            echo '<div class="calio-fancy-chart-empty">' . esc_html((string) ($settings['empty_message'] ?? esc_html__('Add labels and at least one data series to display the chart.', 'calio-addon'))) . '</div>';
            return;
        }

        $classes = [
            'calio-fancy-chart',
            'preset-' . sanitize_html_class((string) ($settings['preset_pack'] ?? 'analytics')),
            'engine-' . sanitize_html_class((string) ($settings['chart_engine'] ?? 'custom')),
            'skin-' . sanitize_html_class((string) ($settings['skin'] ?? 'soft')),
            'legend-' . sanitize_html_class((string) ($settings['legend_position'] ?? 'bottom')),
            'type-' . sanitize_html_class((string) ($settings['chart_type'] ?? 'bar')),
        ];

        $canvas_style = sprintf(
            '--calio-chart-grid:%s;--calio-chart-axis:%s;--calio-chart-value:%s;--calio-chart-target:%s;--calio-chart-target-label:%s;--calio-chart-range:%s;--calio-chart-range-label:%s;--calio-chart-marker:%s;--calio-chart-marker-label:%s;--calio-chart-pad:%dpx;',
            esc_attr($this->sanitize_chart_color((string) ($settings['grid_color'] ?? ''), 'rgba(148, 163, 184, 0.18)')),
            esc_attr($this->sanitize_chart_color((string) ($settings['axis_color'] ?? ''), 'rgba(148, 163, 184, 0.55)')),
            esc_attr($this->sanitize_chart_color((string) ($settings['value_color'] ?? ''), '#0f172a')),
            esc_attr($this->sanitize_chart_color((string) ($settings['target_line_color'] ?? ''), '#dc2626')),
            esc_attr($this->sanitize_chart_color((string) ($settings['target_label_color'] ?? ''), '#dc2626')),
            esc_attr($this->sanitize_chart_color((string) ($settings['range_fill_color'] ?? ''), 'rgba(37, 99, 235, 0.08)')),
            esc_attr($this->sanitize_chart_color((string) ($settings['range_label_color'] ?? ''), '#2563eb')),
            esc_attr($this->sanitize_chart_color((string) ($settings['marker_line_color'] ?? ''), '#0f172a')),
            esc_attr($this->sanitize_chart_color((string) ($settings['marker_label_color'] ?? ''), '#0f172a')),
            (int) (($settings['canvas_inner_padding']['size'] ?? 12))
        );

        $chart_json = wp_json_encode($chart_data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
        echo '<div class="' . esc_attr(implode(' ', $classes)) . '" data-chart="' . esc_attr(is_string($chart_json) ? $chart_json : '{}') . '">';
        $preset_badge_text = trim((string) ($settings['preset_badge_text'] ?? ''));
        if (!empty($settings['title']) || !empty($settings['subtitle']) || (($settings['show_preset_badge'] ?? 'yes') === 'yes')) {
            echo '<div class="calio-fancy-chart-header">';
            if (($settings['show_preset_badge'] ?? 'yes') === 'yes') {
                echo '<span class="calio-fancy-chart-preset-badge">' . esc_html($preset_badge_text !== '' ? $preset_badge_text : $this->get_preset_label((string) ($settings['preset_pack'] ?? 'analytics'))) . '</span>';
            }
            if (!empty($settings['title'])) {
                echo '<h3 class="calio-fancy-chart-title">' . esc_html((string) $settings['title']) . '</h3>';
            }
            if (!empty($settings['subtitle'])) {
                echo '<div class="calio-fancy-chart-subtitle">' . esc_html((string) $settings['subtitle']) . '</div>';
            }
            echo '</div>';
        }
        if (($settings['show_summary_strip'] ?? 'yes') === 'yes' && (!empty($settings['summary_label']) || !empty($settings['summary_value']) || !empty($settings['summary_note']))) {
            echo '<div class="calio-fancy-chart-summary trend-' . esc_attr(sanitize_html_class((string) ($settings['summary_trend'] ?? 'up'))) . '">';
            if (!empty($settings['summary_label'])) {
                echo '<span class="calio-fancy-chart-summary-label">' . esc_html((string) $settings['summary_label']) . '</span>';
            }
            if (!empty($settings['summary_value'])) {
                echo '<span class="calio-fancy-chart-summary-value">' . esc_html((string) $settings['summary_value']) . '</span>';
            }
            if (!empty($settings['summary_note'])) {
                echo '<span class="calio-fancy-chart-summary-note">' . esc_html((string) $settings['summary_note']) . '</span>';
            }
            echo '</div>';
        }
        $kpi_cards = is_array($settings['kpi_cards'] ?? null) ? $settings['kpi_cards'] : [];
        if (($settings['show_kpi_cards'] ?? 'yes') === 'yes' && !empty($kpi_cards)) {
            echo '<div class="calio-fancy-chart-kpis cols-' . esc_attr(sanitize_html_class((string) ($settings['kpi_columns'] ?? '3'))) . '">';
            foreach ($kpi_cards as $kpi) {
                if (!is_array($kpi)) {
                    continue;
                }

                echo '<div class="calio-fancy-chart-kpi trend-' . esc_attr(sanitize_html_class((string) ($kpi['trend'] ?? 'neutral'))) . '">';
                if (!empty($kpi['label'])) {
                    echo '<div class="calio-fancy-chart-kpi-label">' . esc_html((string) $kpi['label']) . '</div>';
                }
                if (!empty($kpi['value'])) {
                    echo '<div class="calio-fancy-chart-kpi-value">' . esc_html((string) $kpi['value']) . '</div>';
                }
                if (!empty($kpi['note'])) {
                    echo '<div class="calio-fancy-chart-kpi-note">' . esc_html((string) $kpi['note']) . '</div>';
                }
                echo '</div>';
            }
            echo '</div>';
        }
        if (($settings['show_export'] ?? 'yes') === 'yes') {
            echo '<div class="calio-fancy-chart-tools">';
            echo '<button type="button" class="calio-fancy-chart-export" data-export-label="' . esc_attr((string) ($settings['export_label'] ?? esc_html__('Download PNG', 'calio-addon'))) . '">' . esc_html((string) ($settings['export_label'] ?? esc_html__('Download PNG', 'calio-addon'))) . '</button>';
            echo '</div>';
        }
        if (($settings['show_data_preview'] ?? 'yes') === 'yes') {
            $preview_rows = max(1, min(12, (int) ($settings['preview_rows'] ?? 4)));
            $preview_source = $this->get_data_source_label((string) ($settings['data_source'] ?? 'manual'));
            $preview_messages = $this->get_chart_validation_messages($chart_data, $settings);
            echo '<div class="calio-fancy-chart-preview">';
            echo '<div class="calio-fancy-chart-preview-head">';
            echo '<strong class="calio-fancy-chart-preview-title">' . esc_html((string) ($settings['preview_title'] ?? esc_html__('Parsed Preview', 'calio-addon'))) . '</strong>';
            echo '<span class="calio-fancy-chart-preview-meta">' . esc_html($preview_source . ' - ' . sprintf(
                /* translators: 1: label count, 2: dataset count. */
                esc_html__('%1$d labels / %2$d series', 'calio-addon'),
                count($chart_data['labels']),
                count($chart_data['datasets'])
            )) . '</span>';
            echo '</div>';
            if (!empty($preview_messages)) {
                echo '<div class="calio-fancy-chart-preview-notes">';
                foreach ($preview_messages as $message) {
                    echo '<div class="calio-fancy-chart-preview-note">' . esc_html($message) . '</div>';
                }
                echo '</div>';
            }
            echo '<div class="calio-fancy-chart-preview-wrap"><table><thead><tr><th>' . esc_html__('Label', 'calio-addon') . '</th>';
            foreach ($chart_data['datasets'] as $dataset) {
                echo '<th>' . esc_html((string) ($dataset['label'] ?? esc_html__('Series', 'calio-addon'))) . '</th>';
            }
            echo '</tr></thead><tbody>';
            foreach (array_slice($chart_data['labels'], 0, $preview_rows, true) as $index => $label) {
                echo '<tr><td>' . esc_html((string) $label) . '</td>';
                foreach ($chart_data['datasets'] as $dataset) {
                    $value = $dataset['values'][$index] ?? '';
                    echo '<td>' . esc_html((string) $value) . '</td>';
                }
                echo '</tr>';
            }
            echo '</tbody></table></div>';
            echo '</div>';
        }
        echo '<div class="calio-fancy-chart-body">';
        if (($settings['legend_position'] ?? 'bottom') !== 'hidden') {
            echo '<div class="calio-fancy-chart-legend" data-legend></div>';
        }
        echo '<div class="calio-fancy-chart-canvas-wrap" style="' . esc_attr($canvas_style) . '"><canvas class="calio-fancy-chart-canvas" aria-label="' . esc_attr((string) ($settings['title'] ?? esc_html__('Chart', 'calio-addon'))) . '"></canvas><div class="calio-fancy-chart-tooltip" data-tooltip hidden></div></div>';
        echo '</div>';
        if (($settings['show_data_table'] ?? 'yes') === 'yes') {
            $collapsed = (($settings['table_collapsed'] ?? 'yes') === 'yes');
            echo '<details class="calio-fancy-chart-table"';
            if (!$collapsed) {
                echo ' open';
            }
            echo '>';
            echo '<summary>' . esc_html((string) ($settings['table_title'] ?? esc_html__('Chart Data', 'calio-addon'))) . '</summary>';
            echo '<div class="calio-fancy-chart-table-wrap"><table><thead><tr><th>' . esc_html__('Label', 'calio-addon') . '</th>';
            foreach ($chart_data['datasets'] as $dataset) {
                echo '<th>' . esc_html((string) ($dataset['label'] ?? esc_html__('Series', 'calio-addon'))) . '</th>';
            }
            echo '</tr></thead><tbody>';
            foreach ($chart_data['labels'] as $index => $label) {
                echo '<tr><td>' . esc_html((string) $label) . '</td>';
                foreach ($chart_data['datasets'] as $dataset) {
                    $value = $dataset['values'][$index] ?? '';
                    echo '<td>' . esc_html((string) $value) . '</td>';
                }
                echo '</tr>';
            }
            echo '</tbody></table></div></details>';
        }
        echo '</div>';
    }

    /**
     * Returns starter dataset content.
     *
     * @return array<int, array<string, string>>
     */
    private function get_default_datasets(): array
    {
        return [
            [
                'label' => esc_html__('Revenue', 'calio-addon'),
                'values' => '18, 24, 21, 28, 34, 39',
                'render_as' => 'bar',
                'stroke' => '#2563eb',
                'fill' => 'rgba(37, 99, 235, 0.18)',
                'point' => '#ffffff',
            ],
            [
                'label' => esc_html__('Pipeline', 'calio-addon'),
                'values' => '12, 18, 16, 21, 27, 31',
                'render_as' => 'line',
                'stroke' => '#0f766e',
                'fill' => 'rgba(15, 118, 110, 0.16)',
                'point' => '#ffffff',
            ],
        ];
    }

    /**
     * Returns starter KPI card content.
     *
     * @return array<int, array<string, string>>
     */
    private function get_default_kpis(): array
    {
        return [
            [
                'label' => esc_html__('Conversion', 'calio-addon'),
                'value' => '4.8%',
                'note' => esc_html__('From last 30 days', 'calio-addon'),
                'trend' => 'up',
            ],
            [
                'label' => esc_html__('CAC', 'calio-addon'),
                'value' => '$42',
                'note' => esc_html__('4% lower than target', 'calio-addon'),
                'trend' => 'down',
            ],
            [
                'label' => esc_html__('Retention', 'calio-addon'),
                'value' => '82%',
                'note' => esc_html__('Holding steady', 'calio-addon'),
                'trend' => 'neutral',
            ],
        ];
    }

    /**
     * Returns starter event marker content.
     *
     * @return array<int, array<string, string>>
     */
    private function get_default_markers(): array
    {
        return [
            [
                'label_match' => 'Apr',
                'title' => esc_html__('Launch', 'calio-addon'),
                'note' => esc_html__('Campaign goes live', 'calio-addon'),
                'tone' => 'accent',
            ],
            [
                'label_match' => 'Jun',
                'title' => esc_html__('Milestone', 'calio-addon'),
                'note' => esc_html__('Target crossed', 'calio-addon'),
                'tone' => 'success',
            ],
        ];
    }

    /**
     * Returns a human-readable label for the selected preset pack.
     */
    private function get_preset_label(string $preset): string
    {
        $labels = [
            'analytics' => esc_html__('Analytics Pack', 'calio-addon'),
            'finance' => esc_html__('Finance Pack', 'calio-addon'),
            'product' => esc_html__('Product Pack', 'calio-addon'),
            'saas' => esc_html__('SaaS Pack', 'calio-addon'),
            'custom' => esc_html__('Custom Pack', 'calio-addon'),
        ];

        return $labels[$preset] ?? esc_html__('Analytics Pack', 'calio-addon');
    }

    /**
     * Returns a human-readable label for the selected data source.
     */
    private function get_data_source_label(string $source): string
    {
        $labels = [
            'manual' => esc_html__('Manual', 'calio-addon'),
            'sample' => esc_html__('Sample Preset', 'calio-addon'),
            'csv' => esc_html__('CSV Import', 'calio-addon'),
            'json' => esc_html__('JSON Import', 'calio-addon'),
        ];

        return $labels[$source] ?? esc_html__('Manual', 'calio-addon');
    }

    /**
     * Builds lightweight validation messages for the parsed preview surface.
     *
     * @param array<string, mixed> $chart_data Normalized chart payload.
     * @param array<string, mixed> $settings Widget settings.
     * @return array<int, string>
     */
    private function get_chart_validation_messages(array $chart_data, array $settings): array
    {
        $messages = [];
        $label_count = count((array) ($chart_data['labels'] ?? []));
        $chart_type = sanitize_key((string) ($settings['chart_type'] ?? 'bar'));
        $dataset_count = count((array) ($chart_data['datasets'] ?? []));

        foreach ((array) ($chart_data['datasets'] ?? []) as $dataset) {
            if (!is_array($dataset)) {
                continue;
            }

            $dataset_label = sanitize_text_field((string) ($dataset['label'] ?? esc_html__('Series', 'calio-addon')));
            $value_count = count((array) ($dataset['values'] ?? []));

            if ($label_count !== $value_count) {
                $messages[] = sprintf(
                    /* translators: 1: dataset label, 2: value count, 3: label count */
                    /* translators: 1: dataset label, 2: dataset value count, 3: chart label count. */
                    esc_html__('%1$s has %2$d values while the chart has %3$d labels.', 'calio-addon'),
                    $dataset_label,
                    $value_count,
                    $label_count
                );
            }
        }

        if (in_array($chart_type, ['pie', 'donut', 'polar'], true) && $dataset_count > 1) {
            $messages[] = esc_html__('Pie, donut and polar charts currently visualize the first dataset only.', 'calio-addon');
        }

        if (!in_array($chart_type, ['bar', 'line', 'area', 'mixed'], true)) {
            if (($settings['show_target_line'] ?? 'yes') === 'yes') {
                $messages[] = esc_html__('Target line works only on bar, line, area and mixed charts.', 'calio-addon');
            }

            if (($settings['show_range_highlight'] ?? '') === 'yes') {
                $messages[] = esc_html__('Range highlight works only on bar, line, area and mixed charts.', 'calio-addon');
            }

            if (($settings['show_event_markers'] ?? 'yes') === 'yes') {
                $messages[] = esc_html__('Event markers work only on bar, line, area and mixed charts.', 'calio-addon');
            }
        }

        return array_values(array_unique($messages));
    }

    /**
     * Converts control values into a normalized chart payload.
     *
     * @param array<string, mixed> $settings Widget settings.
     * @return array<string, mixed>
     */
    private function build_chart_payload(array $settings): array
    {
        $data_source = sanitize_key((string) ($settings['data_source'] ?? 'manual'));
        $chart_data = match ($data_source) {
            'sample' => $this->build_sample_chart_data($settings),
            'csv' => $this->build_csv_chart_data($settings),
            'json' => $this->build_json_chart_data($settings),
            default => $this->build_manual_chart_data($settings),
        };
        $labels = $chart_data['labels'];
        $datasets = $chart_data['datasets'];

        $markers = [];
        foreach ((array) ($settings['event_markers'] ?? []) as $marker) {
            if (!is_array($marker) || empty($marker['label_match']) || empty($marker['title'])) {
                continue;
            }

            $markers[] = [
                'labelMatch' => sanitize_text_field((string) $marker['label_match']),
                'title' => sanitize_text_field((string) $marker['title']),
                'note' => sanitize_text_field((string) ($marker['note'] ?? '')),
                'tone' => sanitize_key((string) ($marker['tone'] ?? 'accent')),
            ];
        }

        return [
            'engine' => sanitize_key((string) ($settings['chart_engine'] ?? 'custom')),
            'type' => sanitize_key((string) ($settings['chart_type'] ?? 'bar')),
            'showGrid' => (($settings['show_grid'] ?? 'yes') === 'yes'),
            'showAxes' => (($settings['show_axes'] ?? 'yes') === 'yes'),
            'showPoints' => (($settings['show_points'] ?? 'yes') === 'yes'),
            'showValues' => (($settings['show_values'] ?? '') === 'yes'),
            'showTooltip' => (($settings['show_tooltip'] ?? 'yes') === 'yes'),
            'hoverEmphasis' => sanitize_key((string) ($settings['hover_emphasis'] ?? 'soft')),
            'animate' => (($settings['enable_animation'] ?? 'yes') === 'yes'),
            'animationDuration' => max(200, min(2200, (int) (($settings['animation_duration']['size'] ?? 900)))),
            'showTargetLine' => (($settings['show_target_line'] ?? 'yes') === 'yes'),
            'targetValue' => (float) ($settings['target_value'] ?? 0),
            'targetLabel' => sanitize_text_field((string) ($settings['target_label'] ?? esc_html__('Target', 'calio-addon'))),
            'showRangeHighlight' => (($settings['show_range_highlight'] ?? '') === 'yes'),
            'rangeStartLabel' => sanitize_text_field((string) ($settings['range_start_label'] ?? '')),
            'rangeEndLabel' => sanitize_text_field((string) ($settings['range_end_label'] ?? '')),
            'rangeLabel' => sanitize_text_field((string) ($settings['range_label'] ?? '')),
            'showEventMarkers' => (($settings['show_event_markers'] ?? 'yes') === 'yes'),
            'markers' => $markers,
            'legendPosition' => sanitize_key((string) ($settings['legend_position'] ?? 'bottom')),
            'labels' => $labels,
            'datasets' => $datasets,
        ];
    }

    /**
     * Normalizes manually entered series input.
     *
     * @param array<string, mixed> $settings Widget settings.
     * @return array{labels: array<int, string>, datasets: array<int, array<string, mixed>>}
     */
    private function build_manual_chart_data(array $settings): array
    {
        $labels = $this->parse_csv_strings((string) ($settings['labels_csv'] ?? ''));
        $datasets = [];

        foreach ((array) ($settings['datasets'] ?? []) as $dataset) {
            if (!is_array($dataset)) {
                continue;
            }

            $values = $this->parse_csv_numbers((string) ($dataset['values'] ?? ''));
            if (empty($values)) {
                continue;
            }

            $datasets[] = [
                'label' => sanitize_text_field((string) ($dataset['label'] ?? 'Series')),
                'values' => $values,
                'renderAs' => sanitize_key((string) ($dataset['render_as'] ?? 'auto')),
                'stroke' => $this->sanitize_chart_color((string) ($dataset['stroke'] ?? ''), '#2563eb'),
                'fill' => $this->sanitize_chart_color((string) ($dataset['fill'] ?? ''), 'rgba(37, 99, 235, 0.18)'),
                'point' => $this->sanitize_chart_color((string) ($dataset['point'] ?? ''), '#ffffff'),
            ];
        }

        return [
            'labels' => $labels,
            'datasets' => $datasets,
        ];
    }

    /**
     * Returns preset sample data for quick chart bootstrapping.
     *
     * @param array<string, mixed> $settings Widget settings.
     * @return array{labels: array<int, string>, datasets: array<int, array<string, mixed>>}
     */
    private function build_sample_chart_data(array $settings): array
    {
        $palette = $this->get_chart_palette();
        $preset = sanitize_key((string) ($settings['sample_preset'] ?? 'analytics_growth'));

        $presets = [
            'analytics_growth' => [
                'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
                'datasets' => [
                    ['label' => esc_html__('Sessions', 'calio-addon'), 'values' => [12000, 14800, 15600, 17100, 19400, 22100], 'renderAs' => 'bar'],
                    ['label' => esc_html__('Conversion Rate', 'calio-addon'), 'values' => [2.8, 3.1, 3.0, 3.3, 3.6, 3.9], 'renderAs' => 'line'],
                ],
            ],
            'finance_forecast' => [
                'labels' => ['Q1', 'Q2', 'Q3', 'Q4'],
                'datasets' => [
                    ['label' => esc_html__('Actual Revenue', 'calio-addon'), 'values' => [320, 410, 460, 520], 'renderAs' => 'bar'],
                    ['label' => esc_html__('Forecast', 'calio-addon'), 'values' => [300, 390, 455, 540], 'renderAs' => 'line'],
                ],
            ],
            'product_engagement' => [
                'labels' => ['Week 1', 'Week 2', 'Week 3', 'Week 4', 'Week 5'],
                'datasets' => [
                    ['label' => esc_html__('Active Users', 'calio-addon'), 'values' => [4200, 4680, 5110, 5440, 5980], 'renderAs' => 'line'],
                    ['label' => esc_html__('Feature Usage', 'calio-addon'), 'values' => [1800, 2100, 2480, 2740, 3210], 'renderAs' => 'bar'],
                ],
            ],
            'saas_pipeline' => [
                'labels' => ['Lead', 'Qualified', 'Demo', 'Proposal', 'Closed'],
                'datasets' => [
                    ['label' => esc_html__('Pipeline Count', 'calio-addon'), 'values' => [180, 120, 72, 38, 19], 'renderAs' => 'bar'],
                ],
            ],
        ];

        $selected = $presets[$preset] ?? $presets['analytics_growth'];
        $datasets = [];

        foreach ($selected['datasets'] as $index => $dataset) {
            $palette_item = $palette[$index % count($palette)];
            $datasets[] = [
                'label' => sanitize_text_field((string) ($dataset['label'] ?? sprintf(
                    /* translators: %d: series number. */
                    esc_html__('Series %d', 'calio-addon'),
                    $index + 1
                ))),
                'values' => array_map('floatval', (array) ($dataset['values'] ?? [])),
                'renderAs' => sanitize_key((string) ($dataset['renderAs'] ?? 'auto')),
                'stroke' => $palette_item['stroke'],
                'fill' => $palette_item['fill'],
                'point' => $palette_item['point'],
            ];
        }

        return [
            'labels' => array_map('sanitize_text_field', (array) ($selected['labels'] ?? [])),
            'datasets' => $datasets,
        ];
    }

    /**
     * Builds chart data from a CSV table.
     *
     * @param array<string, mixed> $settings Widget settings.
     * @return array{labels: array<int, string>, datasets: array<int, array<string, mixed>>}
     */
    private function build_csv_chart_data(array $settings): array
    {
        $rows = $this->parse_csv_table(
            (string) ($settings['csv_input'] ?? ''),
            (string) ($settings['csv_delimiter'] ?? 'comma')
        );

        $skip_top_rows = max(0, (int) ($settings['csv_skip_top_rows'] ?? 0));
        if ($skip_top_rows > 0) {
            $rows = array_slice($rows, $skip_top_rows);
        }

        if (empty($rows)) {
            return ['labels' => [], 'datasets' => []];
        }

        $has_header = (($settings['csv_has_header'] ?? 'yes') === 'yes');
        $start_data_row = max(1, (int) ($settings['csv_start_data_row'] ?? 1));
        $row_limit = max(0, (int) ($settings['csv_row_limit'] ?? 0));
        $stop_at_empty_row = (($settings['csv_stop_at_empty_row'] ?? '') === 'yes');
        $first_column_labels = (($settings['csv_first_column_labels'] ?? 'yes') === 'yes');
        $default_series_style = sanitize_key((string) ($settings['csv_series_style'] ?? 'auto'));
        $label_column_index = max(0, ((int) ($settings['csv_label_column'] ?? 1)) - 1);
        $dataset_start_column = max(0, ((int) ($settings['csv_dataset_start_column'] ?? ($first_column_labels ? 2 : 1))) - 1);
        $dataset_end_column_setting = (int) ($settings['csv_dataset_end_column'] ?? 0);
        $dataset_end_column = $dataset_end_column_setting > 0 ? max(0, $dataset_end_column_setting - 1) : null;
        $series_name_overrides = $this->parse_csv_strings((string) ($settings['csv_series_names'] ?? ''));
        $header_row = [];

        if ($has_header) {
            $header_row = array_shift($rows) ?: [];
        }

        if ($start_data_row > 1) {
            $rows = array_slice($rows, $start_data_row - 1);
        }

        if ($stop_at_empty_row) {
            $rows = $this->truncate_rows_at_empty($rows);
        }

        if ($row_limit > 0) {
            $rows = array_slice($rows, 0, $row_limit);
        }

        if (empty($rows)) {
            return ['labels' => [], 'datasets' => []];
        }

        $column_count = 0;
        foreach ($rows as $row) {
            $column_count = max($column_count, count($row));
        }

        if ($column_count === 0 || ($first_column_labels && $column_count < 2)) {
            return ['labels' => [], 'datasets' => []];
        }

        if ($dataset_start_column >= $column_count) {
            return ['labels' => [], 'datasets' => []];
        }

        $resolved_end_column = $dataset_end_column === null ? ($column_count - 1) : min($dataset_end_column, $column_count - 1);
        if ($resolved_end_column < $dataset_start_column) {
            return ['labels' => [], 'datasets' => []];
        }

        $dataset_columns = range($dataset_start_column, $resolved_end_column);
        if ($first_column_labels) {
            $dataset_columns = array_values(array_filter(
                $dataset_columns,
                static fn (int $column_index): bool => $column_index !== $label_column_index
            ));
        }

        if (empty($dataset_columns)) {
            return ['labels' => [], 'datasets' => []];
        }

        $palette = $this->get_chart_palette();
        $datasets = [];

        foreach ($dataset_columns as $dataset_position => $column_index) {
            $palette_item = $palette[$dataset_position % count($palette)];
            $header_label = $has_header ? trim((string) ($header_row[$column_index] ?? '')) : '';
            $override_label = trim((string) ($series_name_overrides[$dataset_position] ?? ''));
            $datasets[] = [
                'label' => sanitize_text_field($override_label !== '' ? $override_label : ($header_label !== '' ? $header_label : sprintf(
                    /* translators: %d: series number. */
                    esc_html__('Series %d', 'calio-addon'),
                    $dataset_position + 1
                ))),
                'values' => [],
                'renderAs' => $default_series_style,
                'stroke' => $palette_item['stroke'],
                'fill' => $palette_item['fill'],
                'point' => $palette_item['point'],
            ];
        }

        $labels = [];
        foreach ($rows as $row_index => $row) {
            if ($this->is_empty_csv_row($row)) {
                continue;
            }

            $raw_label = $first_column_labels ? trim((string) ($row[$label_column_index] ?? '')) : '';
            $labels[] = sanitize_text_field($raw_label !== '' ? $raw_label : sprintf(
                /* translators: %d: row number. */
                esc_html__('Row %d', 'calio-addon'),
                count($labels) + 1
            ));

            foreach ($dataset_columns as $dataset_position => $column_index) {
                $value = $this->parse_numeric_cell((string) ($row[$column_index] ?? ''));
                $datasets[$dataset_position]['values'][] = $value ?? 0.0;
            }
        }

        if (empty($labels)) {
            return ['labels' => [], 'datasets' => []];
        }

        return [
            'labels' => $labels,
            'datasets' => $datasets,
        ];
    }

    /**
     * Builds chart data from a JSON payload.
     *
     * @param array<string, mixed> $settings Widget settings.
     * @return array{labels: array<int, string>, datasets: array<int, array<string, mixed>>}
     */
    private function build_json_chart_data(array $settings): array
    {
        $raw_json = trim((string) ($settings['json_input'] ?? ''));
        if ($raw_json === '') {
            return ['labels' => [], 'datasets' => []];
        }

        $decoded = json_decode($raw_json, true);
        if (!is_array($decoded)) {
            return ['labels' => [], 'datasets' => []];
        }

        $labels_key = $this->normalize_data_key((string) ($settings['json_labels_path'] ?? 'labels'));
        $datasets_key = $this->normalize_data_key((string) ($settings['json_dataset_path'] ?? 'datasets'));
        $dataset_label_key = $this->normalize_data_key((string) ($settings['json_dataset_label_key'] ?? 'label'));
        $dataset_values_key = $this->normalize_data_key((string) ($settings['json_dataset_values_key'] ?? 'values'));
        $dataset_render_key = $this->normalize_data_key((string) ($settings['json_dataset_render_key'] ?? 'renderAs'));
        $render_fallback = sanitize_key((string) ($settings['json_series_style_fallback'] ?? 'auto'));

        $labels_source = $this->get_nested_value($decoded, $labels_key);
        $datasets_source = $this->get_nested_value($decoded, $datasets_key);
        if (!is_array($labels_source) || !is_array($datasets_source)) {
            return ['labels' => [], 'datasets' => []];
        }

        $labels = array_values(array_filter(array_map(
            static fn ($label): string => sanitize_text_field((string) $label),
            $labels_source
        ), static fn (string $label): bool => $label !== ''));

        if (empty($labels)) {
            return ['labels' => [], 'datasets' => []];
        }

        $palette = $this->get_chart_palette();
        $datasets = [];

        foreach ($datasets_source as $index => $dataset) {
            if (!is_array($dataset)) {
                continue;
            }

            $values_source = $this->get_nested_value($dataset, $dataset_values_key);
            if (!is_array($values_source)) {
                continue;
            }

            $values = [];
            foreach ($values_source as $value) {
                $number = $this->parse_numeric_cell((string) $value);
                $values[] = $number ?? 0.0;
            }

            if (empty($values)) {
                continue;
            }

            $palette_item = $palette[$index % count($palette)];
            $label_value = (string) ($this->get_nested_value($dataset, $dataset_label_key) ?? '');
            $render_value = sanitize_key((string) ($this->get_nested_value($dataset, $dataset_render_key) ?? $render_fallback));

            $datasets[] = [
                'label' => sanitize_text_field($label_value !== '' ? $label_value : sprintf(
                    /* translators: %d: series number. */
                    esc_html__('Series %d', 'calio-addon'),
                    $index + 1
                )),
                'values' => $values,
                'renderAs' => in_array($render_value, ['auto', 'bar', 'line'], true) ? $render_value : $render_fallback,
                'stroke' => $this->sanitize_chart_color((string) ($dataset['stroke'] ?? ''), $palette_item['stroke']),
                'fill' => $this->sanitize_chart_color((string) ($dataset['fill'] ?? ''), $palette_item['fill']),
                'point' => $this->sanitize_chart_color((string) ($dataset['point'] ?? ''), $palette_item['point']),
            ];
        }

        return [
            'labels' => $labels,
            'datasets' => $datasets,
        ];
    }

    /**
     * Parses comma-separated labels into a clean array.
     *
     * @return array<int, string>
     */
    private function parse_csv_strings(string $value): array
    {
        $items = array_map('trim', explode(',', $value));

        return array_values(array_filter(array_map('sanitize_text_field', $items)));
    }

    private function sanitize_chart_color(string $value, string $fallback): string
    {
        $value = trim(wp_strip_all_tags($value));
        if ($value === '') {
            return $fallback;
        }

        $hex = sanitize_hex_color($value);
        if (is_string($hex)) {
            return $hex;
        }

        if (preg_match('/^rgba?\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/i', $value)) {
            return $value;
        }

        return $fallback;
    }

    /**
     * Parses comma-separated numeric strings.
     *
     * @return array<int, float>
     */
    private function parse_csv_numbers(string $value): array
    {
        $items = array_map('trim', explode(',', $value));
        $numbers = [];

        foreach ($items as $item) {
            if ($item === '' || !is_numeric($item)) {
                continue;
            }

            $numbers[] = (float) $item;
        }

        return $numbers;
    }

    /**
     * Parses multiline CSV input into rows.
     *
     * @return array<int, array<int, string>>
     */
    private function parse_csv_table(string $value, string $delimiter_key): array
    {
        $value = trim($value);
        if ($value === '') {
            return [];
        }

        $delimiter_map = [
            'comma' => ',',
            'semicolon' => ';',
            'tab' => "\t",
        ];
        $delimiter = $delimiter_map[$delimiter_key] ?? ',';
        $rows = [];

        foreach (preg_split("/\r\n|\n|\r/", $value) ?: [] as $line) {
            if (trim($line) === '') {
                continue;
            }

            $rows[] = array_map(
                static fn ($cell): string => trim((string) $cell),
                str_getcsv($line, $delimiter)
            );
        }

        return $rows;
    }

    /**
     * Returns whether a parsed CSV row is effectively empty.
     *
     * @param array<int, string> $row CSV row.
     */
    private function is_empty_csv_row(array $row): bool
    {
        foreach ($row as $cell) {
            if (trim($cell) !== '') {
                return false;
            }
        }

        return true;
    }

    /**
     * Stops CSV parsing at the first empty row.
     *
     * @param array<int, array<int, string>> $rows Parsed CSV rows.
     * @return array<int, array<int, string>>
     */
    private function truncate_rows_at_empty(array $rows): array
    {
        $result = [];

        foreach ($rows as $row) {
            if ($this->is_empty_csv_row($row)) {
                break;
            }

            $result[] = $row;
        }

        return $result;
    }

    /**
     * Parses numeric CSV cells, including decimal comma values from semicolon-separated CSV.
     */
    private function parse_numeric_cell(string $value): ?float
    {
        $value = trim(str_replace(["\xc2\xa0", ' '], '', $value));
        if ($value === '') {
            return null;
        }

        if (strpos($value, ',') !== false && strpos($value, '.') === false) {
            $value = str_replace(',', '.', $value);
        } elseif (strpos($value, ',') !== false && strpos($value, '.') !== false) {
            $value = str_replace(',', '', $value);
        }

        return is_numeric($value) ? (float) $value : null;
    }

    /**
     * Provides a reusable color palette for auto-generated CSV series.
     *
     * @return array<int, array{stroke: string, fill: string, point: string}>
     */
    private function get_chart_palette(): array
    {
        return [
            ['stroke' => '#2563eb', 'fill' => 'rgba(37, 99, 235, 0.18)', 'point' => '#ffffff'],
            ['stroke' => '#0f766e', 'fill' => 'rgba(15, 118, 110, 0.18)', 'point' => '#ffffff'],
            ['stroke' => '#d97706', 'fill' => 'rgba(217, 119, 6, 0.18)', 'point' => '#ffffff'],
            ['stroke' => '#9333ea', 'fill' => 'rgba(147, 51, 234, 0.18)', 'point' => '#ffffff'],
            ['stroke' => '#dc2626', 'fill' => 'rgba(220, 38, 38, 0.18)', 'point' => '#ffffff'],
            ['stroke' => '#0891b2', 'fill' => 'rgba(8, 145, 178, 0.18)', 'point' => '#ffffff'],
        ];
    }

    /**
     * Resolves dot-notated array keys for imported data.
     *
     * @param array<string, mixed> $data Source data.
     * @return mixed
     */
    private function get_nested_value(array $data, string $path): mixed
    {
        $path = trim($path);
        if ($path === '') {
            return null;
        }

        $segments = array_values(array_filter(explode('.', $path), static fn (string $segment): bool => $segment !== ''));
        $current = $data;

        foreach ($segments as $segment) {
            if (!is_array($current) || !array_key_exists($segment, $current)) {
                return null;
            }

            $current = $current[$segment];
        }

        return $current;
    }

    /**
     * Normalizes user-provided import keys.
     */
    private function normalize_data_key(string $value): string
    {
        return trim(str_replace(['[', ']'], ['.', ''], $value), " .");
    }
}
