import { useId, useMemo, useState } from 'react'
import type { CSSProperties } from 'react'
import { motion, useReducedMotion } from 'framer-motion'
import { formatCensusMapAxisTick } from '../utils/censusMapTransforms'
import { STATE_CODE_TO_NAME } from '../utils/stateMapping'
import {
  defaultStateSilhouettePublicSrc,
  stateSilhouettePublicSrc,
} from '../utils/wikimediaStateSilhouette'
import { InfoHelpTrigger } from './InfoHelpTrigger'

export type CensusRaceBarRow = {
  id: string
  label: string
  fullName?: string
  value: number
}

function extentForRows(rows: CensusRaceBarRow[]): { min: number; max: number } {
  const vals = rows.map((r) => r.value).filter((x) => Number.isFinite(x))
  if (!vals.length) return { min: 0, max: 1 }
  const rawMin = Math.min(...vals)
  const rawMax = Math.max(...vals)
  const min = Math.min(0, rawMin)
  const max = Math.max(0, rawMax)
  if (min === max) return { min, max: min + 1 }
  return { min, max }
}

function barWidthPercent(value: number, min: number, max: number): number {
  const span = max - min || 1
  return ((value - min) / span) * 100
}

function tickPositionPercent(tickValue: number, min: number, max: number): number {
  const span = max - min || 1
  return ((tickValue - min) / span) * 100
}

const AXIS_TICK_COUNT = 6

function leaderboardSilhouetteSrc(usps: string | null): string | null {
  return stateSilhouettePublicSrc(usps, 'locator') ?? defaultStateSilhouettePublicSrc()
}

function leaderboardSilhouetteAlt(usps: string | null): string {
  if (!usps || usps.length !== 2) {
    return 'United States map silhouette (Wikimedia Commons).'
  }
  const name = STATE_CODE_TO_NAME[usps.toUpperCase()] ?? usps
  return `${name} highlighted on United States map (Wikimedia Commons).`
}

type CensusRaceBarChartProps = {
  rows: CensusRaceBarRow[]
  formatValue: (v: number) => string
  formatBarEnd?: (v: number) => string
  formatAxisTick?: (n: number, valueSpan?: number) => string
  playing?: boolean
  /**
   * Hero silhouette above the #1 row. ``null`` = United States; two-letter USPS = that state.
   * Omit on the national US map to default to the USA silhouette.
   */
  leaderSilhouetteUsps?: string | null
  /** Shown beside the winner row (e.g. selected map year). */
  vintageYear?: string | null
  /** Tooltip for the year badge (ACS window labeling). */
  yearHelp?: string | null
  /** Subtitle under the #1 headline explaining rank direction for the metric. */
  winnerCaption?: string | null
  /** e.g. national benchmark line shown under the top value when ``valueMode`` is raw. */
  nationalContextNote?: string | null
  /** Human-readable metric name in the #1 headline (e.g. “Median household income”). */
  winnerRankLabel?: string | null
  /** Full metric dictionary + ranking copy for the (i) next to the #1 headline. */
  winnerMetricHelp?: string | null
  readingCalloutTitle?: string | null
  readingCalloutLines?: string[] | null
  /** Optional alert when bundled values fail basic sanity checks (stale export column). */
  dataQualityNote?: string | null
  className?: string
  /** When set, the matching row is outlined (paired with map highlight from the parent). */
  selectedRowId?: string | null
  /** Click a bar to select/deselect; parent syncs highlight on the map. */
  onRowClick?: (rowId: string) => void
  /**
   * When set, only the ranked bar rows scroll (winner headline, year badge, and axis stay fixed).
   * Use for long county/place lists; omit on U.S. state strip.
   */
  rowsScrollClassName?: string
}

