<?php
require_once __DIR__ . '/../../utils/generateColors.php';

class Trainingssystem_Plugin_Module_Timeseries_Chart {

public function showTimeseriesChart($atts) {
    $user_id = get_current_user_id();
    
    // User-Modus prüfen: Falls ein anderer User ausgewählt ist, dessen Daten laden
    $selectui = get_user_meta($user_id, 'coach_select_user', true);
    if ($selectui != '') {
        $user_id = $selectui;
    }

    // Datenquellen bestimmen: bevorzugt id/id_date, alternativ x/y (JSON-Arrays)
    $hasDb  = !empty($atts['id']) && !empty($atts['id_date']);
    $calIdParam = isset($atts['cal-id']) ? $atts['cal-id'] : (isset($atts['cal_id']) ? $atts['cal_id'] : null);
    $fieldIdParam = isset($atts['field-id']) ? $atts['field-id'] : (isset($atts['field_id']) ? $atts['field_id'] : null);
    $calHasFormId = !empty($atts['id']);
    $calHasFieldId = !empty($fieldIdParam);
    $calHasExactlyOne = ($calHasFormId xor $calHasFieldId);
    // Fehler, wenn cal-id gesetzt ist und gleichzeitig id UND field-id übergeben wurden
    if (!$hasDb && !empty($calIdParam) && $calHasFormId && $calHasFieldId) {
        return '<div class="alert alert-danger">Fehler: Bei cal-id bitte nur id ODER field-id verwenden, nicht beides.</div>';
    }
    $hasCal = !$hasDb && !empty($calIdParam) && $calHasExactlyOne;
    $hasXY  = !$hasDb && !$hasCal && isset($atts['x']) && isset($atts['y']);

    if (!$hasDb && !$hasCal && !$hasXY) {
        return '<div class="alert alert-danger">Fehler: Entweder id und id_date ODER cal-id und (id ODER field-id) ODER x und y müssen gesetzt sein!</div>';
    }

    // IDs nur setzen, wenn DB-Quelle genutzt wird
    $slider_form_id = $hasDb ? intval($atts['id']) : null;
    $date_form_id   = $hasDb ? intval($atts['id_date']) : null;

    // Optionen und Defaults
    $type_raw       = $atts['type']         ?? 'line';
    $types          = array_map('trim', explode(',', $type_raw));
    $use_tabs       = isset($atts['use_tabs']) ? intval($atts['use_tabs']) : 0;
    $tab_titles     = isset($atts['tab_titles']) && !empty($atts['tab_titles'])
                        ? array_map('trim', explode(',', $atts['tab_titles']))
                        : [];
    $line_color     = trim($atts['line_color']  ?? '') ?: '#007bff';
    $point_color    = trim($atts['point_color'] ?? '') ?: '#007bff';
    $legend_label   = $atts['legend_label'] ?? '';
    $legend         = intval($atts['legend'] ?? 0);
    $ylabels        = $atts['ylabels']     ?? '';
    $is_exercise    = isset($atts['is_exercise']) ? intval($atts['is_exercise']) : 0; // Exercise-Modus
    $table_headers  = $atts['table_headers'] ?? ($is_exercise ? 'Datum|Übungen' : 'Datum|Wert'); // Default table headers for list type
    $x_label        = $atts['x_label']     ?? ''; // X-Achsen Label
    $y_label        = $atts['y_label']     ?? ''; // Y-Achsen Label
    $default_period = $atts['default_period'] ?? 'aktuelle_woche'; // Default Periode
    $exclude_period = $atts['exclude_period'] ?? ''; // Ausgeschlossene Perioden

    // min/max vorbereiten (später je nach Quelle gefüllt)
    $min = isset($atts['min']) ? floatval($atts['min']) : 0;
    $max = isset($atts['max']) ? floatval($atts['max']) : null;
    $fill        = (isset($atts['fill']) && intval($atts['fill'])===1) ? 'true' : 'false';
    $tension     = isset($atts['tension'])    ? floatval($atts['tension'])    : 0.1;
    $borderwidth = isset($atts['borderwidth'])? intval($atts['borderwidth'])  : 1;

    // Rohdaten holen je nach Quelle
    $final = [];
    if ($hasDb) {
        $sl_vals = $this->getFieldDataForCurrentUserByFormId($slider_form_id);
        $dt_vals = $this->getFieldDataForCurrentUserByFormId($date_form_id);
        $combined = [];
        $cnt = min(count($sl_vals), count($dt_vals));
        for ($i = 0; $i < $cnt; $i++) {
            $d = $this->processFieldValue($dt_vals[$i]['value'] ?? null);
            $v = $this->processFieldValue($sl_vals[$i]['value'] ?? null);
            if ($d !== null && $v !== '' && $v !== null) {
                // Für list-Typ auch Strings erlauben, für andere Charts nur Zahlen
                if (in_array('list', $types)) {
                    // Exercise-Modus: IDs zu Titeln auflösen
                    if ($is_exercise) {
                        $v = $this->resolveExerciseIds($v);
                    }
                    $combined[$d][] = $v;
                } else {
                    $combined[$d][] = floatval($v);
                }
            }
        }
        // Mittelwerte pro Datum (nur für numerische Werte)
        foreach ($combined as $d => $arr) {
            if (in_array('list', $types)) {
                // Für list-Typ: ersten Wert nehmen (keine Mittelung)
                $final[$d] = $arr[0];
            } else {
                // Für andere Charts: numerische Mittelung
                $final[$d] = count($arr) > 1
                    ? round(array_sum($arr)/count($arr), 2)
                    : $arr[0];
            }
        }
        ksort($final);
        // min/max aus Form-Attribute ableiten, falls nicht gesetzt
        if ($min === null || $max === null) {
            $fields = Trainingssystem_Plugin_Database::getInstance()->Formfield->getFormfieldsByForm($slider_form_id);
            if (!empty($fields)) {
                $attr = json_decode($fields[0]->getAttributes(), true) ?: [];
                if ($min === null && isset($attr['min'])) $min = floatval($attr['min']);
                if ($max === null && isset($attr['max'])) $max = floatval($attr['max']);
            }
        }
    } elseif ($hasCal) {
        // Kalender-basierte Quelle: cal-id + id (form_id)
        global $wpdb;
        $calendar_id = intval($calIdParam);
        $form_id_for_cal = $calHasFormId ? intval($atts['id']) : null;
        $field_id_for_cal = $calHasFieldId ? intval($fieldIdParam) : null;
        $table_entries = $wpdb->prefix . Trainingssystem_Plugin_Database_Calendar_Entry_Daoimple::$dbprefix;
        $table_formdata = $wpdb->prefix . Trainingssystem_Plugin_Database_Formdata_Daoimple::$dbprefix;
        
        if (!is_null($form_id_for_cal)) {
            $sql = $wpdb->prepare(
                "SELECT ce.entry_date AS entry_date, fd.data AS data\n                 FROM {$table_entries} ce\n                 LEFT JOIN {$table_formdata} fd ON fd.ID = ce.form_data_id\n                 WHERE ce.user_id = %d AND ce.calendar_id = %d AND ce.form_id = %d\n                 ORDER BY ce.entry_date ASC",
                $user_id, $calendar_id, $form_id_for_cal
            );
        } else {
            $sql = $wpdb->prepare(
                "SELECT ce.entry_date AS entry_date, fd.data AS data\n                 FROM {$table_entries} ce\n                 LEFT JOIN {$table_formdata} fd ON fd.ID = ce.form_data_id\n                 WHERE ce.user_id = %d AND ce.calendar_id = %d AND ce.form_field_id = %d\n                 ORDER BY ce.entry_date ASC",
                $user_id, $calendar_id, $field_id_for_cal
            );
        }
        $rows = $wpdb->get_results($sql);
        foreach ($rows as $row) {
            $d = isset($row->entry_date) ? $row->entry_date : null;
            $raw = isset($row->data) ? trim($row->data) : null;
            if ($d && $raw !== null && $raw !== '') {
                // Versuche JSON zu dekodieren (für Skala-Felder)
                $decoded = json_decode($raw, true);
                
                if (in_array('list', $types)) {
                    // Für list-Typ: Exercise-Modus berücksichtigen
                    if ($is_exercise) {
                        // Exercise-Modus: IDs zu Titeln auflösen (vor processFieldValue)
                        $processed_value = $this->resolveExerciseIds($decoded !== null ? $decoded : $raw);
                    } else {
                        $processed_value = $this->processFieldValue($decoded !== null ? $decoded : $raw);
                    }
                    $final[$d] = $processed_value;
                } else {
                    $processed_value = $this->processFieldValue($decoded !== null ? $decoded : $raw);
                    if (is_numeric($processed_value)) {
                        $final[$d] = floatval($processed_value);
                    }
                }
            }
        }
        ksort($final);
        // min/max aus Daten ableiten, falls nicht gesetzt (nur für numerische Charts)
        if (($min === null || $max === null) && !empty($final) && !in_array('list', $types)) {
            $valsOnly = array_values($final);
            if ($min === null) $min = min($valsOnly);
            if ($max === null) $max = max($valsOnly);
        }
    } else {
        // x/y aus Shortcode übernehmen (erst JSON versuchen, dann '|' getrennt)
        $xRaw = isset($atts['x']) ? $atts['x'] : '';
        $yRaw = isset($atts['y']) ? $atts['y'] : '';

        $x = json_decode($xRaw, true);
        $y = json_decode($yRaw, true);

        if (!is_array($x)) { $x = array_map('trim', explode('|', (string)$xRaw)); }
        if (!is_array($y)) { $y = array_map('trim', explode('|', (string)$yRaw)); }

        // Leereinträge entfernen
        $x = array_values(array_filter($x, function($v){ return $v !== '' && $v !== null; }));
        $y = array_values(array_filter($y, function($v){ return $v !== '' && $v !== null; }));

        if (count($x) !== count($y) || count($x) === 0) {
            // Ungültig oder leer -> auslassen
            return '';
        }

        $combined = [];
        $n = count($x);
        for ($i = 0; $i < $n; $i++) {
            $d = is_string($x[$i]) ? $x[$i] : '';
            $v = $y[$i];
            if ($d !== '' && $v !== '' && $v !== null) {
                // Für list-Typ auch Strings erlauben, für andere Charts nur Zahlen
                if (in_array('list', $types)) {
                    // Exercise-Modus: IDs zu Titeln auflösen
                    if ($is_exercise) {
                        $v = $this->resolveExerciseIds($v);
                    }
                    $combined[$d][] = $v;
                } else {
                    $combined[$d][] = floatval($v);
                }
            }
        }
        foreach ($combined as $d => $arr) {
            if (in_array('list', $types)) {
                // Für list-Typ: ersten Wert nehmen (keine Mittelung)
                $final[$d] = $arr[0];
            } else {
                // Für andere Charts: numerische Mittelung
                $final[$d] = count($arr) > 1
                    ? round(array_sum($arr)/count($arr), 2)
                    : $arr[0];
            }
        }
        ksort($final);
        // min/max aus Daten ableiten, falls nicht gesetzt (nur für numerische Charts)
        if (($min === null || $max === null) && !empty($final) && !in_array('list', $types)) {
            $valsOnly = array_values($final);
            if ($min === null) $min = min($valsOnly);
            if ($max === null) $max = max($valsOnly);
        }
    }

    // Perioden-Serien vorbereiten
    $today = new DateTime();
    $cw    = (int)$today->format('W');
    $cy    = (int)$today->format('o');
    $cm    = (int)$today->format('n'); // Aktueller Monat (1-12)
    $cj    = (int)$today->format('Y'); // Aktuelles Jahr
    
    // Aktuelle Woche
    $aktWk = [];
    foreach ($final as $d=>$v) {
        $dt = DateTime::createFromFormat('Y-m-d', $d);
        if ($dt && (int)$dt->format('W')===$cw && (int)$dt->format('o')===$cy) {
            $aktWk[$d] = $v;
        }
    }
    
    // Aktueller Monat
    $aktMonat = [];
    foreach ($final as $d=>$v) {
        $dt = DateTime::createFromFormat('Y-m-d', $d);
        if ($dt && (int)$dt->format('n')===$cm && (int)$dt->format('Y')===$cj) {
            $aktMonat[$d] = $v;
        }
    }
    
    // Aktuelles Jahr
    $aktJahr = [];
    foreach ($final as $d=>$v) {
        $dt = DateTime::createFromFormat('Y-m-d', $d);
        if ($dt && (int)$dt->format('Y')===$cj) {
            $aktJahr[$d] = $v;
        }
    }
    
    // Neue Perioden basierend auf aktuellen Datum
    $letzte_7_tage = [];
    $letzte_14_tage = [];
    $letzte_30_tage = [];
    $letzte_60_tage = [];
    $letzte_90_tage = [];
    $letzte_6_monate = [];
    $letzte_12_monate = [];
    
    foreach ($final as $d=>$v) {
        $dt = DateTime::createFromFormat('Y-m-d', $d);
        if ($dt) {
            $diff = $today->diff($dt)->days;
            
            if ($diff <= 7) $letzte_7_tage[$d] = $v;
            if ($diff <= 14) $letzte_14_tage[$d] = $v;
            if ($diff <= 30) $letzte_30_tage[$d] = $v;
            if ($diff <= 60) $letzte_60_tage[$d] = $v;
            if ($diff <= 90) $letzte_90_tage[$d] = $v;
            if ($diff <= 180) $letzte_6_monate[$d] = $v;
            if ($diff <= 365) $letzte_12_monate[$d] = $v;
        }
    }
    
    $series = [
        'aktuelle_woche'         => $aktWk,
        'aktueller_monat'        => $aktMonat,
        'aktuelles_jahr'         => $aktJahr,
        'letzte_7_tage'          => $letzte_7_tage,
        'letzte_14_tage'         => $letzte_14_tage,
        'letzte_30_tage'         => $letzte_30_tage,
        'letzte_60_tage'         => $letzte_60_tage,
        'letzte_90_tage'         => $letzte_90_tage,
        'letzte_6_monate'        => $letzte_6_monate,
        'letzte_12_monate'       => $letzte_12_monate,
        'woche'                  => $this->gruppiere_nach_woche($final),
        'monat'                  => $this->gruppiere_nach_monat($final),
        'jahr'                   => $this->gruppiere_nach_jahr($final),
        'tage'                   => $final,
        'individueller_zeitraum' => []
    ];
    // Zwei Arrays erstellen: eines für JavaScript-Vergleiche (Y-m-d) und eines für die Anzeige (d.m.Y)
    $labelsArr = []; // Für Anzeige (d.m.Y)
    $datesArr = [];  // Für JavaScript-Vergleiche (Y-m-d)
    foreach ($series as $key => $data) {
        $labelsArr[$key] = [];
        $datesArr[$key] = [];
        foreach (array_keys($data) as $date) {
            // Prüfen ob es ein Datum im Y-m-d Format ist
            if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
                $dt = DateTime::createFromFormat('Y-m-d', $date);
                if ($dt) {
                    $labelsArr[$key][] = $dt->format('d.m.Y'); // Für Anzeige
                    $datesArr[$key][] = $date; // Für JavaScript-Vergleiche (Y-m-d)
                } else {
                    $labelsArr[$key][] = $date;
                    $datesArr[$key][] = $date;
                }
            } else {
                $labelsArr[$key][] = $date;
                $datesArr[$key][] = $date;
            }
        }
    }
    $valuesArr = array_map('array_values', $series);

