import { useState, type FormEvent } from 'react'
import { Modal } from '@/components/ui/Modal'
import { Alert } from '@/components/ui/Alert'
import { Button } from '@/components/ui/Button'
import { Select } from '@/components/ui/Select'
import { TextArea } from '@/components/ui/TextArea'
import { TextField } from '@/components/ui/TextField'
import { errorMessage } from '@/lib/api'
import { conflictGarden, useCreateGarden, useUpdateGarden, type Garden } from '@/lib/gardens'
import {
  dimensionUnitLabel,
  formatCm,
  dimensionInputMode,
  formatDimensionInput,
  isValidDimensionCm,
  MIN_GARDEN_GRID_CM,
  parseDimension,
  type UnitPref,
} from '@/lib/units'

const DEFAULT_METERS = 10 // matches the server's 10 m default
const DEFAULT_GRID_CM = 100 // matches the server's 1 m grid default

// What to say when a field can't be read. Naming the accepted forms beats
// "invalid input", which leaves the person guessing which field and which part.
function entryHint(unit: UnitPref): string {
  return unit === 'imperial'
    ? `Enter sizes as feet and inches — 8' 6", 8', or 8.5 for feet.`
    : 'Enter sizes in meters, e.g. 2.5.'
}

const unitOptions = [
  { value: 'metric', label: 'Metric (m)' },
  { value: 'imperial', label: 'Imperial (ft)' },
]

function dimString(cm: number | undefined, unit: UnitPref): string {
  return cm === undefined ? String(DEFAULT_METERS) : formatDimensionInput(cm, unit)
}

/**
 * Create (no garden) or edit (garden given) form. Dimensions are entered in the
 * selected unit and converted to centimeters for the API; switching units
 * converts the current values so the physical size is preserved. A 409 rebases
 * the form onto the server's fresh row.
 */
