/**
 * Editor-Erweiterungs-Registry. Add-on-Plugins koennen darueber Layouts,
 * Paletten, Preview-Konfig-Helfer und InspectorControls-Sektionen eintragen,
 * ohne den Plugin-Code direkt zu modifizieren.
 *
 * Das Plugin selbst haengt nichts ein - die Standard-Layouts und -Paletten
 * sind direkt in `constants.js`/`attributeHelpers.js` gepflegt.
 *
 * Panel-Sections:
 *   Add-ons rufen `registerPanelSection(panelId, { render, layouts? })` auf;
 *   Panels rendern in ihrer JSX an einer definierten Slot-Position alle
 *   passenden Sections (gefiltert nach optionalem `layouts`-Array).
 *
 *   Bekannte Panel-IDs:
 *     - 'appearance'           : am Ende des AppearancePanel.
 *     - 'appearanceAfterColors': direkt nach der "Data colors"-Auswahl.
 *     - 'axis'                 : am Ende des AxisPanel.
 *
 * Capability-Accessors:
 *   Diese pruefen `window.__CE_PD_SCENE_EXT__`-Hooks und liefern fuer den
 *   Editor-Code layout-spezifische Verhaltens-Flags (z. B. ob ein Layout den
 *   2D/3D-Toggle braucht). Default ist "ja/wahr"; Add-ons registrieren
 *   Ausnahmen.
 */

const w = typeof window !== 'undefined' ? window : globalThis;

function getSceneExt() {
  return (typeof window !== 'undefined' && window.__CE_PD_SCENE_EXT__) ? window.__CE_PD_SCENE_EXT__ : null;
}

