// ─────────────────────────────────────────────────────────────────────────
// Widget Registry — single source of truth for widget METADATA.
//
// WHY THIS EXISTS
// Historically, adding one widget meant editing ~9 places across two stacks
// that share no code (PHP render/sanitize + React editor), plus a duplicate
// type whitelist in the sanitizer. This registry collapses the EDITOR-side
// metadata (palette group, label, icon, category, default node, and — for
// schema-driven widgets — the content-field schema) into one array.
//
// TWO KINDS OF ENTRY
//  1. bespoke: true  → an existing widget with its own hand-written renderer /
//     content-editor / PHP element class / sanitizer case. The registry only
//     carries its palette metadata; nothing about its behavior changes. All 39
//     original widgets are bespoke. They keep matching their explicit `case`
//     in every dispatch switch, so they NEVER touch the generic path.
//  2. schema-driven (no `bespoke` flag) → a new widget defined ONLY here (+ a
//     matching entry in the PHP-side includes/widgets-schema.php). It flows
//     through the generic renderer (GenericRenderer.tsx), generic content
//     editor (GenericContent.tsx), and PHP generic element + sanitizer default
//     branch. No per-widget component or PHP class is written.
//
// Style / Advanced / Animation / Responsive / Global-Styles / copy-paste /
// undo / import-export all operate on the node tree generically already, so
// schema-driven widgets inherit every one of those for free.
// ─────────────────────────────────────────────────────────────────────────

import type { CBNode, ElementType } from '../types';

// The core NodeContent union only models ~11 of the 40+ element types, so a
// widget's seed content is an open bag here (matches how the editor already
// treats most element content as Record<string, unknown> at runtime). This is
// the same loosening the store's updateNode already assumes.
export type WidgetDefaultNode = Partial<Omit<CBNode, 'content'>> & { content?: Record<string, unknown> };

/** Palette group a widget appears under in the Elements panel + Block picker.
 * Mirrors the existing category strings so the derived palette is identical. */
export type WidgetPaletteGroup =
  | 'Layout' | 'Basic' | 'Navigation' | 'Media' | 'Marketing' | 'Content' | 'Forms'
  | 'WooCommerce' | 'Interactive' | 'Utility' | 'Effects' | 'Social' | 'Theme';

/** Control primitives the generic content editor knows how to render. Each
 * maps to an existing primitive in PropertiesPanel.tsx and to a PHP escaper in
 * includes/widgets-schema.php — keep the two in sync (they are the one
 * intentional TS↔PHP duplication). */
export type WidgetFieldControl =
  | 'text' | 'textarea' | 'number' | 'toggle' | 'select' | 'color' | 'url'
  // Visual multi-select: a grid of togglable icon chips whose selected keys are
  // stored as a comma-separated string (same underlying data as a text field,
  // so the PHP renderer/sanitizer are unchanged). Used for e.g. Social Share's
  // "Networks". Options come from `options` (value = stored key) and `glyph`
  // supplies the icon shown per chip.
  | 'multiicon'
  // Full icon-library picker (the shared IconPickerModal — Font Awesome,
  // Bootstrap, Tabler, Material, built-in SVGs, search/favorites). Stores an
  // IconValue object; PHP sanitizes via sanitize_icon_value() and renders via
  // Jalpi_Renderer::render_icon_value(). See GenericContent.tsx.
  | 'icon'
  // Elementor-style segmented button group: a single-select rendered as a row
  // of pill buttons (with optional per-option glyph) instead of a dropdown.
  // Same stored value as `select` (one option key), so PHP/sanitizer are
  // unchanged. Used for the Background "Type" (Classic / Gradient) switcher.
  | 'buttongroup'
  // WordPress media-library image picker. Opens wp.media, stores the chosen
  // attachment URL (same string the PHP renderer already expects for image
  // fields), with a thumbnail preview + remove button. Falls back to a URL
  // prompt when wp.media is unavailable. Used for Sticky Section's bg Image.
  | 'image'
  // Elementor-style slider: a range track + a small numeric box, bounded by
  // the field's min/max/step. Stores the same numeric string a `number` field
  // would, so PHP/sanitizer are unchanged. Used for opacity/angle controls.
  | 'slider'
  // Elementor-style repeater: an ordered list of items, each a collapsible row
  // with its own controls, plus an "+ Add Item" button and per-item
  // duplicate/delete. Backed by TWO existing string fields so PHP is unchanged:
  //   • `key`         — the ordered item keys as a comma-string (e.g. Social
  //                     Share's `networks`), the single source of truth for
  //                     which items exist and their order.
  //   • colorKey (opt)— a companion "key:color,key:color" map for per-item
  //                     custom colors; absent entries fall back to the option's
  //                     official color, so existing content renders identically.
  // Options define the pickable item types (value/label/glyph/color).
  | 'repeater'
  // Rich repeater: an ordered list of OBJECTS, each with its own sub-fields
  // (defined in `itemFields`). Stored as a JSON string in the field's key, so
  // the PHP sanitizer's default text path keeps it intact and a dedicated
  // renderer decodes it. Used by Social Icons (icon + link + color per item).
  | 'itemsRepeater'
  // Elementor-style 4-side dimensions box: Top / Right / Bottom / Left number
  // inputs with a link toggle. Stored as a CSS-shorthand string in the field's
  // key ("8px" when linked, "1px 2px 1px 2px" when independent), so the PHP
  // renderer can pass it straight to border-width / border-radius. Unit from
  // the field's `unit` (default px). Used by Social Share's border controls.
  | 'dimensions4';