    // Perioden-Optionen definieren und filtern
    $all_periods = [
        'aktuelle_woche' => 'Aktuelle Woche',
        'aktueller_monat' => 'Aktueller Monat',
        'aktuelles_jahr' => 'Aktuelles Jahr',
        'letzte_7_tage' => 'Letzte 7 Tage',
        'letzte_14_tage' => 'Letzte 14 Tage',
        'letzte_30_tage' => 'Letzte 30 Tage',
        'letzte_60_tage' => 'Letzte 60 Tage',
        'letzte_90_tage' => 'Letzte 90 Tage',
        'letzte_6_monate' => 'Letzte 6 Monate',
        'letzte_12_monate' => 'Letzte 12 Monate',
        'woche' => 'Durchschnitt pro Woche',
        'monat' => 'Durchschnitt pro Monat',
        'jahr' => 'Durchschnitt pro Jahr',
        'individueller_zeitraum' => 'Individueller Zeitraum'
    ];
    
    // Ausgeschlossene Perioden verarbeiten
    $excluded_periods = [];
    if (!empty($exclude_period)) {
        $excluded_periods = array_map('trim', explode('|', $exclude_period));
    }
    
    // Verfügbare Perioden filtern
    $available_periods = [];
    foreach ($all_periods as $key => $period_label) {
        if (!in_array($key, $excluded_periods)) {
            $available_periods[$key] = $period_label;
        }
    }
    
    // Default Periode validieren
    if (!array_key_exists($default_period, $available_periods)) {
        $default_period = array_key_first($available_periods) ?: 'aktuelle_woche';
    }

    // Farbgenerierung
    if (in_array($type_raw, ['line','radar'])) {
        $bg_js = json_encode($point_color);
        $bd_js = json_encode($line_color);
    } else {
        $count = count($valuesArr['tage']);
        $cols  = trainingssystemGenerateColors($count);
        $bgArr = array_map(function($c){
            $c = ltrim($c,'#');
            if (strlen($c)===6) {
                list($r,$g,$b)=sscanf($c,"%02x%02x%02x");
            } else {
                $r=$g=$b=hexdec(str_repeat(substr($c,0,1),2));
            }
            return "rgba($r,$g,$b,0.5)";
        }, $cols);
        $bg_js = json_encode($bgArr);
        $bd_js = json_encode($cols);
    }

    // JS-Globals aufsetzen
    $js_data = '<script>
window.tspv2ChartData   = ' . json_encode($valuesArr,JSON_UNESCAPED_UNICODE) . ';
window.tspv2ChartLabels = ' . json_encode($labelsArr,JSON_UNESCAPED_UNICODE) . ';
window.tspv2ChartDates  = ' . json_encode($datesArr,JSON_UNESCAPED_UNICODE) . ';
window.tspv2ChartOptions= ' . json_encode([
        'legend_label'    => $legend_label,
        'background_color'=> json_decode($bg_js, true),
        'border_color'    => json_decode($bd_js, true),
        'fill'            => ($fill==='true'),
        'tension'         => $tension,
        'borderwidth'     => $borderwidth,
        'legend'          => $legend,
        'min'             => $min,
        'max'             => $max,
        'ylabels'         => $ylabels ? json_decode($ylabels, true) : new stdClass(),
        'x_label'         => $x_label,
        'y_label'         => $y_label,
        'default_period'  => $default_period,
        'available_periods' => $available_periods
    ], JSON_UNESCAPED_UNICODE) . ';
</script>';

    // JS-Funktion zum (Lazy-)Initialisieren
    $js_init = '<script>
