// Form designer page.
//
// Two-panel layout with a property editor on the left and a live
// @rjsf preview on the right. Converts a flat DesignerField[] model
// into JSON Schema + UI Schema on every keystroke so the preview
// always reflects the current state.

import { useCallback, useEffect, useMemo, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import Form from '@rjsf/core'
import type { RJSFSchema, UiSchema } from '@rjsf/utils'
import validator from '@rjsf/validator-ajv8'
import { metadata } from '@/api/client'
import type { FormDefinition, FormPurpose } from '@/types/api'
import { PageHeader } from '@/components/PageHeader'
import { ErrorBox } from '@/components/ErrorBox'
import { Loading } from '@/components/Loading'
import { vibeWidgets } from '@/components/form-widgets'
import { vibeTemplates } from '@/components/form-widgets/vibeErpTheme'

// ── Types ──────────────────────────────────────────────────────────

interface DesignerField {
  id: string
  key: string
  label: string
  type: 'string' | 'integer' | 'number' | 'boolean'
  required: boolean
  width: 1 | 2 | 3
  placeholder?: string
  helpText?: string
  widgetOverride?: string
  visibleWhen?: { field: string; equals: string }
  isSectionDivider?: boolean
  sectionTitle?: string
}

const FIELD_TYPES = ['string', 'integer', 'number', 'boolean'] as const
const WIDTH_OPTIONS = [1, 2, 3] as const
const PURPOSES: FormPurpose[] = ['create', 'edit', 'user-task', 'view']
const WIDGET_OPTIONS = [
  '',
  'partner-picker',
  'item-picker',
  'uom-selector',
  'location-picker',
  'money-input',
  'quantity-input',
  'textarea',
] as const

// ── Helpers ────────────────────────────────────────────────────────

function uid(): string {
  return crypto.randomUUID()
}

function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
}

function blankField(): DesignerField {
  return {
    id: uid(),
    key: '',
    label: '',
    type: 'string',
    required: false,
    width: 3,
  }
}

function blankSection(): DesignerField {
  return {
    id: uid(),
    key: '',
    label: '',
    type: 'string',
    required: false,
    width: 3,
    isSectionDivider: true,
    sectionTitle: '',
  }
}

// ── Schema builders ────────────────────────────────────────────────

function buildJsonSchema(fields: DesignerField[]): RJSFSchema {
  const properties: Record<string, unknown> = {}
  const required: string[] = []

  for (const f of fields) {
    if (f.isSectionDivider) continue
    if (!f.key) continue
    properties[f.key] = {
      type: f.type,
      title: f.label || f.key,
      ...(f.helpText ? { description: f.helpText } : {}),
    }
    if (f.required) required.push(f.key)
  }

  return {
    type: 'object',
    properties,
    ...(required.length > 0 ? { required } : {}),
  } as RJSFSchema
}

function buildUiSchema(fields: DesignerField[]): UiSchema {
  const schema: Record<string, unknown> = {
    'ui:order': fields.filter((f) => !f.isSectionDivider && f.key).map((f) => f.key),
    'ui:submitButtonOptions': { norender: true },
  }

  for (const f of fields) {
    if (f.isSectionDivider || !f.key) continue
    const fieldUi: Record<string, unknown> = {}
    if (f.placeholder) fieldUi['ui:placeholder'] = f.placeholder
    if (f.widgetOverride) fieldUi['ui:widget'] = f.widgetOverride
    if (f.visibleWhen?.field && f.visibleWhen?.equals) {
      fieldUi['ui:visible'] = f.visibleWhen
    }
    if (Object.keys(fieldUi).length > 0) schema[f.key] = fieldUi
  }

  return schema as UiSchema
}