export function CensusRaceBarChart({
  rows,
  formatValue,
  formatBarEnd,
  formatAxisTick = formatCensusMapAxisTick,
  playing = false,
  leaderSilhouetteUsps,
  vintageYear = null,
  yearHelp = null,
  winnerCaption = null,
  nationalContextNote = null,
  winnerRankLabel = null,
  winnerMetricHelp = null,
  readingCalloutTitle = 'How to read this chart',
  readingCalloutLines = null,
  dataQualityNote = null,
  className = '',
  selectedRowId = null,
  onRowClick,
  rowsScrollClassName,
}: CensusRaceBarChartProps) {
  const reduced = useReducedMotion()
  const readingPanelId = useId()
  const [readingOpen, setReadingOpen] = useState(false)

  const { min, max } = useMemo(() => extentForRows(rows), [rows])
  const valueSpan = max - min
  const ticks = useMemo(() => {
    return Array.from({ length: AXIS_TICK_COUNT }, (_, i) => min + (i / (AXIS_TICK_COUNT - 1)) * (max - min))
  }, [min, max])

  const winner = rows[0]
  const silhouetteUsps = leaderSilhouetteUsps !== undefined ? leaderSilhouetteUsps : null
  const rankWhat = winnerRankLabel?.trim() ? winnerRankLabel.trim() : 'this metric'
  const heroUrl = leaderboardSilhouetteSrc(silhouetteUsps)
  const heroAlt = leaderboardSilhouetteAlt(silhouetteUsps)

  const rowTransition = reduced
    ? { duration: 0.12, ease: 'easeOut' as const }
    : playing
      ? { type: 'spring' as const, stiffness: 260, damping: 34, mass: 0.82 }
      : { type: 'spring' as const, stiffness: 400, damping: 36, mass: 0.72 }

  const fmtEnd = formatBarEnd ?? formatValue

  const widthTransition = reduced
    ? { duration: 0.12, ease: 'easeOut' as const }
    : playing
      ? { type: 'spring' as const, stiffness: 200, damping: 28, mass: 0.88 }
      : { type: 'spring' as const, stiffness: 340, damping: 32, mass: 0.78 }

  /** Rank #, place label, bar, value — keeps the track wide while making order scannable. */
  const gridCols = '2rem minmax(4.5rem, 6.75rem) minmax(0, 1fr) 2.35rem'

  return (
    <div className={`flex min-h-0 w-full min-w-0 max-w-full flex-col ${className}`}>
      {readingCalloutLines?.length ? (
        <div className="mb-2 w-full shrink-0">
          <button
            type="button"
            className="mb-2 inline-flex w-full items-center justify-center gap-1.5 rounded-md border border-amber-200/90 bg-amber-50/60 px-2.5 py-1.5 text-xs font-medium text-amber-950/90 shadow-sm hover:bg-amber-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#354F52] focus-visible:ring-offset-2 sm:w-auto sm:justify-start"
            aria-expanded={readingOpen}
            aria-controls={readingPanelId}
            onClick={() => setReadingOpen((v) => !v)}
          >
            <span>{readingCalloutTitle ?? 'How to read this chart'}</span>
            <span
              className={`text-[10px] text-slate-500 transition-transform ${readingOpen ? 'rotate-180' : ''}`}
              aria-hidden
            >
              ▼
            </span>
          </button>
          {readingOpen ? (
            <div
              id={readingPanelId}
              className="w-full rounded-md border border-amber-100/90 bg-amber-50/45 px-2.5 py-2"
              role="region"
              aria-label={readingCalloutTitle ?? 'How to read this chart'}
            >
              <ul className="space-y-1 text-xs text-slate-800 leading-snug">
                {readingCalloutLines.map((line, i) => (
                  <li key={i} className="flex gap-2">
                    <span className="font-semibold text-amber-700 select-none" aria-hidden>
                      ·
                    </span>
                    <span className="min-w-0 flex-1">{line}</span>
                  </li>
                ))}
              </ul>
            </div>
          ) : null}
        </div>
      ) : null}

      {dataQualityNote ? (
        <div
          className="mb-2 w-full shrink-0 rounded-md border border-rose-200 bg-rose-50/95 px-2.5 py-2 text-sm leading-snug text-rose-950/95 shadow-sm"
          role="alert"
        >
          <span className="text-xs font-semibold uppercase tracking-wide text-rose-900">Data quality — </span>
          {dataQualityNote}
        </div>
      ) : null}

      {winner ? (
        <div className="mb-3 flex w-full min-w-0 shrink-0 flex-col gap-2 border-b border-slate-200 pb-3 sm:flex-row sm:items-start sm:justify-between sm:gap-3">
          <div className="flex min-w-0 flex-1 flex-row items-start gap-3">
            {heroUrl ? (
              <div className="group relative w-[min(5.5rem,28%)] shrink-0 sm:w-20">
                <div className="flex h-[4.5rem] w-full items-center justify-center rounded-lg border border-slate-200/90 bg-gradient-to-b from-slate-50 to-slate-100/90 px-1.5 shadow-sm ring-1 ring-slate-900/5">
                  <img
                    src={heroUrl}
                    alt={heroAlt}
                    className={`max-h-[3.75rem] max-w-full object-contain opacity-90 drop-shadow-[0_2px_4px_rgba(53,79,82,0.12)] transition-[transform,filter] duration-300 ease-out ${
                      reduced ? '' : 'group-hover:scale-[1.03] group-hover:drop-shadow-[0_3px_8px_rgba(53,79,82,0.18)]'
                    }`}
                    loading="lazy"
                    decoding="async"
                  />
                </div>
              </div>
            ) : null}
            <div className="min-w-0 flex-1">
              <div className="flex flex-wrap items-baseline gap-x-1.5 gap-y-0.5">
                <p className="min-w-0 flex-1 text-sm font-extrabold leading-snug tracking-tight text-slate-900 [overflow-wrap:anywhere]">
                  <span className="text-slate-950">{winner.fullName ?? winner.label}</span>{' '}
                  <span className="font-extrabold text-slate-800">ranks #1 for {rankWhat}</span>
                </p>
                {winnerMetricHelp ? (
                  <InfoHelpTrigger
                    topic="Top rank on this metric"
                    align="left"
                    help={winnerMetricHelp}
                    buttonClassName="shrink-0 rounded p-0.5 text-slate-500 hover:bg-slate-100 hover:text-slate-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#354F52]"
                  />
                ) : null}
              </div>
              {winnerCaption ? (
                <div className="mt-1 flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5">
                  <span className="min-w-0 text-[10px] font-semibold leading-snug text-slate-600">
                    {winnerCaption}
                  </span>
                  <span className="shrink-0 whitespace-nowrap font-extrabold tabular-nums text-xs leading-none text-slate-800">
                    {formatValue(winner.value)}
                  </span>
                </div>
              ) : (
                <div className="mt-1 whitespace-nowrap font-extrabold tabular-nums text-xs leading-none text-slate-800">
                  {formatValue(winner.value)}
                </div>
              )}
              {nationalContextNote ? (
                <p className="mt-1.5 text-[10px] leading-snug text-slate-600">{nationalContextNote}</p>
              ) : null}
            </div>
          </div>
          {vintageYear != null && vintageYear !== '' ? (
            <div className="census-map-year-badge flex shrink-0 flex-row items-center gap-1.5 self-start rounded-xl border border-slate-300 bg-white px-2.5 py-1 shadow-md sm:ml-auto">
              <div className="flex flex-row items-baseline gap-2">
                <span className="text-[9px] font-bold uppercase tracking-wider text-slate-500">Year</span>
                <span className="text-xl font-extrabold tabular-nums leading-none text-slate-900">{vintageYear}</span>
              </div>
              {yearHelp ? <InfoHelpTrigger help={yearHelp} topic="ACS year" align="right" /> : null}
            </div>
          ) : null}
        </div>
      ) : null}

      <div
        className={
          rowsScrollClassName?.trim()
            ? `flex min-h-0 flex-col gap-2 overflow-x-hidden overflow-y-auto overscroll-contain py-0.5 [scrollbar-gutter:stable] ${rowsScrollClassName.trim()}`
            : 'flex flex-col gap-2 overflow-x-hidden overflow-y-visible py-0.5'
        }
      >
        {rows.map((r, rowIdx) => {
          const pct = barWidthPercent(r.value, min, max)
          const rankN = rowIdx + 1
          const tip = `#${rankN} — ${rankWhat}: ${formatValue(r.value)} — ${r.fullName ?? r.label}`
          const selected = selectedRowId != null && selectedRowId === r.id
          return (
            <motion.div
              key={r.id}
              layout={!reduced}
              title={tip}
              aria-pressed={onRowClick ? selected : undefined}
              transition={rowTransition}
              role={onRowClick ? 'button' : undefined}
              tabIndex={onRowClick ? 0 : undefined}
              onClick={onRowClick ? () => onRowClick(r.id) : undefined}
              onKeyDown={
                onRowClick
                  ? (e) => {
                      if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault()
                        onRowClick(r.id)
                      }
                    }
                  : undefined
              }
              className={`grid w-full max-w-full min-w-0 shrink-0 items-center gap-x-2 rounded-md outline-none ${
                onRowClick ? 'cursor-pointer hover:bg-slate-50/90 focus-visible:ring-2 focus-visible:ring-[#354F52] focus-visible:ring-offset-1' : ''
              } ${
                selected
                  ? 'bg-amber-50 ring-2 ring-amber-600/90 ring-inset shadow-[inset_0_0_0_1px_rgba(217,119,6,0.25)]'
                  : ''
              }`}
              style={{ gridTemplateColumns: gridCols }}
            >
              <div className="justify-self-end text-[10px] font-extrabold tabular-nums leading-none text-slate-500">
                #{rankN}
              </div>
              <div className="min-w-0 justify-self-stretch text-right text-[11px] font-semibold leading-snug text-slate-900 break-words hyphens-auto [overflow-wrap:anywhere]">
                <span className="inline-flex min-w-0 flex-col items-end gap-0.5">
                  <span>{r.label}</span>
                  {selected ? (
                    <span className="text-[9px] font-bold uppercase tracking-wide text-amber-800">Selected</span>
                  ) : null}
                </span>
              </div>
              <div className="relative h-[22px] min-h-[22px] min-w-0 w-full max-w-full overflow-hidden rounded-md bg-slate-100 ring-1 ring-slate-200/80">
                <motion.div
                  layout={false}
                  className="h-full rounded-r-md bg-[#3d5c55] shadow-inner"
                  initial={false}
                  animate={{ width: `${Math.max(0, Math.min(100, pct))}%` }}
                  transition={widthTransition}
                />
              </div>
              <div className="min-w-0 justify-self-end text-right text-[8.5px] font-bold tabular-nums leading-none text-slate-700">
                {fmtEnd(r.value)}
              </div>
            </motion.div>
          )
        })}
      </div>

      <div
        className="mt-2 grid w-full max-w-full min-w-0 shrink-0 gap-x-2 border-t border-slate-200 bg-white pt-2"
        style={{ gridTemplateColumns: gridCols }}
      >
        <div aria-hidden className="min-w-0" />
        <div aria-hidden className="min-w-0" />
        <div className="relative min-h-[2.35rem] min-w-0 max-w-full">
          <div className="pointer-events-none absolute inset-x-0 top-0 border-b border-slate-500" />
          {ticks.map((t, i) => {
            const n = ticks.length
            const isFirst = i === 0
            const isLast = i === n - 1
            const leftPct = tickPositionPercent(t, min, max)
            const style: CSSProperties = isFirst
              ? { left: 0, transform: 'none' }
              : isLast
                ? { left: '100%', transform: 'translateX(-100%)' }
                : { left: `${leftPct}%`, transform: 'translateX(-50%)' }
            const alignItems: 'flex-start' | 'flex-end' | 'center' = isFirst ? 'flex-start' : isLast ? 'flex-end' : 'center'
            const textClass = isFirst ? 'text-left' : isLast ? 'text-right' : 'text-center'
            return (
              <div key={i} className="absolute top-0 flex flex-col" style={{ ...style, alignItems }}>
                <span className="h-2 w-px shrink-0 bg-slate-600" aria-hidden />
                <span
                  className={`mt-1 max-w-[3.75rem] text-[9px] font-semibold tabular-nums leading-tight text-slate-600 ${textClass}`}
                >
                  {formatAxisTick(t, valueSpan)}
                </span>
              </div>
            )
          })}
        </div>
        <div aria-hidden />
      </div>
    </div>
  )
}
