import { useEffect, useRef, useState, type ChangeEvent } from 'react'
import { Button } from '@/components/ui/Button'
import { TextField } from '@/components/ui/TextField'
import { TextArea } from '@/components/ui/TextArea'
import { useDeleteObject, useUpdateObject } from '@/lib/objects'
import {
  cmFromSpacing,
  dimensionUnitLabel,
  dimensionInputMode,
  formatDimensionInput,
  MIN_DIMENSION_CM,
  parseDimension,
  spacingFromCm,
  spacingUnitLabel,
  type UnitPref,
} from '@/lib/units'
import { kindDef } from './kinds'
import { useEditorStore } from './store'
import type { EditorObject } from './types'

const DEFAULT_COLOR = '#8a8a8a'

/**
 * Property panel for the selected object. Each field commits a PATCH on
 * blur/change (carrying the object's version); dimensions and position are shown
 * in the garden's unit. Keyed by object id in the parent so it re-inits cleanly
 * on selection change.
 */
export function Inspector({
  object,
  gardenId,
  unit,
  onFocus,
  onAddNote,
  noteCount = 0,
  readOnly = false,
}: {
  object: EditorObject
  gardenId: number
  unit: UnitPref
  onFocus?: () => void
  /** Opens the journal scoped to this object. Two taps from a selected bed to
   *  typing is the bar; anything more and the log stays empty. */
  onAddNote?: () => void
  /** How many journal entries are about this object, so the log is discoverable
   *  from the thing it's about rather than being a panel you have to remember. */
  noteCount?: number
  readOnly?: boolean
}) {
  const update = useUpdateObject(gardenId)
  const del = useDeleteObject(gardenId)
  const select = useEditorStore((s) => s.select)

  // Local field state (initialized once; committed on blur/change).
  const [name, setName] = useState(object.name)
  const [notes, setNotes] = useState(object.notes)
  const [width, setWidth] = useState(formatDimensionInput(object.widthCm, unit))
  const [height, setHeight] = useState(formatDimensionInput(object.heightCm, unit))
  const [x, setX] = useState(formatDimensionInput(object.xCm, unit))
  const [y, setY] = useState(formatDimensionInput(object.yCm, unit))
  const [rotation, setRotation] = useState(String(Math.round(object.rotationDeg)))
  const [color, setColor] = useState(object.color ?? DEFAULT_COLOR)
  const [gridSize, setGridSize] = useState(String(spacingFromCm(object.gridSizeCm, unit)))
  const [confirmingDelete, setConfirmingDelete] = useState(false)
  const rootRef = useRef<HTMLDivElement>(null)

  // When the object changes underneath us (e.g. a canvas move/resize/rotate, or
  // an optimistic PATCH result), re-sync the fields — unless the user is
  // actively editing one here, so we don't clobber their typing.
  useEffect(() => {
    if (rootRef.current?.contains(document.activeElement)) return
    setName(object.name)
    setNotes(object.notes)
    setWidth(formatDimensionInput(object.widthCm, unit))
    setHeight(formatDimensionInput(object.heightCm, unit))
    setX(formatDimensionInput(object.xCm, unit))
    setY(formatDimensionInput(object.yCm, unit))
    setRotation(String(Math.round(object.rotationDeg)))
    setColor(object.color ?? DEFAULT_COLOR)
    setGridSize(String(spacingFromCm(object.gridSizeCm, unit)))
  }, [object.version, object.widthCm, object.heightCm, object.xCm, object.yCm, object.rotationDeg, object.name, object.notes, object.color, object.gridSizeCm, unit])

  const patch = (fields: Partial<Omit<EditorObject, 'id' | 'version'>>) => {
    if (readOnly) return // a blur mustn't fire a mutation if the role changed mid-edit
    update.mutate({ id: object.id, version: object.version, ...fields })
  }

  // Commit a dimension/position field. `positive` gates width/height (must be
  // ≥ the server minimum) but not x/y, which may be zero or negative.
  //
  // The no-op guard compares the field's TEXT against the string this field
  // renders for the current value. With compound entry there is no single
  // display number left to compare, and a numeric tolerance would be worse:
  // a bed dragged to some arbitrary cm renders as, say, 2′ 7.9″, and merely
  // tabbing through the field would snap it to a whole inch. Text equality means
  // "you didn't edit this", which is what the guard is actually for. Typing the
  // same value a different way (2' 7" vs 2′ 7″) falls through to the cm compare
  // below and is still a no-op.
  const commitDim = (raw: string, current: number, apply: (cm: number) => void, positive = false) => {
    if (raw.trim() === formatDimensionInput(current, unit)) return
    const cm = parseDimension(raw, unit)
    if (cm === null) return // unparseable: leave the row alone rather than commit a zero
    if (positive && cm < MIN_DIMENSION_CM) return
    if (cm !== current) apply(cm)
  }

  // Commit the bed grid size (entered at spacing scale, cm/in). Compare at display
  // precision so a blur without an edit doesn't fire a spurious PATCH; ignore a
  // sub-1cm value the server would reject.
  const commitGrid = () => {
    const v = parseFloat(gridSize)
    if (!Number.isFinite(v)) return
    if (v === spacingFromCm(object.gridSizeCm, unit)) return
    const cm = cmFromSpacing(v, unit)
    if (cm >= MIN_DIMENSION_CM && cm !== object.gridSizeCm) patch({ gridSizeCm: cm })
  }

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

  return (
    <div ref={rootRef} className="flex flex-col gap-3">
      <div className="flex items-center justify-between">
        <h2 className="text-sm font-semibold text-fg">{kindDef(object.kind)?.label ?? object.kind}</h2>
        <button
          type="button"
          onClick={() => select(null)}
          className="rounded px-1.5 text-sm text-muted hover:text-fg"
          aria-label="Close inspector"
        >
          ✕
        </button>
      </div>

      {readOnly && (
        <p className="rounded-md bg-border/40 px-2 py-1 text-xs text-muted">View only — you can't edit this garden.</p>
      )}

      {!readOnly && object.plantable && onFocus && (
        <Button onClick={onFocus} className="w-full">
          🌱 Plant here
        </Button>
      )}

      {onAddNote && (
        <Button variant="ghost" onClick={onAddNote} className="w-full text-sm">
          {noteCount > 0
            ? `📝 ${noteCount} ${noteCount === 1 ? 'note' : 'notes'}`
            : readOnly
              ? '📝 No notes'
              : '📝 Add note'}
        </Button>
      )}

      {/* A disabled fieldset makes every control below read-only for viewers in
          one shot (no per-input disabled). */}
      <fieldset disabled={readOnly} className="flex min-w-0 flex-col gap-3 border-0 p-0">
        <TextField
          label="Name"
          name="name"
          value={name}
          onChange={(e) => setName(e.target.value)}
          onBlur={() => name !== object.name && patch({ name })}
        />

        <div className="grid grid-cols-2 gap-2">
          <TextField
            label={`Width (${u})`}
            name="width"
            type="text"
            inputMode={inputMode}
            value={width}
            onChange={(e) => setWidth(e.target.value)}
            onBlur={() => commitDim(width, object.widthCm, (cm) => patch({ widthCm: cm }), true)}
          />
          <TextField
            label={`Height (${u})`}
            name="height"
            type="text"
            inputMode={inputMode}
            value={height}
            onChange={(e) => setHeight(e.target.value)}
            onBlur={() => commitDim(height, object.heightCm, (cm) => patch({ heightCm: cm }), true)}
          />
          <TextField
            label={`X (${u})`}
            name="x"
            type="text"
            inputMode={inputMode}
            value={x}
            onChange={(e) => setX(e.target.value)}
            onBlur={() => commitDim(x, object.xCm, (cm) => patch({ xCm: cm }))}
          />
          <TextField
            label={`Y (${u})`}
            name="y"
            type="text"
            inputMode={inputMode}
            value={y}
            onChange={(e) => setY(e.target.value)}
            onBlur={() => commitDim(y, object.yCm, (cm) => patch({ yCm: cm }))}
          />
        </div>

        <TextField
          label="Rotation (°)"
          name="rotation"
          type="number"
          inputMode="numeric"
          step="1"
          value={rotation}
          onChange={(e) => setRotation(e.target.value)}
          onBlur={() => {
            const v = parseFloat(rotation)
            if (Number.isFinite(v) && v !== object.rotationDeg) patch({ rotationDeg: v })
          }}
        />

        <div className="flex items-end gap-2">
          <div className="flex flex-col gap-1.5">
            <label htmlFor="obj-color" className="text-sm font-medium text-fg">
              Color
            </label>
            <input
              id="obj-color"
              type="color"
              value={color}
              // onChange fires continuously while dragging in the native picker;
              // track it locally for the live swatch and commit one PATCH on blur.
              onChange={(e: ChangeEvent<HTMLInputElement>) => setColor(e.target.value)}
              onBlur={() => color !== (object.color ?? DEFAULT_COLOR) && patch({ color })}
              className="h-9 w-14 cursor-pointer rounded-md border border-border bg-surface"
            />
          </div>
          {object.color && (
            <Button
              variant="ghost"
              className="px-2 py-1.5 text-xs"
              onClick={() => {
                setColor(DEFAULT_COLOR)
                patch({ color: null })
              }}
            >
              Clear
            </Button>
          )}
        </div>

        <label className="flex items-center gap-2 text-sm text-fg">
          <input
            type="checkbox"
            checked={object.plantable}
            onChange={(e) => patch({ plantable: e.target.checked })}
            className="h-4 w-4 rounded border-border"
          />
          Plantable
        </label>

        {/* Bed grid: only plantable beds place plants, so the plant-snapping grid
            is shown just for them. */}
        {object.plantable && (
          <div className="flex items-end gap-2">
            <div className="flex-1">
              <TextField
                label={`Bed grid (${spacingUnitLabel(unit)})`}
                name="gridSize"
                type="number"
                inputMode="decimal"
                step="any"
                min="0"
                value={gridSize}
                onChange={(e) => setGridSize(e.target.value)}
                onBlur={commitGrid}
              />
            </div>
            <label className="flex h-9 items-center gap-2 whitespace-nowrap text-sm text-fg">
              <input
                type="checkbox"
                checked={object.snapToGrid}
                onChange={(e) => patch({ snapToGrid: e.target.checked })}
                className="h-4 w-4 rounded border-border"
              />
              Snap plants
            </label>
          </div>
        )}

        <TextArea
          label="Notes"
          name="notes"
          rows={2}
          value={notes}
          onChange={(e) => setNotes(e.target.value)}
          onBlur={() => notes !== object.notes && patch({ notes })}
        />
      </fieldset>

      {!readOnly &&
        (confirmingDelete ? (
          <div className="flex items-center gap-2">
            <Button
              variant="danger"
              className="flex-1"
              disabled={del.isPending}
              onClick={() => {
                select(null)
                del.mutate(object.id)
              }}
            >
              Confirm delete
            </Button>
            <Button variant="ghost" onClick={() => setConfirmingDelete(false)}>
              Cancel
            </Button>
          </div>
        ) : (
          <Button variant="ghost" className="text-red-600 dark:text-red-400" onClick={() => setConfirmingDelete(true)}>
            Delete object
          </Button>
        ))}
    </div>
  )
}
