/**
 * Pure helpers shared by the Form preview (editor) and its tests. The PHP twin lives in
 * includes/Forms/Fields.php: keep both in sync (field ids, option parsing, step grouping).
 */
export type FieldType =
  | 'text'
  | 'email'
  | 'textarea'
  | 'tel'
  | 'number'
  | 'date'
  | 'time'
  | 'select'
  | 'radio'
  | 'checkbox'
  | 'acceptance'
  | 'upload'
  | 'password'
  | 'hidden'
  | 'html'
  | 'step';

export const FIELD_TYPES: FieldType[] = ['text', 'email', 'textarea', 'tel', 'number', 'date', 'time', 'select', 'radio', 'checkbox', 'acceptance', 'upload', 'password', 'hidden', 'html', 'step'];

export interface FieldOption {
  value: string;
  label: string;
}

export interface FormField {
  /** Stable id used in names (`fields[<id>]`) and DOM ids (`zx-<elementId>-<id>`). */
  id: string;
  type: FieldType;
  label: string;
  placeholder: string;
  required: boolean;
  width: '100' | '50' | '33' | '25';
  options: FieldOption[];
  default: string;
  min: string;
  max: string;
  step: string;
  accept: string;
  maxSize: number;
  multiple: boolean;
  rows: number;
  html: string;
  acceptanceText: string;
}

export interface FormStep {
  /** Index in the step list (0-based). */
  index: number;
  title: string;
  fields: FormField[];
}

const WIDTHS = ['100', '50', '33', '25'] as const;

/** Field ids: lower-case letters, digits, `_` and `-` (max 40 chars). */
export function sanitizeFieldId(raw: unknown): string {
  return String(raw ?? '')
    .toLowerCase()
    .replace(/[^a-z0-9_-]/g, '')
    .slice(0, 40);
}

/** Options textarea: one per line, `value|label` or just `label` (value = label). Empty lines are skipped. */
export function parseOptions(raw: unknown): FieldOption[] {
  const out: FieldOption[] = [];
  for (const line of String(raw ?? '').split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed) continue;
    const bar = trimmed.indexOf('|');
    if (bar >= 0) {
      const value = trimmed.slice(0, bar).trim();
      const label = trimmed.slice(bar + 1).trim();
      out.push({ value, label: label || value });
    } else {
      out.push({ value: trimmed, label: trimmed });
    }
  }
  return out;
}

function str(v: unknown): string {
  return v === undefined || v === null ? '' : String(v);
}

/** Normalises the `fields` repeater value into field definitions with unique ids. */
export function normalizeFields(raw: unknown): FormField[] {
  const list = Array.isArray(raw) ? raw : [];
  const used = new Set<string>();
  const out: FormField[] = [];
  list.forEach((item, i) => {
    if (!item || typeof item !== 'object') return;
    const it = item as Record<string, unknown>;
    const type = (FIELD_TYPES as string[]).includes(str(it.type)) ? (str(it.type) as FieldType) : 'text';
    let id = sanitizeFieldId(it.custom_id) || sanitizeFieldId(it._id) || `field_${i + 1}`;
    if (used.has(id)) {
      let n = 2;
      while (used.has(`${id}_${n}`)) n++;
      id = `${id}_${n}`;
    }
    used.add(id);
    const width = (WIDTHS as readonly string[]).includes(str(it.width)) ? (str(it.width) as FormField['width']) : '100';
    const maxSize = Number(it.max_size);
    const rows = Number(it.rows);
    out.push({
      id,
      type,
      label: str(it.label),
      placeholder: str(it.placeholder),
      required: Boolean(it.required) && type !== 'html' && type !== 'step' && type !== 'hidden',
      width,
      options: parseOptions(it.options),
      default: str(it.default),
      min: str(it.min),
      max: str(it.max),
      step: str(it.step),
      accept: str(it.accept),
      maxSize: Number.isFinite(maxSize) && maxSize > 0 ? maxSize : 5,
      multiple: Boolean(it.multiple),
      rows: Number.isFinite(rows) && rows > 0 ? Math.min(30, Math.round(rows)) : 4,
      html: str(it.html),
      acceptanceText: str(it.acceptance_text),
    });
  });
  return out;
}

/**
 * Groups fields into steps. Every `step` item starts a new step (its label is the step title); fields
 * placed before the first `step` item form an untitled first step. Without step items there is one step.
 */
export function groupSteps(fields: FormField[]): FormStep[] {
  const steps: FormStep[] = [];
  let current: FormStep | null = null;
  for (const f of fields) {
    if (f.type === 'step') {
      current = { index: steps.length, title: f.label, fields: [] };
      steps.push(current);
      continue;
    }
    if (!current) {
      current = { index: steps.length, title: '', fields: [] };
      steps.push(current);
    }
    current.fields.push(f);
  }
  if (steps.length === 0) steps.push({ index: 0, title: '', fields: [] });
  return steps;
}

/** `Step {current} of {total}` with the placeholders filled. */
export function stepText(template: string, current: number, total: number): string {
  return (template || 'Step {current} of {total}').replace('{current}', String(current)).replace('{total}', String(total));
}