export interface WidgetFieldOption {
  value: string;
  label: string;
  /** Single-char/emoji glyph shown on the chip for `multiicon` controls. */
  glyph?: string;
  /** Chip accent color for `multiicon` controls. */
  color?: string;
}

export interface WidgetField {
  key: string;
  label: string;
  control: WidgetFieldControl;
  /** For control: 'select' | 'multiicon' */
  options?: WidgetFieldOption[];
  placeholder?: string;
  /** Optional PropGroup this field is grouped under in the content editor. */
  group?: string;
  /** Which inspector tab the field renders in. Default 'content'. Fields
   * marked 'style' render as PropGroups at the top of the generic Style tab
   * instead (see GenericStyleExtras) — so style-ish widget controls live
   * where users expect them, alongside Background/Border/Spacing. */
  tab?: 'content' | 'style';
  /** Conditional visibility: only render this field when another field's
   * value matches. Keeps unused controls hidden (Elementor-style), e.g.
   * gradient fields only show when Background Type = 'gradient'. */
  showIf?: { key: string; equals?: string; notEquals?: string; truthy?: boolean };
  /** For control: 'slider' — range bounds + optional unit suffix (e.g. '%'). */
  min?: number;
  max?: number;
  step?: number;
  unit?: string;
  /** For control: 'repeater' — the companion content key holding the per-item
   * "itemKey:color,itemKey:color" custom-color map. Optional; when omitted the
   * repeater has no per-item color control. */
  colorKey?: string;
  /** For control: 'itemsRepeater' — the sub-fields each object item exposes
   * (rendered as a mini form inside each collapsible row). */
  itemFields?: WidgetField[];
  /** For control: 'itemsRepeater' — which sub-field's value labels the item's
   * collapsed header (falls back to "Item N"). */
  itemTitleKey?: string;
  /** Custom label for the add button (repeater / itemsRepeater). */
  addLabel?: string;
  /** For control: 'itemsRepeater' — when true, "+ Add Item" opens the icon
   * library first and stores the chosen icon into the new item's `icon`
   * sub-field (Elementor-style). Requires an `icon` sub-field. */
  addWithIconPicker?: boolean;
}

export interface WidgetDef {
  type: ElementType;
  label: string;
  icon: string;
  /** Palette group (also the Block-picker category for existing types). */
  category: WidgetPaletteGroup;
  description: string;
  color: string;
  defaultNode: WidgetDefaultNode;
  /** Present only for schema-driven widgets. */
  fields?: WidgetField[];
  /** True for the 39 pre-existing widgets — skips the generic content path. */
  bespoke?: boolean;
}

// ── Bespoke widgets (existing 39) ──────────────────────────────────────────
// Metadata migrated 1:1 from BlockPicker.tsx's BLOCKS. `bespoke: true` means
// "this widget already has hand-written components everywhere — registry is
// metadata only". defaultNode objects are copied verbatim so the derived
// Block-picker output is byte-identical to the old hand-written array.
const b = (
  type: ElementType, label: string, icon: string, category: WidgetPaletteGroup,
  description: string, color: string, defaultNode: WidgetDefaultNode
): WidgetDef => ({ type, label, icon, category, description, color, defaultNode, bespoke: true });