function tspv2InitChartInstance(canvasId, chartType, chartData, chartLabels, chartOptions, chartDates) {
  var seri = chartData,
      lab  = chartLabels,
      dat  = chartDates || window.tspv2ChartDates, // Y-m-d Format für Vergleiche
      opts = chartOptions;
  var savedRanges = (window.tspv2CustomRange = window.tspv2CustomRange || {});
  var savedRanges = (window.tspv2CustomRange = window.tspv2CustomRange || {});
  
  // ZUERST prüfen ob bereits ein Chart existiert und es zerstören
  var canvas = document.getElementById(canvasId);
  if (canvas) {
    var existingChart = Chart.getChart(canvas);
    if (existingChart) {
      try {
        existingChart.destroy();
      } catch (e) {
        console.warn("Existing chart destroy error:", e);
      }
    }
    if (canvas.chart) {
      try {
        canvas.chart.destroy();
      } catch (e) {
        console.warn("Canvas chart destroy error:", e);
      }
      canvas.chart = null;
    }
  }
  
  var ctx = document.getElementById(canvasId).getContext("2d");
  var scaleOpts = {};
  if (chartType==="bar"||chartType==="line") {
    var sg=true;
    scaleOpts = {
      x: { 
        display:sg, 
        grid:{display:sg}, 
        ticks:{maxTicksLimit:10},
        title: {
          display: opts.x_label && opts.x_label.length > 0,
          text: opts.x_label || ""
        }
      },
      y: {
        display:sg, 
        grid:{display:sg},
        min: opts.min, 
        max: opts.max,
        title: {
          display: opts.y_label && opts.y_label.length > 0,
          text: opts.y_label || ""
        },
        ticks: { 
          stepSize:1,
          callback:function(v){return opts.ylabels[v]|| (Number.isInteger(v)?v:"");}
        }
      }
    };
  }
  var chart = new Chart(ctx, {
    type: chartType,
    data: {
      labels: lab["aktuelle_woche"],
      datasets: [{
        label: opts.legend_label || null,
        data: seri["aktuelle_woche"],
        backgroundColor: opts.background_color,
        borderColor:     opts.border_color,
        fill:            opts.fill,
        tension:         opts.tension,
        borderWidth:     opts.borderwidth,
        pointBackgroundColor: opts.background_color,
        pointBorderColor:     opts.border_color
      }]
    },
    options: { responsive:true,
      plugins:{ legend:{ display: opts.legend==1 } },
      scales: scaleOpts
    }
  });

  // Chart-Referenz speichern
  ctx.canvas.chart = chart;

  // Event-Listener mit verzögerter Initialisierung
  setTimeout(function() {
    var sel  = document.getElementById(canvasId+"_periode"),
        pick = document.getElementById(canvasId+"_zeitraum_picker"),
        warn = document.getElementById(canvasId+"_zeitraum_picker_warn"),
        btn  = document.getElementById(canvasId+"_zeitraum_btn"),
        von  = document.getElementById(canvasId+"_von"),
        bis  = document.getElementById(canvasId+"_bis");

    // Funktion zum Prüfen und Anzeigen von Datenwarnungen
    function checkAndShowDataWarning(period) {
      var hasData = false;
      if (period === "individueller_zeitraum") {
        // Bei individuellem Zeitraum keine Warnung anzeigen - wird erst nach Button-Klick geprüft
        var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
        if (noDataWarn) {
          noDataWarn.textContent = "";
          noDataWarn.style.display = "none";
        }
        return false;
      } else {
        hasData = lab[period] && lab[period].length > 0;
      }
      
      // Warnung anzeigen/verstecken
      var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
      if (noDataWarn) {
        if (!hasData) {
          noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
          noDataWarn.style.display = "block";
        } else {
          noDataWarn.textContent = "";
          noDataWarn.style.display = "none";
        }
      }
      
      return hasData;
    }

         // Initial state based on current select value
     var currentSelect = document.getElementById(canvasId+"_periode");
     var currentPeriod = currentSelect ? currentSelect.value : opts.default_period || "aktuelle_woche";
    if (currentPeriod === "individueller_zeitraum") {
      if (pick) pick.style.display = "block";
      chart.data.labels = [];
      chart.data.datasets[0].data = [];
    } else {
      if (pick) pick.style.display = "none";
      chart.data.labels = lab[currentPeriod] || [];
      chart.data.datasets[0].data = seri[currentPeriod] || [];
    }
    checkAndShowDataWarning(currentPeriod);

     // Dropdown Event-Listener
     if (sel && !sel.hasAttribute("data-listener-attached")) {
       sel.setAttribute("data-listener-attached", "true");
       sel.addEventListener("change", function(e){
        var v=e.target.value;
        if (v==="individueller_zeitraum") {
          if (pick) pick.style.display="block"; 
          chart.data.labels=[]; 
          chart.data.datasets[0].data=[];
          checkAndShowDataWarning(v);
        } else {
          if (pick) pick.style.display="none"; 
          chart.data.labels=lab[v]; 
          chart.data.datasets[0].data=seri[v];
          checkAndShowDataWarning(v);
        }
        try {
          chart.update("none"); // "none" verhindert Animation und reduziert Event-Probleme
        } catch (e) {
          console.warn("Chart update failed:", e);
        }
      });
    }

     // Button Event-Listener
     if (btn && !btn.hasAttribute("data-listener-attached")) {
       btn.setAttribute("data-listener-attached", "true");
       btn.addEventListener("click", function(){
        var s=von.value, e=bis.value;
        if (!s||!e){ if (warn) warn.textContent="Bitte beide Daten wählen!"; return; }
        if (new Date(s)>new Date(e)){ if (warn) warn.textContent="Startdatum muss vor Enddatum liegen!"; return; }
        var nl=[], nw=[];
        if (dat && dat["tage"]) {
          dat["tage"].forEach(function(d,i){
            var dt=new Date(d);
            if (dt>=new Date(s)&&dt<=new Date(e)){ nl.push(lab["tage"][i]); nw.push(seri["tage"][i]); }
          });
        }
        chart.data.labels=nl; chart.data.datasets[0].data=nw; 
        try {
          chart.update("none"); // "none" verhindert Animation und reduziert Event-Probleme
        } catch (e) {
          console.warn("Chart update failed:", e);
        }
        
        // Nur eine Warnung anzeigen - die allgemeine Datenwarnung verwenden
        var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
        if (noDataWarn) {
          if (nl.length === 0) {
            noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
            noDataWarn.style.display = "block";
            if (warn) warn.textContent = ""; // Ursprüngliche Warnung ausblenden
          } else {
            noDataWarn.textContent = "";
            noDataWarn.style.display = "none";
            if (warn) warn.textContent = ""; // Ursprüngliche Warnung ausblenden
          }
        }
      });
    }
  }, 100); // 100ms Verzögerung für DOM-Bereitschaft
}

function tspv2InitChart(canvasId, chartType) {
  var seri = window.tspv2ChartData,
      lab  = window.tspv2ChartLabels,
      dat  = window.tspv2ChartDates, // Y-m-d Format für Vergleiche
      opts = window.tspv2ChartOptions;
  var ctx = document.getElementById(canvasId).getContext("2d");
  var scaleOpts = {};
  if (chartType==="bar"||chartType==="line") {
    var sg=true;
    scaleOpts = {
      x: { 
        display:sg, 
        grid:{display:sg}, 
        ticks:{maxTicksLimit:10},
        title: {
          display: opts.x_label && opts.x_label.length > 0,
          text: opts.x_label || ""
        }
      },
      y: {
        display:sg, 
        grid:{display:sg},
        min: opts.min, 
        max: opts.max,
        title: {
          display: opts.y_label && opts.y_label.length > 0,
          text: opts.y_label || ""
        },
        ticks: { 
          stepSize:1,
          callback:function(v){return opts.ylabels[v]|| (Number.isInteger(v)?v:"");}
        }
      }
    };
  }
  var chart = new Chart(ctx, {
    type: chartType,
    data: {
      labels: lab["aktuelle_woche"],
      datasets: [{
        label: opts.legend_label || null,
        data: seri["aktuelle_woche"],
        backgroundColor: opts.background_color,
        borderColor:     opts.border_color,
        fill:            opts.fill,
        tension:         opts.tension,
        borderWidth:     opts.borderwidth,
        pointBackgroundColor: opts.background_color,
        pointBorderColor:     opts.border_color
      }]
    },
    options: { responsive:true,
      plugins:{ legend:{ display: opts.legend==1 } },
      scales: scaleOpts
    }
  });

  // Dropdown & Picker-Listener
  var sel  = document.getElementById(canvasId+"_periode"),
      pick = document.getElementById(canvasId+"_zeitraum_picker"),
      warn = document.getElementById(canvasId+"_zeitraum_picker_warn"),
      btn  = document.getElementById(canvasId+"_zeitraum_btn"),
      von  = document.getElementById(canvasId+"_von"),
      bis  = document.getElementById(canvasId+"_bis");

  // Funktion zum Prüfen und Anzeigen von Datenwarnungen
  function checkAndShowDataWarning(period) {
    var hasData = false;
    if (period === "individueller_zeitraum") {
      // Bei individuellem Zeitraum keine Warnung anzeigen - wird erst nach Button-Klick geprüft
      var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
      if (noDataWarn) {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
      }
      return false;
    } else {
      hasData = lab[period] && lab[period].length > 0;
    }
    
    // Warnung anzeigen/verstecken
    var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
    if (noDataWarn) {
      if (!hasData) {
        noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
        noDataWarn.style.display = "block";
      } else {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
      }
    }
    
    return hasData;
  }

  // Initial state based on current select value (respect saved custom range)
  var currentSelect = document.getElementById(canvasId+"_periode");
  var currentPeriod = currentSelect ? currentSelect.value : opts.default_period || "aktuelle_woche";
  var picker = document.getElementById(canvasId+"_zeitraum_picker");
  if (currentPeriod === "individueller_zeitraum") {
    if (picker) picker.style.display = "block";
    var savedInit = (window.tspv2CustomRange && window.tspv2CustomRange[canvasId]) || null;
    chart.data.labels = savedInit && Array.isArray(savedInit.labels) ? savedInit.labels : [];
    chart.data.datasets[0].data = savedInit && Array.isArray(savedInit.data) ? savedInit.data : [];
    // restore inputs
    var vonEl0 = document.getElementById(canvasId+"_von");
    var bisEl0 = document.getElementById(canvasId+"_bis");
    if (savedInit) {
      if (vonEl0 && savedInit.s) vonEl0.value = savedInit.s;
      if (bisEl0 && savedInit.e) bisEl0.value = savedInit.e;
    }
  } else {
    if (picker) picker.style.display = "none";
    chart.data.labels = lab[currentPeriod] || [];
    chart.data.datasets[0].data = seri[currentPeriod] || [];
  }
  checkAndShowDataWarning(currentPeriod);
  try { chart.update("none"); } catch (e) { console.warn("Chart initial update failed:", e); }

  if (sel) sel.addEventListener("change", function(e){
    var v=e.target.value;
    if (v==="individueller_zeitraum") {
      pick.style.display="block"; 
      chart.data.labels=[]; 
      chart.data.datasets[0].data=[];
      checkAndShowDataWarning(v);
    } else {
      pick.style.display="none"; 
      chart.data.labels=lab[v]; 
      chart.data.datasets[0].data=seri[v];
      checkAndShowDataWarning(v);
    }
    // Chart update ohne Event-Listener Konflikte
    if (chart && chart.data) {
      try {
        chart.update();
      } catch (e) {
        console.warn("Chart update error:", e);
      }
    }
  });

  if (btn) btn.addEventListener("click", function(){
    var s=von.value, e=bis.value;
    if (!s||!e){ warn.textContent="Bitte beide Daten wählen!"; return; }
    if (new Date(s)>new Date(e)){ warn.textContent="Startdatum muss vor Enddatum liegen!"; return; }
    var nl=[], nw=[];
    if (dat && dat["tage"]) {
      dat["tage"].forEach(function(d,i){
        var dt=new Date(d);
        if (dt>=new Date(s)&&dt<=new Date(e)){ nl.push(lab["tage"][i]); nw.push(seri["tage"][i]); }
      });
    }
    chart.data.labels=nl; chart.data.datasets[0].data=nw; 
    // Chart update ohne Event-Listener Konflikte
    if (chart && chart.data) {
      try {
        chart.update(); // Update ohne Animation und Event-Listener
      } catch (e) {
        console.warn("Chart update error:", e);
      }
    }
    
    // Nur eine Warnung anzeigen - die allgemeine Datenwarnung verwenden
    var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
    if (noDataWarn) {
      if (nl.length === 0) {
        noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
        noDataWarn.style.display = "block";
        warn.textContent = ""; // Ursprüngliche Warnung ausblenden
      } else {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
        warn.textContent = ""; // Ursprüngliche Warnung ausblenden
      }
    }
  });
}
</script>';

    // Lazy-Loader falls mehrere Charts
    $js_tabs = '<script>