if (!w.__CE_PD_EDITOR_EXT__) {
  /** @type {Array<{ id: string, iconUrl: string, label?: string, order?: number }>} */
  const layoutOptions = [];
  /** @type {Array<{ value: string, label: string, order?: number, isCustom?: boolean }>} */
  const paletteOptions = [];
  /** @type {Array<(config: object, attributes: object, layout: string) => object>} */
  const previewConfigHandlers = [];
  /** @type {Array<{ render: (props: object) => any, order?: number }>} */
  const inspectorPanels = [];
  /** @type {Array<(attributes: object, layout: string) => string>} */
  const previewKeyBuilders = [];
  /** @type {Record<string, Array<{ render: (ctx: object) => any, layouts?: string[] }>>} */
  const panelSections = Object.create(null);

  w.__CE_PD_EDITOR_EXT__ = {
    registerLayoutOption(opt) {
      if (opt && typeof opt.id === 'string' && opt.id !== '') layoutOptions.push(opt);
    },
    getLayoutOptions() { return layoutOptions.slice(); },
    registerPaletteOption(opt) {
      if (opt && typeof opt.value === 'string' && opt.value !== '') paletteOptions.push(opt);
    },
    getPaletteOptions() { return paletteOptions.slice(); },
    registerPreviewConfigHandler(fn) {
      if (typeof fn === 'function') previewConfigHandlers.push(fn);
    },
    applyConfig(config, attributes, layout) {
      let cfg = config;
      for (let i = 0; i < previewConfigHandlers.length; i++) {
        try {
          const next = previewConfigHandlers[i](cfg, attributes, layout);
          if (next && typeof next === 'object') cfg = next;
        } catch (_) { /* ignore broken handler */ }
      }
      return cfg;
    },
    registerInspectorPanel(panel) {
      if (panel && typeof panel.render === 'function') inspectorPanels.push(panel);
    },
    getInspectorPanels() { return inspectorPanels.slice(); },
    /**
     * Addon-Inspector-Panels nach optionalem `insertAfter`-Slot (z. B. 'camera').
     * Ohne insertAfter-Argument: Panels ohne insertAfter (Standard: Ende).
     */
    getInspectorPanelsByInsertAfter(insertAfter) {
      const filtered = inspectorPanels.filter((p) => (
        insertAfter == null || insertAfter === ''
          ? !p.insertAfter
          : p.insertAfter === insertAfter
      ));
      return filtered.slice().sort((a, b) => (a.order || 0) - (b.order || 0));
    },
    registerPreviewKeyBuilder(fn) {
      if (typeof fn === 'function') previewKeyBuilders.push(fn);
    },
    buildPreviewKey(attributes, layout) {
      if (previewKeyBuilders.length === 0) return '';
      const parts = [];
      for (let i = 0; i < previewKeyBuilders.length; i++) {
        try {
          const v = previewKeyBuilders[i](attributes, layout);
          if (v != null) parts.push(String(v));
        } catch (_) { /* ignore broken builder */ }
      }
      return parts.join('|');
    },

    /* ------------------------------------------------------------------ *
     * Panel-Sections: Add-ons injizieren InspectorControls direkt in die
     * passende Panel-Box, nicht in einen separaten Reiter.
     * ------------------------------------------------------------------ */
    registerPanelSection(panelId, entry) {
      if (typeof panelId !== 'string' || panelId === '') return;
      if (!entry || typeof entry.render !== 'function') return;
      if (!panelSections[panelId]) panelSections[panelId] = [];
      panelSections[panelId].push(entry);
    },
    /**
     * Liefert eine flache Liste an React-Elementen / Fragmenten fuer die
     * Slot-Position `panelId`, gefiltert nach optionalem `layouts`-Array
     * der Eintraege.
     *
     * @param {string} panelId
     * @param {object} ctx     Wird unveraendert an `render(ctx)` weitergereicht.
     * @return {Array<any>}
     */
    renderPanelSections(panelId, ctx) {
      const list = panelSections[panelId] || [];
      const layout = ctx && typeof ctx.layout === 'string' ? ctx.layout : '';
      const out = [];
      for (let i = 0; i < list.length; i++) {
        const entry = list[i];
        if (Array.isArray(entry.layouts) && entry.layouts.indexOf(layout) === -1) continue;
        try {
          const r = entry.render(ctx);
          if (r == null || r === false) continue;
          if (Array.isArray(r)) {
            for (let j = 0; j < r.length; j++) {
              if (r[j] != null && r[j] !== false) out.push(r[j]);
            }
          } else {
            out.push(r);
          }
        } catch (_) { /* ignore broken section */ }
      }
      return out;
    },

    /* ------------------------------------------------------------------ *
     * Layout-Capabilities: Editor-Code prueft diese, um layout-spezifische
     * Controls auszublenden / einzublenden. Default-Wert wird aus dem
     * Standard-Verhalten abgeleitet; Add-ons registrieren Ausnahmen.
     * ------------------------------------------------------------------ */
    layoutSupportsOrthographic(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutSupportsOrthographic;
      if (typeof fn === 'function') {
        const r = fn(layout);
        if (typeof r === 'boolean') return r;
      }
      return true;
    },
    layoutShowsDataLabelFontScale(layout) {
      if (layout === 'wave' || layout === 'pyramid') return true;
      const ext = getSceneExt();
      const fn = ext && ext.layoutShowsDataLabelFontScale;
      return typeof fn === 'function' && fn(layout) === true;
    },
    layoutHidesDataColors(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutHidesDataColors;
      return typeof fn === 'function' && fn(layout) === true;
    },
    layoutHasValueAxisMinMax(layout) {
      if (layout === 'wave') return true;
      const ext = getSceneExt();
      const fn = ext && ext.layoutHasValueAxisMinMax;
      return typeof fn === 'function' && fn(layout) === true;
    },
    layoutHidesAlwaysColorful(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutHidesAlwaysColorful;
      return typeof fn === 'function' && fn(layout) === true;
    },
    layoutHidesCameraPreset(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutHidesCameraPreset;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Pro-Layouts mit Maßstab (Ruler) registrieren sich hier, damit Free's
     * AppearancePanel den "Show ruler"-Toggle und den "Ruler color"-Picker
     * für sie einblendet. Free deckt straight/stacked selbst über
     * RULER_LAYOUTS in constants.js ab. */
    layoutHasRuler(layout) {
      if (layout === 'wave') return true;
      const ext = getSceneExt();
      const fn = ext && ext.layoutHasRuler;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Pro-Layouts mit BoxGeometry-Balken (arc/grouped/horizontal) registrieren
     * sich hier, damit Free's AppearancePanel den "Bar thickness"-Slider und —
     * via gleichem Gate, aber ohne `stacked` — den "Bar corner radius"-Slider
     * einblendet. Free deckt straight/stacked selbst über BAR_LAYOUTS ab.
     * Layouts ohne klassische Box-Bars (radial/wave/surface/bubblePack)
     * registrieren sich bewusst nicht. */
    layoutHasBarThickness(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutHasBarThickness;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Layouts, die zwar den "Bar opacity"-Slider zeigen sollen, aber NICHT die
     * Balken-Dicke (z. B. radial: feste Ringbreiten, aber transparente Ringe).
     * Free's AppearancePanel koppelt die Opacity-Sichtbarkeit darum nicht an
     * `layoutHasBarThickness`, sondern an `showBarThickness || layoutHasBarOpacity`.
     * Box-Bar-Layouts brauchen diesen Hook nicht (laufen schon ueber Bar thickness). */
    layoutHasBarOpacity(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutHasBarOpacity;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Pro-Layouts, deren Maßstab zusätzlich den Stil-Select erlaubt (also
     * `createRuler` mit `rulerStyle` nutzen). Surface bringt einen eigenen
     * Ruler ohne Stil-Varianten mit und registriert diesen Hook bewusst nicht. */
    layoutHasRulerStyle(layout) {
      if (layout === 'wave') return true;
      const ext = getSceneExt();
      const fn = ext && ext.layoutHasRulerStyle;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Pro-Layouts, in denen `ruler.js` die Serien-X-Beschriftung (axisXLabel)
     * rendert — entweder als Series-X-Label am Baseline-Ende (arc/grouped/wave)
     * oder als horizontale Achsen-Beschriftung unter den Ticks (horizontal).
     * Free deckt straight/stacked selbst über `isBarChart()` ab. */
    layoutHasSeriesAxisXLabelField(layout) {
      if (layout === 'wave') return true;
      const ext = getSceneExt();
      const fn = ext && ext.layoutHasSeriesAxisXLabelField;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Pro-Layouts, in denen `ruler.js` den vertikalen Y-Achsen-Titel
     * (axisYLabel) rendert. Horizontal hat keine vertikale Y-Achse und ist
     * hier bewusst ausgenommen; Free deckt straight/stacked über
     * `isBarChart()` ab. */
    layoutHasYAxisLabelField(layout) {
      if (layout === 'wave') return true;
      const ext = getSceneExt();
      const fn = ext && ext.layoutHasYAxisLabelField;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Add-on-Layouts mit positions-/serienbasierten Daten (z. B. Surface): Panel ausblenden.
     * Line/Area (wave) und Bar-Layouts: Sortierung im Editor erlaubt. */
    layoutDisablesSorting(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutDisablesSorting;
      return typeof fn === 'function' && fn(layout) === true;
    },
    /* Layouts ohne echten Achsenbezug, bei denen auch die Zahlenformat-Felder
     * (Unit-Prefix/Suffix) nur als Fallback greifen und das Achsen-Panel sonst
     * leer bzw. wirkungslos waere (z. B. BubblePack). Free's AxisPanel rendert
     * dann gar nichts. Free-Layouts zeigen das Panel; Add-ons melden Ausnahmen. */
    layoutHidesAxisPanel(layout) {
      const ext = getSceneExt();
      const fn = ext && ext.layoutHidesAxisPanel;
      return typeof fn === 'function' && fn(layout) === true;
    },

    /** Line/Area (wave): groupKey + per-point annotation fields in Chart data. */
    barItem: {
      showGroupKey(layout) {
        return layout === 'wave';
      },
      groupKeyHelp(layout) {
        if (layout === 'wave' && typeof wp !== 'undefined' && wp.i18n && wp.i18n.__) {
          return wp.i18n.__('Rows with the same value form one wave line (multi-series).', 'pop3d-charts');
        }
        return null;
      },
      fieldsAfter(layout, bar, onChange, ctx) {
        const { el: $el, __: t, TextControl: TC } = ctx;
        if (layout !== 'wave') return [];
        const out = [
          $el(TC, {
            key: 'annotation',
            label: t('Annotation', 'pop3d-charts'),
            value: bar.annotation || '',
            onChange: (v) => onChange({ ...bar, annotation: v }),
            placeholder: t('e.g. "Launch event"', 'pop3d-charts'),
            help: t('Optional marker label shown above this data point.', 'pop3d-charts'),
            __nextHasNoMarginBottom: true,
            __next40pxDefaultSize: true,
          }),
        ];
        if (bar.annotation) {
          out.push($el(TC, {
            key: 'annotationDesc',
            label: t('Annotation description', 'pop3d-charts'),
            value: bar.annotationDesc || '',
            onChange: (v) => onChange({ ...bar, annotationDesc: v }),
            placeholder: t('Short explanation', 'pop3d-charts'),
            __nextHasNoMarginBottom: true,
            __next40pxDefaultSize: true,
          }));
        }
        return out;
      },
    },
  };
}

export const cePdEditorExt = w.__CE_PD_EDITOR_EXT__;