const BESPOKE: WidgetDef[] = [
  // Layout (structural — these live in ElementsPanel's LAYOUT_ITEMS today)
  b('section', 'Section', '⊞', 'Layout', 'Full-width row wrapper', '#8b5cf6', { type: 'section', children: [], styles: {}, settings: {} }),
  b('container', 'Container', '□', 'Layout', 'Flex/grid content container', '#3b82f6', { type: 'container', settings: { columns: 1 }, children: [], styles: {} }),
  b('column', 'Column', '⊟', 'Layout', 'Column inside a container', '#10b981', { type: 'column', children: [], styles: {}, settings: {} }),

  // Basic
  b('heading', 'Heading', 'H', 'Basic', 'Title or section headline', '#6b7280', { type: 'heading', content: { text: 'New Heading', tag: 'h2' }, styles: { fontSize: '32px', fontWeight: '700', color: '#1a1a1a', lineHeight: '1em' } }),
  b('text', 'Text Editor', '¶', 'Basic', 'Rich text paragraph block', '#6b7280', { type: 'text', content: { html: '' }, styles: { fontSize: '16px', color: '#444444', lineHeight: '1em' } }),
  b('button', 'Button', '⬜', 'Basic', 'Call-to-action button', '#fb923c', { type: 'button', content: { text: 'Click Here', url: '#' }, styles: { display: 'inline-block', backgroundColor: '#1a1a1a', color: '#ffffff', paddingTop: '14px', paddingBottom: '14px', paddingLeft: '32px', paddingRight: '32px', borderRadius: '8px', fontWeight: '600', fontSize: '16px', textDecoration: 'none' } }),
  b('icon', 'Icon', '★', 'Basic', 'Scalable SVG icon', '#fb923c', { type: 'icon', content: { icon: 'star', size: '48px', color: '#1a1a1a', align: 'center' } }),
  b('divider', 'Divider', '─', 'Basic', 'Horizontal rule separator', '#60a5fa', { type: 'divider', styles: { border: 'none', borderTop: '1px solid #e5e7eb', marginTop: '20px', marginBottom: '20px' } }),
  b('spacer', 'Spacer', '↕', 'Basic', 'Vertical whitespace', '#60a5fa', { type: 'spacer', settings: { height: '60px' } }),
  b('accordion', 'Accordion', '⊞', 'Basic', 'Collapsible content panels', '#6b7280', { type: 'accordion', content: { items: [{ title: 'Panel One', body: 'Add your content here.' }, { title: 'Panel Two', body: 'Add your content here.' }] } }),
  b('counter', 'Counter', '①', 'Basic', 'Display a stat number with label', '#6b7280', { type: 'counter', content: { items: [{ startNumber: '0', endNumber: '250', prefix: '', suffix: '+', duration: '2000', thousandSep: false, separator: 'dot', title: 'Projects Done' }, { startNumber: '0', endNumber: '10', prefix: '', suffix: 'k', duration: '2000', thousandSep: false, separator: 'dot', title: 'Happy Clients' }, { startNumber: '0', endNumber: '5', prefix: '', suffix: '★', duration: '2000', thousandSep: false, separator: 'dot', title: 'Awards Won' }] } }),
  b('progress', 'Progress Bar', '▬', 'Basic', 'Skill or completion bar', '#6b7280', { type: 'progress', content: { items: [{ title: 'Design', titleTag: 'h3', showTitle: true, percentage: 90, showPercentage: true, innerText: '', color: '#1a1a1a' }, { title: 'Development', titleTag: 'h3', showTitle: true, percentage: 80, showPercentage: true, innerText: '', color: '#374151' }] } }),
  b('badge', 'Badge', '◉', 'Basic', 'Small inline label or tag', '#fb923c', { type: 'badge', content: { text: 'New', variant: 'primary' }, styles: { fontSize: '13px', fontWeight: '600' } }),
  b('icon-list', 'Icon List', '☑', 'Basic', 'List with icons per item', '#60a5fa', { type: 'icon-list', content: { layout: 'list', defaultIcon: 'check', applyLinkOn: 'full', items: [{ text: 'List Item #1', icon: 'check', link: '', newTab: false }, { text: 'List Item #2', icon: 'check', link: '', newTab: false }, { text: 'List Item #3', icon: 'check', link: '', newTab: false }] } }),
  b('tabs', 'Tabs', '⊟', 'Basic', 'Tabbed content panels', '#6b7280', { type: 'tabs', content: { tabStyle: 'underline', items: [{ label: 'Tab One', content: 'Content for tab one.' }, { label: 'Tab Two', content: 'Content for tab two.' }, { label: 'Tab Three', content: 'Content for tab three.' }] } }),
  // Tabs (Advanced): each tab is a real droppable container holding widgets.
  // content.items[] holds tab metadata; children[] are the per-tab containers,
  // index-aligned. Child ids are reassigned on add (see editorStore add paths).
  b('tabs-adv', 'Tabs (Advanced)', '⊟', 'Basic', 'Tabs where each panel holds real widgets', '#6b7280', {
    type: 'tabs-adv',
    content: {
      tabStyle: 'underline',
      activeTab: 0,
      items: [
        { id: 't1', title: 'Tab #1', icon: '', cssId: '' },
        { id: 't2', title: 'Tab #2', icon: '', cssId: '' },
        { id: 't3', title: 'Tab #3', icon: '', cssId: '' },
      ],
    },
    children: [
      { id: 'tabpanel-1', type: 'container', settings: { columns: 1 }, children: [] },
      { id: 'tabpanel-2', type: 'container', settings: { columns: 1 }, children: [] },
      { id: 'tabpanel-3', type: 'container', settings: { columns: 1 }, children: [] },
    ],
  }),

  b('menu', 'Menu', '☰', 'Navigation', 'Navigation menu with dropdowns', '#0ea5e9', {
    type: 'menu',
    content: {
      // Only the controls the Menu widget exposes. A freshly dropped menu has
      // no WordPress menu selected yet, so the canvas shows a placeholder
      // prompting the author to pick one.
      menuId: 0,
      menuLocation: '',
      layout: 'horizontal',
      submenuIcon: 'chevron',
      showSubmenus: true,
      dropdownTrigger: 'hover',
      responsiveMenu: 'dropdown',
      mobileBreakpoint: 768,
      align: 'left',
      menuWidth: '',
      itemSpacing: '',
      ariaLabel: 'Main navigation',
    },
  }),

  // Advanced Menu — a separate element from Menu, not a variant of it. Its
  // mega panels are real container children (like Tabs (Advanced)), so any
  // widget can be dropped inside one. megaItems maps a WordPress menu-item id
  // to the panel index that holds its content.
  b('adv-menu', 'Advanced Menu', '▤', 'Navigation', 'Mega menu with builder content in dropdown panels', '#0ea5e9', {
    type: 'adv-menu',
    content: {
      menuId: 0,
      menuLocation: '',
      layout: 'horizontal',
      megaTrigger: 'hover',
      panelWidth: 'container',
      panelCustomWidth: '',
      dropdownPosition: 'left',
      align: 'left',
      submenuIndicator: 'arrow',
      animation: 'fade',
      animationDuration: 250,
      openDelay: 100,
      closeDelay: 200,
      breakpoint: 1024,
      mobileLayout: 'accordion',
      stickyMobileHeader: false,
      menuWidth: '',
      itemSpacing: '',
      ariaLabel: 'Main navigation',
      megaItems: {},
    },
    children: [],
  }),

  b('breadcrumbs', 'Breadcrumbs', '⋯', 'Navigation', 'Trail from the site root to this page', '#64748b', {
    type: 'breadcrumbs',
    content: {
      separator: 'chevron',
      homeLabel: 'Home',
      showHome: true,
      showCurrent: true,
      ariaLabel: 'Breadcrumb',
    },
  }),

  // Media
  b('image', 'Image', '⬚', 'Media', 'Photo or illustration', '#34d399', { type: 'image', content: { url: '', alt: '' }, styles: { width: '100%' } }),
  b('video', 'Video', '▶', 'Media', 'YouTube, Vimeo or MP4', '#34d399', { type: 'video', content: { url: '', controls: true }, styles: { width: '100%' } }),
  b('logo', 'Logo', '◈', 'Media', 'Clickable logo image', '#34d399', { type: 'logo', content: { url: '', alt: 'Logo', width: '200px' } }),
  b('maps', 'Google Maps', '⊕', 'Media', 'Embedded map widget', '#34d399', { type: 'maps', content: { address: '', height: '400px' } }),
  b('gallery', 'Gallery', '⊞', 'Media', 'Image gallery with grid, masonry, lightbox and hover effects', '#34d399', { type: 'gallery', content: { layout: 'grid', enableLightbox: false, enableFilter: false, items: [{ id: 'g1', url: '', alt: '', caption: '', linkUrl: '', linkTarget: '_blank', category: '' }, { id: 'g2', url: '', alt: '', caption: '', linkUrl: '', linkTarget: '_blank', category: '' }, { id: 'g3', url: '', alt: '', caption: '', linkUrl: '', linkTarget: '_blank', category: '' }, { id: 'g4', url: '', alt: '', caption: '', linkUrl: '', linkTarget: '_blank', category: '' }, { id: 'g5', url: '', alt: '', caption: '', linkUrl: '', linkTarget: '_blank', category: '' }, { id: 'g6', url: '', alt: '', caption: '', linkUrl: '', linkTarget: '_blank', category: '' }] }, settings: { gal_cols_desktop: '3', gal_cols_tablet: '2', gal_cols_mobile: '1', gal_gap: '12', gal_img_ratio: '1/1', gal_hover: 'zoom', gal_caption_show: 'hover', gal_caption_color: '#ffffff', gal_caption_bg: 'rgba(0,0,0,0.6)', gal_img_br_top: '8', gal_img_br_right: '8', gal_img_br_bottom: '8', gal_img_br_left: '8' } }),
  b('image-carousel', 'Image Carousel', '↔', 'Media', 'Sliding image carousel with autoplay, arrows, lightbox and hover effects', '#34d399', { type: 'image-carousel', content: { items: [{ url: '', alt: '', caption: '', description: '', linkUrl: '', linkNewTab: false }, { url: '', alt: '', caption: '', description: '', linkUrl: '', linkNewTab: false }, { url: '', alt: '', caption: '', description: '', linkUrl: '', linkNewTab: false }] }, settings: { ic_layout: 'classic', ic_cols: '1', ic_gap: '0', ic_effect: 'slide', ic_speed: '400', ic_loop: '1', ic_arrows: '1', ic_dots: '1', ic_lightbox: '0', ic_img_height: '400px', ic_img_fit: 'cover', ic_hover: 'none', ic_caption_pos: 'below' } }),
  b('image-box', 'Image Box', '⊞', 'Media', 'Image with title and description', '#34d399', { type: 'image-box', content: { url: '', alt: '', title: 'Image Title', description: 'Add a short description to complement the image above.', titleTag: 'h3', imagePosition: 'top', linkUrl: '' } }),

  // Marketing
  b('alert', 'Alert', '⚠', 'Marketing', 'Info, success, warning or error notice', '#f59e0b', { type: 'alert', content: { variant: 'info', title: 'Info', text: 'This is an informational alert.' } }),
  b('feature-card', 'Feature Card', '◈', 'Marketing', 'Icon + title + description card', '#6b7280', { type: 'feature-card', content: { icon: 'star', title: 'Amazing Feature', text: 'Describe what makes this feature valuable to your users.' } }),
  b('cta', 'Call To Action', '⊳', 'Marketing', 'Headline + subtext + action buttons', '#fb923c', { type: 'cta', content: { title: 'Ready to get started?', subtitle: 'Join thousands of users already building with us.', primaryText: 'Start for free', primaryUrl: '#', secondaryText: 'Learn more', secondaryUrl: '#' } }),
  b('testimonials', 'Testimonials', '❝❝', 'Marketing', 'Modern testimonial grid or carousel with ratings and author profiles', '#7c3aed', { type: 'testimonials', content: { layout: 'grid', cols: 3, autoplay: 0, items: [{ text: 'This product completely transformed the way our team works. The quality is outstanding and the support is second to none.', name: 'Jane Smith', role: 'CEO, Acme Corp', avatarUrl: '', rating: 5 }, { text: 'Absolutely love the simplicity and power packed into one tool. Our productivity has gone through the roof since we switched.', name: 'Mark Chen', role: 'CTO, Bright Labs', avatarUrl: '', rating: 5 }, { text: 'Best investment we made this year. The onboarding was smooth and the results speak for themselves. Highly recommended!', name: 'Sara Nour', role: 'Founder, Nomad Studio', avatarUrl: '', rating: 4 }] } }),
  b('social-icons', 'Social Icons', '◎', 'Marketing', 'Social media icon links bar', '#fb923c', { type: 'social-icons', content: { iconStyle: 'circle', iconSize: '40px', align: 'center', color: '', items: [{ platform: 'facebook', url: '#' }, { platform: 'instagram', url: '#' }, { platform: 'twitter', url: '#' }, { platform: 'linkedin', url: '#' }] } }),
  b('countdown', 'Countdown', '⏱', 'Marketing', 'Live countdown timer to a target date', '#f59e0b', { type: 'countdown', content: { targetDate: '', label: 'Until launch' } }),
  b('flip-box', 'Flip Box', '⟳', 'Marketing', 'Card with a flipping front and back face', '#9ca3af', { type: 'flip-box', content: { frontTitle: 'Front Side', frontDescription: 'Hover to reveal the back.', frontBg: '#1a1a1a', frontColor: '#ffffff', backTitle: 'Back Side', backDescription: 'This is what your visitors will see on hover.', backBg: '#111827', backColor: '#ffffff', btnText: 'Learn More', btnUrl: '#', height: '220px' } }),
  b('star-rating', 'Star Rating', '★', 'Marketing', 'Standalone star rating display', '#f59e0b', { type: 'star-rating', content: { rating: 4, maxRating: 5, label: '4 out of 5 stars', starColor: '#f59e0b', size: '28px', align: 'left' } }),
  b('html', 'HTML', '</>', 'Marketing', 'Custom HTML block', '#fb923c', { type: 'html', content: { html: '' } }),
  b('shortcode', 'Shortcode', '[ ]', 'Marketing', 'WordPress shortcode', '#fb923c', { type: 'shortcode', content: { shortcode: '[shortcode]' } }),

  // Forms / Widgets
  b('form', 'Form', '⊞', 'Forms', 'CF7, WPForms, Forminator, Gravity Forms', '#9ca3af', { type: 'form', content: { plugin: '', form_id: '' } }),

  // NOTE: pricing-table, stats-card, faq, table, team-member, timeline, and
  // testimonial are fully-implemented bespoke widgets (PHP + editor + sanitizer)
  // but were NOT present in the original BlockPicker BLOCKS array — so they did
  // not appear in the drag palette before this refactor. They are intentionally
  // omitted here too, to keep the derived palette byte-identical. They can be
  // surfaced later by adding entries, but that is a behavior change, not part
  // of this backward-compatible refactor.
];