document.addEventListener("DOMContentLoaded", function(){
  var types = ' . json_encode($types) . ';
  var cids  = types.map((_,i)=>"tschart_tabs_"+i);
  // Erstes initialisieren
  tspv2InitChart(cids[0], types[0]);
  // bei Tab-Wechsel
  document.querySelectorAll(".nav-tabs .nav-link").forEach(function(btn){
    btn.addEventListener("shown.bs.tab", function(e){
      var idx = e.target.getAttribute("data-bs-target").match(/\\d+$/)[0];
      tspv2InitChart(cids[idx], types[idx]);
    });
  });
});
</script>';

    // Rendern
    if (count($types) === 1 && !$use_tabs) {
        // Einzelchart ohne Tabs
        $cid = 'tschart_' . uniqid();
        $var_id = str_replace(['-', '.'], '_', uniqid('tspv2_', true));
        // Farblogik für Einzelchart
        $cur_type = $types[0];
        if (in_array($cur_type, ['line', 'radar'])) {
            $bg_js = json_encode($point_color);
            $bd_js = json_encode($line_color);
        } else {
            $count = count($valuesArr['tage']);
            $cols  = trainingssystemGenerateColors($count);
            $bgArr = array_map(function($c){
                $c = ltrim($c,'#');
                if (strlen($c)===6) {
                    list($r,$g,$b)=sscanf($c,"%02x%02x%02x");
                } else {
                    $r=$g=$b=hexdec(str_repeat(substr($c,0,1),2));
                }
                return "rgba($r,$g,$b,0.5)";
            }, $cols);
            $bg_js = json_encode($bgArr);
            $bd_js = json_encode($cols);
        }
        $js_data = '<script>'
            . 'var ' . $var_id . '_ChartData   = ' . json_encode($valuesArr, JSON_UNESCAPED_UNICODE) . ';'
            . 'var ' . $var_id . '_ChartLabels = ' . json_encode($labelsArr, JSON_UNESCAPED_UNICODE) . ';'
            . 'var ' . $var_id . '_ChartDates  = ' . json_encode($datesArr, JSON_UNESCAPED_UNICODE) . ';'
                         . 'var ' . $var_id . '_ChartOptions= ' . json_encode([
                 'legend_label'    => $legend_label,
                 'background_color'=> json_decode($bg_js, true),
                 'border_color'    => json_decode($bd_js, true),
                 'fill'            => ($fill==='true'),
                 'tension'         => $tension,
                 'borderwidth'     => $borderwidth,
                 'legend'          => $legend,
                 'min'             => $min,
                 'max'             => $max,
                 'ylabels'         => $ylabels ? json_decode($ylabels, true) : new stdClass(),
                 'x_label'         => $x_label,
                 'y_label'         => $y_label,
                 'default_period'  => $default_period,
                 'available_periods' => $available_periods
             ], JSON_UNESCAPED_UNICODE) . ';'
            . '</script>';
        $js_init = '<script>
// Function to initialize list type
function tspv2InitListInstance(canvasId, chartData, chartLabels, chartDates) {
  var seri = chartData,
      lab  = chartLabels,
      dat  = chartDates || window.tspv2ChartDates; // Y-m-d Format für Vergleiche
  var savedRanges = (window.tspv2CustomRange = window.tspv2CustomRange || {});
  
  function updateList(period) {
    var tbody = document.getElementById(canvasId + "_table_body");
    var noDataWarn = document.getElementById(canvasId + "_no_data_warn");
    
    if (!tbody) return;
    
    tbody.innerHTML = "";
    
    if (period === "individueller_zeitraum") {
      var saved = (window.tspv2CustomRange && window.tspv2CustomRange[canvasId]) || null;
      var labels = saved && Array.isArray(saved.labels) ? saved.labels : [];
      var values = saved && Array.isArray(saved.data) ? saved.data : [];
      var vonEl = document.getElementById(canvasId + "_von");
      var bisEl = document.getElementById(canvasId + "_bis");
      if (saved) {
        if (vonEl && saved.s) vonEl.value = saved.s;
        if (bisEl && saved.e) bisEl.value = saved.e;
      }
    } else {
      var labels = lab[period] || [];
      var values = seri[period] || [];
    }
    var hasData = labels.length > 0;
    
    if (hasData) {
      for (var i = 0; i < labels.length; i++) {
        var row = tbody.insertRow();
        var cell1 = row.insertCell(0);
        var cell2 = row.insertCell(1);
        cell1.textContent = labels[i];
        cell2.textContent = values[i];
      }
    }
    
    // Show/hide data warning
    if (noDataWarn) {
      if (!hasData) {
        noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
        noDataWarn.style.display = "block";
      } else {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
      }
    }
  }
  
     // Initialize with currently selected period (fallback: default_period)
   var initialSelect = document.getElementById(canvasId + "_periode");
   var initialPeriod = initialSelect ? initialSelect.value : "aktuelle_woche";
  updateList(initialPeriod);
  var initialPick = document.getElementById(canvasId + "_zeitraum_picker");
  if (initialPick) {
    initialPick.style.display = (initialPeriod === "individueller_zeitraum") ? "block" : "none";
  }
  
  // Dropdown & Picker-Listener
  var sel  = document.getElementById(canvasId + "_periode"),
      pick = document.getElementById(canvasId + "_zeitraum_picker"),
      warn = document.getElementById(canvasId + "_zeitraum_picker_warn"),
      btn  = document.getElementById(canvasId + "_zeitraum_btn"),
      von  = document.getElementById(canvasId + "_von"),
      bis  = document.getElementById(canvasId + "_bis");
  
  if (sel) sel.addEventListener("change", function(e){
    var v = e.target.value;
    if (v === "individueller_zeitraum") {
      if (pick) pick.style.display = "block";
      updateList(v);
    } else {
      if (pick) pick.style.display = "none";
      updateList(v);
    }
  });
  
  if (btn) btn.addEventListener("click", function(){
    var s = von.value, e = bis.value;
    if (!s || !e) { 
      if (warn) warn.textContent = "Bitte beide Daten wählen!"; 
      return; 
    }
    if (new Date(s) > new Date(e)) { 
      if (warn) warn.textContent = "Startdatum muss vor Enddatum liegen!"; 
      return; 
    }
    
    var tbody = document.getElementById(canvasId + "_table_body");
    var noDataWarn = document.getElementById(canvasId + "_no_data_warn");
    
    if (tbody) {
      tbody.innerHTML = "";
      
      var nl = [], nw = [];
      if (dat && dat["tage"]) {
        dat["tage"].forEach(function(d, i){
          var dt = new Date(d);
          if (dt >= new Date(s) && dt <= new Date(e)) { 
            nl.push(lab["tage"][i]); 
            nw.push(seri["tage"][i]); 
          }
        });
      }
      // Persist the chosen range for this tab
      window.tspv2CustomRange = window.tspv2CustomRange || {};
      window.tspv2CustomRange[canvasId] = { labels: nl.slice(), data: nw.slice(), s: s, e: e };
      
      if (nl.length > 0) {
        for (var i = 0; i < nl.length; i++) {
          var row = tbody.insertRow();
          var cell1 = row.insertCell(0);
          var cell2 = row.insertCell(1);
          cell1.textContent = nl[i];
          cell2.textContent = nw[i];
        }
      }
      
      // Show/hide warnings
      if (noDataWarn) {
        if (nl.length === 0) {
          noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
          noDataWarn.style.display = "block";
          if (warn) warn.textContent = "";
        } else {
          noDataWarn.textContent = "";
          noDataWarn.style.display = "none";
          if (warn) warn.textContent = "";
        }
      }
    }
  });
}

function tspv2InitChartInstance(canvasId, chartType, chartData, chartLabels, chartOptions, chartDates) {
  var seri = chartData,
      lab  = chartLabels,
      dat  = chartDates || window.tspv2ChartDates, // Y-m-d Format für Vergleiche
      opts = chartOptions;
  
  // ZUERST prüfen ob bereits ein Chart existiert und es zerstören
  var canvas = document.getElementById(canvasId);
  if (canvas) {
    var existingChart = Chart.getChart(canvas);
    if (existingChart) {
      try {
        existingChart.destroy();
      } catch (e) {
        console.warn("Existing chart destroy error:", e);
      }
    }
    if (canvas.chart) {
      try {
        canvas.chart.destroy();
      } catch (e) {
        console.warn("Canvas chart destroy error:", e);
      }
      canvas.chart = null;
    }
  }
  
  var ctx = document.getElementById(canvasId).getContext("2d");
  var scaleOpts = {};
  if (chartType==="bar"||chartType==="line") {
    var sg=true;
    scaleOpts = {
      x: { 
        display:sg, 
        grid:{display:sg}, 
        ticks:{maxTicksLimit:10},
        title: {
          display: opts.x_label && opts.x_label.length > 0,
          text: opts.x_label || ""
        }
      },
      y: {
        display:sg, 
        grid:{display:sg},
        min: opts.min, 
        max: opts.max,
        title: {
          display: opts.y_label && opts.y_label.length > 0,
          text: opts.y_label || ""
        },
        ticks: { 
          stepSize:1,
          callback:function(v){return opts.ylabels[v]|| (Number.isInteger(v)?v:"");}
        }
      }
    };
  }
  var chart = new Chart(ctx, {
    type: chartType,
    data: {
      labels: lab["aktuelle_woche"],
      datasets: [{
        label: opts.legend_label || null,
        data: seri["aktuelle_woche"],
        backgroundColor: opts.background_color,
        borderColor:     opts.border_color,
        fill:            opts.fill,
        tension:         opts.tension,
        borderWidth:     opts.borderwidth,
        pointBackgroundColor: opts.background_color,
        pointBorderColor:     opts.border_color
      }]
    },
    options: { responsive:true,
      plugins:{ legend:{ display: opts.legend==1 } },
      scales: scaleOpts
    }
  });

  // Chart-Referenz speichern
  ctx.canvas.chart = chart;

  var sel  = document.getElementById(canvasId+"_periode"),
      pick = document.getElementById(canvasId+"_zeitraum_picker"),
      warn = document.getElementById(canvasId+"_zeitraum_picker_warn"),
      btn  = document.getElementById(canvasId+"_zeitraum_btn"),
      von  = document.getElementById(canvasId+"_von"),
      bis  = document.getElementById(canvasId+"_bis");

  // Funktion zum Prüfen und Anzeigen von Datenwarnungen
  function checkAndShowDataWarning(period) {
    var hasData = false;
    if (period === "individueller_zeitraum") {
      // Bei individuellem Zeitraum keine Warnung anzeigen - wird erst nach Button-Klick geprüft
      var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
      if (noDataWarn) {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
      }
      return false;
    } else {
      hasData = lab[period] && lab[period].length > 0;
    }
    
    // Warnung anzeigen/verstecken
    var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
    if (noDataWarn) {
      if (!hasData) {
        noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
        noDataWarn.style.display = "block";
      } else {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
      }
    }
    
    return hasData;
  }

  // Initiale Datenprüfung
  checkAndShowDataWarning("aktuelle_woche");

  if (sel) sel.addEventListener("change", function(e){
    var canvas = document.getElementById(canvasId);
    var currentChart = (canvas && Chart.getChart(canvas)) || (canvas ? canvas.chart : null);
    if (!currentChart) { return; }
    var v=e.target.value;
    if (v==="individueller_zeitraum") {
      pick.style.display="block"; 
      currentChart.data.labels=[]; 
      currentChart.data.datasets[0].data=[];
      checkAndShowDataWarning(v);
    } else {
      pick.style.display="none"; 
      currentChart.data.labels=lab[v]; 
      currentChart.data.datasets[0].data=seri[v];
      checkAndShowDataWarning(v);
    }
    try {
      currentChart.update();
    } catch (e) {
      console.warn("Chart update error:", e);
    }
  });

  if (btn) btn.addEventListener("click", function(){
    var canvas = document.getElementById(canvasId);
    var currentChart = (canvas && Chart.getChart(canvas)) || (canvas ? canvas.chart : null);
    if (!currentChart) { return; }
    var s=von.value, e=bis.value;
    if (!s||!e){ warn.textContent="Bitte beide Daten wählen!"; return; }
    if (new Date(s)>new Date(e)){ warn.textContent="Startdatum muss vor Enddatum liegen!"; return; }
    var nl=[], nw=[];
    if (dat && dat["tage"]) {
      dat["tage"].forEach(function(d,i){
        var dt=new Date(d);
        if (dt>=new Date(s)&&dt<=new Date(e)){ nl.push(lab["tage"][i]); nw.push(seri["tage"][i]); }
      });
    }
    currentChart.data.labels=nl; currentChart.data.datasets[0].data=nw; 
    try {
      currentChart.update();
    } catch (e) {
      console.warn("Chart update error:", e);
    }
    
    // Nur eine Warnung anzeigen - die allgemeine Datenwarnung verwenden
    var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
    if (noDataWarn) {
      if (nl.length === 0) {
        noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
        noDataWarn.style.display = "block";
        warn.textContent = ""; // Ursprüngliche Warnung ausblenden
      } else {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
        warn.textContent = ""; // Ursprüngliche Warnung ausblenden
      }
    }
  });
}
</script>';
        // Special handling for single list type
        if ($types[0] === 'list') {
            $header_parts = explode('|', $table_headers);
            $date_header = isset($header_parts[0]) ? trim($header_parts[0]) : 'Datum';
            $value_header = isset($header_parts[1]) ? trim($header_parts[1]) : 'Wert';
            
                         // Y-Labels Anzeige für List-Type
             $ylabels_display = '';
             if (!empty($ylabels)) {
                 $ylabels_array = json_decode($ylabels, true);
                 if (is_array($ylabels_array) && !empty($ylabels_array)) {
                     $ylabels_items = [];
                     foreach ($ylabels_array as $key => $value) {
                         $ylabels_items[] = "<span class='badge bg-light text-dark me-3'><strong>{$key}:</strong> {$value}</span>";
                     }
                     $ylabels_display = "<div style='font-size: 0.875rem; float:right;'>"
                         . implode('', $ylabels_items)
                         . "</div>";
                 }
             }
            
                         // Dropdown-Optionen generieren
             $dropdown_options = '';
             foreach ($available_periods as $key => $period_label) {
                 $selected = ($key === $default_period) ? ' selected' : '';
                 $dropdown_options .= "<option value='{$key}'{$selected}>{$period_label}</option>";
             }
             
             return $js_data
                 . $js_init
                 . "<div class='mb-2'>"
                 . "<select id='{$cid}_periode' class='form-select form-select-sm' style='width:auto;display:inline-block;margin-right:10px;'>"
                 . $dropdown_options
                 . "</select>"
                . $ylabels_display
                . "</div>"
                . "<div id='{$cid}_zeitraum_picker' class='mb-2' style='display:none;'>"
                .   "<div class='d-flex align-items-end gap-2 flex-wrap'>"
                .     "<div><label class='form-label mb-0' for='{$cid}_von'>Von:</label>"
                .     "<input type='date' id='{$cid}_von' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                .     "<div><label class='form-label mb-0' for='{$cid}_bis'>Bis:</label>"
                .     "<input type='date' id='{$cid}_bis' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                .     "<button id='{$cid}_zeitraum_btn' class='btn btn-primary btn-sm'>Anzeigen</button>"
                .   "</div>"
                .   "<div id='{$cid}_zeitraum_picker_warn' class='text-danger mt-1' style='min-height:1.5em;'></div>"
                . "</div>"
                . "<div id='{$cid}_no_data_warn' class='text-warning mt-2' style='min-height:1.5em;display:none;'></div>"
                . "<div id='{$cid}_table_container' class='table-responsive'>"
                . "<table id='{$cid}_table' class='table table-striped table-hover'>"
                . "<thead><tr><th>{$date_header}</th><th>{$value_header}</th></tr></thead>"
                . "<tbody id='{$cid}_table_body'></tbody>"
                . "</table></div>"
                . "<script>tspv2InitListInstance('{$cid}', {$var_id}_ChartData, {$var_id}_ChartLabels, {$var_id}_ChartDates);</script>";
                 } else {
             // Dropdown-Optionen generieren für andere Chart-Typen
             $dropdown_options = '';
             foreach ($available_periods as $key => $period_label) {
                 $selected = ($key === $default_period) ? ' selected' : '';
                 $dropdown_options .= "<option value='{$key}'{$selected}>{$period_label}</option>";
             }
             
             return $js_data
                 . $js_init
                 . "<div class='mb-2'><select id='{$cid}_periode' class='form-select form-select-sm' style='width:auto;display:inline-block;margin-right:10px;'>"
                 . $dropdown_options
                 . "</select></div>"
                . "<div id='{$cid}_zeitraum_picker' class='mb-2' style='display:none;'>"
                .   "<div class='d-flex align-items-end gap-2 flex-wrap'>"
                .     "<div><label class='form-label mb-0' for='{$cid}_von'>Von:</label>"
                .     "<input type='date' id='{$cid}_von' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                .     "<div><label class='form-label mb-0' for='{$cid}_bis'>Bis:</label>"
                .     "<input type='date' id='{$cid}_bis' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                .     "<button id='{$cid}_zeitraum_btn' class='btn btn-primary btn-sm'>Anzeigen</button>"
                .   "</div>"
                .   "<div id='{$cid}_zeitraum_picker_warn' class='text-danger mt-1' style='min-height:1.5em;'></div>"
                . "</div>"
                . "<div id='{$cid}_no_data_warn' class='text-warning mt-2' style='min-height:1.5em;display:none;'></div>"
                . "<canvas id='{$cid}'></canvas>"
                . "<script>tspv2InitChartInstance('{$cid}','{$types[0]}',{$var_id}_ChartData,{$var_id}_ChartLabels,{$var_id}_ChartOptions,{$var_id}_ChartDates);</script>";
        }
    } else {
        // Tab-System für mehrere Charts oder wenn use_tabs=1
        $container_id = 'tschart_tabs_' . uniqid();
        $container_var = str_replace(['-', '.'], '_', uniqid('tspv2c_', true));
        
        // Tab-Titel generieren
        $default_titles = [
            'line' => 'Liniendiagramm',
            'bar' => 'Balkendiagramm', 
            'pie' => 'Kreisdiagramm',
            'radar' => 'Radardiagramm',
            'polarArea' => 'Polar Area',
            'doughnut' => 'Doughnut',
            'list' => 'Liste'
        ];
        $chart_titles = [];
        foreach ($types as $i => $type) {
            $chart_titles[] = isset($tab_titles[$i]) ? $tab_titles[$i] : ($default_titles[$type] ?? ucfirst($type));
        }
        
                 // Tab-Navigation erstellen - Bootstrap-Style
         $tab_nav = '<ul class="nav nav-tabs" id="' . $container_id . '_nav" role="tablist">';
         foreach ($chart_titles as $i => $title) {
             $active_class = ($i === 0) ? ' active' : '';
             $tab_nav .= '<li class="nav-item" role="presentation">'
                      . '<button class="nav-link' . $active_class . '" id="' . $container_id . '_tab_' . $i . '" '
                      . 'data-tab-target="' . $container_id . '_content_' . $i . '" '
                      . 'type="button" role="tab" aria-controls="' . $container_id . '_content_' . $i . '" '
                      . 'aria-selected="' . ($i === 0 ? 'true' : 'false') . '">' . $title . '</button>'
                      . '</li>';
         }
         $tab_nav .= '</ul>';
        
                 // Tab-Inhalte erstellen
         $tab_content = '<div class="tab-content" id="' . $container_id . '_content">';
         foreach ($types as $i => $type) {
             $active_class = ($i === 0) ? ' show active' : '';
             $cid = $container_id . '_chart_' . $i;
             $var_id = str_replace(['-', '.'], '_', uniqid('tspv2_', true));
            
            // Farblogik pro Typ
            if (in_array($type, ['line', 'radar'])) {
                $bg_js = json_encode($point_color);
                $bd_js = json_encode($line_color);
            } else {
                $count = count($valuesArr['tage']);
                $cols  = trainingssystemGenerateColors($count);
                $bgArr = array_map(function($c){
                    $c = ltrim($c,'#');
                    if (strlen($c)===6) {
                        list($r,$g,$b)=sscanf($c,"%02x%02x%02x");
                    } else {
                        $r=$g=$b=hexdec(str_repeat(substr($c,0,1),2));
                    }
                    return "rgba($r,$g,$b,0.5)";
                }, $cols);
                $bg_js = json_encode($bgArr);
                $bd_js = json_encode($cols);
            }
            
            // JS-Daten nur einmal für das erste Tab generieren (container-spezifische Vars, keine window.*)
            if ($i === 0) {
                $js_data = '<script>'
                    . 'var ' . $container_var . '_ChartData   = ' . json_encode($valuesArr, JSON_UNESCAPED_UNICODE) . ';'
                    . 'var ' . $container_var . '_ChartLabels = ' . json_encode($labelsArr, JSON_UNESCAPED_UNICODE) . ';'
                    . 'var ' . $container_var . '_ChartDates  = ' . json_encode($datesArr, JSON_UNESCAPED_UNICODE) . ';'
                                         . 'var ' . $container_var . '_ChartOptions= ' . json_encode([
                         'legend_label'    => $legend_label,
                         'background_color'=> json_decode($bg_js, true),
                         'border_color'    => json_decode($bd_js, true),
                         'fill'            => ($fill==='true'),
                         'tension'         => $tension,
                         'borderwidth'     => $borderwidth,
                         'legend'          => $legend,
                         'min'             => $min,
                         'max'             => $max,
                         'ylabels'         => $ylabels ? json_decode($ylabels, true) : new stdClass(),
                         'x_label'         => $x_label,
                         'y_label'         => $y_label,
                         'default_period'  => $default_period,
                         'available_periods' => $available_periods
                     ], JSON_UNESCAPED_UNICODE) . ';'
                    . '</script>';
            } else {
                $js_data = '';
            }
            
            // Special handling for list type
            if ($type === 'list') {
                $header_parts = explode('|', $table_headers);
                $date_header = isset($header_parts[0]) ? trim($header_parts[0]) : 'Datum';
                $value_header = isset($header_parts[1]) ? trim($header_parts[1]) : 'Wert';
                
                                 // Y-Labels Anzeige für List-Type in Tabs
                 $ylabels_display = '';
                 if (!empty($ylabels)) {
                     $ylabels_array = json_decode($ylabels, true);
                     if (is_array($ylabels_array) && !empty($ylabels_array)) {
                         $ylabels_items = [];
                         foreach ($ylabels_array as $key => $value) {
                             $ylabels_items[] = "<span class='badge bg-light text-dark me-3'><strong>{$key}:</strong> {$value}</span>";
                         }
                         $ylabels_display = "<div style='font-size: 0.875rem; float:right'>"
                             . implode('', $ylabels_items)
                             . "</div>";
                     }
                 }
                
                                 // Dropdown-Optionen generieren für Tab-List-Charts
                 $dropdown_options = '';
                 foreach ($available_periods as $key => $period_label) {
                     $selected = ($key === $default_period) ? ' selected' : '';
                     $dropdown_options .= "<option value='{$key}'{$selected}>{$period_label}</option>";
                 }
                 
                 $tab_content .= '<div class="tab-pane fade' . $active_class . '" id="' . $container_id . '_content_' . $i . '" '
                     . 'role="tabpanel" aria-labelledby="' . $container_id . '_tab_' . $i . '" style="padding: 0.5rem;">'
                     . $js_data
                     . "<div class='mb-2'>"
                     . "<select id='{$cid}_periode' class='form-select form-select-sm' style='width:auto;display:inline-block;margin-right:10px;'>"
                     . $dropdown_options
                     . "</select>"
                    . $ylabels_display
                    . "</div>"
                    . "<div id='{$cid}_zeitraum_picker' class='mb-2' style='display:none;'>"
                    .   "<div class='d-flex align-items-end gap-2 flex-wrap'>"
                    .     "<div><label class='form-label mb-0' for='{$cid}_von'>Von:</label>"
                    .     "<input type='date' id='{$cid}_von' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                    .     "<div><label class='form-label mb-0' for='{$cid}_bis'>Bis:</label>"
                    .     "<input type='date' id='{$cid}_bis' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                    .     "<button id='{$cid}_zeitraum_btn' class='btn btn-primary btn-sm'>Anzeigen</button>"
                    .   "</div>"
                    .   "<div id='{$cid}_zeitraum_picker_warn' class='text-danger mt-1' style='min-height:1.5em;'></div>"
                    . "</div>"
                    . "<div id='{$cid}_no_data_warn' class='text-warning mt-2' style='min-height:1.5em;display:none;'></div>"
                    . "<div id='{$cid}_table_container' class='table-responsive'>"
                    . "<table id='{$cid}_table' class='table table-striped table-hover'>"
                    . "<thead><tr><th>{$date_header}</th><th>{$value_header}</th></tr></thead>"
                    . "<tbody id='{$cid}_table_body'></tbody>"
                    . "</table></div>"
                    . "</div>";
                         } else {
                 // Dropdown-Optionen generieren für Tab-Other-Charts
                 $dropdown_options = '';
                 foreach ($available_periods as $key => $period_label) {
                     $selected = ($key === $default_period) ? ' selected' : '';
                     $dropdown_options .= "<option value='{$key}'{$selected}>{$period_label}</option>";
                 }
                 
                 $tab_content .= '<div class="tab-pane fade' . $active_class . '" id="' . $container_id . '_content_' . $i . '" '
                     . 'role="tabpanel" aria-labelledby="' . $container_id . '_tab_' . $i . '" style="padding: 0.5rem;">'
                     . $js_data
                     . "<div class='mb-2'><select id='{$cid}_periode' class='form-select form-select-sm' style='width:auto;display:inline-block;margin-right:10px;'>"
                     . $dropdown_options
                     . "</select></div>"
                    . "<div id='{$cid}_zeitraum_picker' class='mb-2' style='display:none;'>"
                    .   "<div class='d-flex align-items-end gap-2 flex-wrap'>"
                    .     "<div><label class='form-label mb-0' for='{$cid}_von'>Von:</label>"
                    .     "<input type='date' id='{$cid}_von' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                    .     "<div><label class='form-label mb-0' for='{$cid}_bis'>Bis:</label>"
                    .     "<input type='date' id='{$cid}_bis' class='form-control form-control-sm' style='width:auto;display:inline-block;'></div>"
                    .     "<button id='{$cid}_zeitraum_btn' class='btn btn-primary btn-sm'>Anzeigen</button>"
                    .   "</div>"
                    .   "<div id='{$cid}_zeitraum_picker_warn' class='text-danger mt-1' style='min-height:1.5em;'></div>"
                    . "</div>"
                    . "<div id='{$cid}_no_data_warn' class='text-warning mt-2' style='min-height:1.5em;display:none;'></div>"
                    . "<canvas id='{$cid}'></canvas>"
                    . "</div>";
            }
        }
        $tab_content .= '</div>';
        
                 // JavaScript für Tab-Funktionalität - Bootstrap-Style mit eigenständiger Logik
         $js_tabs = '<script>
// Function to initialize list type
function tspv2InitListInstance(canvasId, chartData, chartLabels, chartDates) {
  var seri = chartData,
      lab  = chartLabels,
      dat  = chartDates || window.tspv2ChartDates; // Y-m-d Format für Vergleiche
  
  function updateList(period) {
    var tbody = document.getElementById(canvasId + "_table_body");
    var noDataWarn = document.getElementById(canvasId + "_no_data_warn");
    
    if (!tbody) return;
    
    tbody.innerHTML = "";
    
    if (period === "individueller_zeitraum") {
      // Handle individual time period - will be handled by button click
      if (noDataWarn) {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
      }
      return;
    }
    
    var labels = lab[period] || [];
    var values = seri[period] || [];
    var hasData = labels.length > 0;
    
    if (hasData) {
      for (var i = 0; i < labels.length; i++) {
        var row = tbody.insertRow();
        var cell1 = row.insertCell(0);
        var cell2 = row.insertCell(1);
        cell1.textContent = labels[i];
        cell2.textContent = values[i];
      }
    }
    
    // Show/hide data warning
    if (noDataWarn) {
      if (!hasData) {
        noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
        noDataWarn.style.display = "block";
      } else {
        noDataWarn.textContent = "";
        noDataWarn.style.display = "none";
      }
    }
  }
  
  // Initialize with current week
  updateList("aktuelle_woche");
  
  // Dropdown & Picker-Listener
  var sel  = document.getElementById(canvasId + "_periode"),
      pick = document.getElementById(canvasId + "_zeitraum_picker"),
      warn = document.getElementById(canvasId + "_zeitraum_picker_warn"),
      btn  = document.getElementById(canvasId + "_zeitraum_btn"),
      von  = document.getElementById(canvasId + "_von"),
      bis  = document.getElementById(canvasId + "_bis");
  
  if (sel) sel.addEventListener("change", function(e){
    var v = e.target.value;
    if (v === "individueller_zeitraum") {
      if (pick) pick.style.display = "block";
      updateList(v);
    } else {
      if (pick) pick.style.display = "none";
      updateList(v);
    }
  });
  
  if (btn) btn.addEventListener("click", function(){
    var s = von.value, e = bis.value;
    if (!s || !e) { 
      if (warn) warn.textContent = "Bitte beide Daten wählen!"; 
      return; 
    }
    if (new Date(s) > new Date(e)) { 
      if (warn) warn.textContent = "Startdatum muss vor Enddatum liegen!"; 
      return; 
    }
    
    var tbody = document.getElementById(canvasId + "_table_body");
    var noDataWarn = document.getElementById(canvasId + "_no_data_warn");
    
    if (tbody) {
      tbody.innerHTML = "";
      
      var nl = [], nw = [];
      if (dat && dat["tage"]) {
        dat["tage"].forEach(function(d, i){
          var dt = new Date(d);
          if (dt >= new Date(s) && dt <= new Date(e)) { 
            nl.push(lab["tage"][i]); 
            nw.push(seri["tage"][i]); 
          }
        });
      }
      
      if (nl.length > 0) {
        for (var i = 0; i < nl.length; i++) {
          var row = tbody.insertRow();
          var cell1 = row.insertCell(0);
          var cell2 = row.insertCell(1);
          cell1.textContent = nl[i];
          cell2.textContent = nw[i];
        }
      }
      
      // Show/hide warnings
      if (noDataWarn) {
        if (nl.length === 0) {
          noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
          noDataWarn.style.display = "block";
          if (warn) warn.textContent = "";
        } else {
          noDataWarn.textContent = "";
          noDataWarn.style.display = "none";
          if (warn) warn.textContent = "";
        }
      }
    }
  });
}

// Function to initialize chart types (line, bar, etc.)
function tspv2InitChartInstance(canvasId, chartType, chartData, chartLabels, chartOptions, chartDates) {
  var seri = chartData,
      lab  = chartLabels,
      dat  = chartDates || window.tspv2ChartDates, // Y-m-d Format für Vergleiche
      opts = chartOptions;
  
  // ZUERST prüfen ob bereits ein Chart existiert und es zerstören
  var canvas = document.getElementById(canvasId);
  if (canvas) {
    var existingChart = Chart.getChart(canvas);
    if (existingChart) {
      try {
        existingChart.destroy();
      } catch (e) {
        console.warn("Existing chart destroy error:", e);
      }
    }
    if (canvas.chart) {
      try {
        canvas.chart.destroy();
      } catch (e) {
        console.warn("Canvas chart destroy error:", e);
      }
      canvas.chart = null;
    }
  }
  
  var ctx = document.getElementById(canvasId).getContext("2d");
  var scaleOpts = {};
  if (chartType==="bar"||chartType==="line") {
    var sg=true;
    scaleOpts = {
      x: { 
        display:sg, 
        grid:{display:sg}, 
        ticks:{maxTicksLimit:10},
        title: {
          display: opts.x_label && opts.x_label.length > 0,
          text: opts.x_label || ""
        }
      },
      y: {
        display:sg, 
        grid:{display:sg},
        min: opts.min, 
        max: opts.max,
        title: {
          display: opts.y_label && opts.y_label.length > 0,
          text: opts.y_label || ""
        },
        ticks: { 
          stepSize:1,
          callback:function(v){return opts.ylabels[v]|| (Number.isInteger(v)?v:"");}
        }
      }
    };
  }
  var chart = new Chart(ctx, {
    type: chartType,
    data: {
      labels: lab["aktuelle_woche"],
      datasets: [{
        label: opts.legend_label || null,
        data: seri["aktuelle_woche"],
        backgroundColor: opts.background_color,
        borderColor:     opts.border_color,
        fill:            opts.fill,
        tension:         opts.tension,
        borderWidth:     opts.borderwidth,
        pointBackgroundColor: opts.background_color,
        pointBorderColor:     opts.border_color
      }]
    },
    options: { responsive:true,
      plugins:{ legend:{ display: opts.legend==1 } },
      scales: scaleOpts
    }
  });
  
  // Chart-Referenz speichern
  ctx.canvas.chart = chart;
  
  // Event-Listener mit verzögerter Initialisierung
  setTimeout(function() {
    var sel  = document.getElementById(canvasId+"_periode"),
        pick = document.getElementById(canvasId+"_zeitraum_picker"),
        warn = document.getElementById(canvasId+"_zeitraum_picker_warn"),
        btn  = document.getElementById(canvasId+"_zeitraum_btn"),
        von  = document.getElementById(canvasId+"_von"),
        bis  = document.getElementById(canvasId+"_bis");
  
    // Funktion zum Prüfen und Anzeigen von Datenwarnungen
    function checkAndShowDataWarning(period) {
      var hasData = false;
      if (period === "individueller_zeitraum") {
        // Bei individuellem Zeitraum keine Warnung anzeigen - wird erst nach Button-Klick geprüft
        var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
        if (noDataWarn) {
          noDataWarn.textContent = "";
          noDataWarn.style.display = "none";
        }
        return false;
      } else {
        hasData = lab[period] && lab[period].length > 0;
      }
      
      // Warnung anzeigen/verstecken
      var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
      if (noDataWarn) {
        if (!hasData) {
          noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
          noDataWarn.style.display = "block";
        } else {
          noDataWarn.textContent = "";
          noDataWarn.style.display = "none";
        }
      }
      
      return hasData;
    }
  
    // Initial state based on current select value (respect saved custom range)
    var currentSelect = document.getElementById(canvasId+"_periode");
    var currentPeriod = currentSelect ? currentSelect.value : opts.default_period || "aktuelle_woche";
    var chartCanvas = document.getElementById(canvasId);
    var currentChart = (chartCanvas && Chart.getChart(chartCanvas)) || (chartCanvas ? chartCanvas.chart : null);
    if (currentPeriod === "individueller_zeitraum") {
      if (pick) pick.style.display = "block";
      var savedInit = (window.tspv2CustomRange && window.tspv2CustomRange[canvasId]) || null;
      if (currentChart) {
        currentChart.data.labels = savedInit && Array.isArray(savedInit.labels) ? savedInit.labels : [];
        currentChart.data.datasets[0].data = savedInit && Array.isArray(savedInit.data) ? savedInit.data : [];
      }
      // restore inputs
      var vonEl0 = document.getElementById(canvasId+"_von");
      var bisEl0 = document.getElementById(canvasId+"_bis");
      if (savedInit) {
        if (vonEl0 && savedInit.s) vonEl0.value = savedInit.s;
        if (bisEl0 && savedInit.e) bisEl0.value = savedInit.e;
      }
    } else {
      if (pick) pick.style.display = "none";
      if (currentChart) {
        currentChart.data.labels = lab[currentPeriod] || [];
        currentChart.data.datasets[0].data = seri[currentPeriod] || [];
      }
    }
    checkAndShowDataWarning(currentPeriod);
    try { if (currentChart) currentChart.update("none"); } catch (e) { console.warn("Chart initial update failed:", e); }
  
    // Dropdown Event-Listener
    if (sel && !sel.hasAttribute("data-listener-attached")) {
      sel.setAttribute("data-listener-attached", "true");
      sel.addEventListener("change", function(e){
        var canvas = document.getElementById(canvasId);
        var currentChart = (canvas && Chart.getChart(canvas)) || (canvas ? canvas.chart : null);
        if (!currentChart) { return; }
        var v=e.target.value;
        if (v==="individueller_zeitraum") {
          if (pick) pick.style.display="block"; 
          var saved = (window.tspv2CustomRange && window.tspv2CustomRange[canvasId]) || null;
          currentChart.data.labels = saved && Array.isArray(saved.labels) ? saved.labels : [];
          currentChart.data.datasets[0].data = saved && Array.isArray(saved.data) ? saved.data : [];
          // Restore inputs if saved
          if (saved) {
            if (von) von.value = saved.s || "";
            if (bis) bis.value = saved.e || "";
          }
          checkAndShowDataWarning(v);
        } else {
          if (pick) pick.style.display="none"; 
          currentChart.data.labels=lab[v]; 
          currentChart.data.datasets[0].data=seri[v];
          checkAndShowDataWarning(v);
        }
        try {
          currentChart.update("none"); // "none" verhindert Animation und reduziert Event-Probleme
        } catch (e) {
          console.warn("Chart update failed:", e);
        }
      });
    }
  
    // Button Event-Listener
    if (btn && !btn.hasAttribute("data-listener-attached")) {
      btn.setAttribute("data-listener-attached", "true");
      btn.addEventListener("click", function(){
        var canvas = document.getElementById(canvasId);
        var currentChart = (canvas && Chart.getChart(canvas)) || (canvas ? canvas.chart : null);
        if (!currentChart) { return; }
        var s=von.value, e=bis.value;
        if (!s||!e){ if (warn) warn.textContent="Bitte beide Daten wählen!"; return; }
        if (new Date(s)>new Date(e)){ if (warn) warn.textContent="Startdatum muss vor Enddatum liegen!"; return; }
        var nl=[], nw=[];
        if (dat && dat["tage"]) {
          dat["tage"].forEach(function(d,i){
            var dt=new Date(d);
            if (dt>=new Date(s)&&dt<=new Date(e)){ nl.push(lab["tage"][i]); nw.push(seri["tage"][i]); }
          });
        }
        currentChart.data.labels=nl; currentChart.data.datasets[0].data=nw; 
        // Persist range for this tab
        window.tspv2CustomRange = window.tspv2CustomRange || {};
        window.tspv2CustomRange[canvasId] = { labels: nl.slice(), data: nw.slice(), s: s, e: e };
        try {
          currentChart.update("none"); // "none" verhindert Animation und reduziert Event-Probleme
        } catch (e) {
          console.warn("Chart update failed:", e);
        }
        
        // Nur eine Warnung anzeigen - die allgemeine Datenwarnung verwenden
        var noDataWarn = document.getElementById(canvasId+"_no_data_warn");
        if (noDataWarn) {
          if (nl.length === 0) {
            noDataWarn.textContent = "Keine Daten für den gewählten Zeitraum vorhanden.";
            noDataWarn.style.display = "block";
            if (warn) warn.textContent = ""; // Ursprüngliche Warnung ausblenden
          } else {
            noDataWarn.textContent = "";
            noDataWarn.style.display = "none";
            if (warn) warn.textContent = ""; // Ursprüngliche Warnung ausblenden
          }
        }
      });
    }
  }, 100); // 100ms Verzögerung für DOM-Bereitschaft
}

document.addEventListener("DOMContentLoaded", function(){
  var containerId = "' . $container_id . '";
  var types = ' . json_encode($types) . ';
  var dataVarData = ' . $container_var . '_ChartData;
  var dataVarLabels = ' . $container_var . '_ChartLabels;
  var dataVarDates = ' . $container_var . '_ChartDates;
  var dataVarOptions = ' . $container_var . '_ChartOptions;
  
  // Erstes Chart initialisieren
  setTimeout(function() {
    if (types[0] === "list") {
      tspv2InitListInstance(containerId + "_chart_0", dataVarData, dataVarLabels, dataVarDates);
    } else {
      tspv2InitChartInstance(containerId + "_chart_0", types[0], dataVarData, dataVarLabels, dataVarOptions, dataVarDates);
    }
  }, 100);
  
     // Tab-Funktionalität mit Bootstrap-Style
   var tabContainer = document.getElementById(containerId + "_nav");
   if (tabContainer) {
     tabContainer.addEventListener("click", function(e) {
       if (e.target && e.target.classList.contains("nav-link")) {
         e.preventDefault();
         
         // Prüfen ob der Tab bereits aktiv ist
         if (e.target.classList.contains("active")) {
           return; // Tab ist bereits aktiv, nichts tun
         }
         
         var targetId = e.target.getAttribute("data-tab-target");
         if (targetId) {
           var chartIndex = targetId.match(/_content_(\\d+)/);
           if (chartIndex && chartIndex[1]) {
             var chartId = containerId + "_chart_" + chartIndex[1];
             var tabIndex = parseInt(chartIndex[1]);
             
             // ZUERST alle bestehenden Charts zerstören (vor Tab-Wechsel!)
             var allCanvases = document.querySelectorAll("#" + containerId + " canvas");
             allCanvases.forEach(function(canvas) {
               // Chart aus Registry entfernen
               var existingChart = Chart.getChart(canvas);
               if (existingChart) {
                 try {
                   existingChart.destroy();
                 } catch (e) {
                   console.warn("Registry chart destroy error:", e);
                 }
               }
               // Chart-Referenz löschen
               if (canvas.chart) {
                 try {
                   canvas.chart.destroy();
                 } catch (e) {
                   console.warn("Chart destroy error:", e);
                 }
                 canvas.chart = null;
               }
               // Zusätzlich: Alle Chart.js Instanzen mit dieser Canvas-ID zerstören
               var chartId = canvas.id;
               if (chartId) {
                 var chartInstance = Chart.getChart(chartId);
                 if (chartInstance) {
                   try {
                     chartInstance.destroy();
                   } catch (e) {
                     console.warn("Chart ID destroy error:", e);
                   }
                 }
               }
             });
             
             // Bootstrap-Style Tab-Wechsel
             var allTabs = document.querySelectorAll("#" + containerId + "_content .tab-pane");
             var allTabButtons = document.querySelectorAll("#" + containerId + "_nav .nav-link");
             
             // Alle Tabs ausblenden
             allTabs.forEach(function(tab) {
               tab.classList.remove("show", "active");
             });
             
             // Alle Tab-Buttons deaktivieren
             allTabButtons.forEach(function(btn) {
               btn.classList.remove("active");
               btn.setAttribute("aria-selected", "false");
             });
             
             // Gewählten Tab anzeigen
             var targetTab = document.getElementById(targetId);
             if (targetTab) {
               targetTab.classList.add("show", "active");
             }
             
             // Tab-Button aktivieren
             e.target.classList.add("active");
             e.target.setAttribute("aria-selected", "true");
             
             // Chart initialisieren wenn noch nicht initialisiert
             setTimeout(function() {
               if (types[tabIndex] === "list") {
                 // List ohne Canvas initialisieren
                 tspv2InitListInstance(chartId, dataVarData, dataVarLabels, dataVarDates);
               } else {
                 var canvas = document.getElementById(chartId);
                 if (canvas) {
                   // Neues Chart erstellen (Canvas ist bereits sauber)
                   tspv2InitChartInstance(chartId, types[tabIndex], dataVarData, dataVarLabels, dataVarOptions, dataVarDates);
                 }
               }
               // Auswahl des eigenen Tabs erzwingen
               var selEl = document.getElementById(chartId + "_periode");
               if (selEl) {
                 try { selEl.dispatchEvent(new Event("change")); } catch (e) {}
               }
             }, 300);
           }
         }
       }
     });
   }
});
</script>';
        
        return $tab_nav . $tab_content . $js_tabs;
    }
}

    // Platzhalter für Datenzugriff: Holt Werte für ein Feld und den aktuellen User
    private function getFieldDataForCurrentUser($field_id) {
        $user_id = get_current_user_id();
        
        // User-Modus prüfen: Falls ein anderer User ausgewählt ist, dessen Daten laden
        $selectui = get_user_meta($user_id, 'coach_select_user', true);
        if ($selectui != '') {
            $user_id = $selectui;
        }
        $data = Trainingssystem_Plugin_Database::getInstance()->Formdata->getAllFormfieldData($field_id, $user_id);
        $values = [];
        foreach ($data as $entry) {
            $values[] = [
                'value' => $entry->getData(),
                'date' => $entry->getSaveDate(),
                'formgroup' => $entry->getFormGroup()
            ];
        }
        return $values;
    }

    private function getFieldDataForCurrentUserByFormId($form_id, &$debugField = null) {
        $user_id = get_current_user_id();
        
        // User-Modus prüfen: Falls ein anderer User ausgewählt ist, dessen Daten laden
        $selectui = get_user_meta($user_id, 'coach_select_user', true);
        if ($selectui != '') {
            $user_id = $selectui;
        }
        $fields = Trainingssystem_Plugin_Database::getInstance()->Formfield->getFormfieldsByForm($form_id);
        $found_field = isset($fields[0]) ? $fields[0] : null;
        if ($debugField !== null) {
            $debugField['found_field'] = $found_field;
        }
        if (!$found_field) return [];
        $field_id = $found_field->getFieldId();
        $data = Trainingssystem_Plugin_Database::getInstance()->Formdata->getAllFormfieldData($field_id, $user_id);
        $values = [];
        foreach ($data as $entry) {
            $values[] = [
                'value' => $entry->getData(),
                'date' => $entry->getSaveDate(),
                'formgroup' => $entry->getFormGroup()
            ];
        }
        return $values;
    }

    // --- Gruppierungsfunktionen als Methoden der Klasse ---
    /**
     * Gruppiert Werte nach Kalenderwoche
     */
    private function gruppiere_nach_woche($daten) {
        $result = [];
        foreach ($daten as $datum => $wert) {
            $ts = strtotime($datum);
            $jahr = date('o', $ts); // ISO-Jahr
            $kw = date('W', $ts);   // Kalenderwoche
            $label = sprintf('KW %02d/%d', $kw, $jahr);
            if (!isset($result[$label])) $result[$label] = [];
            $result[$label][] = $wert;
        }
        // Mittelwerte berechnen
        $final = [];
        foreach ($result as $label => $werte) {
            // Nur numerische Werte für array_sum verwenden
            $numeric_werte = array_filter($werte, 'is_numeric');
            if (!empty($numeric_werte)) {
                $final[$label] = round(array_sum($numeric_werte) / count($numeric_werte), 2);
            }
        }
        ksort($final);
        return $final;
    }
    /**
     * Gruppiert Werte nach Monat
     */
    private function gruppiere_nach_monat($daten) {
        $result = [];
        $formatter = new IntlDateFormatter(
            'de_DE', 
            IntlDateFormatter::NONE, 
            IntlDateFormatter::NONE, 
            null, 
            null, 
            'MMM yyyy'
        );
        
        foreach ($daten as $datum => $wert) {
            $ts = strtotime($datum);
            $label = $formatter->format($ts); // z.B. "Jul 2025"
            if (!isset($result[$label])) $result[$label] = [];
            $result[$label][] = $wert;
        }
        // Mittelwerte berechnen
        $final = [];
        foreach ($result as $label => $werte) {
            // Nur numerische Werte für array_sum verwenden
            $numeric_werte = array_filter($werte, 'is_numeric');
            if (!empty($numeric_werte)) {
                $final[$label] = round(array_sum($numeric_werte) / count($numeric_werte), 2);
            }
        }
        ksort($final);
        return $final;
    }
    /**
     * Gruppiert Werte nach Jahr
     */
    private function gruppiere_nach_jahr($daten) {
        $result = [];
        foreach ($daten as $datum => $wert) {
            $ts = strtotime($datum);
            $jahr = date('Y', $ts);
            if (!isset($result[$jahr])) $result[$jahr] = [];
            $result[$jahr][] = $wert;
        }
        // Mittelwerte berechnen
        $final = [];
        foreach ($result as $label => $werte) {
            // Nur numerische Werte für array_sum verwenden
            $numeric_werte = array_filter($werte, 'is_numeric');
            if (!empty($numeric_werte)) {
                $final[$label] = round(array_sum($numeric_werte) / count($numeric_werte), 2);
            }
        }
        ksort($final);
        return $final;
    }

    // Hilfsfunktion zur korrekten Verarbeitung von Daten (normal oder Skala-Array)
    private function processFieldValue($value) {
        // Wenn es ein Array ist (Skala-Feld), nehmen wir den ersten Wert
        if (is_array($value)) {
            return isset($value[0]) ? $value[0] : null;
        }
        // Ansonsten geben wir den Wert direkt zurück
        return $value;
    }

    // Hilfsfunktion um Exercise-IDs zu Titeln aufzulösen
    private function resolveExerciseIds($value) {
        if (empty($value)) {
            return $value;
        }
        
        // Falls es bereits ein Array ist (bereits dekodiert)
        if (is_array($value)) {
            $titles = [];
            foreach ($value as $exercise_id) {
                $post = get_post($exercise_id);
                if ($post && $post->post_type === 'uebungen') {
                    $titles[] = $post->post_title;
                } else {
                    $titles[] = "Exercise #{$exercise_id}"; // Fallback falls Exercise nicht gefunden
                }
            }
            return implode(', ', $titles);
        }
        
        // Versuche JSON zu dekodieren (für Arrays von IDs als String)
        $decoded = json_decode($value, true);
        if (is_array($decoded)) {
            $titles = [];
            foreach ($decoded as $exercise_id) {
                $post = get_post($exercise_id);
                if ($post && $post->post_type === 'uebungen') {
                    $titles[] = $post->post_title;
                } else {
                    $titles[] = "Exercise #{$exercise_id}"; // Fallback falls Exercise nicht gefunden
                }
            }
            return implode(', ', $titles);
        }
        
        // Falls es ein String mit kommagetrennten IDs ist (z.B. "131,961,959")
        if (is_string($value) && strpos($value, ',') !== false) {
            $ids = array_map('trim', explode(',', $value));
            $titles = [];
            foreach ($ids as $exercise_id) {
                if (is_numeric($exercise_id)) {
                    $post = get_post($exercise_id);
                    if ($post && $post->post_type === 'uebungen') {
                        $titles[] = $post->post_title;
                    } else {
                        $titles[] = "Exercise #{$exercise_id}"; // Fallback falls Exercise nicht gefunden
                    }
                }
            }
            return implode(', ', $titles);
        }
        
        // Falls es eine einzelne ID ist (String)
        if (is_numeric($value)) {
            $post = get_post($value);
            if ($post && $post->post_type === 'uebungen') {
                return $post->post_title;
            } else {
                return "Exercise #{$value}"; // Fallback falls Exercise nicht gefunden
            }
        }
        
        // Falls es bereits ein Titel oder anderer Wert ist
        return $value;
    }
} 