/** Reverse a persisted FormDefinition back into DesignerField[]. */
function hydrateFields(def: FormDefinition): DesignerField[] {
  const jsonSchema = def.jsonSchema as {
    properties?: Record<string, { type?: string; title?: string; description?: string }>
    required?: string[]
  }
  const uiSchema = def.uiSchema as Record<string, unknown>
  const order = (uiSchema['ui:order'] as string[] | undefined) ?? Object.keys(jsonSchema.properties ?? {})
  const requiredSet = new Set<string>(jsonSchema.required ?? [])

  return order.map((key) => {
    const prop = jsonSchema.properties?.[key]
    const fieldUi = (uiSchema[key] ?? {}) as Record<string, unknown>
    return {
      id: uid(),
      key,
      label: prop?.title ?? key,
      type: (prop?.type ?? 'string') as DesignerField['type'],
      required: requiredSet.has(key),
      width: 3 as const,
      placeholder: (fieldUi['ui:placeholder'] as string) ?? undefined,
      helpText: prop?.description ?? undefined,
      widgetOverride: (fieldUi['ui:widget'] as string) ?? undefined,
      visibleWhen: fieldUi['ui:visible']
        ? (fieldUi['ui:visible'] as { field: string; equals: string })
        : undefined,
    }
  })
}

// ── Component ──────────────────────────────────────────────────────