// ── Schema-driven pilot widgets (proof of the generic path) ────────────────
// These have NO bespoke component and NO dedicated PHP class. They are defined
// entirely by the fields below (mirrored server-side in widgets-schema.php).

// NOTE: STICKY_SECTION and BUSINESS_HOURS widgets fully removed per user request.

// ── Batch: self-contained schema widgets ───────────────────────────────────
// Each is defined ONLY here + in includes/widgets-schema.php + a render case
// in class-jalpi-element-generic.php (+ an editor preview in GenericRenderer).
// No bespoke component or PHP class. Fields drive the auto-generated content
// editor; defaultNode seeds a sensible starting state.

const BATCH: WidgetDef[] = [
  // Effects
  {
    type: 'marquee', label: 'Marquee', icon: '🏃', category: 'Effects',
    description: 'Continuously scrolling text ticker.', color: '#8b5cf6',
    defaultNode: { type: 'marquee', content: { text: 'Breaking news · Announcement · Scrolling message ·', speed: '20', direction: 'left', pauseOnHover: true, itemSpacing: '60' }, styles: { fontSize: '18px', color: '#1a1a1a' } },
    fields: [
      { key: 'text', label: 'Text', control: 'text' },
      { key: 'speed', label: 'Speed (s)', control: 'number', placeholder: '20' },
      { key: 'direction', label: 'Direction', control: 'select', options: [{ value: 'left', label: 'Left' }, { value: 'right', label: 'Right' }] },
      { key: 'pauseOnHover', label: 'Pause on Hover', control: 'toggle' },
      { key: 'itemSpacing', label: 'Item Spacing (px)', control: 'number', placeholder: '60', group: 'Marquee Style', tab: 'style' },
    ],
  },
  {
    type: 'typewriter-text', label: 'Typewriter Text', icon: '⌨️', category: 'Effects',
    description: 'Types out a rotating list of phrases.', color: '#8b5cf6',
    defaultNode: { type: 'typewriter-text', content: { words: 'Designer\nDeveloper\nCreator', tag: 'h2', typeSpeed: '100', loop: true }, styles: { fontSize: '32px', fontWeight: '700', color: '#1a1a1a' } },
    fields: [
      { key: 'words', label: 'Words (one per line)', control: 'textarea' },
      { key: 'tag', label: 'Tag', control: 'select', options: ['h1', 'h2', 'h3', 'h4', 'p', 'span'].map((v) => ({ value: v, label: v.toUpperCase() })) },
      { key: 'typeSpeed', label: 'Type Speed (ms)', control: 'number', placeholder: '100' },
      { key: 'loop', label: 'Loop', control: 'toggle' },
      { key: 'cursorStyle', label: 'Cursor Style', control: 'select', group: 'Cursor', tab: 'style', options: [{ value: 'bar', label: 'Bar |' }, { value: 'underscore', label: 'Underscore _' }, { value: 'block', label: 'Block ▌' }] },
      { key: 'cursorColor', label: 'Cursor Color', control: 'color', group: 'Cursor', tab: 'style' },
      { key: 'cursorWidth', label: 'Cursor Width (px)', control: 'number', placeholder: '2', group: 'Cursor', tab: 'style' },
    ],
  },
  {
    type: 'shape-divider', label: 'Shape Divider', icon: '〰️', category: 'Effects',
    description: 'Decorative SVG section divider.', color: '#8b5cf6',
    defaultNode: { type: 'shape-divider', content: { shape: 'wave', color: '#1a1a1a', height: '60', flip: false } },
    fields: [
      { key: 'shape', label: 'Shape', control: 'select', options: [{ value: 'wave', label: 'Wave' }, { value: 'tilt', label: 'Tilt' }, { value: 'curve', label: 'Curve' }, { value: 'triangle', label: 'Triangle' }] },
      { key: 'color', label: 'Shape Color', control: 'color', group: 'Shape Style', tab: 'style' },
      { key: 'height', label: 'Height (px)', control: 'number', placeholder: '60', group: 'Shape Style', tab: 'style' },
      { key: 'widthPct', label: 'Width (%)', control: 'number', placeholder: '100', group: 'Shape Style', tab: 'style' },
      { key: 'flip', label: 'Flip Horizontally', control: 'toggle', group: 'Shape Style', tab: 'style' },
      { key: 'zIndex', label: 'Z-Index (stacking)', control: 'number', placeholder: '0', group: 'Shape Style', tab: 'style' },
    ],
  },

];

