'use client'

import { useEffect, useState } from 'react'
import { Eye, EyeOff, GripVertical, Lock, Plus, X } from 'lucide-react'
import {
  DndContext,
  KeyboardSensor,
  PointerSensor,
  closestCenter,
  useSensor,
  useSensors,
  type DragEndEvent,
} from '@dnd-kit/core'
import {
  SortableContext,
  sortableKeyboardCoordinates,
  useSortable,
  verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { MediaPickerModal } from '../../components/MediaPickerModal.js'
import type { PageSection } from '../../lib/page-editor-service.js'
import type { ResolvedRepeater, SectionSettings } from './section-types.js'
import {
  blankRepeaterRow,
  getSectionType,
  resolveRepeaterField,
  type SectionFieldDef,
} from './section-types.js'

interface SectionInspectorProps {
  section: PageSection
  canEdit: boolean
  onClose: () => void
  onContentChange: (patch: Record<string, unknown>) => void
  onSettingsChange: (patch: Partial<SectionSettings>) => void
  onMetaChange: (patch: { name?: string; internalLabel?: string }) => void
  onToggleVisibility: () => void
}

const LABEL = 'text-foreground mb-1 block text-xs font-medium'
const INPUT =
  'border-input bg-input-background text-foreground focus-visible:ring-ring w-full rounded-md border px-2.5 py-1.5 text-sm focus-visible:ring-2 focus-visible:outline-none'

export function SectionInspector({
  section,
  canEdit,
  onClose,
  onContentChange,
  onSettingsChange,
  onMetaChange,
  onToggleVisibility,
}: SectionInspectorProps) {
  const def = getSectionType(section.sectionType)

  // Escape closes the inspector.
  useEffect(() => {
    function onKey(e: KeyboardEvent) {
      if (e.key === 'Escape') onClose()
    }
    document.addEventListener('keydown', onKey)
    return () => document.removeEventListener('keydown', onKey)
  }, [onClose])

  // Unmanaged sections (no registered type) are read-only: show why instead of
  // editable controls, so the externally-managed content is never altered here.
  if (section.unmanaged) {
    return (
      <aside
        role="dialog"
        aria-label={`${section.name} (read-only)`}
        className="bg-card border-border flex h-full w-80 shrink-0 flex-col border-l"
      >
        <div className="border-border flex items-center justify-between border-b px-4 py-3">
          <div className="min-w-0">
            <h2 className="text-foreground truncate text-sm font-medium">{section.name}</h2>
            <p className="text-muted-foreground text-xs">Managed outside the editor</p>
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="Close inspector"
            className="text-muted-foreground hover:text-foreground hover:bg-accent rounded p-1"
          >
            <X className="h-4 w-4" aria-hidden />
          </button>
        </div>
        <div className="flex-1 space-y-3 overflow-y-auto p-4">
          <div className="border-border bg-muted text-muted-foreground space-y-2 rounded-md border p-3 text-sm">
            <p className="text-foreground flex items-center gap-2 font-medium">
              <Lock size={14} aria-hidden /> Read-only section
            </p>
            <p>
              The section type{' '}
              <code className="bg-background rounded px-1 py-0.5 text-xs">
                {section.sectionType}
              </code>{' '}
              isn&apos;t registered in this CMS, so it can&apos;t be edited here. It is preserved
              exactly as-is on every save.
            </p>
            <p>
              Edit it in your seed script or code, or register it via{' '}
              <code className="bg-background rounded px-1 py-0.5 text-xs">sections.types</code> in
              your config to make it editable.
            </p>
          </div>
        </div>
      </aside>
    )
  }

  return (
    <aside
      role="dialog"
      aria-label={`Edit ${section.name}`}
      className="bg-card border-border flex h-full w-80 shrink-0 flex-col border-l"
    >
      <div className="border-border flex items-center justify-between border-b px-4 py-3">
        <div className="min-w-0">
          <h2 className="text-foreground truncate text-sm font-medium">{section.name}</h2>
          <p className="text-muted-foreground text-xs">{def?.name ?? 'Section'}</p>
        </div>
        <button
          type="button"
          onClick={onClose}
          aria-label="Close inspector"
          className="text-muted-foreground hover:text-foreground hover:bg-accent rounded p-1"
        >
          <X className="h-4 w-4" aria-hidden />
        </button>
      </div>

      <div className="flex-1 space-y-6 overflow-y-auto p-4">
        {!canEdit && (
          <p className="border-border bg-muted text-muted-foreground rounded-md border p-2 text-xs">
            You have read-only access to this page.
          </p>
        )}

        {/* Content fields (schema-driven) */}
        <fieldset disabled={!canEdit} className="space-y-3">
          <legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
            Content
          </legend>
          {def?.fields.map((field) => (
            <ContentField
              key={field.key}
              field={field}
              value={section.content[field.key]}
              onChange={(v) => onContentChange({ [field.key]: v })}
              onPatch={(patch) => onContentChange(patch)}
            />
          ))}
        </fieldset>

        {/* Section meta + visibility */}
        <fieldset disabled={!canEdit} className="space-y-3">
          <legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
            Section settings
          </legend>
          <div>
            <label className={LABEL} htmlFor="insp-name">
              Section name
            </label>
            <input
              id="insp-name"
              className={INPUT}
              value={section.name}
              onChange={(e) => onMetaChange({ name: e.target.value })}
            />
          </div>
          <div>
            <label className={LABEL} htmlFor="insp-internal">
              Internal label
            </label>
            <input
              id="insp-internal"
              className={INPUT}
              placeholder="Team-only note (optional)"
              value={section.internalLabel ?? ''}
              onChange={(e) => onMetaChange({ internalLabel: e.target.value })}
            />
          </div>
          <button
            type="button"
            onClick={onToggleVisibility}
            disabled={!canEdit}
            aria-pressed={!section.visible}
            className="border-border text-foreground hover:bg-accent flex w-full items-center justify-between rounded-md border px-2.5 py-1.5 text-sm disabled:opacity-50"
          >
            <span className="flex items-center gap-2">
              {section.visible ? (
                <Eye className="h-4 w-4" aria-hidden />
              ) : (
                <EyeOff className="h-4 w-4" aria-hidden />
              )}
              {section.visible ? 'Visible on page' : 'Hidden from page'}
            </span>
            <span className="text-muted-foreground text-xs">
              {section.visible ? 'Hide' : 'Show'}
            </span>
          </button>
        </fieldset>

        {/* Appearance */}
        <fieldset disabled={!canEdit} className="space-y-3">
          <legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
            Appearance
          </legend>
          <div>
            <label className={LABEL} htmlFor="insp-bg">
              Background
            </label>
            <select
              id="insp-bg"
              className={INPUT}
              value={section.settings.backgroundType ?? 'none'}
              onChange={(e) =>
                onSettingsChange({
                  backgroundType: e.target.value as SectionSettings['backgroundType'],
                })
              }
            >
              <option value="none">White</option>
              <option value="solid">Solid color</option>
              <option value="gradient">Gradient</option>
            </select>
          </div>
          {section.settings.backgroundType && section.settings.backgroundType !== 'none' && (
            <div>
              <label className={LABEL} htmlFor="insp-bgcolor">
                Color
              </label>
              <select
                id="insp-bgcolor"
                className={INPUT}
                value={section.settings.backgroundColor ?? 'purple'}
                onChange={(e) => onSettingsChange({ backgroundColor: e.target.value })}
              >
                <option value="purple">Purple</option>
                <option value="muted">Light gray</option>
              </select>
            </div>
          )}
          <div>
            <label className={LABEL} htmlFor="insp-pad">
              Spacing
            </label>
            <select
              id="insp-pad"
              className={INPUT}
              value={section.settings.paddingY ?? 'lg'}
              onChange={(e) =>
                onSettingsChange({ paddingY: e.target.value as SectionSettings['paddingY'] })
              }
            >
              <option value="sm">Small</option>
              <option value="md">Medium</option>
              <option value="lg">Large</option>
              <option value="xl">Extra large</option>
            </select>
          </div>
        </fieldset>

        {/* Advanced */}
        <fieldset disabled={!canEdit} className="space-y-3">
          <legend className="text-muted-foreground mb-2 text-[10px] font-medium tracking-wider uppercase">
            Advanced
          </legend>
          <div>
            <label className={LABEL} htmlFor="insp-anchor">
              Anchor ID
            </label>
            <input
              id="insp-anchor"
              className={INPUT}
              placeholder="e.g. features"
              value={section.settings.anchorId ?? ''}
              onChange={(e) => onSettingsChange({ anchorId: e.target.value })}
            />
          </div>
          <div>
            <label className={LABEL} htmlFor="insp-css">
              CSS class
            </label>
            <input
              id="insp-css"
              className={INPUT}
              placeholder="custom-class"
              value={section.settings.cssClass ?? ''}
              onChange={(e) => onSettingsChange({ cssClass: e.target.value })}
            />
          </div>
        </fieldset>
      </div>
    </aside>
  )
}

// ─── Field renderers ─────────────────────────────────────────────────

function ContentField({
  field,
  value,
  onChange,
  onPatch,
  id: idProp,
}: {
  field: SectionFieldDef
  value: unknown
  onChange: (v: unknown) => void
  /**
   * Optional multi-key patch sink — lets media fields persist companion
   * dimension keys (width/height) atomically with the URL.
   */
  onPatch?: (patch: Record<string, unknown>) => void
  /** Explicit input id (repeater rows pass a row-scoped id to stay unique). */
  id?: string
}) {
  const id = idProp ?? `insp-field-${field.key}`
  const text = typeof value === 'string' ? value : ''

  const repeater = resolveRepeaterField(field)
  if (repeater) {
    return (
      <RepeaterField
        fieldKey={field.key}
        label={field.label}
        repeater={repeater}
        value={value}
        onChange={onChange}
      />
    )
  }

  if (field.type === 'boolean') {
    return <BooleanField id={id} field={field} value={value === true} onChange={onChange} />
  }

  if (field.type === 'relationship') {
    return <RelationshipField id={id} field={field} value={value} onChange={onChange} />
  }

  if (field.type === 'media') {
    return <MediaField id={id} field={field} value={text} onChange={onChange} onPatch={onPatch} />
  }

  if (field.type === 'select') {
    return (
      <div>
        <label className={LABEL} htmlFor={id}>
          {field.label}
        </label>
        <select id={id} className={INPUT} value={text} onChange={(e) => onChange(e.target.value)}>
          {field.options?.map((o) => (
            <option key={o.value} value={o.value}>
              {o.label}
            </option>
          ))}
        </select>
      </div>
    )
  }

  if (field.type === 'textarea' || field.type === 'richText') {
    return (
      <div>
        <label className={LABEL} htmlFor={id}>
          {field.label}
          {field.required && <span className="text-destructive ml-0.5">*</span>}
        </label>
        <textarea
          id={id}
          className={`${INPUT} resize-y ${field.type === 'richText' ? 'min-h-[140px]' : 'min-h-[72px]'}`}
          value={text}
          placeholder={field.placeholder}
          onChange={(e) => onChange(e.target.value)}
        />
        {field.help && <p className="text-muted-foreground mt-1 text-xs">{field.help}</p>}
      </div>
    )
  }

  return (
    <div>
      <label className={LABEL} htmlFor={id}>
        {field.label}
        {field.required && <span className="text-destructive ml-0.5">*</span>}
      </label>
      <input
        id={id}
        type={field.type === 'url' ? 'url' : 'text'}
        className={INPUT}
        value={text}
        placeholder={field.placeholder}
        onChange={(e) => onChange(e.target.value)}
      />
      {field.help && <p className="text-muted-foreground mt-1 text-xs">{field.help}</p>}
    </div>
  )
}

/**
 * Companion content keys that hold an image field's intrinsic dimensions.
 * Picking from the media library persists width/height alongside the URL so the
 * public renderer can use the CLS-safe `next/image` path without waiting on
 * read-time `/resolve` enrichment. Mirrors the keys the section renderers read:
 *   `imageUrl` → `imageWidth` / `imageHeight`; `url` (gallery row) → `width` / `height`.
 */
export function companionDimensionKeys(fieldKey: string): { width: string; height: string } {
  if (fieldKey === 'url') return { width: 'width', height: 'height' }
  if (fieldKey.endsWith('Url')) {
    const base = fieldKey.slice(0, -'Url'.length)
    return { width: `${base}Width`, height: `${base}Height` }
  }
  return { width: `${fieldKey}Width`, height: `${fieldKey}Height` }
}

function MediaField({
  id,
  field,
  value,
  onChange,
  onPatch,
}: {
  id: string
  field: SectionFieldDef
  value: string
  onChange: (v: unknown) => void
  /**
   * When provided, picking from the library applies a multi-key patch (URL +
   * intrinsic width/height) in one update. Falls back to `onChange` (URL only)
   * for manual entry and when no patch sink is wired.
   */
  onPatch?: (patch: Record<string, unknown>) => void
}) {
  const [open, setOpen] = useState(false)
  return (
    <div>
      <label className={LABEL} htmlFor={id}>
        {field.label}
      </label>
      <div className="flex gap-2">
        <input
          id={id}
          className={`${INPUT} flex-1`}
          placeholder="https://…"
          value={value}
          onChange={(e) => onChange(e.target.value)}
        />
        <button
          type="button"
          onClick={() => setOpen(true)}
          className="bg-accent text-foreground border-border hover:bg-accent/80 rounded-md border px-3 py-1.5 text-xs font-medium"
        >
          Browse
        </button>
      </div>
      {value && (
        <div className="border-border bg-muted mt-2 overflow-hidden rounded-md border">
          <img src={value} alt="" className="h-28 w-full object-contain" />
        </div>
      )}
      <MediaPickerModal
        open={open}
        onClose={() => setOpen(false)}
        onSelectItem={(item) => {
          if (!onPatch) return
          const { width: wKey, height: hKey } = companionDimensionKeys(field.key)
          const patch: Record<string, unknown> = { [field.key]: item.url }
          if (typeof item.width === 'number') patch[wKey] = item.width
          if (typeof item.height === 'number') patch[hKey] = item.height
          onPatch(patch)
        }}
        onSelect={(url) => {
          // onSelectItem handles persistence when a patch sink is wired; only
          // fall back to a URL-only update when it isn't.
          if (!onPatch) onChange(url)
          setOpen(false)
        }}
        accept="image/*"
      />
    </div>
  )
}

function BooleanField({
  id,
  field,
  value,
  onChange,
}: {
  id: string
  field: SectionFieldDef
  value: boolean
  onChange: (v: unknown) => void
}) {
  return (
    <div>
      <button
        type="button"
        id={id}
        role="switch"
        aria-checked={value}
        onClick={() => onChange(!value)}
        className="border-border text-foreground hover:bg-accent flex w-full items-center justify-between rounded-md border px-2.5 py-1.5 text-sm"
      >
        <span>{field.label}</span>
        <span
          aria-hidden
          className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors ${
            value ? 'bg-primary' : 'bg-muted'
          }`}
        >
          <span
            className={`bg-card inline-block h-4 w-4 transform rounded-full transition-transform ${
              value ? 'translate-x-4' : 'translate-x-0.5'
            }`}
          />
        </span>
      </button>
      {field.help && <p className="text-muted-foreground mt-1 text-xs">{field.help}</p>}
    </div>
  )
}

function RelationshipField({
  id,
  field,
  value,
  onChange,
}: {
  id: string
  field: SectionFieldDef
  value: unknown
  onChange: (v: unknown) => void
}) {
  // Stored as an array of slugs; edited as a newline/comma-separated list
  // so authors can hand-pick referenced documents without a live picker.
  const slugs = Array.isArray(value) ? (value as string[]) : []
  const text = slugs.join('\n')
  return (
    <div>
      <label className={LABEL} htmlFor={id}>
        {field.label}
      </label>
      <textarea
        id={id}
        className={`${INPUT} min-h-[72px] resize-y`}
        placeholder="one-slug-per-line"
        value={text}
        onChange={(e) =>
          onChange(
            e.target.value
              .split(/[\n,]/)
              .map((s) => s.trim())
              .filter(Boolean),
          )
        }
      />
      {field.help && <p className="text-muted-foreground mt-1 text-xs">{field.help}</p>}
    </div>
  )
}

type RowRecord = Record<string, unknown>

// Field types whose values are short enough to scan in a row header. Long-form
// fields (textarea, richText) and non-text values (media, url) stay out.
const SUMMARY_FIELD_TYPES = new Set(['text', 'select'])
const SUMMARY_MAX_LENGTH = 60

/**
 * Derive a scannable one-line summary for a repeater row from its first two
 * short text values — e.g. a pricing row renders as "Pro · $79/mo" instead of
 * "Plan 2". Returns `null` when the row has no usable values yet, so the
 * caller falls back to the positional "Plan 2" label.
 */
export function repeaterRowSummary(fields: SectionFieldDef[], row: RowRecord): string | null {
  const parts: string[] = []
  for (const field of fields) {
    if (parts.length >= 2) break
    if (!SUMMARY_FIELD_TYPES.has(field.type)) continue
    const raw = row[field.key]
    const value = typeof raw === 'string' ? raw.trim() : typeof raw === 'number' ? String(raw) : ''
    if (!value) continue
    if (field.type === 'select') {
      const option = field.options?.find((o) => o.value === value)
      parts.push(option?.label ?? value)
    } else {
      parts.push(value)
    }
  }
  if (parts.length === 0) return null
  const joined = parts.join(' · ')
  return joined.length > SUMMARY_MAX_LENGTH
    ? `${joined.slice(0, SUMMARY_MAX_LENGTH - 1).trimEnd()}…`
    : joined
}

/**
 * Generic repeater editor: an orderable list of rows, each row a small form of
 * the repeater's sub-fields. Drives the generic `repeater` field type and the
 * `stats` / `gallery` shorthands (resolved upstream to the same row model), so
 * there's one editor for every structured-array field.
 *
 * Rows are stored as a plain array on `content[fieldKey]`. Drag/reorder uses an
 * index-derived row id (`row-0`, `row-1`, …); since every edit commits a fresh
 * array this stays consistent and matches the prior stats/gallery behavior.
 */
function RepeaterField({
  fieldKey,
  label,
  repeater,
  value,
  onChange,
}: {
  fieldKey: string
  label: string
  repeater: ResolvedRepeater
  value: unknown
  onChange: (v: unknown) => void
}) {
  const rows: RowRecord[] = Array.isArray(value) ? (value as RowRecord[]) : []
  const atCap = typeof repeater.maxRows === 'number' && rows.length >= repeater.maxRows
  const rowKey = (i: number): string => `row-${i}`

  const sensors = useSensors(
    useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
    useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
  )

  function commit(next: RowRecord[]) {
    onChange(next)
  }
  function update(index: number, patch: RowRecord) {
    commit(rows.map((r, i) => (i === index ? { ...r, ...patch } : r)))
  }
  function remove(index: number) {
    commit(rows.filter((_, i) => i !== index))
  }
  function add() {
    if (atCap) return
    commit([...rows, blankRepeaterRow(repeater.fields)])
  }
  function handleDragEnd(event: DragEndEvent) {
    const { active, over } = event
    if (!over || active.id === over.id) return
    // Map the dnd id (index-derived rowKey) back to array positions.
    const ids = rows.map((_, i) => rowKey(i))
    const from = ids.indexOf(String(active.id))
    const to = ids.indexOf(String(over.id))
    if (from === -1 || to === -1) return
    const next = rows.slice()
    const [moved] = next.splice(from, 1)
    next.splice(to, 0, moved!)
    commit(next)
  }

  const lower = repeater.rowLabel.toLowerCase()

  return (
    <div>
      <span className={LABEL}>{label}</span>
      {rows.length === 0 ? (
        <p className="border-border text-muted-foreground mb-2 rounded-md border border-dashed px-2 py-3 text-center text-xs">
          No {lower}s yet.
        </p>
      ) : (
        <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
          <SortableContext
            items={rows.map((_, i) => rowKey(i))}
            strategy={verticalListSortingStrategy}
          >
            <div className="space-y-3">
              {rows.map((row, i) => (
                <SortableRepeaterRow
                  key={rowKey(i)}
                  id={rowKey(i)}
                  index={i}
                  fieldKey={fieldKey}
                  rowLabel={repeater.rowLabel}
                  fields={repeater.fields}
                  row={row}
                  reorderable={rows.length > 1}
                  onPatch={(patch) => update(i, patch)}
                  onRemove={() => remove(i)}
                />
              ))}
            </div>
          </SortableContext>
        </DndContext>
      )}
      <button
        type="button"
        onClick={add}
        disabled={atCap}
        className="border-border text-foreground hover:bg-accent mt-2 flex w-full items-center justify-center gap-1.5 rounded-md border border-dashed px-2 py-1.5 text-xs font-medium disabled:cursor-not-allowed disabled:opacity-50"
      >
        <Plus className="h-3.5 w-3.5" aria-hidden />
        Add {lower}
      </button>
      {atCap && (
        <p className="text-muted-foreground mt-1 text-xs">Maximum of {repeater.maxRows} reached.</p>
      )}
    </div>
  )
}

function SortableRepeaterRow({
  id,
  index,
  fieldKey,
  rowLabel,
  fields,
  row,
  reorderable,
  onPatch,
  onRemove,
}: {
  id: string
  index: number
  fieldKey: string
  rowLabel: string
  fields: SectionFieldDef[]
  row: RowRecord
  reorderable: boolean
  onPatch: (patch: RowRecord) => void
  onRemove: () => void
}) {
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
    id,
    disabled: !reorderable,
  })
  const style = {
    transform: CSS.Transform.toString(transform),
    transition,
    zIndex: isDragging ? 10 : undefined,
  }

  const summary = repeaterRowSummary(fields, row)

  return (
    <div
      ref={setNodeRef}
      style={style}
      className={`border-border rounded-md border p-2 ${isDragging ? 'bg-card shadow-md' : ''}`}
    >
      <div className="mb-2 flex items-center justify-between">
        <span className="flex items-center gap-1">
          {reorderable && (
            <button
              type="button"
              aria-label={`Drag to reorder ${rowLabel} ${index + 1}`}
              className="text-muted-foreground hover:text-foreground cursor-grab touch-none"
              {...attributes}
              {...listeners}
            >
              <GripVertical className="h-3.5 w-3.5" aria-hidden />
            </button>
          )}
          <span
            className="text-muted-foreground max-w-[200px] truncate text-xs"
            title={summary ? `${rowLabel} ${index + 1}: ${summary}` : undefined}
          >
            {summary ?? `${rowLabel} ${index + 1}`}
          </span>
        </span>
        <button
          type="button"
          onClick={onRemove}
          aria-label={`Remove ${rowLabel.toLowerCase()} ${index + 1}`}
          className="text-muted-foreground hover:text-destructive rounded p-0.5"
        >
          <X className="h-3.5 w-3.5" aria-hidden />
        </button>
      </div>
      <div className="space-y-2">
        {fields.map((sub) => (
          <ContentField
            key={sub.key}
            id={`insp-${fieldKey}-${index}-${sub.key}`}
            field={sub}
            value={row[sub.key]}
            onChange={(v) => onPatch({ [sub.key]: v })}
            onPatch={(patch) => onPatch(patch)}
          />
        ))}
      </div>
    </div>
  )
}
