/**
 * Field preview for the form editor canvas.
 *
 * Renders the same markup and classes as the live form (includes/class-formglut-shortcode.php)
 * and is styled by the same stylesheet (assets/src/frontend/formglut-frontend.css), so the editor,
 * the admin Preview and the shortcode look identical. When the PHP renderer changes, change this
 * file the same way. Controls are inert here: the canvas turns off pointer events inside fields.
 * The stylesheet reaches the editor as resources/assets/form-frontend.css, which
 * FormGlut_Admin enqueues on every FormGlut admin page with the other built CSS files.
 */
import React from 'react';
import { __, sprintf } from '@wordpress/i18n';
import { COUNTRIES } from './countries.js';
import { DIAL_CODES, flagOf } from './dialCodes.js';
import { applyFilters } from '../extend';

/** Server data the preview needs (currency, keys, upload limit); set by class-formglut-admin.php. */
export const previewData = () => (typeof window !== 'undefined' && window.formglut_admin && window.formglut_admin.preview) || {};

const cls = (...parts) => parts.filter(Boolean).join(' ');
const words = (v) => String(v || '').split(' ').map((c) => c.trim()).filter(Boolean).join(' ');
const isNum = (v) => v !== '' && v !== null && v !== undefined && !Number.isNaN(Number(v));
const HEX = /^#([0-9a-f]{3}){1,2}$/i;
const hex = (v) => (typeof v === 'string' && HEX.test(v) ? v : '');

/* ── Styles: same rules as build_field_styles() ─────────────────────── */

export function fieldStyles(f) {
  const vars = {};
  const px = (k) => (isNum(f[k]) ? Math.abs(parseInt(f[k], 10)) : null);
  if (hex(f.bg_color)) vars['--fg-bg'] = f.bg_color;
  if (hex(f.border_color)) vars['--fg-border'] = f.border_color;
  if (hex(f.text_color)) vars['--fg-color'] = f.text_color;
  if (px('border_radius') !== null) vars['--fg-radius'] = px('border_radius') + 'px';
  const pad = ['padding_top', 'padding_right', 'padding_bottom', 'padding_left'].map(px);
  if (pad.some((v) => v !== null)) vars['--fg-pad'] = `${pad[0] ?? 10}px ${pad[1] ?? 14}px ${pad[2] ?? 10}px ${pad[3] ?? 14}px`;
  const mar = ['margin_top', 'margin_right', 'margin_bottom', 'margin_left'].map((k) => px(k) || 0);
  if (mar.some(Boolean)) vars['--fg-margin'] = mar.join('px ') + 'px';

  let placement = f.label_placement && f.label_placement !== 'default' ? f.label_placement : 'top';
  if (!['top', 'bottom', 'left', 'right', 'hidden'].includes(placement)) placement = 'top';

  const label = {};
  if (placement === 'left' || placement === 'right') {
    const lw = f.label_width || 'auto';
    if (lw === 'custom' && f.label_width_custom) label.width = Math.abs(parseInt(f.label_width_custom, 10)) + 'px';
    else if (/^\d{1,4}(px|%)$/.test(lw) && lw !== '100%') label.width = lw;
  }

  const wrapper = {};
  const fw = String(f.field_width || '100%');
  if (fw === 'custom' && f.field_width_custom) wrapper.maxWidth = Math.abs(parseInt(f.field_width_custom, 10)) + 'px';
  else if (/^\d{1,4}(px|%)$/.test(fw) && fw !== '100%') wrapper.maxWidth = fw;

  return { input: Object.keys(vars).length ? vars : undefined, label: Object.keys(label).length ? label : undefined, wrapper: Object.keys(wrapper).length ? wrapper : undefined, placement };
}

/* ── Submit buttons ─────────────────────────────────────────────────── */

/** Default submit button: same rules as build_button_styles(). */
export function submitButtonStyles(btn = {}) {
  const st = {};
  if (hex(btn.bg_color) && btn.bg_color.toLowerCase() !== '#e94560') { st.backgroundColor = btn.bg_color; st.borderColor = btn.bg_color; }
  if (hex(btn.text_color)) st.color = btn.text_color;
  if (isNum(btn.border_radius)) st.borderRadius = Math.abs(parseInt(btn.border_radius, 10)) + 'px';
  if (isNum(btn.font_size)) st.fontSize = Math.abs(parseInt(btn.font_size, 10)) + 'px';
  if (btn.font_weight) st.fontWeight = Math.abs(parseInt(btn.font_weight, 10)) || undefined;
  if (isNum(btn.height)) st.height = Math.abs(parseInt(btn.height, 10)) + 'px';
  st.padding = { small: '6px 16px', medium: '8px 24px' }[btn.size] || '10px 32px';
  const align = btn.alignment || 'full';
  if (!['left', 'center', 'right'].includes(align)) st.width = '100%';
  return { button: st, actions: { textAlign: ['left', 'center', 'right'].includes(align) ? align : 'center' } };
}

/** Custom Submit Button field: same rules as submit_button_style(). */
export function customSubmitStyle(f) {
  const color = hex(f.button_bg_color) || 'var(--fg-accent)';
  const text = hex(f.button_text_color);
  const [padding, fontSize] = ({ small: ['8px 16px', 13], large: ['14px 32px', 17] })[f.button_size] || ['12px 24px', 15];
  const radius = { square: 0, rounded: 8, pill: 999 }[f.button_shape] ?? 8;
  const looks = {
    outline: { background: 'transparent', color: text || color, borderColor: color },
    secondary: { background: hex(f.button_bg_color) ? color : '#f1f5f9', color: text || '#334155', borderColor: hex(f.button_bg_color) ? color : '#e2e8f0' },
    primary: { background: color, color: text || '#fff', borderColor: color },
  };
  return { padding, fontSize, borderRadius: radius, borderWidth: 1, borderStyle: 'solid', fontWeight: 600, cursor: 'pointer', width: f.button_width === 'full' ? '100%' : 'auto', ...(looks[f.button_style] || looks.primary) };
}

