import { memo, type KeyboardEvent, type PointerEvent } from 'react'
import { objectTransform } from './shared'
import { kindDef, objectDisplayName } from './kinds'
import type { EditorObject } from './types'

const DEFAULT_FILL = '#8a8a8a'

// Default fills by kind (overridable per object via object.color). Muted, earthy
// tones so plops (added in #15) read clearly on top.
const kindColors: Record<string, string> = {
  bed: '#8a6d4b',
  grow_bag: '#9c7a52',
  container: '#6b7f8a',
  in_ground: '#7a6a4a',
  tree: '#4f7a4f',
  path: '#b8b0a0',
  structure: DEFAULT_FILL,
}

// Label font size = this fraction of the object's smaller side, clamped to a
// readable cm range. Corner radius = this fraction of the smaller half-side.
const LABEL_FONT_FACTOR = 0.28
const LABEL_FONT_MIN_CM = 8
const LABEL_FONT_MAX_CM = 40
const CORNER_RADIUS_FACTOR = 0.06

function fillFor(o: EditorObject): string {
  return o.color ?? kindColors[o.kind] ?? DEFAULT_FILL
}

/**
 * One garden object in world (cm) space: a centered rect or ellipse (a circle
 * when width == height), rotated about its center, with the object's name. The
 * parent world <g> applies the viewport scale, so geometry is authored in cm and
 * strokes use vector-effect=non-scaling-stroke to stay a constant pixel width at
 * any zoom. memo'd so a pan/zoom (which only changes the world <g> transform)
 * doesn't re-render every object. Full move/resize/rotate come in #11.
 */
export const ObjectShape = memo(function ObjectShape({
  object,
  selected,
  onSelect,
}: {
  object: EditorObject
  selected: boolean
  onSelect: (id: number) => void
}) {
  const fill = fillFor(object)
  const halfW = Math.max(0, object.widthCm / 2)
  const halfH = Math.max(0, object.heightCm / 2)
  const fontCm = Math.max(
    LABEL_FONT_MIN_CM,
    Math.min(LABEL_FONT_MAX_CM, Math.min(object.widthCm, object.heightCm) * LABEL_FONT_FACTOR),
  )

  function handleDown(e: PointerEvent) {
    e.stopPropagation() // don't let the canvas treat this as an empty-space pan/deselect
    onSelect(object.id)
  }

  // Keyboard path into selection (#84): the arrow-key nudge handler already
  // exists but only ever acted on a pointer selection, so it was unreachable
  // without a mouse. Enter/Space on a focused object selects it, which is the
  // step that was missing.
  function handleKey(e: KeyboardEvent) {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault()
      e.stopPropagation()
      onSelect(object.id)
    }
  }

  const stroke = selected ? '#2f7a3e' : '#00000033'
  const strokeWidth = selected ? 2 : 1

  // A concise accessible name: the object's label plus its kind's canonical
  // label, e.g. "North Bed, In-ground" — reusing kindDef so it never diverges
  // from what the UI shows (an ad-hoc kind.replace() gave "in ground"). The
  // dimensions aren't included; they need the garden's unit context this
  // component doesn't hold, so they're a follow-up.
  const kindLabel = kindDef(object.kind)?.label ?? object.kind
  const label = `${objectDisplayName(object)}, ${kindLabel}`

  // Keyboard focus needs to be VISIBLE — that's the point of making the canvas
  // keyboard-reachable. The `object-shape` class carries a :focus-visible rule
  // (styles/index.css) that draws a dashed ring; :focus-visible means it shows
  // for keyboard focus but NOT a mouse click, which is exactly what we want. CSS
  // rather than React state because onFocus on an SVG <g> is unreliable and a
  // presentation attribute is overridden by any CSS rule.
  return (
    <g
      className="object-shape"
      transform={objectTransform(object)}
      onPointerDown={handleDown}
      onKeyDown={handleKey}
      role="button"
      tabIndex={0}
      aria-label={label}
      // aria-current, not aria-pressed: selecting an object isn't a toggle (a
      // toggle is what aria-pressed means). aria-current marks it as the active
      // item among the objects. Omitted, not "false", when unselected.
      aria-current={selected || undefined}
      style={{ cursor: 'pointer' }}
    >
      {object.shape === 'circle' ? (
        <ellipse
          cx={0}
          cy={0}
          rx={halfW}
          ry={halfH}
          fill={fill}
          fillOpacity={0.85}
          stroke={stroke}
          strokeWidth={strokeWidth}
          vectorEffect="non-scaling-stroke"
        />
      ) : (
        <rect
          x={-halfW}
          y={-halfH}
          width={halfW * 2}
          height={halfH * 2}
          rx={Math.min(halfW, halfH) * CORNER_RADIUS_FACTOR}
          fill={fill}
          fillOpacity={0.85}
          stroke={stroke}
          strokeWidth={strokeWidth}
          vectorEffect="non-scaling-stroke"
        />
      )}

      {object.name && (
        <text
          x={0}
          y={0}
          fontSize={fontCm}
          textAnchor="middle"
          dominantBaseline="central"
          fill="#ffffff"
          style={{ pointerEvents: 'none', userSelect: 'none' }}
        >
          {object.name}
        </text>
      )}
    </g>
  )
})