// ── Social widgets ─────────────────────────────────────────────────────────
const SOCIAL: WidgetDef[] = [
  {
    type: 'social-share', label: 'Social Share', icon: '🔗', category: 'Social',
    description: 'A row of linked social icons — pick any icon, set its link.', color: '#ec4899',
    defaultNode: {
      type: 'social-share',
      content: {
        items: JSON.stringify([
          { icon: { type: 'font', lib: 'fab', name: 'facebook-f', cls: 'fab fa-facebook-f' }, link: '', color: '#1877f2' },
          { icon: { type: 'font', lib: 'fab', name: 'x-twitter', cls: 'fab fa-x-twitter' }, link: '', color: '#000000' },
          { icon: { type: 'font', lib: 'fab', name: 'youtube', cls: 'fab fa-youtube' }, link: '', color: '#ff0000' },
        ]),
        style: 'filled', shape: 'rounded',
      },
    },
    fields: [
      {
        // "+ Add Item" opens the icon library; each picked icon becomes an item
        // with its own Link + Color. See ItemsRepeaterControl in GenericContent.
        key: 'items', label: 'Social Icons', control: 'itemsRepeater',
        addLabel: 'Add Item', itemTitleKey: 'link', addWithIconPicker: true,
        itemFields: [
          { key: 'icon', label: 'Icon', control: 'icon' },
          { key: 'link', label: 'Link', control: 'url', placeholder: 'Type or paste your URL' },
          { key: 'color', label: 'Color', control: 'color' },
        ],
      },
      { key: 'style', label: 'Style', control: 'select', options: [{ value: 'filled', label: 'Filled' }, { value: 'outline', label: 'Outline' }] },
      { key: 'shape', label: 'Shape', control: 'select', options: [{ value: 'circle', label: 'Circle' }, { value: 'rounded', label: 'Rounded' }, { value: 'square', label: 'Square' }] },

      // ── STYLE TAB — Elementor-style Icon Hover + Icon groups ──────────────
      // Icon Hover: a one-click hover animation applied to each icon.
      { key: 'hoverAnimation', label: 'Hover Animation', control: 'select', group: 'Icon Hover', tab: 'style', options: [
        { value: 'none', label: 'None' },
        { value: 'grow', label: 'Grow' },
        { value: 'shrink', label: 'Shrink' },
        { value: 'pulse', label: 'Pulse' },
        { value: 'pulse-grow', label: 'Pulse Grow' },
        { value: 'pulse-shrink', label: 'Pulse Shrink' },
        { value: 'push', label: 'Push' },
        { value: 'pop', label: 'Pop' },
        { value: 'bounce-in', label: 'Bounce In' },
        { value: 'bounce-out', label: 'Bounce Out' },
        { value: 'rotate', label: 'Rotate' },
        { value: 'float', label: 'Float' },
        { value: 'sink', label: 'Sink' },
        { value: 'wobble-horizontal', label: 'Wobble Horizontal' },
        { value: 'wobble-vertical', label: 'Wobble Vertical' },
        { value: 'buzz', label: 'Buzz' },
      ] },

      // Icon: sizing / spacing / border for the icon row.
      { key: 'iconColorMode', label: 'Color', control: 'select', group: 'Icon', tab: 'style', options: [{ value: 'official', label: 'Official Color' }, { value: 'custom', label: 'Custom' }] },
      { key: 'iconColor', label: 'Custom Color', control: 'color', group: 'Icon', tab: 'style', showIf: { key: 'iconColorMode', equals: 'custom' } },
      { key: 'iconSize', label: 'Size', control: 'slider', min: 6, max: 120, step: 1, unit: 'px', placeholder: '18', group: 'Icon', tab: 'style' },
      { key: 'iconPadding', label: 'Padding', control: 'slider', min: 0, max: 5, step: 0.1, unit: 'em', placeholder: '0.5', group: 'Icon', tab: 'style' },
      { key: 'iconSpacing', label: 'Spacing', control: 'slider', min: 0, max: 60, step: 1, unit: 'px', placeholder: '8', group: 'Icon', tab: 'style' },
      { key: 'iconRowsGap', label: 'Rows Gap', control: 'slider', min: 0, max: 60, step: 1, unit: 'px', placeholder: '0', group: 'Icon', tab: 'style' },
      { key: 'iconBorderType', label: 'Border Type', control: 'select', group: 'Icon', tab: 'style', options: [
        { value: 'default', label: 'Default' },
        { value: 'none', label: 'None' },
        { value: 'solid', label: 'Solid' },
        { value: 'dashed', label: 'Dashed' },
        { value: 'dotted', label: 'Dotted' },
        { value: 'double', label: 'Double' },
      ] },
      { key: 'iconBorderWidth', label: 'Border Width', control: 'dimensions4', unit: 'px', group: 'Icon', tab: 'style', showIf: { key: 'iconBorderType', notEquals: 'none' } },
      { key: 'iconBorderColor', label: 'Border Color', control: 'color', group: 'Icon', tab: 'style', showIf: { key: 'iconBorderType', notEquals: 'none' } },
      { key: 'iconBorderRadius', label: 'Border Radius', control: 'dimensions4', unit: 'px', group: 'Icon', tab: 'style' },
    ],
  },
  {
    type: 'whatsapp-button', label: 'WhatsApp Button', icon: '💬', category: 'Social',
    description: 'Click-to-chat WhatsApp button (inline or floating).', color: '#25d366',
    defaultNode: { type: 'whatsapp-button', content: { phone: '', message: 'Hi! I have a question.', label: 'Chat on WhatsApp', floating: false, bgColor: '#25d366' } },
    fields: [
      { key: 'phone', label: 'Phone (with country code)', control: 'text', placeholder: '15551234567' },
      { key: 'message', label: 'Prefilled Message', control: 'textarea' },
      { key: 'label', label: 'Button Label', control: 'text' },
      { key: 'floating', label: 'Floating (fixed bottom-right)', control: 'toggle' },
      { key: 'bgColor', label: 'Background', control: 'color' },
    ],
  },
  {
    type: 'youtube-gallery', label: 'YouTube Gallery', icon: '▶️', category: 'Social',
    description: 'Grid of YouTube videos (lazy-loaded on click, no API).', color: '#ff0000',
    defaultNode: { type: 'youtube-gallery', content: { videos: 'dQw4w9WgXcQ\nhttps://youtu.be/9bZkp7q19f0', columns: '3' } },
    fields: [
      { key: 'videos', label: 'Video IDs / URLs (one per line)', control: 'textarea' },
      { key: 'columns', label: 'Columns', control: 'number', placeholder: '3' },
    ],
  },
  {
    type: 'facebook-feed', label: 'Facebook Post', icon: '📘', category: 'Social',
    description: 'Embed a public Facebook post (single-post oEmbed).', color: '#1877f2',
    defaultNode: { type: 'facebook-feed', content: { postUrl: '', width: '500' } },
    fields: [
      { key: 'postUrl', label: 'Facebook Post URL', control: 'url', placeholder: 'https://www.facebook.com/…' },
      { key: 'width', label: 'Width (px)', control: 'number', placeholder: '500' },
    ],
  },
  {
    type: 'instagram-feed', label: 'Instagram Post', icon: '📸', category: 'Social',
    description: 'Embed a public Instagram post/reel (single-post oEmbed).', color: '#e1306c',
    defaultNode: { type: 'instagram-feed', content: { postUrl: '' } },
    fields: [
      { key: 'postUrl', label: 'Instagram Post URL', control: 'url', placeholder: 'https://www.instagram.com/p/…' },
    ],
  },
];