/* ── Helpers shared with the PHP renderer ───────────────────────────── */

function selectionHint(f) {
  const min = Math.abs(parseInt(f.min_selections, 10)) || 0;
  const max = Math.abs(parseInt(f.max_selections, 10)) || 0;
  let text = '';
  if (min && max) text = sprintf( __( 'Select between %1$d and %2$d options', 'formglut' ), min, max );
  else if (min) text = sprintf( __( 'Select at least %d options', 'formglut' ), min );
  else if (max) text = sprintf( __( 'Select up to %d options', 'formglut' ), max );
  return text ? <div className="formglut-choice-hint">{text}</div> : null;
}

const countryText = (f, code) => {
  const name = COUNTRIES[code];
  let t = f.display_format === 'code' ? code : (f.display_format === 'both' ? `${name} (${code})` : name);
  if (f.flag_type === 'emoji') t = flagOf(code) + ' ' + t;
  return t;
};

function countryOptions(f) {
  let codes = Object.keys(COUNTRIES);
  const inc = Array.isArray(f.included_countries) ? f.included_countries : [];
  const exc = Array.isArray(f.excluded_countries) ? f.excluded_countries : [];
  if (f.country_list === 'include' && inc.length) codes = codes.filter((c) => inc.includes(c));
  else if (f.country_list === 'exclude' && exc.length) codes = codes.filter((c) => !exc.includes(c));
  const top = codes.filter((c) => (Array.isArray(f.top_countries) ? f.top_countries : []).includes(c));
  return (
    <>
      {top.map((c) => <option key={'t' + c} value={c}>{countryText(f, c)}</option>)}
      {top.length > 0 && <option disabled>──────────</option>}
      {codes.map((c) => <option key={c} value={c}>{countryText(f, c)}</option>)}
    </>
  );
}

export function phoneMask(f) {
  if (f.phone_format === 'custom') return f.custom_format || '';
  return { us: '(999) 999-9999', uk: '9999 999999' }[f.phone_format] || '';
}

function numberStep(f) {
  const step = f.step ?? '';
  const decimals = f.decimals === '' || f.decimals === undefined || f.decimals === null ? null : Math.abs(parseInt(f.decimals, 10));
  if (decimals !== null && !Number.isNaN(decimals) && (step === '' || (decimals > 0 && Number(step) === 1))) {
    return decimals === 0 ? '1' : '0.' + '0'.repeat(decimals - 1) + '1';
  }
  return step;
}

/** Same text as initCounters() in field-options.js shows for an empty or pre-filled box. */
function counter(f, value, maxChars, minChars) {
  const maxWords = Math.abs(parseInt(f.max_words, 10)) || 0;
  const show = f.show_counter === undefined || !!f.show_counter;
  if (!show || (!maxChars && !maxWords && !minChars)) return null;
  const v = String(value ?? '');
  const chars = v.length;
  const wordCount = v.trim() ? v.trim().split(/\s+/u).length : 0;
  let text;
  if (maxWords) text = sprintf( __( '%1$d / %2$d words', 'formglut' ), wordCount, maxWords );
  else if (maxChars) text = sprintf( __( '%1$d / %2$d characters', 'formglut' ), chars, maxChars );
  else text = sprintf( __( '%d characters', 'formglut' ), chars );
  if (minChars && chars > 0 && chars < minChars) text += ' · ' + sprintf( __( 'at least %d', 'formglut' ), minChars );
  return <div className={cls('formglut-counter', ((maxWords && wordCount > maxWords) || (maxChars && chars >= maxChars)) && 'is-over')}>{text}</div>;
}

function Affixes({ f, children }) {
  const prefix = f.prefix_label || '';
  const suffix = f.suffix_label || '';
  if (!prefix && !suffix) return children;
  return (
    <div className="formglut-input-group">
      {prefix && <span className="formglut-input-prefix" dangerouslySetInnerHTML={{ __html: prefix }} />}
      {children}
      {suffix && <span className="formglut-input-suffix" dangerouslySetInnerHTML={{ __html: suffix }} />}
    </div>
  );
}

const HelpTip = ({ text }) => (
  <span className="formglut-help-tip">
    <span className="formglut-help-icon">
      <svg width="14" height="14" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="10" cy="10" r="9" stroke="currentColor" strokeWidth="1.5" /><path d="M10 9v5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /><circle cx="10" cy="6.5" r="0.75" fill="currentColor" /></svg>
    </span>
    <span className="formglut-help-tooltip">{text}</span>
  </span>
);

/* ── Controls: same as standard_control() ───────────────────────────── */

