/**
 * pop3d-charts – Block-Editor (ES Module, Script-Module-API).
 *
 * Dieser Einstiegspunkt ist nur noch ein schlanker Orchestrator. Die eigentliche
 * UI ist nach `editor/` und `editor/panels/` aufgeteilt; jeder PanelBody
 * liegt in einer eigenen Datei und bekommt genau die Props, die er braucht.
 *
 * `editor/editorData.js` MUSS als erstes importiert werden: es liest die vom
 * PHP-Filter `script_module_data_ce-pd-editor` eingebetteten Daten und ruft
 * `setLocaleData()` auf, bevor irgendein anderes Modul `__()` nutzt.
 */
import { patternPreviewUrls } from './editor/editorData.js';
import './editor/panels/waveAppearance.js';
import './editor/panels/pyramidAppearance.js';
import { sortBarsWithIndices, getPieSliceLabel, mergeDiagramMaxHByBp } from './editor/attributeHelpers.js';
import { ENABLE_DIAGRAM_MAX_HEIGHT, isPieLayout } from './editor/constants.js';
import { cePdEditorExt } from './editor/editorExt.js';
import { buildPreviewWrapper, defaultBars, defaultBar, emptyDatasetBar } from './editor/previewWrapper.js';
import { DiagramTypePanel } from './editor/panels/DiagramTypePanel.js';
import { TitleFootnotePanel } from './editor/panels/TitleFootnotePanel.js';
import { ExportPanel } from './editor/panels/ExportPanel.js';
import { CameraPanel } from './editor/panels/CameraPanel.js';
import { AppearancePanel } from './editor/panels/AppearancePanel.js';
import { BackgroundPanel } from './editor/panels/BackgroundPanel.js';
import { LegendPanel } from './editor/panels/LegendPanel.js';
import { SortingPanel } from './editor/panels/SortingPanel.js';
import { AxisPanel } from './editor/panels/AxisPanel.js';
import { ChartDataPanel } from './editor/panels/ChartDataPanel.js';
import { ImportExportPanel } from './editor/panels/ImportExportPanel.js';
import { downloadDataUrl } from './editor/dataIO.js';
import {
  resolvePieCalloutLineLength,
  resolvePieCalloutFontScale,
  resolvePieAngleBasis,
  resolvePyramidSegmentGap,
  resolvePyramidInverted,
  resolvePyramidSliceLabelMode,
  resolvePyramidLabelAutoContrast,
  resolvePyramidAnnotation,
  resolvePyramidAnnotationLineLength,
  resolvePyramidAnnotationFontScale,
} from './shared/config.js';
import { normalizeRulerStyle } from './scene/ruler.js';

const { registerBlockType } = wp.blocks;
const { createElement: el, Fragment, useEffect, useLayoutEffect, useRef, useState, useCallback } = wp.element;
const { __ } = wp.i18n;
const { useSelect, useDispatch } = wp.data;
const { InspectorControls, useBlockProps } = wp.blockEditor;

