import { useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import { sankey as d3Sankey, sankeyLinkHorizontal } from 'd3-sankey'
import api from '../lib/api'

// "Follow the money" — a tabbed Sankey flow hero (Money Moves lens on the
// homepage). Three lenses, ALL traced to the warehouse via GET /api/money-flow:
//   spending (real money decisions) · grants (990 Schedule I) · economy (real
//   nonprofit revenue decomposition). No fabricated numbers: a lens with no real
//   data renders an honest empty state; we never draw an invented diagram.

interface FlowMeta {
  title: string
  subtitle?: string | null
  url?: string | null
  source_label?: string | null
}
interface FlowNode {
  name: string
  /** Per-node drill-down target (grantor or grantee). null = not clickable. */
  url?: string | null
}
interface FlowLink {
  source: number
  target: number
  value: number
  value_label: string
  meta: FlowMeta
}
interface FlowLens {
  accent: string
  head_amount: string
  head_label: string
  /** Aggregate drill-down for the headline figure (decisions / grants / nonprofits). */
  head_url?: string | null
  count_label: string
  nodes: FlowNode[]
  links: FlowLink[]
  placeholder: boolean
}
interface MoneyFlowResp {
  location_label: string
  lenses: { spending: FlowLens; grants: FlowLens; economy: FlowLens; government: FlowLens }
}

export interface FollowTheMoneyProps {
  embedded?: boolean
  stateCode?: string
  city?: string
  /** County name — used by the Government-budget lens to stack county + school district. */
  county?: string
  national?: boolean
  /** Free-text search; filters spending/contract/grant flows server-side. */
  query?: string
  /** WHEN selector value (month|quarter|year|fiveyear|all|auto) — scopes the spending lens by date. */
  window?: string
}

type LensKey = 'spending' | 'grants' | 'economy' | 'government'
const TABS: { key: LensKey; label: string }[] = [
  { key: 'government', label: 'Government budget' },
  { key: 'spending', label: 'Public spending' },
  { key: 'grants', label: 'Grants' },
  { key: 'economy', label: 'Nonprofit economy' },
]

// Plain-language explainer shown under the headline for each lens — the 990
// revenue buckets in the economy lens especially aren't self-explanatory.
const LENS_BLURB: Record<LensKey, React.ReactNode> = {
  government: (
    <>
      The layers of government you fund — <b className="font-semibold text-gray-700">city</b>,{' '}
      <b className="font-semibold text-gray-700">county</b>, <b className="font-semibold text-gray-700">state</b>, and your{' '}
      <b className="font-semibold text-gray-700">school district</b> — flowing into what they spend it on. Each is
      shown as <b className="font-semibold text-gray-700">your per-resident share</b> (budget ÷ population), so the
      figures compare honestly and the state doesn&rsquo;t dwarf the city. Real U.S. Census finances.
    </>
  ),
  spending: (
    <>Real budget decisions local government made — each flow is one vote, sized by the dollars involved. Click a flow for the decision.</>
  ),
  grants: (
    <>
      Grants nonprofits and foundations gave each other, from IRS 990 Schedule&nbsp;I filings. Money flows
      left (funder) → right (recipient); click a flow for that grant&rsquo;s details.
    </>
  ),
  economy: (
    <>
      Where this area&rsquo;s nonprofits get their money, in the three buckets the IRS&nbsp;990 form uses:{' '}
      <b className="font-semibold text-gray-700">Contributions &amp; grants</b> (donations plus government and
      foundation grants), <b className="font-semibold text-gray-700">Program service revenue</b> (fees they earn
      doing their actual work — tuition, hospital care, tickets, memberships), and{' '}
      <b className="font-semibold text-gray-700">Other revenue</b> (investments, rentals, asset sales). It&rsquo;s a
      snapshot of total sector revenue drawn as a flow — not funder&rarr;recipient transfers.
    </>
  ),
}

const W = 800
const H = 300

// d3-sankey node/link after layout (geometry attached to copies of our data).
type LaidNode = FlowNode & { x0: number; x1: number; y0: number; y1: number; value: number }
type LaidLink = Omit<FlowLink, 'source' | 'target'> & {
  source: LaidNode
  target: LaidNode
  width: number
}

const trunc = (s: string, n: number) => (s.length > n ? s.slice(0, n - 1) + '…' : s)

interface TipState {
  x: number
  y: number
  meta: FlowMeta
  valueLabel: string
  accent: string
}

export default function FollowTheMoney({
  embedded = false,
  stateCode,
  city,
  county,
  national = false,
  query,
  window,
}: FollowTheMoneyProps) {
  const navigate = useNavigate()
  // Lead with the Government-budget lens (the resident's own tax dollars) when a
  // location is known; the decision-based lenses are the deeper dive.
  const [tab, setTab] = useState<LensKey>(stateCode && !national ? 'government' : 'spending')
  const [tip, setTip] = useState<TipState | null>(null)

  const scopedState = national ? undefined : stateCode || undefined
  const scopedCity = national ? undefined : city || undefined
  const scopedCounty = national ? undefined : county || undefined
  const q = query?.trim() || undefined
  // 'auto'/'all' impose no date filter server-side; only send a concrete window.
  const win = window && window !== 'auto' && window !== 'all' ? window : undefined

  const { data, isLoading, isError } = useQuery({
    queryKey: ['money-flow', national, scopedState, scopedCity, scopedCounty, q, win],
    queryFn: () =>
      api
        .get('/money-flow', {
          params: { state: scopedState, city: scopedCity, county: scopedCounty, q, window: win },
        })
        .then((r) => r.data as MoneyFlowResp),
    staleTime: 5 * 60 * 1000,
  })

  const lens = data?.lenses[tab]
  const accent = lens?.accent || '#0d9488'

  // Compute the Sankey layout for the active lens (only when it has real links).
  const laid = useMemo(() => {
    if (!lens || lens.placeholder || lens.links.length === 0) return null
    try {
      const layout = d3Sankey<LaidNode, LaidLink>()
        .nodeWidth(11)
        .nodePadding(22)
        .extent([
          // Wide left gutter (220px) so long left-side names (grantors like
          // "AUBURN UNIVERSITY FOUNDATION") render right-anchored with room to
          // breathe — the wider the gutter, the less the labels get clipped.
          [220, 14],
          // Symmetric 220px right gutter so the two-line target labels — name +
          // dollar value — get the same room. Pulling both gutters in also
          // narrows the middle flow band, trading bar width for text width.
          [W - 220, H - 14],
        ])
      const graph = layout({
        nodes: lens.nodes.map((n) => ({ ...n })) as LaidNode[],
        links: lens.links.map((l) => ({ ...l })) as unknown as LaidLink[],
      })
      const linkPath = sankeyLinkHorizontal<LaidNode, LaidLink>()
      return {
        nodes: graph.nodes as LaidNode[],
        links: (graph.links as LaidLink[]).map((l) => ({ link: l, d: linkPath(l) || '' })),
      }
    } catch {
      return null
    }
  }, [lens])

  const onSvgLeave = () => setTip(null)
  const onLinkMove = (e: React.MouseEvent, l: LaidLink) =>
    setTip({ x: e.clientX, y: e.clientY, meta: l.meta, valueLabel: l.value_label, accent })
  const onLinkClick = (l: LaidLink) => {
    if (l.meta.url) navigate(l.meta.url)
  }

  const header = (
    <div className="mb-4 flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
      <div>
        {!embedded && (
          <h2 className="text-3xl font-bold text-[#0f2b2b] md:text-4xl" style={{ fontFamily: "'Fraunces', serif" }}>
            Follow the money
          </h2>
        )}
        <p className={`max-w-2xl text-sm text-gray-500 ${embedded ? '' : 'mt-2 md:text-base'}`}>
          Public money and grants flow from funders into projects, nonprofits, and vendors —{' '}
          {data?.location_label ? (
            <span className="font-medium text-gray-700">{data.location_label}</span>
          ) : (
            'one flow'
          )}
          , three lenses.
        </p>
      </div>
    </div>
  )

  const body = (
    <>
      {header}
      <div className="overflow-hidden rounded-2xl border border-gray-200 bg-white">
        <div className="h-1 w-full transition-colors" style={{ background: accent }} />
        {/* tabs */}
        <div className="flex gap-1 border-b border-gray-200 px-4 pt-3">
          {TABS.map((t) => {
            const on = t.key === tab
            return (
              <button
                key={t.key}
                type="button"
                onClick={() => setTab(t.key)}
                className={`-mb-px border-b-2 px-3 pb-3 pt-2 text-sm font-semibold transition-colors ${
                  on ? 'text-[#0f2b2b]' : 'border-transparent text-gray-400 hover:text-gray-700'
                }`}
                style={on ? { borderColor: accent } : undefined}
              >
                {t.label}
              </button>
            )
          })}
        </div>

        {/* flow header */}
        <div className="flex items-baseline justify-between gap-3 px-5 pb-1 pt-3">
          <div className="text-[13px] text-gray-500">
            {lens && !lens.placeholder ? (
              lens.head_url ? (
                <button
                  type="button"
                  onClick={() => navigate(lens.head_url!)}
                  title="See the full breakdown"
                  className="group inline-flex items-baseline gap-1 text-left text-gray-500 transition-colors hover:text-gray-800"
                >
                  <b className="text-[15px] font-bold text-[#0f2b2b] group-hover:underline">{lens.head_amount}</b>
                  <span>{lens.head_label}</span>
                  <span aria-hidden className="text-gray-400 transition-transform group-hover:translate-x-0.5">
                    &rarr;
                  </span>
                </button>
              ) : (
                <>
                  <b className="text-[15px] font-bold text-[#0f2b2b]">{lens.head_amount}</b> {lens.head_label}
                </>
              )
            ) : (
              <span className="text-gray-400">—</span>
            )}
          </div>
          <div className="font-mono text-[11.5px] text-gray-400">{lens?.count_label}</div>
        </div>

        {/* plain-language explainer for the active lens */}
        {lens && !lens.placeholder && (
          <p className="px-5 pb-1 pt-0.5 text-[12px] leading-relaxed text-gray-500">{LENS_BLURB[tab]}</p>
        )}

        {/* flow area */}
        <div className="px-3 pb-4 pt-1">
          {isLoading ? (
            <div className="h-[260px] animate-pulse rounded-xl bg-gray-50" />
          ) : isError ? (
            <div className="flex h-[200px] items-center justify-center rounded-xl border border-dashed border-gray-200 px-6 text-center text-sm text-gray-400">
              Couldn&rsquo;t load the money flow right now.{' '}
              <b className="ml-1 text-gray-600">Please try again.</b>
            </div>
          ) : !laid ? (
            <div className="flex h-[200px] items-center justify-center rounded-xl border border-dashed border-gray-200 px-6 text-center text-sm text-gray-400">
              No {TABS.find((t) => t.key === tab)?.label.toLowerCase()} flows available for this area yet.
            </div>
          ) : (
            // key={tab} remounts the diagram on lens switch so the open
            // animation (links draw in left→right, then bars + labels rise)
            // replays each time.
            <svg key={tab} viewBox={`0 0 ${W} ${H}`} width="100%" role="img" aria-label="Money flow Sankey diagram">
              <style>{`
                @keyframes ftm-draw { to { stroke-dashoffset: 0; } }
                @keyframes ftm-rise { from { opacity: 0; transform: translateY(7px); } to { opacity: 1; transform: none; } }
                @keyframes ftm-flow { to { stroke-dashoffset: -260; } }
                @keyframes ftm-fadein { to { opacity: 1; } }
                .ftm-link { stroke-dasharray: 1; stroke-dashoffset: 1; animation: ftm-draw 0.9s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
                .ftm-node { opacity: 0; animation: ftm-rise 0.5s ease-out forwards; }
                /* Perpetual "money in motion" dashes streaming along each flow,
                   layered over the static band once it has drawn in. */
                .ftm-flow { opacity: 0; animation: ftm-fadein 0.4s ease-out forwards, ftm-flow 3.5s linear infinite; }
                @media (prefers-reduced-motion: reduce) {
                  .ftm-link { animation: none; stroke-dashoffset: 0; }
                  .ftm-node { animation: none; opacity: 1; transform: none; }
                  .ftm-flow { display: none; }
                }
              `}</style>
              {laid.links.map(({ link, d }, i) => (
                <g key={i}>
                  <path
                    className="ftm-link"
                    pathLength={1}
                    d={d}
                    fill="none"
                    stroke={accent}
                    strokeOpacity={0.32}
                    strokeWidth={Math.max(2, link.width)}
                    style={{ cursor: link.meta.url ? 'pointer' : 'default', animationDelay: `${i * 70}ms` }}
                    onMouseMove={(e) => onLinkMove(e, link)}
                    onMouseLeave={onSvgLeave}
                    onClick={() => onLinkClick(link)}
                  />
                  {/* white dash stream — pointer-events pass through to the band */}
                  <path
                    className="ftm-flow"
                    d={d}
                    fill="none"
                    stroke="#fff"
                    strokeWidth={2}
                    strokeOpacity={0.55}
                    strokeDasharray="2 11"
                    pointerEvents="none"
                    style={{ animationDelay: `${i * 70 + 650}ms, ${i * 70 + 650}ms` }}
                  />
                </g>
              ))}
              {laid.nodes.map((n, i) => {
                const leftSide = n.x0 < W / 2
                const isSource = i === 0 && tab !== 'grants'
                // Right-side (target) nodes still resolve their feeding link for the
                // hover tooltip + two-line value label.
                const feed = leftSide ? undefined : lensTargetLink(lens, n)
                // Drill-down comes from the node's OWN url now (set server-side on
                // both sides), so left-side grantor nodes are clickable too.
                const nodeUrl = n.url
                const clickable = !!nodeUrl
                const labelX = leftSide ? n.x0 - 8 : n.x1 + 8
                const cy = (n.y0 + n.y1) / 2
                return (
                  <g
                    key={i}
                    className="ftm-node"
                    style={{ cursor: clickable ? 'pointer' : 'default', animationDelay: `${260 + i * 55}ms` }}
                    onMouseMove={
                      feed
                        ? (e) =>
                            setTip({
                              x: e.clientX,
                              y: e.clientY,
                              meta: feed.meta,
                              valueLabel: feed.value_label,
                              accent,
                            })
                        : undefined
                    }
                    onMouseLeave={feed ? onSvgLeave : undefined}
                    onClick={clickable ? () => navigate(nodeUrl!) : undefined}
                  >
                    <rect
                      x={n.x0}
                      y={n.y0}
                      width={n.x1 - n.x0}
                      height={Math.max(2, n.y1 - n.y0)}
                      rx={2}
                      fill={isSource ? '#44403c' : tab === 'economy' ? '#a78bfa' : '#78716c'}
                    />
                    {leftSide ? (
                      <text
                        x={labelX}
                        y={cy}
                        dy="0.32em"
                        textAnchor="end"
                        fontSize={12}
                        fill="#44403c"
                        style={{ fontFamily: "'DM Sans', sans-serif" }}
                      >
                        {trunc(n.name, 30)}
                      </text>
                    ) : (
                      // Two lines: name on top, the dollar value beneath in the
                      // lens accent. Splitting them keeps the value on-screen
                      // instead of being clipped off the right edge.
                      <text x={labelX} y={cy} textAnchor="start" style={{ fontFamily: "'DM Sans', sans-serif" }}>
                        <tspan x={labelX} dy="-0.25em" fontSize={12} fontWeight={600} fill="#44403c">
                          {trunc(n.name, 30)}
                        </tspan>
                        <tspan
                          x={labelX}
                          dy="1.25em"
                          fontSize={11}
                          fontWeight={700}
                          fill={accent}
                          style={{ fontFamily: "'DM Mono', ui-monospace, monospace" }}
                        >
                          {feed?.value_label || lensValueLabel(lens, n)}
                        </tspan>
                      </text>
                    )}
                  </g>
                )
              })}
            </svg>
          )}
        </div>
      </div>

      <p className="mt-3 text-[12.5px] leading-relaxed text-gray-500">
        Live from the warehouse — spending is real money-flagged decisions, grants are 990 Schedule I
        flows, and the nonprofit-economy tab is a real decomposition of sector revenue (a snapshot drawn
        as a flow), not invented funder&rarr;grantee edges.
      </p>

      {tip && (
        <div
          className="pointer-events-none fixed z-20 max-w-[260px] rounded-lg bg-[#1c1917] px-3 py-2 text-xs leading-snug text-white shadow-lg"
          style={{ left: tip.x + 14, top: tip.y + 14 }}
        >
          <div className="font-semibold">{tip.meta.title}</div>
          {tip.meta.subtitle && <div className="mt-0.5 text-[11px] text-stone-300">{tip.meta.subtitle}</div>}
          <div className="mt-1 flex items-center gap-1.5 font-mono text-[10.5px]">
            <span className="inline-block h-1.5 w-1.5 rounded-full" style={{ background: tip.accent }} />
            {tip.valueLabel}
            {tip.meta.source_label ? ` · ${tip.meta.source_label}` : ''}
          </div>
        </div>
      )}
    </>
  )

  if (embedded) {
    return (
      <div id="follow-the-money" className="scroll-mt-4">
        {body}
      </div>
    )
  }
  return (
    <section id="follow-the-money" className="bg-white px-4 py-16">
      <div className="mx-auto max-w-4xl">{body}</div>
    </section>
  )
}

// The link that feeds a right-side (target) node — its meta.url is the
// drill-down target and its value_label is the node's pre-formatted amount.
function lensTargetLink(lens: FlowLens | undefined, node: LaidNode): FlowLink | undefined {
  if (!lens) return undefined
  const idx = lens.nodes.findIndex((n) => n.name === node.name)
  return lens.links.find((l) => l.target === idx)
}

// Right-side node value label: reuse the feeding link's pre-formatted
// value_label (never re-format numbers on the client).
function lensValueLabel(lens: FlowLens | undefined, node: LaidNode): string {
  return lensTargetLink(lens, node)?.value_label || ''
}