export function FormDesignerPage() {
  const { slug: routeSlug } = useParams<{ slug: string }>()
  const navigate = useNavigate()
  const isEdit = Boolean(routeSlug)

  // ── Top bar state ──
  const [title, setTitle] = useState('')
  const [entityName, setEntityName] = useState('')
  const [purpose, setPurpose] = useState<FormPurpose>('create')
  const [slug, setSlug] = useState('')
  const [slugTouched, setSlugTouched] = useState(false)
  const [existingVersion, setExistingVersion] = useState(0)

  // ── Field list state ──
  const [fields, setFields] = useState<DesignerField[]>([blankField()])
  const [expandedId, setExpandedId] = useState<string | null>(null)

  // ── Loading / error state ──
  const [loading, setLoading] = useState(isEdit)
  const [saving, setSaving] = useState(false)
  const [error, setError] = useState<Error | null>(null)

  // ── Preview form data ──
  const [previewData, setPreviewData] = useState<Record<string, unknown>>({})

  // ── Load existing form in edit mode ──
  useEffect(() => {
    if (!routeSlug) return
    setLoading(true)
    metadata
      .getForm(routeSlug)
      .then((def) => {
        setTitle(def.title)
        setEntityName(def.entityName)
        setPurpose(def.purpose)
        setSlug(def.slug)
        setSlugTouched(true)
        setExistingVersion(def.version)
        const hydrated = hydrateFields(def)
        setFields(hydrated.length > 0 ? hydrated : [blankField()])
      })
      .catch((err: unknown) =>
        setError(err instanceof Error ? err : new Error(String(err))),
      )
      .finally(() => setLoading(false))
  }, [routeSlug])

  // ── Auto-slug from title ──
  useEffect(() => {
    if (!slugTouched && title) {
      setSlug(slugify(title))
    }
  }, [title, slugTouched])

  // ── Field mutations ──
  const updateField = useCallback(
    (id: string, patch: Partial<DesignerField>) => {
      setFields((prev) =>
        prev.map((f) => (f.id === id ? { ...f, ...patch } : f)),
      )
    },
    [],
  )

  const removeField = useCallback((id: string) => {
    setFields((prev) => {
      const next = prev.filter((f) => f.id !== id)
      return next.length > 0 ? next : [blankField()]
    })
  }, [])

  const moveField = useCallback((id: string, direction: 'up' | 'down') => {
    setFields((prev) => {
      const idx = prev.findIndex((f) => f.id === id)
      if (idx < 0) return prev
      const target = direction === 'up' ? idx - 1 : idx + 1
      if (target < 0 || target >= prev.length) return prev
      const next = [...prev]
      ;[next[idx], next[target]] = [next[target], next[idx]]
      return next
    })
  }, [])

  const addField = useCallback(() => {
    const f = blankField()
    setFields((prev) => [...prev, f])
    setExpandedId(f.id)
  }, [])

  const addSection = useCallback(() => {
    const s = blankSection()
    setFields((prev) => [...prev, s])
    setExpandedId(s.id)
  }, [])

  // ── Computed schemas ──
  const jsonSchema = useMemo(() => buildJsonSchema(fields), [fields])
  const uiSchema = useMemo(() => buildUiSchema(fields), [fields])

  // ── Save ──
  const handleSave = async () => {
    if (!slug) {
      setError(new Error('Slug is required.'))
      return
    }
    if (!entityName) {
      setError(new Error('Entity name is required.'))
      return
    }
    setSaving(true)
    setError(null)
    try {
      await metadata.saveForm(slug, {
        slug,
        entityName,
        title: title || slug,
        purpose,
        jsonSchema: jsonSchema as Record<string, unknown>,
        uiSchema: uiSchema as Record<string, unknown>,
        version: existingVersion + 1,
      })
      navigate('/admin/metadata')
    } catch (err: unknown) {
      setError(err instanceof Error ? err : new Error(String(err)))
    } finally {
      setSaving(false)
    }
  }

  // ── Collect non-divider field keys for the visibility dropdown ──
  const fieldKeys = useMemo(
    () => fields.filter((f) => !f.isSectionDivider && f.key).map((f) => f.key),
    [fields],
  )

  if (loading) return <Loading />

  return (
    <div>
      <PageHeader
        title={isEdit ? 'Edit Form Definition' : 'New Form Definition'}
        subtitle="Design a metadata-driven form with a live preview."
      />

      {/* ── Top bar ── */}
      <div className="card p-4 mb-4">
        <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-5">
          <div>
            <label className="block text-sm font-medium text-slate-700">Title</label>
            <input
              type="text"
              value={title}
              onChange={(e) => setTitle(e.target.value)}
              placeholder="My Form"
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-slate-700">Entity name</label>
            <input
              type="text"
              value={entityName}
              onChange={(e) => setEntityName(e.target.value)}
              placeholder="SalesOrder"
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-slate-700">Purpose</label>
            <select
              value={purpose}
              onChange={(e) => setPurpose(e.target.value as FormPurpose)}
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            >
              {PURPOSES.map((p) => (
                <option key={p} value={p}>
                  {p}
                </option>
              ))}
            </select>
          </div>
          <div>
            <label className="block text-sm font-medium text-slate-700">Slug</label>
            <input
              type="text"
              value={slug}
              onChange={(e) => {
                setSlugTouched(true)
                setSlug(e.target.value)
              }}
              placeholder="auto-generated"
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            />
          </div>
          <div className="flex items-end gap-2">
            <button
              type="button"
              className="btn-primary"
              disabled={saving}
              onClick={handleSave}
            >
              {saving ? 'Saving...' : 'Save'}
            </button>
            <button
              type="button"
              className="btn-secondary"
              onClick={() => navigate('/admin/metadata')}
            >
              Discard
            </button>
          </div>
        </div>
      </div>

      {error && (
        <div className="mb-4">
          <ErrorBox error={error} />
        </div>
      )}

      {/* ── Two-panel layout ── */}
      <div className="grid grid-cols-1 gap-4 lg:grid-cols-5">
        {/* ── Left panel: field list (3/5 = 60%) ── */}
        <div className="lg:col-span-3 space-y-2">
          <h2 className="text-xs font-semibold uppercase tracking-wide text-slate-400 mb-3">
            Fields
          </h2>

          {fields.map((field, idx) => (
            <FieldRow
              key={field.id}
              field={field}
              index={idx}
              total={fields.length}
              isExpanded={expandedId === field.id}
              fieldKeys={fieldKeys}
              onToggleExpand={() =>
                setExpandedId(expandedId === field.id ? null : field.id)
              }
              onUpdate={(patch) => updateField(field.id, patch)}
              onRemove={() => removeField(field.id)}
              onMove={(dir) => moveField(field.id, dir)}
            />
          ))}

          <div className="flex gap-2 pt-2">
            <button type="button" className="btn-secondary" onClick={addField}>
              + Add Field
            </button>
            <button type="button" className="btn-secondary" onClick={addSection}>
              + Add Section Divider
            </button>
          </div>
        </div>

        {/* ── Right panel: live preview (2/5 = 40%) ── */}
        <div className="lg:col-span-2">
          <h2 className="text-xs font-semibold uppercase tracking-wide text-slate-400 mb-3">
            Live Preview
          </h2>
          <div className="card p-6">
            {Object.keys(jsonSchema.properties ?? {}).length === 0 ? (
              <p className="text-sm text-slate-400 italic">
                Add at least one field with a key to see the preview.
              </p>
            ) : (
              <Form
                schema={jsonSchema}
                uiSchema={uiSchema}
                formData={previewData}
                validator={validator}
                widgets={vibeWidgets}
                templates={vibeTemplates}
                onChange={(e) =>
                  setPreviewData(
                    (e.formData as Record<string, unknown>) ?? {},
                  )
                }
                formContext={{ formData: previewData }}
              />
            )}
          </div>
        </div>
      </div>
    </div>
  )
}

