import { useMemo } from 'react'
import { useWidgetId, useWidgetShallow, type WidgetState } from '../stores'
import { CategoryUI } from './category-ui'
import type {
  CategoryKey,
  CategoryLabels,
  CategorySeriesConfig,
  CategorySize,
  CategoryWidgetData,
} from './types'

interface CategorySlice {
  data: CategoryWidgetData
  rawData: CategoryWidgetData | undefined
  formatter?: (value: number) => string
  labelFormatter?: (value: string | number) => string | number
}

const categorySelector = (s: WidgetState): CategorySlice => ({
  data: (s.data ?? []) as CategoryWidgetData,
  rawData: (s.rawData ?? undefined) as CategoryWidgetData | undefined,
  formatter: s.formatter,
  labelFormatter: s.labelFormatter,
})

function maxFromCategoryData(d: CategoryWidgetData | undefined): number {
  if (!d) return 0
  let m = 0
  for (const series of d) {
    for (const item of series) if (item.value > m) m = item.value
  }
  return m
}

export interface CategoryProps {
  /** Currently-selected category names. Destination-owned. */
  selection?: readonly CategoryKey[]
  /** Fires when a row is clicked. Consumer updates the destination's store. */
  onSelectionChange?: (next: readonly CategoryKey[]) => void
  /** Per-series metadata. Enables the legend + overrides palette per index. */
  series?: readonly CategorySeriesConfig[]
  /**
   * Cap visible rows; overflow folds into an "Others <count>" row. Default 20
   * (when omitted). Pass `0` to swap the cap for a scrollable viewport
   * (composers use this when the user opens the SearcherToggle —
   * `maxItems = searcherOpen ? 0 : userMaxItems`). Pass `null` to disable
   * the cap entirely without adding a scroll viewport.
   */
  maxItems?: number | null
  /** Labels for the "Other" overflow row. */
  labels?: CategoryLabels
  /**
   * When provided, the "Other" overflow row becomes a button that fires this
   * callback. Composers wire it to expand the widget (e.g. flip the
   * `show-all` flag) so every category is shown. Forwarded to
   * {@link CategoryUI}.
   */
  onShowAll?: () => void
  /**
   * Manual override for the bar-width denominator. When omitted, the
   * bridge auto-fills from the widget store's `rawData` so bar widths
   * stay coherent across data transforms (e.g., the Searcher filtering
   * rows in and out — bars don't rescale just because the larger rows
   * got hidden). Pass an explicit number to fix the denominator
   * regardless of the data.
   */
  maxOverride?: number
  /**
   * Visual density of the bar primitive. `'small'` (default) keeps the
   * historical 4px-tall pill; `'medium'` switches to a 12px-tall track
   * with a 2px corner radius. Forwarded as-is to {@link CategoryUI}.
   */
  size?: CategorySize
  /**
   * Multi-series stacked mode. Forwarded as-is to {@link CategoryUI}.
   * Composers typically wire this from
   * `useTransformEnabled(id, 'stack-toggle')`. No-op for single-series.
   */
  stacked?: boolean
}

/**
 * Stateful Category bridge — reads `data`, `formatter`, and
 * `labelFormatter` (post-pipeline) from the per-widget store and
 * forwards them to the pure {@link CategoryUI}. `selection` and
 * `onSelectionChange` follow the destination-owned principle: the
 * consumer keeps the list in their own store and passes it through.
 */
export function Category({
  selection,
  onSelectionChange,
  series,
  maxItems,
  labels,
  onShowAll,
  maxOverride,
  size,
  stacked,
}: CategoryProps) {
  const id = useWidgetId()
  const slice = useWidgetShallow(id, categorySelector)
  // Auto-fill `maxOverride` from `rawData` so bar widths stay coherent
  // across data transforms (e.g., the Searcher filtering rows in/out).
  // Consumer's explicit value always wins; we only fill when omitted
  // AND `rawData` has at least one positive value (CategoryUI's
  // `maxOverride > 0` guard would reject 0 anyway, but skipping is
  // cleaner — it lets the live fallback path handle the empty case).
  const naturalMax = useMemo(
    () => maxFromCategoryData(slice.rawData),
    [slice.rawData],
  )
  const effectiveMaxOverride =
    maxOverride ?? (naturalMax > 0 ? naturalMax : undefined)
  return (
    <CategoryUI
      data={slice.data}
      formatter={slice.formatter}
      labelFormatter={slice.labelFormatter}
      selection={selection}
      onSelectionChange={onSelectionChange}
      series={series}
      maxItems={maxItems}
      labels={labels}
      onShowAll={onShowAll}
      maxOverride={effectiveMaxOverride}
      size={size}
      stacked={stacked}
    />
  )
}
