import * as i0 from '@angular/core';
import { EventEmitter, ViewChild, Output, Input, Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { W as WtoolboxService, k as WuicClientException, l as WuicErrorCodes, G as GlobalHandler, M as MetadataProviderService, T as TranslationManagerService, U as UserInfoService, A as ArchetypeConfiguratorComponent } from './wuic-framework-lib-wuic-framework-lib-DJZAYFw8.mjs';
import * as i5 from 'primeng/chart';
import { ChartModule } from 'primeng/chart';
import * as i8 from '@ngx-translate/core';
import { TranslateModule } from '@ngx-translate/core';
import * as i1 from '@angular/platform-browser';
import * as i1$1 from '@angular/router';

class ChartListComponent {
    titleService;
    cd;
    route;
    trslSrv;
    userInfo;
    /**
     * Input dal componente padre per hardcoded route; usata nella configurazione e nel rendering del componente.
     */
    hardcodedRoute;
    /**
     * Input dal componente padre per parent record; usata nella configurazione e nel rendering del componente.
     */
    parentRecord;
    /**
     * Input dal componente padre per parent meta info; usata nella configurazione e nel rendering del componente.
     */
    parentMetaInfo;
    /**
     * Input dal componente padre per datasource; usata nella configurazione e nel rendering del componente.
     */
    datasource;
    /**
     * Input dal componente padre per hardcoded datasource; usata nella configurazione e nel rendering del componente.
     */
    hardcodedDatasource;
    /**
     * Input dal componente padre per hide toolbar; quando true nasconde la toolbar chart.
     */
    hideToolbar = false;
    /**
     * Evento emesso quando l'utente seleziona un punto/serie nel p-chart.
     */
    onChartDataSelect = new EventEmitter();
    /**
     * Evento emesso quando viene eseguito il drilldown chart (prima/dopo callback custom metadata).
     */
    onChartDrillDown = new EventEmitter();
    /**
     * Evento emesso quando il componente riceve dati validi dal datasource e ricostruisce il dataset chart.
     */
    onChartDataBound = new EventEmitter();
    /**
     * Riferimento a elementi o componenti figli usato dalla logica UI per chart ref.
     */
    chartRef;
    /**
     * Riferimento a elementi o componenti figli usato dalla logica UI per chart host ref.
     */
    chartHostRef;
    // @ViewChild("fcEventContent") eventContent: TemplateRef<any>;
    // @ViewChild("calendar") calendar: FullCalendarComponent;
    /**
     * Collezione dati per chart options, consumata dal rendering e dalle operazioni del componente.
     */
    chartOptions;
    /**
     * Collezione dati per records, consumata dal rendering e dalle operazioni del componente.
     */
    records = [];
    /**
     * Configurazione di presentazione per content renderers, usata nel rendering del componente.
     */
    contentRenderers = new Map();
    /**
     * Collezione dati per metas, consumata dal rendering e dalle operazioni del componente.
     */
    metas;
    /**
     * Proprieta di stato del componente per cols, usata dalla logica interna e dal template.
     */
    cols;
    /**
     * Metadati completi della route corrente (tabella, colonne, regole) usati per costruire UI e logica runtime.
     */
    metaInfo;
    /**
     * Flag di stato che governa il comportamento UI/logico relativo a loading.
     */
    loading = false;
    /**
     * Proprieta di stato del componente per title field, usata dalla logica interna e dal template.
     */
    titleField = 'title';
    /**
     * Configurazione di presentazione per item template string, usata nel rendering del componente.
     */
    itemTemplateString;
    /**
     * Proprieta di stato del componente per counter, usata dalla logica interna e dal template.
     */
    counter = 0;
    /**
     * Configurazione di presentazione per item template, usata nel rendering del componente.
     */
    itemTemplate;
    /**
     * Proprieta di stato del componente per title function, usata dalla logica interna e dal template.
     */
    titleFunction;
    /**
     * Proprieta di stato del componente per basic data, usata dalla logica interna e dal template.
     */
    basicData;
    /**
     * Collezione dati per basic options, consumata dal rendering e dalle operazioni del componente.
     */
    basicOptions;
    /**
     * Proprieta di stato del componente per data, usata dalla logica interna e dal template.
     */
    data;
    /**
     * Proprieta di stato del componente per total records, usata dalla logica interna e dal template.
     */
    totalRecords = 0;
    /**
     * Proprieta di stato del componente per cutoff value, usata dalla logica interna e dal template.
     */
    cutoffValue = 0;
    /**
     * Proprieta di stato del componente per last result info, usata dalla logica interna e dal template.
     */
    lastResultInfo;
    /**
     * Flag di stato che governa il comportamento UI/logico relativo a show config popup.
     */
    showConfigPopup = false;
    /**
     * Collezione dati per chart types, consumata dal rendering e dalle operazioni del componente.
     */
    chartTypes = ['bar', 'line', 'pie', 'doughnut', 'radar', 'polarArea', 'bubble', 'scatter'];
    /**
     * Valore corrente selezionato per selected chart type, usato dai flussi interattivi del componente.
     */
    selectedChartType = 'bar';
    /**
     * Collezione dati per legend positions, consumata dal rendering e dalle operazioni del componente.
     */
    legendPositions = ['top', 'right', 'bottom', 'left'];
    /**
     * Collezione dati per animation easings, consumata dal rendering e dalle operazioni del componente.
     */
    animationEasings = ['linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeOutQuart'];
    /**
     * Collezione dati per palette presets, consumata dal rendering e dalle operazioni del componente.
     */
    palettePresets = ['default', 'warm', 'cool', 'pastel'];
    /**
     * Proprieta di stato del componente per ui, usata dalla logica interna e dal template.
     */
    ui = {
        legendDisplay: true,
        legendPosition: 'top',
        titleDisplay: false,
        titleText: '',
        tooltipEnabled: true,
        maintainAspectRatio: false,
        animationDuration: 700,
        animationEasing: 'easeOutQuart',
        sortEnabled: true,
        sortDir: 'desc',
        stacked: false,
        indexAxis: 'x',
        showGridX: true,
        showGridY: true,
        beginAtZero: false,
        tension: 0.3,
        barThickness: 0,
        cutout: 50,
        palette: 'default'
    };
    /**
     * Flag di stato che governa il comportamento UI/logico relativo a has user chart type override.
     */
    hasUserChartTypeOverride = false;
    /**
     * Proprieta di stato del componente per chart bottom padding, usata dalla logica interna e dal template.
     */
    chartBottomPadding = 16;
    /**
     * Proprieta di stato del componente per chart resize pass delays ms, usata dalla logica interna e dal template.
     */
    chartResizePassDelaysMs = [60, 220];
    /**
     * Collezione dati per default chart options, consumata dal rendering e dalle operazioni del componente.
     */
    defaultChartOptions;
    /**
     * Collezione dati per meta chart options, consumata dal rendering e dalle operazioni del componente.
     */
    metaChartOptions = {};
    /**
     * Proprieta di stato del componente per theme observer, usata dalla logica interna e dal template.
     */
    themeObserver;
    /**
     * Proprieta di stato del componente per chart resize observer, usata dalla logica interna e dal template.
     */
    chartResizeObserver;
    /**
     * Proprieta di stato del componente per pending chart resize handle, usata dalla logica interna e dal template.
     */
    pendingChartResizeHandle;
    /**
     * Collezione dati per pending chart measure timeouts, consumata dal rendering e dalle operazioni del componente.
     */
    pendingChartMeasureTimeouts = [];
    /**
     * Proprieta di stato del componente per datasource ready subscription, usata dalla logica interna e dal template.
     */
    datasourceReadySubscription;
    /**
     * Proprieta di stato del componente per fetch info subscription, usata dalla logica interna e dal template.
     */
    fetchInfoSubscription;
    /**
     * Proprieta di stato del componente per route name, usata dalla logica interna e dal template.
     */
    routeName;
    /**
     * Proprieta di stato del componente per chart host height px, usata dalla logica interna e dal template.
     */
    chartHostHeightPx;
    /**
     * Proprieta di stato del componente per on window resize, usata dalla logica interna e dal template.
     */
    onWindowResize = () => {
        this.measureAndResizeChart(true);
    };
    /**
     * Proprieta di stato del componente per on window scroll, usata dalla logica interna e dal template.
     */
    onWindowScroll = () => {
        this.measureAndResizeChart();
    };
    /**
     * Inietta servizi di UI/sessione usati dal componente chart e inizializza
     * le opzioni base del grafico con il tema corrente.
     * @param titleService Servizio Angular `Title`.
     * @param cd `ChangeDetectorRef` per refresh espliciti.
     * @param route Route attiva per ricavare il nome route quando non hardcoded.
     * @param trslSrv Servizio traduzioni per messaggi di conferma/successo.
     * @param userInfo Servizio utente per controlli ruolo admin.
     */
    constructor(titleService, cd, route, trslSrv, userInfo) {
        this.titleService = titleService;
        this.cd = cd;
        this.route = route;
        this.trslSrv = trslSrv;
        this.userInfo = userInfo;
        this.defaultChartOptions = {
            maintainAspectRatio: false,
            responsive: true,
            plugins: {}
        };
        this.basicOptions = this.deepMerge({}, this.defaultChartOptions, this.getThemeChartOptions());
    }
    /**
     * Espone se l'utente corrente ha privilegi amministrativi.
     * @returns `true` se utente admin.
     */
    get isAdmin() {
        return this.userInfo.isCurrentUserAdmin();
    }
    /**
     * Inizializza il componente preparando stato, sottoscrizioni e primi caricamenti richiesti.
     */
    ngOnInit() {
        this.routeName = this.hardcodedRoute ? this.hardcodedRoute : this.route.snapshot.paramMap.get('route') || '';
        this.observeThemeClassChanges();
        this.attachLayoutListeners();
        if (this.hardcodedDatasource) {
            this.datasource = new BehaviorSubject(this.hardcodedDatasource);
            this.subscribeToDS();
        }
        else if (this.datasource?.value) {
            this.subscribeToDS();
        }
        else if (this.datasource?.subscribe) {
            this.datasourceReadySubscription?.unsubscribe();
            this.datasourceReadySubscription = this.datasource.subscribe((ds) => {
                if (ds) {
                    this.datasourceReadySubscription?.unsubscribe();
                    this.datasourceReadySubscription = undefined;
                    this.subscribeToDS();
                }
            });
        }
    }
    /**
     * Rilascia risorse e sottoscrizioni per evitare leak e stati pendenti.
     */
    ngOnDestroy() {
        this.datasourceReadySubscription?.unsubscribe();
        this.fetchInfoSubscription?.unsubscribe();
        this.themeObserver?.disconnect();
        this.chartResizeObserver?.disconnect();
        this.detachLayoutListeners();
        if (typeof window !== 'undefined' && this.pendingChartResizeHandle) {
            window.cancelAnimationFrame(this.pendingChartResizeHandle);
            this.pendingChartResizeHandle = undefined;
        }
        this.clearPendingChartMeasureTimeouts();
    }
    /**
     * Sottoscrive il `fetchInfo$` del datasource, applica configurazione chart da metadati
     * (tipo/opzioni), aggiorna titolo pagina e prepara dataset/resize del grafico.
     */
    subscribeToDS() {
        this.fetchInfoSubscription?.unsubscribe();
        if (!this.datasource?.value?.fetchInfo$) {
            return;
        }
        this.fetchInfoSubscription = this.datasource.value.fetchInfo$.subscribe((info) => {
            const dataSourceRoute = this.datasource?.value?.route?.value || this.routeName;
            if (info && dataSourceRoute == info.metaInfo?.tableMetadata?.md_route_name) {
                this.metaInfo = info.metaInfo;
                const metaChartOptions = info.metaInfo.tableMetadata.extraProps?.archetypes?.chart?.options || {};
                const metaChartType = info.metaInfo.tableMetadata.extraProps?.archetypes?.chart?.type || "bar";
                this.metaChartOptions = metaChartOptions;
                // Seed UI controls (stacked/indexAxis) from dataOptions when present.
                // Permette config 'stacked: true' + 'indexAxis: y' nel mdpropsbag.archetypes.chart.dataOptions
                // direttamente, senza che l'utente debba toggle-are nel chart settings dialog.
                const seedDataOptions = info.metaInfo.tableMetadata.extraProps?.archetypes?.chart?.dataOptions || {};
                if (seedDataOptions.stacked !== undefined && !this.hasUserChartTypeOverride) {
                    this.ui.stacked = !!seedDataOptions.stacked;
                }
                if (seedDataOptions.indexAxis && !this.hasUserChartTypeOverride) {
                    this.ui.indexAxis = (String(seedDataOptions.indexAxis).toLowerCase() === 'y') ? 'y' : 'x';
                }
                this.basicOptions = this.deepMerge({}, this.defaultChartOptions, this.getThemeChartOptions(), this.metaChartOptions);
                if (!this.hasUserChartTypeOverride) {
                    this.selectedChartType = metaChartType;
                }
                this.refreshChartOptions();
                this.startChartResizeObserver();
                this.scheduleChartResizePasses();
                let title = this.metaInfo.tableMetadata.md_display_string;
                this.titleService.setTitle(title);
                if (info.resultInfo.dato) {
                    this.lastResultInfo = info.resultInfo;
                    this.totalRecords = this.getTotalForCutoff(info.resultInfo);
                    const configuredCutoff = Number(this.metaInfo.tableMetadata.extraProps?.archetypes?.chart?.dataOptions?.cutOffCount || 0);
                    this.cutoffValue = configuredCutoff > 0 ? Math.min(configuredCutoff, this.totalRecords || configuredCutoff) : this.totalRecords;
                    this.data = this.parseData(info.resultInfo, this.cutoffValue);
                    this.onChartDataBound.emit({
                        metaInfo: this.metaInfo,
                        data: this.data,
                        rawResult: info.resultInfo
                    });
                    this.scheduleChartResizePasses();
                }
                else {
                    this.datasource.value.fetchData();
                }
            }
        });
    }
    /**
   * Verifica una condizione di stato o di validita normalizzando e trasformando collezioni di record.
   * @returns Esito booleano del controllo effettuato dal metodo (true quando la condizione verificata risulta soddisfatta).
   */
    hasGroupingApplied() {
        return Array.isArray(this.datasource?.value?.groupInfo) && this.datasource.value.groupInfo.length > 0;
    }
    /**
     * Calcola il totale usato dal cutoff chart scegliendo tra `totalGroups` e `totalRowCount`
     * in base alla presenza di grouping; usa la lunghezza di `dato` come fallback.
     * @param result Risultato datasource corrente.
     * @returns Totale non negativo da usare per limite cutoff.
     */
    getTotalForCutoff(result) {
        const useTotalGroups = this.hasGroupingApplied();
        const groupTotal = Number(result?.totalGroups || 0);
        const rowTotal = Number(result?.totalRowCount || 0);
        const fallbackTotal = Array.isArray(result?.dato) ? result.dato.length : 0;
        const total = useTotalGroups ? (groupTotal || fallbackTotal) : (rowTotal || fallbackTotal);
        return Math.max(0, total);
    }
    /**
     * Esegue il callback di drill-down configurato nei metadati chart
     * passando record cliccato, opzioni chart e dati correnti.
     * @param $event Evento click punto/barra del grafico.
     */
    drillDown($event) {
        this.onChartDataSelect.emit($event);
        let clicked = this.datasource?.value?.resultInfo?.dato?.[$event?.element?.index];
        this.onChartDrillDown.emit({
            event: $event,
            clickedItem: clicked,
            chartOptions: this.chartOptions,
            data: this.data
        });
        if (this.metaInfo.tableMetadata.extraProps?.archetypes?.chart?.drillDown) {
            clicked = this.datasource.value.resultInfo.dato[$event.element.index];
            // `new Function(paramList, body)` richiede il `paramList` come
            // stringa di NOMI separati da virgola — niente type annotations
            // TypeScript (`: any`) che il parser JS nativo non conosce,
            // altrimenti butta "SyntaxError: Arg string terminates parameters early".
            //
            // `utility` e' iniettato come 4 parametro perche' molti drillDown scripts
            // legacy fanno riferimento a `utility.chartDrillDown(...)` senza che il
            // framework abbia mai dichiarato quel namespace → ReferenceError.
            // Passiamo `window.utility` se la host app l'ha definito (crm/wuictest
            // possono estendere via `(window as any).utility = { ... }`), altrimenti
            // forniamo un default minimo con chartDrillDown no-op + log per debug.
            const drillDown = this.metaInfo.tableMetadata.extraProps.archetypes.chart.drillDown;
            const utility = this.resolveDrillDownUtility();
            // skills/sql-injection-defense + skills/typed-localized-exceptions:
            // user-supplied JS dal metadata DEVE passare per runUserCallbackSync
            // → typed `errors.client.user_callback.failed` su throw, niente raw stack.
            const ctx = { archetype: 'chart', route: this.metaInfo?.tableMetadata?.md_route_name };
            if (typeof drillDown === 'string') {
                // Iniettiamo `utility` SOLO nel path string-eval (legacy metadata che fanno
                // riferimento a `utility.chartDrillDown`). Quando drillDown e' gia' una
                // function ref, il caller ha accesso diretto ai suoi imports/closure →
                // non serve iniettare utility (evita anche TS2554 su signature 3-args).
                WtoolboxService.runUserCallbackSync('archetypes.chart.drillDown', () => new Function("clickedItem, chartOptions, data, utility", drillDown)(clicked, this.chartOptions, this.data, utility), [], ctx);
            }
            else {
                WtoolboxService.runUserCallbackSync('archetypes.chart.drillDown', () => drillDown(clicked, this.chartOptions, this.data), [], ctx);
            }
        }
    }
    /**
     * Ritorna un oggetto `utility` da iniettare nel scope del drillDown script.
     * Prima controlla se l'app host ha definito `window.utility` (pattern
     * legacy): se si', lo usa tel-quel. Altrimenti fornisce un default minimo
     * con `chartDrillDown` no-op che logga il click per debug — senza crash.
     */
    resolveDrillDownUtility() {
        const fromHost = window?.utility;
        if (fromHost && typeof fromHost === 'object')
            return fromHost;
        return {
            chartDrillDown: (clickedItem, _chartOptions, _data) => {
                console.warn("[chart-list] drillDown script references 'utility' but host app did not define window.utility. No-op fallback.", { clickedItem });
            }
        };
    }
    /**
  * Recupera i dati/valori richiesti da `getSeriesValue`.
  * @param row Dato/record su cui il metodo applica elaborazioni o aggiornamenti.
  * @param field Parametro utilizzato dal metodo nel flusso elaborativo.
  * @returns Valore risolto da `getSeriesValue` in base ai criteri implementati.
  */
    getSeriesValue(row, field) {
        if (!row || !field) {
            return undefined;
        }
        if (row[field] !== undefined) {
            return row[field];
        }
        const aggregateSuffixes = ['_SUM', '_AVG', '_MIN', '_MAX', '_COUNT'];
        for (const suffix of aggregateSuffixes) {
            const key = `${field}${suffix}`;
            if (row[key] !== undefined) {
                return row[key];
            }
        }
        const prefixed = Object.keys(row).find(k => k.startsWith(`${field}_`));
        if (prefixed) {
            return row[prefixed];
        }
        return undefined;
    }
    /**
  * Recupera i dati/valori richiesti da `getLabelValue`.
  * @param row Dato/record su cui il metodo applica elaborazioni o aggiornamenti.
  * @param field Parametro utilizzato dal metodo nel flusso elaborativo.
  * @returns Valore stringa restituito da `getLabelValue`.
  */
    getLabelValue(row, field) {
        if (!row) {
            return '';
        }
        if (field && row[field] !== undefined) {
            return row[field];
        }
        const descriptiveKey = Object.keys(row).find(k => !k.startsWith('__') && k.includes('___') && row[k] !== undefined && row[k] !== null && row[k] !== '');
        if (descriptiveKey) {
            return row[descriptiveKey];
        }
        const firstStringKey = Object.keys(row).find(k => !k.startsWith('__') && typeof row[k] === 'string' && row[k].trim().length > 0);
        if (firstStringKey) {
            return row[firstStringKey];
        }
        const firstKey = Object.keys(row).find(k => !k.startsWith('__'));
        return firstKey ? row[firstKey] : '';
    }
    /**
     * Legge il cutoff da input HTML e lo applica al dataset visualizzato.
     * @param event Evento `input/change`.
     */
    onCutoffChanged(event) {
        const value = Number(event.target?.value || 0);
        this.applyCutoffValue(value);
    }
    /**
     * Applica il cutoff ricevuto dal controllo numerico/slider.
     * @param value Nuovo limite massimo di elementi da mostrare.
     */
    onCutoffValueChanged(value) {
        this.applyCutoffValue(value);
    }
    /**
     * Normalizza il cutoff entro [1, totalRecords], persiste l'opzione in metadata locale
     * e ricostruisce i dati chart se l'ultimo risultato e disponibile.
     * @param value Valore cutoff richiesto.
     */
    applyCutoffValue(value) {
        this.cutoffValue = Math.max(1, Math.min(Number(value || 0), this.totalRecords || Number(value || 0) || 1));
        this.persistChartArchetypeOptions();
        if (this.lastResultInfo) {
            this.data = this.parseData(this.lastResultInfo, this.cutoffValue);
        }
    }
    /**
     * Apre il popup configurazione caricando prima lo stato dal `md_props_bag`.
     */
    openConfigPopup() {
        if (!this.isAdmin) {
            return;
        }
        this.syncChartConfigFromPropsBag();
        this.showConfigPopup = true;
    }
    /**
     * Chiude il popup configurazione chart.
     */
    closeConfigPopup() {
        this.showConfigPopup = false;
    }
    /**
     * Esporta il grafico corrente in PNG usando `toBase64Image()` e download via link temporaneo.
     */
    exportChart() {
        const chart = this.chartRef?.chart;
        if (!chart) {
            return;
        }
        const image = chart.toBase64Image();
        if (!image) {
            return;
        }
        const fileName = `${(this.metaInfo?.tableMetadata?.md_route_name || 'chart')}.png`;
        const link = document.createElement('a');
        link.href = image;
        link.download = fileName;
        link.click();
    }
    /**
     * Handler cambio tipo chart: aggiorna opzioni, persiste la scelta e ricalcola il dataset.
     */
    onChartTypeChanged() {
        this.hasUserChartTypeOverride = true;
        this.refreshChartOptions();
        this.persistChartArchetypeOptions();
        if (this.lastResultInfo) {
            this.data = this.parseData(this.lastResultInfo, this.cutoffValue);
        }
    }
    /**
     * Handler cambio opzioni widget UI: ricalcola opzioni chart, persiste e rigenera dati.
     */
    onWidgetOptionsChanged() {
        this.refreshChartOptions();
        this.persistChartArchetypeOptions();
        if (this.lastResultInfo) {
            this.data = this.parseData(this.lastResultInfo, this.cutoffValue);
        }
    }
    /**
     * Ricompone le opzioni finali del grafico facendo merge tra base/theme/widget
     * e pianifica un resize del canvas.
     */
    // Chart.js + PrimeNG controller registry — la lista deve riflettere i
    // controller registrati di default. Se l'utente configura un type non
    // presente, falliamo PRIMA del render con typed envelope, invece di
    // lasciare propagare il "is not a registered controller" raw da PrimeNG.
    static KNOWN_CHART_TYPES = new Set([
        'bar', 'line', 'pie', 'doughnut', 'polarArea', 'radar', 'bubble', 'scatter'
    ]);
    refreshChartOptions() {
        const widgetOptions = this.buildWidgetChartOptions();
        this.chartOptions = this.deepMerge({}, this.basicOptions, widgetOptions);
        this.chartOptions.type = this.selectedChartType || this.chartOptions.type || 'bar';
        // skills/typed-localized-exceptions: pre-validate type per evitare il
        // raw stack PrimeNG `Registry._get` quando type=metadata non noto.
        if (!ChartListComponent.KNOWN_CHART_TYPES.has(this.chartOptions.type)) {
            const exc = new WuicClientException(WuicErrorCodes.archetypeInitFailed('chart'), {
                archetype: 'chart',
                phase: 'refreshChartOptions',
                route: this.metaInfo?.tableMetadata?.md_route_name,
                innerName: 'UnknownChartType',
                innerMessage: `Chart type '${this.chartOptions.type}' is not a registered controller. Known types: ${Array.from(ChartListComponent.KNOWN_CHART_TYPES).join(', ')}`,
            }, { surface: 'archetype', targetName: 'ChartListComponent.refreshChartOptions' });
            GlobalHandler.emitClientException(exc);
            // Fallback al default 'bar' per non rompere il render successivo.
            this.chartOptions.type = 'bar';
        }
        this.scheduleChartResize();
    }
    /**
     * Costruisce l'oggetto opzioni Chart.js partendo dai controlli UI
     * (legend/title/tooltip/scales/stacking/cutout/indexAxis/animazione).
     * @returns Opzioni widget da mergeare con base+tema.
     */
    buildWidgetChartOptions() {
        const options = {
            maintainAspectRatio: !!this.ui.maintainAspectRatio,
            responsive: true,
            animation: {
                duration: Math.max(0, Number(this.ui.animationDuration || 0)),
                easing: this.ui.animationEasing || 'easeOutQuart',
                // After render: ricalcola spazio disponibile e adatta il canvas.
                onComplete: () => this.onChartAfterRender()
            },
            plugins: {
                legend: {
                    display: !!this.ui.legendDisplay,
                    position: this.ui.legendPosition || 'top'
                },
                title: {
                    display: !!this.ui.titleDisplay,
                    text: this.ui.titleText || ''
                },
                tooltip: {
                    enabled: !!this.ui.tooltipEnabled
                }
            }
        };
        if (this.isCartesianChart()) {
            options.scales = {
                x: {
                    stacked: !!this.ui.stacked,
                    grid: { display: !!this.ui.showGridX }
                },
                y: {
                    stacked: !!this.ui.stacked,
                    beginAtZero: !!this.ui.beginAtZero,
                    grid: { display: !!this.ui.showGridY }
                }
            };
        }
        if (this.isBarChart()) {
            options.indexAxis = this.ui.indexAxis || 'x';
        }
        if (this.isDoughnutLikeChart()) {
            options.cutout = `${Math.max(0, Math.min(95, Number(this.ui.cutout || 0)))}%`;
        }
        return options;
    }
    /**
     * Osserva i cambi classe sul root document per intercettare switch tema
     * e rigenerare opzioni colore/layout del grafico.
     */
    observeThemeClassChanges() {
        const root = document?.documentElement;
        if (!root || typeof MutationObserver === 'undefined') {
            return;
        }
        this.themeObserver = new MutationObserver((mutations) => {
            const classChanged = mutations.some(m => m.type === 'attributes' && m.attributeName === 'class');
            if (!classChanged) {
                return;
            }
            this.basicOptions = this.deepMerge({}, this.defaultChartOptions, this.getThemeChartOptions(), this.metaChartOptions);
            this.refreshChartOptions();
            this.scheduleChartResizePasses();
        });
        this.themeObserver.observe(root, { attributes: true, attributeFilter: ['class'] });
    }
    /**
     * Deriva palette e opzioni visuali chart dalle CSS variables correnti
     * (testo, bordi, tooltip, griglie) con fallback light/dark.
     * @returns Blocco opzioni tema Chart.js.
     */
    getThemeChartOptions() {
        const root = document?.documentElement;
        const style = getComputedStyle(root);
        const darkEnabled = root?.classList?.contains('theme-dark');
        const textColor = (style.getPropertyValue('--text-color') || '').trim() || (darkEnabled ? '#f5f5f5' : '#222222');
        const textColorSecondary = (style.getPropertyValue('--text-color-secondary') || '').trim() || (darkEnabled ? '#d4d4d4' : '#666666');
        const surfaceBorder = (style.getPropertyValue('--surface-border') || '').trim() || (darkEnabled ? '#3f3f46' : '#e5e7eb');
        const tooltipBg = darkEnabled ? 'rgba(17, 24, 39, 0.9)' : 'rgba(255, 255, 255, 0.95)';
        return {
            plugins: {
                legend: {
                    labels: { color: textColor }
                },
                title: {
                    color: textColor
                },
                tooltip: {
                    titleColor: textColor,
                    bodyColor: textColorSecondary,
                    backgroundColor: tooltipBg,
                    borderColor: surfaceBorder,
                    borderWidth: 1
                }
            },
            scales: {
                x: {
                    ticks: { color: textColorSecondary },
                    grid: { color: surfaceBorder, drawBorder: false }
                },
                y: {
                    ticks: { color: textColorSecondary },
                    grid: { color: surfaceBorder, drawBorder: false }
                }
            }
        };
    }
    /**
     * Indica se il tipo chart selezionato usa assi cartesiani.
     * @returns `true` per bar/line/scatter/bubble.
     */
    isCartesianChart() {
        return ['bar', 'line', 'scatter', 'bubble'].includes(this.selectedChartType);
    }
    /**
     * Indica se il tipo chart usa opzioni tipiche dei grafici lineari.
     * @returns `true` per line/radar.
     */
    isLineLikeChart() {
        return ['line', 'radar'].includes(this.selectedChartType);
    }
    /**
     * Verifica se il tipo chart corrente e `bar`.
     * @returns `true` se tipo selezionato e bar.
     */
    isBarChart() {
        return this.selectedChartType === 'bar';
    }
    /**
     * Verifica se il tipo chart e della famiglia doughnut/pie.
     * @returns `true` per doughnut o pie.
     */
    isDoughnutLikeChart() {
        return ['doughnut', 'pie'].includes(this.selectedChartType);
    }
    /**
  * Recupera i dati/valori richiesti da `getPaletteColors`.
  * @param count Parametro utilizzato dal metodo nel flusso elaborativo.
  * @returns Collezione `string[]` derivata dalla trasformazione dei dati nel metodo `getPaletteColors`.
  */
    getPaletteColors(count) {
        const size = Math.max(1, count || 1);
        const repeatToSize = (seed) => Array.from({ length: size }, (_, i) => seed[i % seed.length]);
        if (this.ui.palette === 'warm')
            return repeatToSize(['#ef4444', '#f97316', '#f59e0b', '#eab308', '#fb7185']);
        if (this.ui.palette === 'cool')
            return repeatToSize(['#0ea5e9', '#06b6d4', '#14b8a6', '#22c55e', '#3b82f6']);
        if (this.ui.palette === 'pastel')
            return repeatToSize(['#93c5fd', '#a7f3d0', '#fde68a', '#fbcfe8', '#c4b5fd']);
        const colors = [];
        for (let i = 0; i < size; i++) {
            const hue = Math.round((360 / size) * i);
            colors.push(`hsl(${hue}, 72%, 52%)`);
        }
        return colors;
    }
    /**
  * Applica aggiornamenti di stato tramite `applyDatasetAppearance` mantenendo coerenti UI e dati.
  * @param chartData Parametro utilizzato dal metodo nel flusso elaborativo.
  */
    applyDatasetAppearance(chartData) {
        if (!chartData?.datasets?.length)
            return;
        chartData.datasets.forEach((ds) => {
            const points = Array.isArray(ds?.data) ? ds.data.length : 1;
            const palette = this.getPaletteColors(points);
            if (!ds.backgroundColor)
                ds.backgroundColor = palette;
            if (this.isLineLikeChart()) {
                ds.tension = Math.max(0, Math.min(1, Number(this.ui.tension || 0)));
            }
            if (this.isBarChart() && Number(this.ui.barThickness || 0) > 0) {
                ds.barThickness = Math.max(1, Number(this.ui.barThickness));
            }
        });
    }
    /**
     * Esegue un merge profondo di oggetti plain:
     * gli array vengono clonati, gli oggetti ricorsivamente uniti, i primitivi sovrascritti.
     * @param target Oggetto destinazione.
     * @param sources Sorgenti da fondere in ordine.
     * @returns Oggetto mergeato.
     */
    deepMerge(target, ...sources) {
        const out = target || {};
        for (const source of sources) {
            if (!source || typeof source !== 'object') {
                continue;
            }
            for (const key of Object.keys(source)) {
                const value = source[key];
                if (Array.isArray(value)) {
                    out[key] = value.slice();
                    continue;
                }
                if (value && typeof value === 'object') {
                    const base = (out[key] && typeof out[key] === 'object' && !Array.isArray(out[key])) ? out[key] : {};
                    out[key] = this.deepMerge(base, value);
                    continue;
                }
                out[key] = value;
            }
        }
        return out;
    }
    /**
     * Aggiorna in memoria `extraProps.archetypes.chart` con tipo, opzioni widget
     * e valore cutoff, serializzando anche `md_props_bag` locale.
     */
    persistChartArchetypeOptions() {
        if (!this.metaInfo?.tableMetadata) {
            return;
        }
        const extraProps = this.ensureMergedExtraProps();
        extraProps.archetypes = extraProps.archetypes || {};
        const existingChart = extraProps.archetypes.chart || {};
        const existingDataOptions = existingChart.dataOptions || {};
        extraProps.archetypes.chart = Object.assign({}, existingChart, {
            type: this.selectedChartType,
            options: this.buildWidgetChartOptions(),
            dataOptions: Object.assign({}, existingDataOptions, {
                cutOffCount: this.cutoffValue
            })
        });
        this.metaInfo.tableMetadata.extraProps = extraProps;
        this.metaInfo.tableMetadata.md_props_bag = JSON.stringify(extraProps);
    }
    /**
     * Salva su backend il `md_props_bag` aggiornato per la tabella metadata corrente
     * e invalida la cache route; eseguibile solo da admin.
     */
    async saveChartMetadataPropsBag() {
        if (!this.isAdmin) {
            return;
        }
        if (!this.metaInfo?.tableMetadata?.md_id) {
            return;
        }
        const userId = this.userInfo.getuserInfo()?.user_id;
        if (!userId) {
            return;
        }
        this.persistChartArchetypeOptions();
        const extraProps = this.ensureMergedExtraProps();
        extraProps.archetypes = extraProps.archetypes || {};
        const existingChart = extraProps.archetypes.chart || {};
        const existingDataOptions = existingChart.dataOptions || {};
        extraProps.archetypes.chart = Object.assign({}, existingChart, {
            type: this.selectedChartType,
            options: this.buildWidgetChartOptions(),
            dataOptions: Object.assign({}, existingDataOptions, {
                cutOffCount: this.cutoffValue
            })
        });
        const mdPropsBag = JSON.stringify(extraProps);
        const currentTableMetadata = await this.loadCurrentTableMetadataRecord(userId);
        if (!currentTableMetadata) {
            return;
        }
        const entity = Object.assign({}, currentTableMetadata, {
            md_props_bag: mdPropsBag,
            __original: currentTableMetadata
        });
        await WtoolboxService.http.post(MetadataProviderService.updateUri, {
            route: MetadataProviderService.metaTableRoute,
            user_id: userId,
            entity: entity
        }).toPromise();
        this.metaInfo.tableMetadata.extraProps = extraProps;
        this.metaInfo.tableMetadata.md_props_bag = mdPropsBag;
        await WtoolboxService.http.post(MetadataProviderService.flushCacheUri, {
            route: this.metaInfo.tableMetadata.md_route_name
        }).toPromise();
        WtoolboxService.messageNotificationService.add({
            severity: 'success',
            summary: this.trslSrv.instant('success'),
            detail: this.trslSrv.instant('metadata.table_saved')
        });
    }
    /**
     * Mergea extraProps correnti con quelle parseate dal `md_props_bag`,
     * preservando la struttura `archetypes`.
     * @returns Oggetto extraProps mergeato.
     */
    ensureMergedExtraProps() {
        let propsFromBag = {};
        try {
            propsFromBag = JSON.parse(this.metaInfo?.tableMetadata?.md_props_bag || '{}') || {};
        }
        catch {
            propsFromBag = {};
        }
        const current = this.metaInfo?.tableMetadata?.extraProps || {};
        const merged = Object.assign({}, propsFromBag, current);
        merged.archetypes = Object.assign({}, propsFromBag?.archetypes || {}, current?.archetypes || {});
        return merged;
    }
    /**
     * Rilegge configurazione chart da `md_props_bag` e la riflette nello stato UI
     * (tipo, opzioni plugin/scales, cutoff, cutout), quindi rigenera le opzioni runtime.
     */
    syncChartConfigFromPropsBag() {
        const chartFromBag = this.getChartPropsFromPropsBag();
        if (!chartFromBag) {
            return;
        }
        this.selectedChartType = chartFromBag.type || this.selectedChartType;
        this.metaChartOptions = chartFromBag.options || {};
        this.basicOptions = this.deepMerge({}, this.defaultChartOptions, this.getThemeChartOptions(), this.metaChartOptions);
        const opts = chartFromBag.options || {};
        const plugins = opts.plugins || {};
        const scales = opts.scales || {};
        const x = scales.x || {};
        const y = scales.y || {};
        this.ui.legendDisplay = !!(plugins.legend?.display ?? this.ui.legendDisplay);
        this.ui.legendPosition = plugins.legend?.position || this.ui.legendPosition;
        this.ui.titleDisplay = !!(plugins.title?.display ?? this.ui.titleDisplay);
        this.ui.titleText = plugins.title?.text || this.ui.titleText;
        this.ui.tooltipEnabled = !!(plugins.tooltip?.enabled ?? this.ui.tooltipEnabled);
        this.ui.maintainAspectRatio = !!(opts.maintainAspectRatio ?? this.ui.maintainAspectRatio);
        this.ui.animationDuration = Number(opts.animation?.duration ?? this.ui.animationDuration) || this.ui.animationDuration;
        this.ui.animationEasing = opts.animation?.easing || this.ui.animationEasing;
        this.ui.stacked = !!(x.stacked ?? y.stacked ?? this.ui.stacked);
        this.ui.showGridX = !!(x.grid?.display ?? this.ui.showGridX);
        this.ui.showGridY = !!(y.grid?.display ?? this.ui.showGridY);
        this.ui.beginAtZero = !!(y.beginAtZero ?? this.ui.beginAtZero);
        this.ui.indexAxis = opts.indexAxis || this.ui.indexAxis;
        const cutout = String(opts.cutout ?? '').replace('%', '').trim();
        const cutoutNum = Number(cutout);
        if (Number.isFinite(cutoutNum) && cutoutNum >= 0) {
            this.ui.cutout = cutoutNum;
        }
        const cutOffCount = Number(chartFromBag.dataOptions?.cutOffCount || 0);
        if (cutOffCount > 0) {
            this.cutoffValue = Math.min(cutOffCount, this.totalRecords || cutOffCount);
        }
        this.refreshChartOptions();
    }
    /**
     * Applica configurazione archetype ricevuta dal configuratore,
     * aggiorna `md_props_bag` locale e ricostruisce dati/opzioni chart.
     * @param nextConfig Configurazione archetype da mergeare.
     */
    onArchetypeConfigApply(nextConfig) {
        if (!nextConfig || !this.metaInfo?.tableMetadata) {
            return;
        }
        const extraProps = this.ensureMergedExtraProps();
        extraProps.archetypes = extraProps.archetypes || {};
        const existingChart = extraProps.archetypes.chart || {};
        extraProps.archetypes.chart = Object.assign({}, existingChart, nextConfig);
        this.metaInfo.tableMetadata.extraProps = extraProps;
        this.metaInfo.tableMetadata.md_props_bag = JSON.stringify(extraProps);
        this.syncChartConfigFromPropsBag();
        if (this.lastResultInfo) {
            this.data = this.parseData(this.lastResultInfo, this.cutoffValue);
        }
    }
    /**
     * Restituisce la configurazione chart attuale preferendo quella serializzata in `md_props_bag`
     * e fallback su `extraProps.archetypes.chart`.
     * @returns Configurazione chart corrente o `null`.
     */
    getChartConfigValue() {
        return this.getChartPropsFromPropsBag() || this.metaInfo?.tableMetadata?.extraProps?.archetypes?.chart || null;
    }
    /**
     * Estrae la sezione `archetypes.chart` dal JSON `md_props_bag`.
     * @returns Configurazione chart o `null` se assente/non parseabile.
     */
    getChartPropsFromPropsBag() {
        try {
            const parsed = JSON.parse(this.metaInfo?.tableMetadata?.md_props_bag || '{}');
            return parsed?.archetypes?.chart || null;
        }
        catch {
            return null;
        }
    }
    /**
  * Carica dati e li armonizza per l'uso nel componente usando i metadati per determinare campi, chiavi e comportamento runtime, allineando i record al formato atteso dal framework, coordinando chiamate verso servizi applicativi.
  * @param userId Identificativo tecnico usato per lookup, confronto o aggiornamento mirato.
  * @returns Promise che completa il flusso asincrono e restituisce un risultato di tipo `Promise<any | null>`.
  */
    async loadCurrentTableMetadataRecord(userId) {
        const payload = await WtoolboxService.http.post(MetadataProviderService.readUri, {
            user_id: userId,
            route: MetadataProviderService.metaTableRoute,
            lookup_table_id: 0,
            SortInfo: [],
            GroupInfo: [],
            PageInfo: { pageSize: 1, currentPage: 1 },
            filterInfo: {
                logic: 'AND',
                filters: [{ field: 'md_id', operatore: 'eq', value: this.metaInfo.tableMetadata.md_id.toString(), fixed: true }]
            },
            logicOperator: 'AND',
            has_server_operation: true,
            aggregates: [],
            columnRestrictionList: [],
            formula_lookup: '',
            mc_id: 0
        }).toPromise();
        return payload?.results?.[0] || null;
    }
    /**
  * Interpreta e normalizza input/configurazione in `parseData` per l'utilizzo nel componente.
  * @param result Parametro utilizzato dal metodo nel flusso elaborativo.
  * @param runtimeCutOff Parametro utilizzato dal metodo nel flusso elaborativo.
  * @returns Struttura dati prodotta da `parseData` dopo normalizzazione/elaborazione.
  */
    parseData(result, runtimeCutOff) {
        // skills/typed-localized-exceptions: wrap il lifecycle parseData con
        // try/catch → typed `errors.client.archetype.chart.init_failed` (al posto
        // di lasciare propagare uncaught TypeError tipo "datasets.forEach is not
        // a function" → dialog raw stack).
        try {
            return this._parseDataInternal(result, runtimeCutOff);
        }
        catch (err) {
            try {
                err.__wuicHandled = true;
            }
            catch { /* primitive throw */ }
            const innerMessage = err instanceof Error ? err.message
                : (err && typeof err === 'object' && typeof err.message === 'string') ? err.message
                    : String(err);
            const exc = new WuicClientException(WuicErrorCodes.archetypeInitFailed('chart'), {
                archetype: 'chart',
                phase: 'parseData',
                route: this.metaInfo?.tableMetadata?.md_route_name,
                innerName: err?.constructor?.name || (typeof err),
                innerMessage: innerMessage?.slice(0, 500),
            }, { surface: 'archetype', targetName: 'ChartListComponent.parseData', cause: err });
            GlobalHandler.emitClientException(exc);
            return undefined;
        }
    }
    _parseDataInternal(result, runtimeCutOff) {
        let chartData;
        // = {
        //   labels: ['Q1', 'Q2', 'Q3', 'Q4'],
        //   datasets: [
        //     {
        //       label: 'Sales',
        //       data: [540, 325, 702, 620],
        //       // backgroundColor: ['rgba(255, 159, 64, 0.2)', 'rgba(75, 192, 192, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(153, 102, 255, 0.2)'],
        //       // borderColor: ['rgb(255, 159, 64)', 'rgb(75, 192, 192)', 'rgb(54, 162, 235)', 'rgb(153, 102, 255)'],
        //       // borderWidth: 1
        //     }
        //   ]
        // };
        if (this.metaInfo.tableMetadata.extraProps?.archetypes?.chart?.dataOptions) {
            let dataOptions = this.metaInfo.tableMetadata.extraProps.archetypes.chart.dataOptions;
            if (dataOptions.getChartData) {
                chartData = typeof dataOptions.getChartData === 'string' ? new Function("data", dataOptions.getChartData)(result) : dataOptions.getChartData(result);
            }
            else {
                chartData = {
                    labels: [],
                    datasets: []
                };
                let data = Array.isArray(result[dataOptions.dataProperty]) ? result[dataOptions.dataProperty] : [];
                const groupRows = data.filter((d) => d && d.__group_header === 1);
                const chartRows = groupRows.length ? groupRows : data;
                const sortField = dataOptions.datasets?.[0]?.dataField;
                let orderedRows = [...chartRows];
                if (sortField && this.ui.sortEnabled) {
                    orderedRows.sort((a, b) => {
                        const av = Number(this.getSeriesValue(a, sortField) ?? 0);
                        const bv = Number(this.getSeriesValue(b, sortField) ?? 0);
                        return this.ui.sortDir === 'asc' ? (av - bv) : (bv - av);
                    });
                }
                const effectiveCutOff = Number(runtimeCutOff || dataOptions.cutOffCount || 0);
                if (effectiveCutOff > 0) {
                    orderedRows = orderedRows.slice(0, effectiveCutOff);
                }
                chartData.labels = orderedRows.map((d) => this.getLabelValue(d, dataOptions.datasets?.[0]?.labelField));
                dataOptions.datasets.forEach((ds) => {
                    if (ds.parseData) {
                        chartData.datasets.push(typeof ds.parseData === 'string' ? new Function("data", ds.parseData)(result) : ds.parseData(data));
                    }
                    else if (ds.labelField && ds.dataField) {
                        let dsInstance = {
                            label: ds.label
                        };
                        dsInstance.data = orderedRows.map((d) => this.getSeriesValue(d, ds.dataField));
                        // Static colors per dataset (multi-series stacked / semantic palette).
                        // Priority: backgroundColorField (per-row) > backgroundColor (static dataset)
                        // > generateRandomColor (random per row) > applyDatasetAppearance (theme palette).
                        // Cast `any` perche' ChartDatasetOptions del modello custom non espone
                        // backgroundColor diretto (solo backgroundColorField), ma Chart.js runtime
                        // accetta entrambi.
                        const dsCfgAny = ds;
                        if (dsCfgAny.backgroundColorField) {
                            dsInstance.backgroundColor = orderedRows.map((d) => d[dsCfgAny.backgroundColorField]);
                        }
                        else if (dsCfgAny.backgroundColor) {
                            dsInstance.backgroundColor = dsCfgAny.backgroundColor;
                        }
                        else if (dsCfgAny.generateRandomColor) {
                            dsInstance.backgroundColor = orderedRows.map(() => {
                                return 'rgba(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',0.8)';
                            });
                        }
                        // Cast `any` per Chart.js types: ChartDatasetOptions limita le keys
                        // sul livello root, ma a runtime Chart.js le accetta tutte (chart-type-specific).
                        const dsAny = ds;
                        const dsInstAny = dsInstance;
                        if (dsAny.borderColor)
                            dsInstAny.borderColor = dsAny.borderColor;
                        if (dsAny.borderWidth !== undefined)
                            dsInstAny.borderWidth = dsAny.borderWidth;
                        // `stack` raggruppa datasets in stacked bar separati per gruppo
                        if (dsAny.stack)
                            dsInstAny.stack = dsAny.stack;
                        // Pass-through bar sizing controls (Chart.js bar/horizontal-bar):
                        // evita che con poche categorie i bar si espandano a tutta altezza.
                        // maxBarThickness=24 => bar mai oltre 24px anche con 2-3 categorie.
                        if (dsAny.maxBarThickness !== undefined)
                            dsInstAny.maxBarThickness = dsAny.maxBarThickness;
                        if (dsAny.barThickness !== undefined)
                            dsInstAny.barThickness = dsAny.barThickness;
                        if (dsAny.barPercentage !== undefined)
                            dsInstAny.barPercentage = dsAny.barPercentage;
                        if (dsAny.categoryPercentage !== undefined)
                            dsInstAny.categoryPercentage = dsAny.categoryPercentage;
                        if (ds.borderColorField) {
                            dsInstance.borderColor = orderedRows.map((d) => d[ds.borderColorField]);
                            dsInstance.borderWidth = 1;
                        }
                        chartData.datasets.push(dsInstance);
                    }
                });
                this.applyDatasetAppearance(chartData);
            }
            return chartData;
        }
    }
    /**
     * Inizializza un `ResizeObserver` sul contenitore chart per innescare
     * pass di misura/resize quando il layout cambia dimensione.
     */
    startChartResizeObserver() {
        if (this.chartResizeObserver || !this.chartHostRef?.nativeElement || typeof ResizeObserver === 'undefined') {
            return;
        }
        this.chartResizeObserver = new ResizeObserver(() => {
            this.scheduleChartResizePasses();
        });
        // Observe the chart host itself
        this.chartResizeObserver.observe(this.chartHostRef.nativeElement);
        // Also observe the root container (.chart-list-root) — when the filter-bar
        // expands/collapses, it changes the root height but NOT the chart host
        // (which has a fixed px height). Without this, the chart doesn't recalculate.
        const rootEl = this.chartHostRef.nativeElement.closest('.chart-list-root');
        if (rootEl) {
            this.chartResizeObserver.observe(rootEl);
        }
        // Also observe the host element itself (the bounded-repeater content area)
        const hostEl = this.chartHostRef.nativeElement.closest('wuic-chart-list');
        if (hostEl) {
            this.chartResizeObserver.observe(hostEl);
        }
    }
    /**
     * Pianifica un resize canvas nel prossimo frame annullando richieste pendenti.
     */
    scheduleChartResize() {
        if (typeof window === 'undefined') {
            return;
        }
        if (this.pendingChartResizeHandle) {
            window.cancelAnimationFrame(this.pendingChartResizeHandle);
        }
        this.pendingChartResizeHandle = window.requestAnimationFrame(() => {
            this.pendingChartResizeHandle = undefined;
            this.chartRef?.chart?.resize();
        });
    }
    /**
     * Calcola l'altezza disponibile nel layout e aggiorna `chartHostHeightPx`
     * adattandosi allo spazio reale disponibile.
     */
    updateChartHostHeight() {
        if (typeof window === 'undefined' || !this.chartHostRef?.nativeElement) {
            return false;
        }
        const chartHost = this.chartHostRef.nativeElement;
        // Temporarily clear inline height so the layout reflects true available
        // space. Without this, after filter-bar collapse the ancestor rects stay
        // limited by the old (smaller) chart height — circular dependency.
        const prevH = chartHost.style.height;
        const prevMH = chartHost.style.maxHeight;
        chartHost.style.height = '';
        chartHost.style.maxHeight = '';
        const hostRect = chartHost.getBoundingClientRect();
        const hostTop = hostRect.top;
        const layoutContainer = this.getLayoutScrollableContainer(chartHost);
        const layoutBottom = layoutContainer ? layoutContainer.getBoundingClientRect().bottom : window.innerHeight;
        let nextHeight = Math.floor(layoutBottom - hostTop - this.chartBottomPadding);
        // Se c'e un overflow verticale residuo, riduciamo il chart host del delta
        // cosi il contenitore non mostra scrollbar.
        if (layoutContainer) {
            const overflowDelta = layoutContainer.scrollHeight - layoutContainer.clientHeight;
            if (overflowDelta > 0) {
                nextHeight -= overflowDelta;
            }
        }
        // Cap al bounding del TD/cella dashboard. Quando il chart-list e' embedded
        // come widget di cella in una dashboard, NON deve espandersi oltre il box
        // riservato dalla cella — altrimenti lo strato sotto (es. tabella) viene
        // spinto fuori dal viewport. Solo TD veri (DOM tag) o ancestor con classe
        // CRM_HOME_CELL del designer (non qualsiasi ancestor con height esplicita,
        // perche' il chart-list ha un wrapper interno con height inline che
        // creerebbe un cap-loop).
        let constrainAncestor = chartHost.parentElement;
        while (constrainAncestor && constrainAncestor !== layoutContainer) {
            const isDashboardCell = constrainAncestor.tagName === 'TD'
                || (constrainAncestor.className && constrainAncestor.className.indexOf('CRM_HOME_CELL') >= 0);
            if (isDashboardCell) {
                const ancRect = constrainAncestor.getBoundingClientRect();
                const cappedHeight = Math.floor(ancRect.bottom - hostTop - this.chartBottomPadding);
                if (cappedHeight > 0 && cappedHeight < nextHeight) {
                    nextHeight = cappedHeight;
                }
                break;
            }
            constrainAncestor = constrainAncestor.parentElement;
        }
        // Restore inline styles before updating the binding
        chartHost.style.height = prevH;
        chartHost.style.maxHeight = prevMH;
        nextHeight = Math.max(0, nextHeight);
        if (this.chartHostHeightPx === nextHeight) {
            return false;
        }
        this.chartHostHeightPx = nextHeight;
        return true;
    }
    /**
     * Risale il DOM e ritorna il primo contenitore con overflow verticale gestito
     * (`auto`, `scroll`, `overlay`) usato come viewport di layout.
     * @param startElement Parametro utilizzato dal metodo nel flusso elaborativo.
     * @returns Contenitore scrollabile trovato o `null`.
     */
    getLayoutScrollableContainer(startElement) {
        let node = startElement.parentElement;
        while (node) {
            const style = window.getComputedStyle(node);
            const overflowY = style.overflowY || '';
            if ((overflowY.includes('auto') || overflowY.includes('scroll') || overflowY.includes('overlay')) && node.clientHeight > 0) {
                return node;
            }
            node = node.parentElement;
        }
        return null;
    }
    /**
     * Esegue un resize immediato e ulteriori pass differiti per gestire transizioni/layout asincroni.
     */
    scheduleChartResizePasses() {
        if (typeof window === 'undefined') {
            return;
        }
        this.clearPendingChartMeasureTimeouts();
        this.measureAndResizeChart(true);
        this.chartResizePassDelaysMs.forEach((delay) => {
            const handle = window.setTimeout(() => {
                this.measureAndResizeChart();
            }, delay);
            this.pendingChartMeasureTimeouts.push(handle);
        });
    }
    /**
     * Annulla tutti i timeout di misura chart ancora pendenti.
     */
    clearPendingChartMeasureTimeouts() {
        if (typeof window === 'undefined') {
            return;
        }
        this.pendingChartMeasureTimeouts.forEach((timeoutId) => window.clearTimeout(timeoutId));
        this.pendingChartMeasureTimeouts = [];
    }
    /**
     * Ricalcola spazio disponibile e richiede il resize del grafico.
     */
    measureAndResizeChart(forceResize = false) {
        const hostHeightChanged = this.updateChartHostHeight();
        if (forceResize || hostHeightChanged) {
            this.scheduleChartResize();
        }
    }
    /**
     * Hook eseguito al termine del render Chart.js: riallinea l'altezza
     * e ridimensiona solo quando necessario per evitare scrollbar.
     */
    onChartAfterRender() {
        this.measureAndResizeChart();
    }
    /**
     * Registra listener globali resize/scroll per mantenere il grafico sincronizzato col layout.
     */
    parentResizeObserver;
    attachLayoutListeners() {
        if (typeof window === 'undefined') {
            return;
        }
        window.addEventListener('resize', this.onWindowResize);
        window.addEventListener('scroll', this.onWindowScroll, true);
        // Note: filter-bar toggle dispatches a synthetic window 'resize' event
        // (see filter-bar.component.ts toggleCollapse), so chart height
        // recalculation is already handled by the onWindowResize listener above.
    }
    /**
     * Rimuove i listener globali registrati da `attachLayoutListeners`.
     */
    detachLayoutListeners() {
        if (typeof window === 'undefined') {
            return;
        }
        window.removeEventListener('resize', this.onWindowResize);
        window.removeEventListener('scroll', this.onWindowScroll, true);
        this.parentResizeObserver?.disconnect();
        this.parentResizeObserver = undefined;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChartListComponent, deps: [{ token: i1.Title }, { token: i0.ChangeDetectorRef }, { token: i1$1.ActivatedRoute }, { token: TranslationManagerService }, { token: UserInfoService }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ChartListComponent, isStandalone: true, selector: "wuic-chart-list", inputs: { hardcodedRoute: "hardcodedRoute", parentRecord: "parentRecord", parentMetaInfo: "parentMetaInfo", datasource: "datasource", hardcodedDatasource: "hardcodedDatasource", hideToolbar: "hideToolbar" }, outputs: { onChartDataSelect: "onChartDataSelect", onChartDrillDown: "onChartDrillDown", onChartDataBound: "onChartDataBound" }, viewQueries: [{ propertyName: "chartRef", first: true, predicate: ["chartRef"], descendants: true }, { propertyName: "chartHostRef", first: true, predicate: ["chartHostRef"], descendants: true }], ngImport: i0, template: "@if (chartOptions) {\n  <div class=\"chart-list-root\">\n    @if (!hideToolbar) {\n    <div class=\"chart-list-toolbar\">\n      @if (isAdmin) {\n      <button class=\"p-element p-button p-component\" (click)=\"openConfigPopup()\">{{'chart.settings' | translate}}</button>\n      }\n      <button class=\"p-element p-button p-component\" (click)=\"exportChart()\">{{'chart.export' | translate}}</button>\n      @if (isAdmin) {\n      <button class=\"p-element p-button p-component\" (click)=\"saveChartMetadataPropsBag()\">{{ 'metadata.save_table' | translate }}</button>\n      }\n    </div>\n    }\n\n    <div class=\"chart-list-chart-host\" #chartHostRef [style.height.px]=\"chartHostHeightPx\" [style.max-height.px]=\"chartHostHeightPx\">\n      <p-chart #chartRef [type]=\"chartOptions.type\" [data]=\"data\" [options]=\"chartOptions\" width=\"100%\"\n        height=\"100%\" [responsive]=\"true\" (onDataSelect)=\"drillDown($event)\" />\n    </div>\n  </div>\n\n  <wuic-archetype-configurator\n    [visible]=\"showConfigPopup\"\n    (visibleChange)=\"showConfigPopup = $event\"\n    [archetype]=\"'chart'\"\n    [metaInfo]=\"metaInfo\"\n    [value]=\"getChartConfigValue()\"\n    (applyConfig)=\"onArchetypeConfigApply($event)\">\n  </wuic-archetype-configurator>\n}\n", styles: [":host{display:block;height:100%;min-height:0;overflow:hidden}.chart-list-root{display:flex;flex-direction:column;height:100%;width:100%;min-height:0;overflow:hidden}.chart-list-cutoff{flex:0 0 auto;padding:.5rem 0}.chart-list-toolbar{flex:0 0 auto;padding:.5rem 0;display:flex;gap:.5rem;flex-wrap:wrap}.chart-list-config{flex:0 0 auto;padding:.5rem 0}.chart-list-modal-backdrop{position:fixed;inset:0;background:#0006;z-index:1200;display:flex;align-items:center;justify-content:center;padding:1rem}.chart-list-modal{width:min(900px,100%);max-height:85vh;overflow:auto;background:#fff;color:#111827;border-radius:8px;box-shadow:0 10px 32px #00000040;padding:1rem}.chart-list-modal-header{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.75rem}.chart-list-modal-header h4{margin:0}.chart-list-config-row{display:flex;flex-direction:column;gap:.3rem;margin-bottom:.5rem}.chart-list-inline{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}.chart-list-inline label{display:inline-flex;align-items:center;gap:.35rem}.chart-list-type-select,.chart-list-inline select,.chart-list-input,.chart-list-input-sm{background:#fff;color:#111827;border:1px solid #d1d5db;border-radius:6px;padding:.35rem .45rem}.chart-list-config-label{font-weight:600;font-size:.9rem}.chart-list-type-select,.chart-list-input,.chart-list-input-sm{width:100%;box-sizing:border-box}.chart-list-input-sm{width:8rem}:host-context(.theme-dark) .chart-list-modal-backdrop{background:#000000a6}:host-context(.theme-dark) .chart-list-modal{background:#111827;color:#e5e7eb;box-shadow:0 10px 32px #0000008c}:host-context(.theme-dark) .chart-list-config-label,:host-context(.theme-dark) .chart-list-inline label{color:#e5e7eb}:host-context(.theme-dark) .chart-list-type-select,:host-context(.theme-dark) .chart-list-inline select,:host-context(.theme-dark) .chart-list-input,:host-context(.theme-dark) .chart-list-input-sm{background:#0f172a;color:#e5e7eb;border-color:#374151}:host-context(.theme-dark) .chart-list-type-select:focus,:host-context(.theme-dark) .chart-list-inline select:focus,:host-context(.theme-dark) .chart-list-input:focus,:host-context(.theme-dark) .chart-list-input-sm:focus{outline:1px solid #60a5fa;outline-offset:0}.chart-list-cutoff-label{display:block;font-weight:600}.chart-list-cutoff-slider{width:100%}.chart-list-chart-host{flex:1 1 auto;min-height:0;overflow:hidden}.chart-list-chart-host p-chart{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: ChartModule }, { kind: "component", type: i5.UIChart, selector: "p-chart", inputs: ["type", "plugins", "width", "height", "responsive", "ariaLabel", "ariaLabelledBy", "data", "options"], outputs: ["onDataSelect"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: ArchetypeConfiguratorComponent, selector: "wuic-archetype-configurator", inputs: ["visible", "archetype", "metaInfo", "value"], outputs: ["visibleChange", "applyConfig"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ChartListComponent, decorators: [{
            type: Component,
            args: [{ selector: 'wuic-chart-list', imports: [ChartModule, TranslateModule, ArchetypeConfiguratorComponent], template: "@if (chartOptions) {\n  <div class=\"chart-list-root\">\n    @if (!hideToolbar) {\n    <div class=\"chart-list-toolbar\">\n      @if (isAdmin) {\n      <button class=\"p-element p-button p-component\" (click)=\"openConfigPopup()\">{{'chart.settings' | translate}}</button>\n      }\n      <button class=\"p-element p-button p-component\" (click)=\"exportChart()\">{{'chart.export' | translate}}</button>\n      @if (isAdmin) {\n      <button class=\"p-element p-button p-component\" (click)=\"saveChartMetadataPropsBag()\">{{ 'metadata.save_table' | translate }}</button>\n      }\n    </div>\n    }\n\n    <div class=\"chart-list-chart-host\" #chartHostRef [style.height.px]=\"chartHostHeightPx\" [style.max-height.px]=\"chartHostHeightPx\">\n      <p-chart #chartRef [type]=\"chartOptions.type\" [data]=\"data\" [options]=\"chartOptions\" width=\"100%\"\n        height=\"100%\" [responsive]=\"true\" (onDataSelect)=\"drillDown($event)\" />\n    </div>\n  </div>\n\n  <wuic-archetype-configurator\n    [visible]=\"showConfigPopup\"\n    (visibleChange)=\"showConfigPopup = $event\"\n    [archetype]=\"'chart'\"\n    [metaInfo]=\"metaInfo\"\n    [value]=\"getChartConfigValue()\"\n    (applyConfig)=\"onArchetypeConfigApply($event)\">\n  </wuic-archetype-configurator>\n}\n", styles: [":host{display:block;height:100%;min-height:0;overflow:hidden}.chart-list-root{display:flex;flex-direction:column;height:100%;width:100%;min-height:0;overflow:hidden}.chart-list-cutoff{flex:0 0 auto;padding:.5rem 0}.chart-list-toolbar{flex:0 0 auto;padding:.5rem 0;display:flex;gap:.5rem;flex-wrap:wrap}.chart-list-config{flex:0 0 auto;padding:.5rem 0}.chart-list-modal-backdrop{position:fixed;inset:0;background:#0006;z-index:1200;display:flex;align-items:center;justify-content:center;padding:1rem}.chart-list-modal{width:min(900px,100%);max-height:85vh;overflow:auto;background:#fff;color:#111827;border-radius:8px;box-shadow:0 10px 32px #00000040;padding:1rem}.chart-list-modal-header{display:flex;align-items:center;justify-content:space-between;gap:.75rem;margin-bottom:.75rem}.chart-list-modal-header h4{margin:0}.chart-list-config-row{display:flex;flex-direction:column;gap:.3rem;margin-bottom:.5rem}.chart-list-inline{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap}.chart-list-inline label{display:inline-flex;align-items:center;gap:.35rem}.chart-list-type-select,.chart-list-inline select,.chart-list-input,.chart-list-input-sm{background:#fff;color:#111827;border:1px solid #d1d5db;border-radius:6px;padding:.35rem .45rem}.chart-list-config-label{font-weight:600;font-size:.9rem}.chart-list-type-select,.chart-list-input,.chart-list-input-sm{width:100%;box-sizing:border-box}.chart-list-input-sm{width:8rem}:host-context(.theme-dark) .chart-list-modal-backdrop{background:#000000a6}:host-context(.theme-dark) .chart-list-modal{background:#111827;color:#e5e7eb;box-shadow:0 10px 32px #0000008c}:host-context(.theme-dark) .chart-list-config-label,:host-context(.theme-dark) .chart-list-inline label{color:#e5e7eb}:host-context(.theme-dark) .chart-list-type-select,:host-context(.theme-dark) .chart-list-inline select,:host-context(.theme-dark) .chart-list-input,:host-context(.theme-dark) .chart-list-input-sm{background:#0f172a;color:#e5e7eb;border-color:#374151}:host-context(.theme-dark) .chart-list-type-select:focus,:host-context(.theme-dark) .chart-list-inline select:focus,:host-context(.theme-dark) .chart-list-input:focus,:host-context(.theme-dark) .chart-list-input-sm:focus{outline:1px solid #60a5fa;outline-offset:0}.chart-list-cutoff-label{display:block;font-weight:600}.chart-list-cutoff-slider{width:100%}.chart-list-chart-host{flex:1 1 auto;min-height:0;overflow:hidden}.chart-list-chart-host p-chart{display:block;height:100%}\n"] }]
        }], ctorParameters: () => [{ type: i1.Title }, { type: i0.ChangeDetectorRef }, { type: i1$1.ActivatedRoute }, { type: TranslationManagerService }, { type: UserInfoService }], propDecorators: { hardcodedRoute: [{
                type: Input
            }], parentRecord: [{
                type: Input
            }], parentMetaInfo: [{
                type: Input
            }], datasource: [{
                type: Input
            }], hardcodedDatasource: [{
                type: Input
            }], hideToolbar: [{
                type: Input
            }], onChartDataSelect: [{
                type: Output
            }], onChartDrillDown: [{
                type: Output
            }], onChartDataBound: [{
                type: Output
            }], chartRef: [{
                type: ViewChild,
                args: ['chartRef']
            }], chartHostRef: [{
                type: ViewChild,
                args: ['chartHostRef']
            }] } });

export { ChartListComponent };
//# sourceMappingURL=wuic-framework-lib-chart-list.component-C9_EDkGu.mjs.map