// ── Icon Box ───────────────────────────────────────────────────────────────
// Demonstrates the full icon-library picker (control: 'icon') in a schema
// widget: pick ANY icon from Font Awesome / Bootstrap / Tabler / Material /
// built-in SVGs, plus size, color, optional title/text and link.
const ICON_BOX: WidgetDef = {
  type: 'icon-box', label: 'Icon Box', icon: '⭐', category: 'Basic',
  description: 'A chosen icon with optional title, text and link.', color: '#fb923c',
  defaultNode: {
    type: 'icon-box',
    content: { icon: { type: 'svg', lib: 'builtin', name: 'star' }, size: '48', color: '#1a1a1a', title: 'Feature', text: 'Describe this feature.', link: '', align: 'center' },
    styles: {},
  },
  fields: [
    { key: 'icon', label: 'Icon', control: 'icon' },
    { key: 'size', label: 'Icon Size (px)', control: 'number', placeholder: '48' },
    { key: 'color', label: 'Icon Color', control: 'color' },
    { key: 'align', label: 'Alignment', control: 'select', options: [{ value: 'left', label: 'Left' }, { value: 'center', label: 'Center' }, { value: 'right', label: 'Right' }] },
    { key: 'title', label: 'Title', control: 'text' },
    { key: 'text', label: 'Text', control: 'textarea' },
    { key: 'link', label: 'Link URL', control: 'url', placeholder: 'https://…' },
  ],
};