// ── FieldRow sub-component ─────────────────────────────────────────

interface FieldRowProps {
  field: DesignerField
  index: number
  total: number
  isExpanded: boolean
  fieldKeys: string[]
  onToggleExpand: () => void
  onUpdate: (patch: Partial<DesignerField>) => void
  onRemove: () => void
  onMove: (direction: 'up' | 'down') => void
}

function FieldRow({
  field,
  index,
  total,
  isExpanded,
  fieldKeys,
  onToggleExpand,
  onUpdate,
  onRemove,
  onMove,
}: FieldRowProps) {
  if (field.isSectionDivider) {
    return (
      <div className="card p-3 border-l-4 border-indigo-300">
        <div className="flex items-center gap-2">
          <ReorderButtons
            index={index}
            total={total}
            onMove={onMove}
          />
          <span className="text-xs font-semibold uppercase text-indigo-500 mr-2">
            Section
          </span>
          <input
            type="text"
            value={field.sectionTitle ?? ''}
            onChange={(e) => onUpdate({ sectionTitle: e.target.value })}
            placeholder="Section title"
            className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-sm"
          />
          <button
            type="button"
            onClick={onRemove}
            className="text-slate-400 hover:text-rose-500 text-sm px-1"
            title="Remove section"
          >
            x
          </button>
        </div>
      </div>
    )
  }

  return (
    <div className="card p-3">
      {/* ── Compact row ── */}
      <div className="flex items-center gap-2">
        <ReorderButtons index={index} total={total} onMove={onMove} />

        <input
          type="text"
          value={field.key}
          onChange={(e) => onUpdate({ key: e.target.value.replace(/\s+/g, '_') })}
          placeholder="field_key"
          className="w-28 rounded-md border border-slate-300 px-2 py-1 text-sm font-mono"
        />
        <input
          type="text"
          value={field.label}
          onChange={(e) => onUpdate({ label: e.target.value })}
          placeholder="Label"
          className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-sm"
        />
        <select
          value={field.type}
          onChange={(e) =>
            onUpdate({ type: e.target.value as DesignerField['type'] })
          }
          className="w-24 rounded-md border border-slate-300 px-2 py-1 text-sm"
        >
          {FIELD_TYPES.map((t) => (
            <option key={t} value={t}>
              {t}
            </option>
          ))}
        </select>
        <label className="flex items-center gap-1 text-sm text-slate-600 whitespace-nowrap">
          <input
            type="checkbox"
            checked={field.required}
            onChange={(e) => onUpdate({ required: e.target.checked })}
            className="rounded border-slate-300"
          />
          Req
        </label>
        <select
          value={field.width}
          onChange={(e) =>
            onUpdate({ width: Number(e.target.value) as 1 | 2 | 3 })
          }
          className="w-20 rounded-md border border-slate-300 px-2 py-1 text-sm"
          title="Column span"
        >
          {WIDTH_OPTIONS.map((w) => (
            <option key={w} value={w}>
              {w} col{w > 1 ? 's' : ''}
            </option>
          ))}
        </select>
        <button
          type="button"
          onClick={onToggleExpand}
          className="text-slate-400 hover:text-slate-600 text-sm px-1"
          title="Toggle details"
        >
          {isExpanded ? '\u25B2' : '\u25BC'}
        </button>
        <button
          type="button"
          onClick={onRemove}
          className="text-slate-400 hover:text-rose-500 text-sm px-1"
          title="Remove field"
        >
          x
        </button>
      </div>

      {/* ── Expanded detail panel ── */}
      {isExpanded && (
        <div className="mt-3 ml-10 grid grid-cols-1 gap-3 sm:grid-cols-2 border-t border-slate-100 pt-3">
          <div>
            <label className="block text-sm font-medium text-slate-700">
              Label (English)
            </label>
            <input
              type="text"
              value={field.label}
              onChange={(e) => onUpdate({ label: e.target.value })}
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-slate-700">
              Placeholder
            </label>
            <input
              type="text"
              value={field.placeholder ?? ''}
              onChange={(e) =>
                onUpdate({ placeholder: e.target.value || undefined })
              }
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            />
          </div>
          <div className="sm:col-span-2">
            <label className="block text-sm font-medium text-slate-700">
              Help text
            </label>
            <input
              type="text"
              value={field.helpText ?? ''}
              onChange={(e) =>
                onUpdate({ helpText: e.target.value || undefined })
              }
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            />
          </div>
          <div>
            <label className="block text-sm font-medium text-slate-700">
              Widget override
            </label>
            <select
              value={field.widgetOverride ?? ''}
              onChange={(e) =>
                onUpdate({ widgetOverride: e.target.value || undefined })
              }
              className="mt-1 w-full rounded-md border border-slate-300 px-3 py-2 text-sm"
            >
              {WIDGET_OPTIONS.map((w) => (
                <option key={w} value={w}>
                  {w || '(default)'}
                </option>
              ))}
            </select>
          </div>
          <div>
            <label className="block text-sm font-medium text-slate-700">
              Visibility condition
            </label>
            <div className="mt-1 flex items-center gap-2">
              <span className="text-sm text-slate-500">Show when</span>
              <select
                value={field.visibleWhen?.field ?? ''}
                onChange={(e) =>
                  onUpdate({
                    visibleWhen: e.target.value
                      ? {
                          field: e.target.value,
                          equals: field.visibleWhen?.equals ?? '',
                        }
                      : undefined,
                  })
                }
                className="rounded-md border border-slate-300 px-2 py-1 text-sm"
              >
                <option value="">(always)</option>
                {fieldKeys
                  .filter((k) => k !== field.key)
                  .map((k) => (
                    <option key={k} value={k}>
                      {k}
                    </option>
                  ))}
              </select>
              <span className="text-sm text-slate-500">equals</span>
              <input
                type="text"
                value={field.visibleWhen?.equals ?? ''}
                onChange={(e) =>
                  onUpdate({
                    visibleWhen: field.visibleWhen?.field
                      ? { field: field.visibleWhen.field, equals: e.target.value }
                      : undefined,
                  })
                }
                className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-sm"
                placeholder="value"
                disabled={!field.visibleWhen?.field}
              />
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

// ── Reorder buttons ────────────────────────────────────────────────

function ReorderButtons({
  index,
  total,
  onMove,
}: {
  index: number
  total: number
  onMove: (direction: 'up' | 'down') => void
}) {
  return (
    <div className="flex flex-col">
      <button
        type="button"
        onClick={() => onMove('up')}
        disabled={index === 0}
        className="text-slate-400 hover:text-slate-600 disabled:opacity-30 text-xs leading-none"
        title="Move up"
      >
        &#9650;
      </button>
      <button
        type="button"
        onClick={() => onMove('down')}
        disabled={index === total - 1}
        className="text-slate-400 hover:text-slate-600 disabled:opacity-30 text-xs leading-none"
        title="Move down"
      >
        &#9660;
      </button>
    </div>
  )
}