registerBlockType('pop3d-charts/diagram', {
  edit: function Edit(props) {
    const { attributes, setAttributes, isSelected, clientId } = props;
    /**
     * Das Three.js-Canvas fängt pointerdown in der Capture-Phase ab (OrbitControls
     * `stopPropagation`), wodurch Gutenberg den Block beim Klick nicht selektiert.
     * Ein transparentes Overlay über der Vorschau fängt den ersten Klick ab und
     * wählt den Block aus; sobald er ausgewählt ist, verschwindet das Overlay und
     * das Diagramm ist normal interaktiv (gängiges WP-Muster für interaktive Blöcke).
     */
    const { selectBlock } = useDispatch('core/block-editor');
    /** Inserter/Pattern-Vorschau setzt das über BlockPreview auf die Editor-Settings, nicht auf useBlockEditContext(). */
    const isEditorPreviewMode = useSelect((select) => {
      const getSettings = select('core/block-editor')?.getSettings;
      if (typeof getSettings !== 'function') return false;
      return getSettings().isPreviewMode === true;
    }, []);
    const bars = attributes.bars && attributes.bars.length > 0 ? attributes.bars : defaultBars();
    const blockProps = useBlockProps();

    /* Akkordeon: nur ein InspectorControls-Panel ist gleichzeitig offen. */
    const [openedPanelId, setOpenedPanelId] = useState('diagram-type');
    const panelState = useCallback((id) => ({
      opened: openedPanelId === id,
      onToggle: () => setOpenedPanelId((curr) => (curr === id ? null : id)),
    }), [openedPanelId]);

    const renderAddonInspectorPanels = (insertAfter) => cePdEditorExt
      .getInspectorPanelsByInsertAfter(insertAfter)
      .map((panel) => el(Fragment, { key: panel.id || Math.random() },
        panel.render({ attributes, setAttributes, layout, openState: panelState(panel.id) })
      ));

    const layout = attributes.defaultLayout || 'straight';

    const layoutPreviewUrl = patternPreviewUrls[layout];
    const patternStaticPreviewSrc = (isEditorPreviewMode
      && typeof layoutPreviewUrl === 'string'
      && layoutPreviewUrl.trim() !== '')
      ? layoutPreviewUrl.trim()
      : '';
    const sortOrder = attributes.sortOrder || 'custom';
    const barsOrdered = sortBarsWithIndices(bars, sortOrder, layout);

    const previewMountRef = useRef(null);
    const previewEffectSkipRef = useRef(false);
    const previewRefreshDebounceRef = useRef(null);

    /**
     * Kompakter Cache-Key aller renderrelevanten Attribute. Ändert er sich,
     * triggert das useEffect unten einen Re-Init der Three.js-Scene (debounced).
     * Achsentitel axisXLabel/axisYLabel (Bar/Wave) siehe useLayoutEffect + __cePdEditorPreviewApplyAxisLabelsFromConfig(axisX, axisY).
     * Scatter-Achsen nur bei Layout surface (Troika in surface.js).
     */
    const previewExtKey = (typeof window !== 'undefined'
      && window.__CE_PD_EDITOR_EXT__
      && typeof window.__CE_PD_EDITOR_EXT__.buildPreviewKey === 'function')
      ? String(window.__CE_PD_EDITOR_EXT__.buildPreviewKey(attributes, layout) || '')
      : '';

    const diagramPreviewKey = [
      layout,
      attributes.defaultProjection || '3d',
      String(attributes.cameraPreset || 'default'),
      String(typeof attributes.startZoom === 'number' ? attributes.startZoom : 1),
      attributes.viewerProjectionToggleEnabled !== false ? '1' : '0',
      attributes.backgroundType,
      attributes.colorScheme || 'vivid',
      attributes.ignoreBarColors === true ? '1' : '0',
      String(attributes.defaultDataColor || '').trim(),
      attributes.alwaysColorful ? '1' : '0',
      attributes.rulerEnabled !== false ? '1' : '0',
      normalizeRulerStyle(attributes.rulerStyle),
      attributes.interactionEnabled !== false ? '1' : '0',
      attributes.gridEnabled !== false ? '1' : '0',
      attributes.backgroundTransparent ? '1' : '0',
      attributes.backgroundColor || '',
      attributes.gridLineColor || '',
      attributes.gradientFloorColor || '',
      attributes.rulerColor || '',
      String(attributes.barLabelColor || ''),
      String(attributes.groupLabelColor || ''),
      sortOrder,
      attributes.viewerSortEnabled === true ? '1' : '0',
      JSON.stringify(ENABLE_DIAGRAM_MAX_HEIGHT ? mergeDiagramMaxHByBp(attributes.diagramMaxHeightByBreakpoint) : null),
      JSON.stringify(attributes.axisY || {}),
      String(typeof attributes.barThicknessScale === 'number' ? attributes.barThicknessScale : 1),
      String(typeof attributes.barOpacity === 'number' ? attributes.barOpacity : 1),
      String(typeof attributes.dataLabelFontScale === 'number' ? attributes.dataLabelFontScale : 1),
      String(isPieLayout(layout) ? getPieSliceLabel(attributes) : ''),
      String(isPieLayout(layout) ? (typeof attributes.pieInnerRadius === 'number' ? attributes.pieInnerRadius : 0.5) : ''),
      String(isPieLayout(layout) ? (typeof attributes.pieThickness === 'number' ? attributes.pieThickness : 1) : ''),
      String(isPieLayout(layout) ? resolvePieAngleBasis(attributes) : ''),
      String(isPieLayout(layout) ? resolvePieCalloutLineLength({ pieCalloutLineLength: attributes.pieCalloutLineLength }) : ''),
      String(isPieLayout(layout) ? resolvePieCalloutFontScale({ pieCalloutFontScale: attributes.pieCalloutFontScale }) : ''),
      String(isPieLayout(layout) ? String(attributes.pieCalloutColor || '').trim() : ''),
      isPieLayout(layout) && attributes.pieLabelAutoContrast !== false ? '1' : '0',
      String(layout === 'pieStairs' ? (typeof attributes.stairStepHeight === 'number' ? attributes.stairStepHeight : 0.18) : ''),
      layout === 'pyramid' ? String(resolvePyramidSegmentGap(attributes)) : '',
      layout === 'pyramid' ? (attributes.pyramidInverted === true ? '1' : '0') : '',
      layout === 'pyramid' ? String(resolvePyramidSliceLabelMode(attributes)) : '',
      layout === 'pyramid' && resolvePyramidLabelAutoContrast(attributes) ? '1' : (layout === 'pyramid' ? '0' : ''),
      layout === 'pyramid' && resolvePyramidAnnotation(attributes) ? '1' : (layout === 'pyramid' ? '0' : ''),
      layout === 'pyramid' ? String(resolvePyramidAnnotationLineLength(attributes)) : '',
      layout === 'pyramid' ? String(resolvePyramidAnnotationFontScale(attributes)) : '',
      layout === 'wave' ? String(attributes.waveStyle || '') : '',
      layout === 'wave' ? String(attributes.waveRibbonDepth ?? '') : '',
      layout === 'wave' ? String(attributes.waveRibbonOpacity ?? '') : '',
      layout === 'wave' ? (attributes.waveAreaFill === false ? '0' : '1') : '',
      layout === 'wave' ? String(attributes.waveAreaFillOpacity ?? '') : '',
      layout === 'wave' ? String(attributes.waveBaselineValue ?? '') : '',
      layout === 'wave' ? String(attributes.waveGradientDirection || '') : '',
      layout === 'wave' ? String(attributes.waveLineThickness ?? '') : '',
      layout === 'wave' ? String(attributes.waveSmoothness ?? '') : '',
      layout === 'wave' ? (attributes.waveShowPoints === false ? '0' : '1') : '',
      layout === 'wave' ? String(attributes.wavePointSize ?? '') : '',
      layout === 'wave' ? (attributes.waveMultiSeries !== false ? '1' : '0') : '',
      layout === 'wave' ? String(attributes.waveLabelAtNodes || '') : '',
      layout === 'wave' ? String(attributes.waveLabelAlongFloor || '') : '',
      layout === 'wave' ? String(attributes.waveAnnotationLineLength ?? '') : '',
      layout === 'wave' ? String(attributes.waveAnnotationFontScale ?? '') : '',
      attributes.labelsUnderBars === true ? '1' : '0',
      String(attributes.straightBarLabelAbove || ''),
      String(attributes.straightBarLabelBelow || ''),
      attributes.stackedBarSumAbove === true ? '1' : '0',
      attributes.legendEnabled !== false ? '1' : '0',
      attributes.legendVisibleDefault !== false ? '1' : '0',
      attributes.legendPosition || 'left',
      attributes.legendOutsideChart === true ? '1' : '0',
      String(typeof attributes.barCornerRadius === 'number' ? attributes.barCornerRadius : 1),
      attributes.barGradientEnabled === true ? '1' : '0',
      String(typeof attributes.sceneYScale === 'number' ? attributes.sceneYScale : 1),
      previewExtKey,
      bars.map((b) => [b.value, b.unit || '', b.headline || '', b.paragraph || '', b.smallPrint || '', b.groupKey || '', b.color || '', b.scatterX ?? '', b.scatterY ?? '', b.annotation || '', b.annotationDesc || ''].join('|')).join(';'),
    ].join('|');

    /**
     * Titel, Untertitel und Fußnote: nur HTML im Preview (previewWrapper), kein data-config.
     * Kein Eintrag in diagramPreviewKey — vermeidet debounced Voll-Re-Init; stattdessen leichtes Reflow.
     */
    const editorTitleFootnoteReflowRafRef = useRef(null);
    useEffect(() => {
      const wrap = previewMountRef.current && previewMountRef.current.querySelector && previewMountRef.current.querySelector('.ce-diagram-wrapper.ce-diagram-editor-preview');
      if (!wrap || typeof wrap.__cePdEditorPreviewReflowLayout !== 'function') return undefined;
      if (editorTitleFootnoteReflowRafRef.current != null) {
        cancelAnimationFrame(editorTitleFootnoteReflowRafRef.current);
        editorTitleFootnoteReflowRafRef.current = null;
      }
      editorTitleFootnoteReflowRafRef.current = requestAnimationFrame(() => {
        editorTitleFootnoteReflowRafRef.current = null;
        if (typeof wrap.__cePdEditorPreviewReflowLayout === 'function') wrap.__cePdEditorPreviewReflowLayout();
      });
      return () => {
        if (editorTitleFootnoteReflowRafRef.current != null) {
          cancelAnimationFrame(editorTitleFootnoteReflowRafRef.current);
          editorTitleFootnoteReflowRafRef.current = null;
        }
      };
    }, [attributes.chartTitle, attributes.chartSubtitle, attributes.chartFootnote]);

    /**
     * axisXLabel / axisYLabel: Werte direkt aus Attributen (verlässlicher als data-config-Timing).
     * Kurzes Retry bis die Scene den Hook gesetzt hat (Three lädt async).
     */
    const editorAxisLabelTimerRef = useRef(null);
    useLayoutEffect(() => {
      const wrap = previewMountRef.current && previewMountRef.current.querySelector && previewMountRef.current.querySelector('.ce-diagram-wrapper.ce-diagram-editor-preview');
      if (!wrap) return undefined;
      const axX = String(attributes.axisXLabel ?? '').trim().slice(0, 48);
      const axY = String(attributes.axisYLabel ?? '').trim().slice(0, 48);
      if (editorAxisLabelTimerRef.current != null) {
        clearTimeout(editorAxisLabelTimerRef.current);
        editorAxisLabelTimerRef.current = null;
      }
      let attempts = 0;
      const maxAttempts = 60;
      const tick = () => {
        editorAxisLabelTimerRef.current = null;
        if (typeof wrap.__cePdEditorPreviewApplyAxisLabelsFromConfig === 'function') {
          wrap.__cePdEditorPreviewApplyAxisLabelsFromConfig(axX, axY);
          return;
        }
        attempts += 1;
        if (attempts < maxAttempts) {
          editorAxisLabelTimerRef.current = window.setTimeout(tick, 50);
        }
      };
      editorAxisLabelTimerRef.current = window.setTimeout(tick, 0);
      return () => {
        if (editorAxisLabelTimerRef.current != null) {
          clearTimeout(editorAxisLabelTimerRef.current);
          editorAxisLabelTimerRef.current = null;
        }
      };
    }, [layout, attributes.axisXLabel, attributes.axisYLabel]);

    /** Erstmaliges Render überspringen, dann bei Attributänderung debounced neu initialisieren. */
    useEffect(() => {
      if (!previewEffectSkipRef.current) {
        previewEffectSkipRef.current = true;
        return;
      }
      if (previewRefreshDebounceRef.current) {
        clearTimeout(previewRefreshDebounceRef.current);
        previewRefreshDebounceRef.current = null;
      }
      previewRefreshDebounceRef.current = setTimeout(() => {
        previewRefreshDebounceRef.current = null;
        const wrap = previewMountRef.current && previewMountRef.current.querySelector && previewMountRef.current.querySelector('.ce-diagram-wrapper.ce-diagram-editor-preview');
        if (wrap && typeof window !== 'undefined' && typeof window.__cePdRefreshEditorPreview === 'function') {
          window.__cePdRefreshEditorPreview(wrap);
        }
      }, 280);
      return () => {
        if (previewRefreshDebounceRef.current) {
          clearTimeout(previewRefreshDebounceRef.current);
          previewRefreshDebounceRef.current = null;
        }
      };
    }, [diagramPreviewKey]);

    /** Aspekt-Hint von der Runtime nach initScene übernehmen (nur eine Richtung). */
    useEffect(() => {
      const root = previewMountRef.current;
      if (!root) return;
      const onAspect = (e) => {
        const val = e.detail && e.detail.aspect;
        if (typeof val !== 'number' || !(val > 0)) return;
        const rounded = Math.round(val * 100) / 100;
        if (rounded > 0 && Math.abs(rounded - (attributes.aspectHint || 0)) > 0.01) {
          setAttributes({ aspectHint: rounded });
        }
      };
      root.addEventListener('ce-pd-aspect-computed', onAspect);
      return () => root.removeEventListener('ce-pd-aspect-computed', onAspect);
    }, [attributes.aspectHint]);

    const onExportPng = () => {
      const root = previewMountRef.current;
      if (!root) return;
      const wrap = root.querySelector('.ce-diagram-wrapper.ce-diagram-editor-preview');
      const notifyError = (msg) => {
        if (typeof window !== 'undefined' && window.wp?.data?.dispatch) {
          try {
            window.wp.data.dispatch('core/notices').createErrorNotice(msg, { type: 'snackbar', isDismissible: true });
          } catch (_) { /* ignore */ }
        }
      };
      if (!wrap || typeof wrap._cePdExportPng !== 'function') {
        notifyError(__('Diagram preview is still loading. Try again in a moment.', 'pop3d-charts'));
        return;
      }
      const dataUrl = wrap._cePdExportPng(3);
      if (!dataUrl) {
        notifyError(__('Could not create the image. Make sure the diagram preview is visible.', 'pop3d-charts'));
        return;
      }
      downloadDataUrl('ce-diagram-export.png', dataUrl);
    };

    return el('div', { ...blockProps, style: { position: 'relative', width: '100%', alignSelf: 'stretch', ...(blockProps.style || {}) } },
      patternStaticPreviewSrc === '' && el(InspectorControls, null,
        el(DiagramTypePanel, { layout, attributes, setAttributes, openState: panelState('diagram-type') }),
        el(ImportExportPanel, {
          bars,
          setAttributes,
          openState: panelState('import-export'),
        }),
        el(ChartDataPanel, {
          attributes,
          setAttributes,
          bars,
          barsOrdered,
          layout,
          defaultBar,
          emptyDatasetBar,
          openState: panelState('chart-data'),
        }),
        el(AxisPanel, { attributes, setAttributes, layout, bars, openState: panelState('axis') }),
        el(AppearancePanel, { attributes, setAttributes, layout, openState: panelState('appearance') }),
        el(TitleFootnotePanel, { attributes, setAttributes, openState: panelState('title-footnote') }),
        el(SortingPanel, { attributes, setAttributes, layout, openState: panelState('sorting') }),
        el(BackgroundPanel, { attributes, setAttributes, openState: panelState('background') }),
        el(LegendPanel, { attributes, setAttributes, openState: panelState('legend') }),
        el(CameraPanel, { attributes, setAttributes, layout, openState: panelState('camera') }),
        renderAddonInspectorPanels('camera'),
        el(ExportPanel, { attributes, setAttributes, onExportPng, openState: panelState('export') }),
        renderAddonInspectorPanels(null)
      ),
      el('div', {
        key: 'ce-diagram-preview-mount',
        ref: previewMountRef,
      },
      patternStaticPreviewSrc !== ''
        ? el('div', { className: 'ce-diagram-pattern-static-preview' },
          el('img', {
            src: patternStaticPreviewSrc,
            alt: '',
            decoding: 'async',
            loading: 'lazy',
            style: { width: '100%', height: 'auto', display: 'block', verticalAlign: 'top' },
          })
        )
        : buildPreviewWrapper(attributes, bars, props.clientId)),
      (patternStaticPreviewSrc === '' && !isSelected) && el('div', {
        className: 'ce-diagram-editor-select-overlay',
        'aria-hidden': 'true',
        onMouseDown: (e) => {
          e.preventDefault();
          selectBlock(clientId);
        },
        style: { position: 'absolute', inset: 0, zIndex: 10, cursor: 'pointer' },
      })
    );
  },
  save: () => null,
});