export function GardenFormModal({ garden, onClose }: { garden?: Garden; onClose: () => void }) {
  const isEdit = !!garden
  const create = useCreateGarden()
  const update = useUpdateGarden()
  const pending = create.isPending || update.isPending

  const [name, setName] = useState(garden?.name ?? '')
  const [unit, setUnit] = useState<UnitPref>(garden?.unitPref ?? 'metric')
  const [width, setWidth] = useState(() => dimString(garden?.widthCm, garden?.unitPref ?? 'metric'))
  const [height, setHeight] = useState(() => dimString(garden?.heightCm, garden?.unitPref ?? 'metric'))
  const [gridSize, setGridSize] = useState(() =>
    formatDimensionInput(garden?.gridSizeCm ?? DEFAULT_GRID_CM, garden?.unitPref ?? 'metric'),
  )
  const [snapToGrid, setSnapToGrid] = useState(garden?.snapToGrid ?? false)
  const [notes, setNotes] = useState(garden?.notes ?? '')
  const [version, setVersion] = useState(garden?.version ?? 0)
  const [conflict, setConflict] = useState<string | null>(null)
  const [formError, setFormError] = useState<string | null>(null)

  function changeUnit(next: UnitPref) {
    // Re-render each field in the new unit, preserving the physical size. An
    // unparseable field is left as typed rather than blanked.
    const convert = (s: string) => {
      const cm = parseDimension(s, unit)
      return cm === null ? s : formatDimensionInput(cm, next)
    }
    setWidth(convert(width))
    setHeight(convert(height))
    // The garden grid is a layout concern, so it lives at the same scale as the
    // garden's own dimensions — same helpers, same unit label. (The *bed* grid in
    // the object inspector is a plant-spacing concern and stays at cm/in.)
    setGridSize(convert(gridSize))
    setUnit(next)
  }

  // Converted once per render and used by both the submit handler and the
  // too-fine hint, so the two can never disagree about what was entered.
  const gridSizeCm = parseDimension(gridSize, unit)
  // Soft floor: hint, don't refuse. A garden-scale grid this fine is usually
  // someone reaching for plant spacing, which lives on the bed instead — but it
  // is a legitimate choice for a very small garden, so the save still goes through.
  const gridTooFine = gridSizeCm !== null && gridSizeCm > 0 && gridSizeCm < MIN_GARDEN_GRID_CM

  async function onSubmit(e: FormEvent) {
    e.preventDefault()
    setFormError(null)
    setConflict(null)

    if (!name.trim()) {
      setFormError('Enter a name for the garden.')
      return
    }
    // Validate the converted centimeter values against the same bounds the
    // server enforces, so sub-cm or over-100m sizes fail here with a clear
    // message instead of a generic server error.
    const widthCm = parseDimension(width, unit)
    const heightCm = parseDimension(height, unit)
    if (widthCm === null || heightCm === null) {
      setFormError(entryHint(unit))
      return
    }
    if (!isValidDimensionCm(widthCm) || !isValidDimensionCm(heightCm)) {
      setFormError('Width and height must be between 1 cm and 100 m.')
      return
    }
    if (gridSizeCm === null) {
      setFormError(entryHint(unit))
      return
    }
    if (!isValidDimensionCm(gridSizeCm)) {
      setFormError('Grid size must be between 1 cm and 100 m.')
      return
    }

    const input = {
      name: name.trim(),
      widthCm,
      heightCm,
      unitPref: unit,
      notes: notes.trim(),
      gridSizeCm,
      snapToGrid,
    }

    try {
      if (isEdit) {
        await update.mutateAsync({ id: garden.id, ...input, version })
      } else {
        await create.mutateAsync(input)
      }
      onClose()
    } catch (err) {
      const current = conflictGarden(err)
      if (current) {
        // Someone else changed this garden: rebase the form onto the fresh row so
        // a re-save applies against the current version.
        setVersion(current.version)
        setName(current.name)
        setUnit(current.unitPref)
        setWidth(dimString(current.widthCm, current.unitPref))
        setHeight(dimString(current.heightCm, current.unitPref))
        setGridSize(formatDimensionInput(current.gridSizeCm, current.unitPref))
        setSnapToGrid(current.snapToGrid)
        setNotes(current.notes)
        setConflict('This garden changed elsewhere. The latest values are shown — review and save again.')
        return
      }
      setFormError(errorMessage(err, isEdit ? 'Could not save changes.' : 'Could not create the garden.'))
    }
  }

  const unitLabel = dimensionUnitLabel(unit)
  const inputMode = dimensionInputMode(unit)

  return (
    <Modal title={isEdit ? 'Edit garden' : 'New garden'} onClose={onClose} busy={pending}>
      <form onSubmit={onSubmit} className="flex flex-col gap-3">
        {conflict && <Alert tone="info">{conflict}</Alert>}

        <TextField label="Name" name="name" required value={name} onChange={(e) => setName(e.target.value)} />

        <Select
          label="Units"
          name="unitPref"
          value={unit}
          onChange={(e) => changeUnit(e.target.value as UnitPref)}
          options={unitOptions}
        />

        <div className="grid grid-cols-2 gap-3">
          <TextField
            label={`Width (${unitLabel})`}
            name="width"
            type="text"
            inputMode={inputMode}
            required
            value={width}
            onChange={(e) => setWidth(e.target.value)}
          />
          <TextField
            label={`Height (${unitLabel})`}
            name="height"
            type="text"
            inputMode={inputMode}
            required
            value={height}
            onChange={(e) => setHeight(e.target.value)}
          />
        </div>

        <div>
          <div className="flex items-end gap-3">
            <div className="flex-1">
              <TextField
                label={`Garden grid (${unitLabel})`}
                name="gridSize"
                type="text"
                inputMode={inputMode}
                value={gridSize}
                onChange={(e) => setGridSize(e.target.value)}
              />
            </div>
            <label className="flex h-9 items-center gap-2 whitespace-nowrap text-sm text-fg">
              <input
                type="checkbox"
                checked={snapToGrid}
                onChange={(e) => setSnapToGrid(e.target.checked)}
                className="h-4 w-4 rounded border-border"
              />
              Snap objects
            </label>
          </div>
          {gridTooFine && (
            <p className="mt-1 text-xs text-muted">
              {formatCm(gridSizeCm, unit)} is very fine for a whole-garden grid. Plant spacing lives on each bed
              (Bed grid in the inspector), not here.
            </p>
          )}
        </div>

        <TextArea label="Notes" name="notes" rows={2} value={notes} onChange={(e) => setNotes(e.target.value)} />

        {formError && <Alert>{formError}</Alert>}

        <div className="mt-1 flex justify-end gap-2">
          <Button type="button" variant="ghost" onClick={onClose} disabled={pending}>
            Cancel
          </Button>
          <Button type="submit" disabled={pending}>
            {pending ? 'Saving…' : isEdit ? 'Save' : 'Create garden'}
          </Button>
        </div>
      </form>
    </Modal>
  )
}