function Control({ f, type, c }) {
  const { id, ph, value, defaults, elClass, s } = c;
  const options = Array.isArray(f.options) ? f.options : [];
  const optValue = (o) => (o && o.value !== undefined && o.value !== '' ? String(o.value) : String((o && o.label) || ''));

  // Input of an add-on standard type (formglut.fieldPreview.control): the label, help text and
  // styles around it come from StandardField, like formglut_field_control in PHP.
  if (!STANDARD_TYPES.includes(type)) {
    const control = applyFilters('formglut.fieldPreview.control', null, f, type, c);
    if (control) return control;
  }

  switch (type) {
    case 'textarea': {
      const max = Math.abs(parseInt(f.max_length, 10)) || Math.abs(parseInt(f.character_limit, 10)) || 0;
      const min = Math.abs(parseInt(f.min_length, 10)) || 0;
      const resize = ['vertical', 'horizontal', 'both', 'none'].includes(f.resize) ? f.resize : 'vertical';
      return (
        <>
          <Affixes f={f}>
            <textarea id={id} placeholder={ph} className={elClass} rows={Math.abs(parseInt(f.rows, 10)) || 4} cols={Math.abs(parseInt(f.cols, 10)) || undefined}
              dir={f.enable_rtl ? 'rtl' : undefined} readOnly tabIndex={-1} value={Array.isArray(value) ? '' : String(value ?? '')} style={{ ...(s.input || {}), resize }} />
          </Affixes>
          {counter(f, value, max, min)}
        </>
      );
    }

    case 'select': {
      const select = (
        <select id={id} className={cls(elClass, 'formglut-select')} value={defaults[0] ?? ''} onChange={() => {}} tabIndex={-1} style={s.input}>
          {ph !== '' && <option value="" disabled={f.disable_first_option !== false}>{ph}</option>}
          {options.map((o, i) => <option key={i} value={optValue(o)} disabled={!!o.disabled}>{o.label}</option>)}
        </select>
      );
      return f.searchable ? <div className="formglut-search-select"><input type="search" className="formglut-search-input" placeholder={__( 'Type to search…', 'formglut' )} readOnly tabIndex={-1} />{select}</div> : select;
    }

    case 'multiselect':
      return (
        <div className="formglut-multiselect-wrapper">
          <select id={id} className={cls(elClass, 'formglut-select')} multiple size={Math.min(Math.max(options.length, 2), 6)} value={defaults} onChange={() => {}} tabIndex={-1} style={s.input}>
            {options.map((o, i) => <option key={i} value={optValue(o)} disabled={!!o.disabled}>{o.label}</option>)}
          </select>
          {f.select_all_button && <button type="button" className="formglut-select-all-btn" tabIndex={-1}>{__( 'Select All', 'formglut' )}</button>}
          {selectionHint(f)}
        </div>
      );

    case 'radio':
    case 'checkbox': {
      const isRadio = type === 'radio';
      const layout = f.layout || (f.inline ? 'inline' : 'default');
      return (
        <>
          <div className={cls('formglut-choice-group', 'formglut-choice-layout-' + layout, words(f.element_class))}>
            {options.map((o, i) => (
              <label key={i} className="formglut-choice">
                <input type={isRadio ? 'radio' : 'checkbox'} checked={defaults.includes(optValue(o))} disabled={!!o.disabled} onChange={() => {}} tabIndex={-1} />
                <span>{o.label}</span>
              </label>
            ))}
            {f.enable_other && (
              <label className="formglut-choice formglut-choice-other">
                <input type={isRadio ? 'radio' : 'checkbox'} checked={false} onChange={() => {}} tabIndex={-1} />
                <span>{f.other_label || __( 'Other', 'formglut' )}</span>
                <input type="text" className="formglut-other-input" placeholder={f.other_placeholder || __( 'Please specify', 'formglut' )} readOnly tabIndex={-1} />
              </label>
            )}
          </div>
          {!isRadio && selectionHint(f)}
        </>
      );
    }

    case 'name':
    case 'date_range':
    case 'address':
      return <MultiPart f={f} type={type} c={c} />;

    case 'password': {
      const toggleText = f.show_text || __( 'Show', 'formglut' );
      const toggle = f.show_toggle === undefined || f.show_toggle ? <button type="button" className="formglut-password-toggle" tabIndex={-1}>{toggleText}</button> : null;
      return (
        <>
          <div className="formglut-password-wrap"><input type="password" id={id} placeholder={ph} className={elClass} readOnly tabIndex={-1} style={s.input} />{toggle}</div>
          {f.enable_strength_meter && <div className="formglut-strength"><div className="formglut-strength-bar"><span /></div><span className="formglut-strength-label">{__( 'Password strength', 'formglut' )}</span></div>}
          {f.requirements_hint && <div className="formglut-choice-hint">{f.requirements_hint}</div>}
          {f.require_confirmation && (
            <div className="formglut-confirm-field">
              <label className="formglut-sublabel">{f.confirmation_label || __( 'Confirm Password', 'formglut' )}</label>
              <div className="formglut-password-wrap"><input type="password" placeholder={f.confirmation_placeholder || ''} className={elClass} readOnly tabIndex={-1} style={s.input} />{toggle}</div>
            </div>
          )}
        </>
      );
    }

    case 'payment_item': {
      const cur = previewData().currency || { symbol: '$', decimals: 2 };
      if ((f.item_type || 'fixed') === 'custom') {
        const min = Number(f.min_amount) || 0;
        return (
          <div className="formglut-input-group">
            <span className="formglut-input-prefix">{cur.symbol}</span>
            <input type="number" className={cls(elClass, 'formglut-pay-item')} placeholder={ph !== '' ? ph : Math.max(min, 0).toFixed(cur.decimals)} readOnly tabIndex={-1} style={s.input} />
          </div>
        );
      }
      const price = Math.max(0, Number(f.amount) || 0);
      return <div className="formglut-pay-price">{cur.symbol + price.toLocaleString(undefined, { minimumFractionDigits: cur.decimals, maximumFractionDigits: cur.decimals })}</div>;
    }

    case 'stripe_card': {
      const p = previewData();
      const cur = p.currency || { symbol: '$', decimals: 2 };
      if (!p.stripe_ready) return <div className="formglut-captcha-notice">{__( 'Add your Stripe keys in FormGlut → Global Settings → Payments to accept payments.', 'formglut' )}</div>;
      return (
        <div className="formglut-stripe">
          {(f.show_total === undefined || f.show_total) && <div className="formglut-stripe-total">{__( 'Total', 'formglut' )} <strong className="formglut-stripe-amount">{cur.symbol + (0).toFixed(cur.decimals)}</strong></div>}
          <div className="formglut-stripe-element fg-stripe-mock"><span>{__( 'Card number', 'formglut' )}</span><span>MM / YY · CVC</span></div>
          {p.stripe_mode === 'test' && <div className="formglut-stripe-test">{__( 'Test mode: use card 4242 4242 4242 4242, any future date and any CVC.', 'formglut' )}</div>}
        </div>
      );
    }

    case 'calculation': {
      const decimals = isNum(f.decimals) ? Math.abs(parseInt(f.decimals, 10)) : 2;
      return <input type="text" readOnly tabIndex={-1} className={cls(elClass, 'formglut-calc')} value={(f.calc_prefix || '') + (0).toFixed(decimals) + (f.calc_suffix || '')} title={f.formula || ''} style={s.input} />;
    }

    case 'toggle':
      return (
        <label className="formglut-toggle">
          <input type="checkbox" className={cls(elClass, 'formglut-toggle-input')} checked={!!f.default_on} onChange={() => {}} tabIndex={-1} />
          <span className="formglut-toggle-track" aria-hidden="true" /><span className="formglut-toggle-text">{f.toggle_text || ''}</span>
        </label>
      );

    case 'star_rating': {
      const max = Math.max(3, Math.min(10, Math.abs(parseInt(f.max_stars ?? 5, 10)) || 5));
      const size = ['small', 'medium', 'large'].includes(f.star_size) ? f.star_size : 'medium';
      const color = hex(f.star_color);
      const stars = [];
      for (let i = max; i >= 1; i--) {
        stars.push(<input key={'i' + i} type="radio" className="formglut-star-input" checked={String(i) === String(value)} onChange={() => {}} tabIndex={-1} />);
        stars.push(<label key={'l' + i}><span className="screen-reader-text">{sprintf( __( '%1$d of %2$d stars', 'formglut' ), i, max )}</span>★</label>);
      }
      return (
        <>
          <div className={'formglut-stars formglut-stars-' + size} role="radiogroup" style={color ? { '--formglut-star': color } : undefined}>{stars}</div>
          {f.show_labels && <div className="formglut-star-word" aria-live="polite" />}
        </>
      );
    }

    case 'rich_text': {
      const tools = f.toolbar === 'full' ? ['B', 'I', 'U', '•', '1.', '🔗', '❝'] : ['B', 'I', '•', '1.'];
      const height = Math.max(80, Math.abs(parseInt(f.editor_height ?? 160, 10)) || 160);
      const content = Array.isArray(value) ? '' : String(value ?? '');
      return (
        <div className={cls('formglut-richtext', !content && 'is-empty')} style={s.input}>
          <div className="formglut-richtext-bar" role="toolbar">{tools.map((t) => <button key={t} type="button" tabIndex={-1}>{t}</button>)}</div>
          <div className="formglut-richtext-area" data-placeholder={ph} style={{ minHeight: (height - 40) + 'px' }} dangerouslySetInnerHTML={{ __html: content }} />
          <textarea className={cls(elClass, 'formglut-richtext-value')} value={content} readOnly hidden />
        </div>
      );
    }

    case 'math_captcha': {
      const q = { subtract: '9 − 4', multiply: '3 × 4' }[f.operation] || '3 + 4';
      return <div className="formglut-math"><span className="formglut-math-q">{q} =</span><input type="text" className={elClass} readOnly tabIndex={-1} style={s.input} /></div>;
    }

    case 'file_upload': {
      const IMAGE = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
      const BLOCKED = ['php', 'php3', 'php4', 'php5', 'php7', 'phtml', 'phar', 'exe', 'js', 'sh', 'bat', 'cgi', 'pl', 'py', 'htaccess', 'svg', 'html', 'htm'];
      let types = f.images_only ? IMAGE : [...new Set(String(f.allowed_types || '').split(',').map((t) => t.trim().replace(/^\./, '').trim().toLowerCase()).filter(Boolean))].filter((t) => !BLOCKED.includes(t));
      if (!types.length) types = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'doc', 'docx', 'txt'];
      const serverMb = Number(previewData().upload_max_mb) || Infinity;
      const maxMb = Math.round(Math.min(Number(f.max_size) > 0 ? Number(f.max_size) : 5, serverMb) * 100) / 100;
      const multi = !!f.multiple;
      const maxFiles = multi ? Math.max(1, Math.abs(parseInt(f.max_files ?? 3, 10)) || 1) : 1;
      const hint = types.join(', ').toUpperCase() + ' · ' + sprintf( __( 'max %s MB', 'formglut' ), maxMb ) + (multi ? ' · ' + sprintf( __( 'up to %d files', 'formglut' ), maxFiles ) : '');
      return (
        <div className="formglut-upload" style={s.input}>
          <input type="file" className={cls(elClass, 'formglut-file-input')} tabIndex={-1} />
          <label className="formglut-upload-btn">{f.button_text || __( 'Choose file', 'formglut' )}</label>
          <span className="formglut-upload-hint"><span className="formglut-upload-names">{multi ? __( 'or drop files here', 'formglut' ) : __( 'or drop a file here', 'formglut' )}</span><small>{hint}</small></span>
        </div>
      );
    }

    case 'range_slider': {
      const min = isNum(f.min) ? Number(f.min) : 0;
      const max = isNum(f.max) ? Number(f.max) : 100;
      const step = isNum(f.step) && Number(f.step) > 0 ? Number(f.step) : 1;
      const val = isNum(value) ? Number(value) : min;
      const color = hex(f.track_color);
      return (
        <div className="formglut-range" style={color ? { '--formglut-range-color': color } : undefined}>
          {(f.show_value === undefined || f.show_value) && <div className="formglut-range-value">{(f.value_prefix || '') + val + (f.value_suffix || '')}</div>}
          <input type="range" min={min} max={max} step={step} value={val} onChange={() => {}} tabIndex={-1} className={f.element_class || undefined} />
          <div className="formglut-range-ends"><span>{f.min_label !== undefined && f.min_label !== '' ? f.min_label : min}</span><span>{f.max_label !== undefined && f.max_label !== '' ? f.max_label : max}</span></div>
        </div>
      );
    }

    case 'color_picker': {
      const picker = f.picker_type || 'swatches';
      const def = hex(f.default_color);
      const swatches = picker === 'picker' ? [] : (Array.isArray(f.swatches) ? f.swatches : []).filter(hex);
      const size = ['small', 'medium', 'large'].includes(f.swatch_size) ? f.swatch_size : 'medium';
      return (
        <div className={cls('formglut-color-picker', 'formglut-swatch-' + size, f.element_class)}>
          {swatches.map((col, i) => <button key={i} type="button" className={cls('formglut-swatch', col.toLowerCase() === def.toLowerCase() && 'selected')} style={{ background: col }} tabIndex={-1} />)}
          {picker !== 'swatches' && <input type="color" className="formglut-color-input" value={def || '#000000'} onChange={() => {}} tabIndex={-1} />}
        </div>
      );
    }

    case 'signature': {
      const height = Math.max(80, Math.min(600, parseInt(f.pad_height, 10) || 180));
      return (
        <div className={cls('formglut-signature', f.element_class)}>
          <div className="formglut-signature-pad" style={{ height, background: hex(f.pad_background) || '#ffffff', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#94a3b8', fontSize: 13 }}>{__( 'Visitors sign here', 'formglut' )}</div>
          <div className="formglut-signature-tools"><button type="button" className="formglut-signature-clear" tabIndex={-1}>{f.clear_text || __( 'Clear', 'formglut' )}</button></div>
        </div>
      );
    }

    case 'country_select': {
      const select = (
        <select id={id} className={cls(elClass, 'formglut-select')} value={typeof value === 'string' ? value : ''} onChange={() => {}} tabIndex={-1} style={s.input}>
          <option value="">{ph !== '' ? ph : __( 'Select a country', 'formglut' )}</option>
          {countryOptions(f)}
        </select>
      );
      return f.searchable ? <div className="formglut-search-select"><input type="search" className="formglut-search-input" placeholder={__( 'Type to search…', 'formglut' )} readOnly tabIndex={-1} />{select}</div> : select;
    }

    case 'spinner': {
      const pos = ['both', 'left', 'right'].includes(f.button_position) ? f.button_position : 'both';
      const btns = f.show_buttons === undefined || !!f.show_buttons;
      return (
        <div className={'formglut-spinner formglut-spinner-' + pos}>
          {btns && <button type="button" className="formglut-spin-btn formglut-spin-dec" tabIndex={-1}>{f.decrement_label || '-'}</button>}
          <input type="number" id={id} placeholder={ph} value={Array.isArray(value) ? '' : String(value ?? '')} readOnly tabIndex={-1} className={elClass} style={s.input} />
          {btns && <button type="button" className="formglut-spin-btn formglut-spin-inc" tabIndex={-1}>{f.increment_label || '+'}</button>}
        </div>
      );
    }

    case 'currency':
    case 'percentage': {
      const isCurrency = type === 'currency';
      const symbol = isCurrency ? (f.currency_symbol ?? '$') : '%';
      const before = (f.symbol_position || (isCurrency ? 'before' : 'after')) === 'before';
      const input = <input type="number" id={id} placeholder={ph} value={Array.isArray(value) ? '' : String(value ?? '')} readOnly tabIndex={-1} className={elClass} style={s.input} />;
      return <Affixes f={{ [before ? 'prefix_label' : 'suffix_label']: symbol }}>{input}</Affixes>;
    }

    case 'time':
      return ['12', '24'].includes(String(f.time_format || ''))
        ? <input type="text" id={id} value={String(value ?? '')} readOnly tabIndex={-1} className={cls(elClass, 'flatpickr-input')} style={s.input} />
        : <input type="time" id={id} value={String(value ?? '')} readOnly tabIndex={-1} className={elClass} style={s.input} />;

    case 'masked_input': {
      const mask = f.custom_mask || '';
      const mph = ph !== '' ? ph : mask.replace(/9/g, '#').replace(/[a*]/g, '?');
      return (
        <>
          <Affixes f={f}><input type="text" id={id} placeholder={mph} value={Array.isArray(value) ? '' : String(value ?? '')} readOnly tabIndex={-1} className={elClass} style={s.input} /></Affixes>
          {f.mask_hint && <div className="formglut-choice-hint">{f.mask_hint}</div>}
        </>
      );
    }

    default: { // text, email, number, url, phone, date
      let inputType = { email: 'email', number: 'number', url: 'url', phone: 'tel', date: f.date_type === 'datetime' ? 'datetime-local' : 'date' }[type] || 'text';
      if (type === 'url' && (f.allow_relative || f.validate_url === false)) inputType = 'text';
      let placeholder = ph;
      if (type === 'date') {
        if (f.use_picker) inputType = 'text';
        else placeholder = '';
      }
      if (type === 'phone') {
        const mask = phoneMask(f);
        if (mask && placeholder === '') placeholder = mask.replace(/9/g, '#');
      }
      const numAttrs = type === 'number' ? { min: isNum(f.min_value) ? f.min_value : undefined, max: isNum(f.max_value) ? f.max_value : undefined, step: isNum(numberStep(f)) ? numberStep(f) : undefined } : {};
      const val = Array.isArray(value) ? '' : String(value ?? '');
      let control = <Affixes f={f}><input type={inputType} id={id} placeholder={placeholder || undefined} value={val} readOnly tabIndex={-1} className={cls(elClass, type === 'date' && f.use_picker && 'flatpickr-input')} style={s.input} {...numAttrs} /></Affixes>;
      if (type === 'phone' && f.show_country_code) {
        const iso = DIAL_CODES[f.default_country] ? f.default_country : 'US';
        control = (
          <div className="formglut-phone-cc">
            <Affixes f={f}>
              <select className="formglut-dial-code" value={iso} onChange={() => {}} tabIndex={-1}><option value={iso}>{`${flagOf(iso)} ${COUNTRIES[iso] || iso} +${DIAL_CODES[iso]}`}</option></select>
              <input type={inputType} id={id} placeholder={placeholder || undefined} value={val} readOnly tabIndex={-1} className={elClass} style={s.input} />
            </Affixes>
          </div>
        );
      }
      const charLimit = type === 'text' ? Math.abs(parseInt(f.character_limit, 10)) || 0 : 0;
      return (
        <>
          {control}
          {type === 'text' && counter(f, val, charLimit, 0)}
          {type === 'email' && f.confirm_email && (
            <div className="formglut-confirm-field">
              <label className="formglut-sublabel">{f.confirm_label || __( 'Confirm Email Address', 'formglut' )}</label>
              <Affixes f={f}><input type="email" placeholder={f.confirm_placeholder || undefined} readOnly tabIndex={-1} className={elClass} style={s.input} /></Affixes>
            </div>
          )}
        </>
      );
    }
  }
}

/** Name, date range and address: same as multipart_parts() / multipart_control(). */
function MultiPart({ f, type, c }) {
  const get = (k, fallback = '') => (f[k] !== undefined && f[k] !== null && f[k] !== '' ? String(f[k]) : fallback);
  const shown = (k, def) => (f[k] !== undefined ? !!f[k] : def);
  let parts;
  if (type === 'name') {
    parts = [['prefix', get('prefix_label', __( 'Title', 'formglut' )), '', shown('show_prefix', false)]];
    [['first', true], ['middle', false], ['last', true]].forEach(([p, def]) => parts.push([p, get(p + '_name_label'), get(p + '_name_placeholder'), shown('show_' + p + '_name', def)]));
    parts.push(['suffix', get('suffix_label', __( 'Suffix', 'formglut' )), 'Jr, Sr, III', shown('show_suffix', false)]);
  } else if (type === 'date_range') {
    parts = [['start', get('start_label', __( 'Start Date', 'formglut' )), '', true], ['end', get('end_label', __( 'End Date', 'formglut' )), '', true]];
  } else {
    parts = [
      ['street1', get('street1_label'), get('street1_placeholder'), true],
      ['street2', get('street2_label'), get('street2_placeholder'), shown('include_street2', true)],
      ['city', get('city_label'), get('city_placeholder'), shown('include_city', true)],
      ['state', get('state_label'), get('state_placeholder'), shown('include_state', true)],
      ['zip', get('zip_label'), get('zip_placeholder'), shown('include_zip', true)],
      ['country', get('country_label', __( 'Country', 'formglut' )), '', shown('include_country', false)],
    ];
  }
  parts = parts.filter((p) => p[3]);
  let cols = 2;
  if (type === 'name') cols = f.name_layout === 'vertical' ? 1 : parts.length;
  else if (type === 'address') cols = f.address_layout === 'vertical' ? 1 : Math.max(1, Math.min(3, Math.abs(parseInt(f.grid_columns ?? 2, 10)) || 2));

  const titles = String(f.prefix_options || 'Mr, Mrs, Ms, Mx, Dr').split(',').map((t) => t.trim()).filter(Boolean);
  return (
    <div className={'formglut-subfields formglut-subfields-' + Math.max(1, cols)}>
      {parts.map(([key, label, ph]) => {
        let input;
        if (type === 'name' && key === 'prefix') {
          input = <select className={cls(c.elClass, 'formglut-select')} value="" onChange={() => {}} tabIndex={-1} style={c.s.input}><option value="">—</option>{titles.map((t) => <option key={t} value={t}>{t}</option>)}</select>;
        } else if (key === 'country') {
          input = <select className={cls(c.elClass, 'formglut-select')} value="" onChange={() => {}} tabIndex={-1} style={c.s.input}><option value="">{__( 'Select a country', 'formglut' )}</option>{countryOptions({})}</select>;
        } else {
          input = <input type={type === 'date_range' ? 'date' : 'text'} placeholder={ph || undefined} className={c.elClass} readOnly tabIndex={-1} style={c.s.input} />;
        }
        return (
          <div key={key} className={cls('formglut-subfield', 'formglut-subfield-' + key, (key === 'street1' || key === 'street2') && 'formglut-subfield-full')}>
            {label !== '' && <label className="formglut-sublabel">{label}</label>}
            {input}
          </div>
        );
      })}
    </div>
  );
}

/* ── Field wrappers ─────────────────────────────────────────────────── */

/** Types drawn with the shared input template; add-ons add theirs with formglut.standardFieldTypes. */
const standardTypes = () => applyFilters('formglut.standardFieldTypes', STANDARD_TYPES);
const STANDARD_TYPES = ['text', 'email', 'textarea', 'select', 'multiselect', 'number', 'radio', 'checkbox', 'url', 'phone', 'date', 'name', 'country_select', 'spinner', 'currency', 'percentage', 'time', 'date_range', 'address', 'masked_input', 'password', 'range_slider', 'color_picker', 'file_upload', 'toggle', 'star_rating', 'rich_text', 'math_captcha', 'calculation', 'payment_item', 'stripe_card', 'signature'];

/** Same as render_standard_field(). */
function StandardField({ f, type, extra }) {
  const s = fieldStyles(f);
  const isChoice = type === 'radio' || type === 'checkbox';
  const value = f.default_value ?? '';
  const defaults = (Array.isArray(value) ? value : (value === '' ? [] : [value])).map(String);
  const c = { id: f.id, ph: f.placeholder ?? '', value, defaults, elClass: cls('formglut-input', words(f.element_class)), s };

  const help = f.help_text || '';
  const helpPos = f.help_text_position || 'below';
  const helpLine = help && helpPos !== 'tooltip' ? <div className="formglut-help-text">{help}</div> : null;
  const Tag = isChoice ? 'div' : 'label';
  const label = f.label && s.placement !== 'hidden'
    ? <Tag className="formglut-label" style={s.label}><span className="formglut-label-text">{f.label}</span>{f.required && <> <span className="formglut-required">*</span></>}{help && helpPos === 'tooltip' && <HelpTip text={help} />}</Tag>
    : null;

  return (
    <div className={cls('formglut-field', 'formglut-field-' + type, f.hidden && 'formglut-hidden', type === 'calculation' && f.hide_on_form && 'formglut-calc-hidden', extra, 'formglut-label-' + s.placement)} style={s.wrapper}>
      {['top', 'left'].includes(s.placement) && label}
      <div className="formglut-field-body">
        {helpPos === 'above' && helpLine}
        <Control f={f} type={type} c={c} />
        {helpPos !== 'above' && helpLine}
      </div>
      {['bottom', 'right'].includes(s.placement) && label}
    </div>
  );
}

/** Same as render_consent_field(). */
function ConsentField({ f, type, extra }) {
  const isGdpr = type === 'gdpr_agreement';
  const mode = ['box', 'modal', 'link', 'none'].includes(f.display_type) ? f.display_type : 'box';
  let link = null;
  if (isGdpr && f.policy_url) link = <> <a href={f.policy_url}>{__( 'Privacy Policy', 'formglut' )}</a></>;
  else if (!isGdpr && mode === 'modal') link = <> <a href="#">{f.link_text || __( 'View Terms', 'formglut' )}</a></>;
  else if (!isGdpr && mode === 'link' && f.link_url) link = <> <a href={f.link_url}>{f.link_text || __( 'View Terms', 'formglut' )}</a></>;
  const content = !isGdpr && f.terms_content ? f.terms_content : '';
  const right = !isGdpr && f.checkbox_position === 'right';
  return (
    <div className={cls('formglut-field', 'formglut-field-' + type, f.hidden && 'formglut-hidden', extra)}>
      {mode === 'box' && content && <div className="formglut-terms-box" style={{ maxHeight: (Math.abs(parseInt(f.scroll_height, 10)) || 200) + 'px' }} dangerouslySetInnerHTML={{ __html: content }} />}
      {isGdpr && f.policy_text && <p className="formglut-choice-hint">{f.policy_text}</p>}
      <label className={cls('formglut-consent', right && 'formglut-consent-right')}>
        <input type="checkbox" className={cls('formglut-consent-input', words(f.element_class))} checked={isGdpr && !!f.default_checked} onChange={() => {}} tabIndex={-1} />
        <span>{f.label || ''}{link}{f.required && <> <span className="formglut-required">*</span></>}</span>
      </label>
      {isGdpr && (f.show_storage_info === undefined || f.show_storage_info) && f.storage_duration_text && <p className="formglut-choice-hint">{String(f.storage_duration_text).replace('{days}', String(Math.abs(parseInt(f.storage_days ?? 365, 10)) || 0))}</p>}
      {isGdpr && f.show_withdraw_link && f.withdraw_text && <p className="formglut-choice-hint">{f.withdraw_text}{f.withdraw_email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(f.withdraw_email) && <> <a href={'mailto:' + f.withdraw_email}>{f.withdraw_email}</a></>}</p>}
      {f.help_text && <div className="formglut-help-text">{f.help_text}</div>}
    </div>
  );
}

/** Editor-only box for fields that have no visible output on the live form. */
const NoteBox = ({ icon, children, note }) => (
  <div className="fg-placeholder-box">{icon}{children}<span className="fg-placeholder-note">{note}</span></div>
);

/**
 * Render one field as the live form does.
 * @param {object} props.field   Field config.
 * @param {object} [props.icons] Editor icons for placeholder boxes: { hidden, hash, code }.
 */
export default function FieldPreview({ field: f, icons = {} }) {
  const type = f.type || 'text';
  // Add-ons draw their whole field preview with formglut.fieldPreview (return an element, or null).
  const custom = applyFilters('formglut.fieldPreview', null, f, { icons });
  if (custom) return custom;
  const extra = words([f.container_class, f.css_class].filter(Boolean).join(' '));
  const el = words(f.element_class);

  switch (type) {
    case 'unique_id': {
      const sample = f.id_type === 'random' ? 'K7Q2M9XA' : f.id_type === 'date' ? '20260930-001' : String(Math.max(1, Number(f.start_number) || 1)).padStart(Math.max(1, Math.min(12, Number(f.number_length) || 1)), '0');
      return <NoteBox icon={icons.hash} note={__( 'Created on submit, not shown on the form', 'formglut' )}> <strong>{f.label || __( 'Unique ID', 'formglut' )}</strong><code>{(f.id_prefix || '') + sample + (f.id_suffix || '')}</code></NoteBox>;
    }
    case 'hidden':
      return <NoteBox icon={icons.hidden} note={__( 'Not visible on the form', 'formglut' )}> <strong>{f.label || __( 'Hidden Field', 'formglut' )}</strong><span className="fg-placeholder-meta">{f.prefill_source ? `${f.prefill_source}: ${f.prefill_key || ''}` : (f.default_value ? `"${f.default_value}"` : __( '(empty)', 'formglut' ))}</span></NoteBox>;
    case 'form_step':
      return (
        <div className="fg-step-break">
          <div className="fg-step-break-nav"><span className="fg-step-btn ghost">← {f.prev_text || __( 'Previous', 'formglut' )}</span><span className="fg-step-btn">{f.next_text || __( 'Next', 'formglut' )} →</span></div>
          <div className="fg-step-break-line"><span>{__( 'Step break', 'formglut' )}{f.step_title ? ' · ' + f.step_title : ''}</span></div>
        </div>
      );
    case 'shortcode':
    case 'action_hook':
      return <NoteBox icon={icons.code} note={type === 'shortcode' && f.run_shortcode === false ? __( 'Shortcode disabled', 'formglut' ) : __( 'Output appears on the live form and in Preview', 'formglut' )}> <code>{type === 'shortcode' ? f.shortcode_content : `do_action( '${f.hook_name || ''}' )`}</code></NoteBox>;
    case 'recaptcha':
    case 'hcaptcha':
    case 'turnstile':
      return <CaptchaPreview f={f} type={type} extra={extra} />;
    case 'reset_button': {
      const align = ['left', 'center', 'right'].includes(f.button_alignment) ? f.button_alignment : 'left';
      return <div className={cls('formglut-field', 'formglut-field-reset_button', f.hidden && 'formglut-hidden', extra)} style={{ textAlign: align }}><button type="reset" className={cls('formglut-reset-btn', el)} tabIndex={-1}>{f.button_text || __( 'Clear form', 'formglut' )}</button></div>;
    }
    case 'custom_submit_button': {
      const align = ['left', 'center', 'right'].includes(f.button_alignment) ? f.button_alignment : 'left';
      return (
        <div className={cls('formglut-field', 'formglut-field-custom_submit_button', 'formglut-form-actions', f.hidden && 'formglut-hidden', extra)} style={{ textAlign: align }}>
          <button type="button" className={cls('formglut-submit-btn', 'formglut-custom-submit', f.element_class)} style={customSubmitStyle(f)} tabIndex={-1}><span className="formglut-btn-text">{f.button_text || __( 'Submit', 'formglut' )}</span><span className="formglut-btn-spinner" style={{ display: 'none' }}>&nbsp;&hellip;</span></button>
        </div>
      );
    }
    case 'terms_conditions':
    case 'gdpr_agreement':
      return <ConsentField f={f} type={type} extra={extra} />;
    case 'section_break': {
      const align = ['left', 'center', 'right'].includes(f.alignment) ? f.alignment : 'left';
      const bg = hex(f.background_color);
      const tc = hex(f.text_color);
      const tstyle = tc ? { color: tc } : undefined;
      const lineStyle = ['solid', 'dashed', 'dotted'].includes(f.divider_style) ? f.divider_style : 'solid';
      return (
        <div className={cls('formglut-field', 'formglut-field-section_break', f.hidden && 'formglut-hidden', extra, f.collapsible && f.default_collapsed && 'formglut-section-collapsed')} style={{ textAlign: align, ...(bg ? { background: bg, padding: '12px 16px', borderRadius: 8 } : {}) }}>
          <div className="formglut-section-head">
            <div className="formglut-section-text">
              {f.title && <h3 className={cls('formglut-section-title', el)} style={tstyle}>{f.title}</h3>}
              {f.description && <p className="formglut-section-desc" style={tstyle}>{f.description}</p>}
            </div>
            {f.collapsible && <button type="button" className="formglut-section-toggle" tabIndex={-1}>{f.default_collapsed ? (f.toggle_text_closed || __( 'Show', 'formglut' )) : (f.toggle_text_open || __( 'Hide', 'formglut' ))}</button>}
          </div>
          {f.show_divider && <hr className="formglut-section-divider" style={{ borderTopStyle: lineStyle, borderTopWidth: Math.max(1, Math.abs(parseInt(f.divider_thickness ?? 1, 10)) || 1) + 'px', ...(hex(f.divider_color) ? { borderTopColor: f.divider_color } : {}) }} />}
        </div>
      );
    }
    case 'html':
      return <div className={cls('formglut-field', 'formglut-field-html', 'formglut-html-content', f.hidden && 'formglut-hidden', extra, el)} dangerouslySetInnerHTML={{ __html: f.html_content || '' }} />;
    case 'heading': {
      const Tag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(f.heading_level) ? f.heading_level : 'h2';
      const align = ['left', 'center', 'right'].includes(f.alignment) ? f.alignment : 'left';
      const lineStyle = ['solid', 'dashed', 'dotted'].includes(f.divider_style) ? f.divider_style : 'solid';
      return (
        <div className={cls('formglut-field', 'formglut-field-heading', 'formglut-heading', f.hidden && 'formglut-hidden', extra)} style={{ textAlign: align }}>
          <Tag className={cls('formglut-heading-text', el)} style={hex(f.custom_color) ? { color: f.custom_color } : undefined}>{f.text !== undefined && f.text !== '' ? f.text : (f.label || '')}</Tag>
          {f.description && <p className="formglut-heading-desc">{f.description}</p>}
          {f.show_divider && <hr className="formglut-heading-divider" style={{ borderTopStyle: lineStyle, ...(hex(f.divider_color) ? { borderTopColor: f.divider_color } : {}) }} />}
        </div>
      );
    }
    default:
      return <StandardField f={f} type={standardTypes().includes(type) ? type : 'text'} extra={extra} />;
  }
}

/** Captchas: the live form shows the provider widget (or an admin notice when keys are missing). */
function CaptchaPreview({ f, type, extra }) {
  const names = { recaptcha: 'reCAPTCHA', hcaptcha: 'hCaptcha', turnstile: 'Cloudflare Turnstile' };
  const cfg = (previewData().captcha || {})[type] || {};
  if (!cfg.ready) {
    return <div className="formglut-field formglut-captcha-notice">{sprintf( __( '%s keys are not set, so this check is skipped. Add them in FormGlut → Settings. (Only admins see this.)', 'formglut' ), names[type] )}</div>;
  }
  const invisible = (type === 'recaptcha' && cfg.version !== 'v2') || (type === 'turnstile' && f.appearance === 'interaction-only');
  return (
    <div className={cls('formglut-field', 'formglut-field-' + type, 'formglut-captcha', invisible && 'formglut-captcha-invisible', extra)}>
      {invisible
        ? <div className="fg-captcha-box fg-captcha-invisible">{type === 'recaptcha' ? __( 'reCAPTCHA v3 runs invisibly when the form is submitted', 'formglut' ) : __( 'Turnstile is shown only when Cloudflare needs an interaction', 'formglut' )}</div>
        : (
          <div className={cls('fg-captcha-box', f.theme === 'dark' ? 'fg-captcha-dark' : 'fg-captcha-light', f.size === 'compact' && 'fg-captcha-compact', f.size === 'flexible' && 'fg-captcha-flexible')}>
            <span className="fg-captcha-check" /> {type === 'turnstile' ? __( 'Verify you are human', 'formglut' ) : __( "I'm not a robot", 'formglut' )}
            <span className="fg-captcha-brand">{names[type]}</span>
          </div>
        )}
    </div>
  );
}