// ── WooCommerce ────────────────────────────────────────────────────────────
// Bespoke: the grid runs a real WC_Query server-side and paginates over AJAX,
// neither of which the schema path can express. Its controls live in
// ProductsGridContent / ProductsGridStyleTab in PropertiesPanel.tsx, and its
// canvas preview in ProductsGridPreview (CanvasNode). Registry entry is
// metadata only — same contract as the other 39 bespoke widgets.
const WOOCOMMERCE: WidgetDef[] = [
  b(
    'products-grid', 'Products Grid', '▦', 'WooCommerce',
    'WooCommerce product grid with live query, skins and pagination', '#7f54b3',
    {
      type: 'products-grid',
      content: {
        // Layout
        skin: 'classic', perPage: 8, rows: 1, useRows: false,
        equalHeight: true, masonry: false,
        // Query
        source: 'latest', categories: '', tags: '', brands: '',
        includeProducts: '', excludeProducts: '', includeIds: '', excludeIds: '',
        stockStatus: 'any', productStatus: 'publish',
        orderBy: 'date', order: 'DESC', offset: 0, ignoreSticky: false,
        // Product elements
        showImage: true, showSaleBadge: true, showTitle: true, showRating: true,
        showPrice: true, showExcerpt: false, showAddToCart: true,
        showWishlist: false, showCompare: false, showQuickView: false,
        showCategories: false, showSku: false,
        // Image + button behaviour
        secondaryImage: false, lazyLoad: true,
        buttonText: '', showQuantity: false, ajaxAddToCart: true,
        // Pagination + empty state
        pagination: 'none', loadMoreText: 'Load More',
        emptyMessage: 'No products found.', emptyIcon: '🛒',
      },
      settings: {
        pg_cols_desktop: '4', pg_cols_tablet: '2', pg_cols_mobile: '1',
        pg_col_gap: '24', pg_row_gap: '32',
        pg_img_ratio: '1/1', pg_img_fit: 'cover', pg_img_hover: 'zoom',
        pg_img_size: 'woocommerce_thumbnail',
        pg_card_align: 'left', pg_card_radius: '12',
        pg_badge_pos: 'top-left', pg_btn_icon: 'none', pg_btn_icon_pos: 'before',
      },
      styles: {},
    }
  ),
  b(
    'product-categories', 'Product Categories', '🗂', 'WooCommerce',
    'WooCommerce category grid, list or carousel with images and counts', '#7f54b3',
    {
      type: 'product-categories',
      content: {
        // Layout
        skin: 'classic', layout: 'grid', equalHeight: true,
        // Query
        source: 'all', includeCategories: '', excludeCategories: '', categoryIds: '',
        parentCategory: 0, hideEmpty: true, number: 12,
        orderBy: 'name', order: 'ASC',
        // Category content
        showImage: true, showName: true, showCount: true,
        showDescription: false, showButton: false,
        // Image + button
        lazyLoad: true, buttonText: 'View Products', openNewWindow: false,
        // Carousel
        slidesPerView: 4, slidesToScroll: 1, carouselLoop: true,
        autoplay: 0, carouselSpeed: 400, pauseOnHover: true,
        showArrows: true, showDots: true,
        // Empty state
        emptyMessage: 'No product categories found.', emptyIcon: '🗂',
      },
      settings: {
        pc_cols_desktop: '4', pc_cols_tablet: '2', pc_cols_mobile: '1',
        pc_col_gap: '24', pc_row_gap: '24', pc_align: 'center',
        pc_img_ratio: '1/1', pc_img_fit: 'cover', pc_img_hover: 'zoom',
        pc_img_size: 'woocommerce_thumbnail',
        pc_card_radius: '12', pc_btn_icon: 'none', pc_btn_icon_pos: 'after',
      },
      styles: {},
    }
  ),
  b(
    'single-product', 'Single Product', '🛍', 'WooCommerce',
    'Full single-product layout: gallery, price, cart, tabs and related', '#7f54b3',
    {
      type: 'single-product',
      content: {
        // Source
        source: 'current', productId: 0, customProductId: 0, previewProduct: 0,
        // Layout / structure
        layout: 'default', galleryPosition: 'left', thumbPosition: 'horizontal',
        imageLink: 'lightbox', titleTag: 'h1',
        showGallery: true, showTitle: true, showRating: true, showPrice: true,
        showExcerpt: true, showAddToCart: true, showMeta: true, showShare: false,
        showTabs: true, showRelated: true, showUpsells: false,
        // Gallery
        showThumbs: true, lazyLoad: true,
        // Rating
        showReviewCount: true, linkToReviews: false, showEmptyRating: false,
        // Price
        showRegularPrice: true, showSalePrice: true, showSaleBadge: true,
        // Cart
        showQuantity: true, quantityType: 'input', buttonText: '', ajaxAddToCart: true,
        // Meta
        metaSku: true, metaCategories: true, metaTags: true,
        metaSeparator: ', ', metaShowLabels: true,
        // Share
        shareNetworks: 'facebook,twitter,linkedin,pinterest,whatsapp',
        // Tabs
        defaultTab: 'description', tabPosition: 'top',
        // Related / upsells
        relatedCount: 4, relatedColumns: 4, relatedOrderBy: 'rand',
        upsellsCount: 4, upsellsColumns: 4,
        // Empty state
        emptyMessage: 'No product selected — choose a product in the widget settings.',
      },
      settings: {
        sp_gallery_width: '50', sp_col_gap: '40',
        sp_img_size: 'woocommerce_single', sp_img_radius: '8', sp_img_hover: 'none',
        sp_thumb_size: '72', sp_thumb_gap: '10',
        sp_title_size: '30', sp_title_weight: '700',
        sp_price_size: '24', sp_price_weight: '700',
        sp_btn_icon: 'none', sp_btn_icon_pos: 'before', sp_share_style: 'circle',
      },
      styles: {},
    }
  ),
];

export const WIDGET_REGISTRY: WidgetDef[] = [
  ...BESPOKE,
  ...BATCH,
  ...SOCIAL,
  ICON_BOX,
  ...WOOCOMMERCE,
];

// ── Lookups ────────────────────────────────────────────────────────────────

const BY_TYPE = new Map<string, WidgetDef>(WIDGET_REGISTRY.map((w) => [w.type, w]));

export function getWidgetDef(type: string): WidgetDef | undefined {
  return BY_TYPE.get(type);
}

/** Schema-driven (non-bespoke) widget → its field schema, else undefined.
 * The generic content editor / renderer use this; bespoke widgets return
 * undefined and keep their hand-written components. */
export function getWidgetFields(type: string): WidgetField[] | undefined {
  const def = BY_TYPE.get(type);
  return def && !def.bespoke ? def.fields : undefined;
}

export function isSchemaWidget(type: string): boolean {
  const def = BY_TYPE.get(type);
  return !!def && !def.bespoke;
}
