import type { Href } from 'expo-router'
import type { IconName } from './types'

// Intent-driven menu spec. The screen describes intents; the renderer decides
// placement, overflow demotion, and visual grouping. See MENU_HOLISTIC_V3.md
// §3.1 for the mental model.

export type IntentWeight = 'primary' | 'secondary' | 'tertiary'
export type IntentState = 'idle' | 'busy' | 'success' | 'disabled'

export interface BackIntent {
  kind: 'back'
  id?: string
  label?: string
  // 'close' → x-icon + router.dismiss() for formSheet/modal screens.
  dismissMode?: 'back' | 'close'
  // Used when router.canGoBack() is false (deep-link entry).
  fallbackHref?: Href
  // Override; default behavior is canGoBack-gated dispatch.
  onPress?: () => void
  accessibilityLabel?: string
  accessibilityHint?: string
}

export interface NavIntent {
  kind: 'nav'
  id?: string
  icon: IconName
  label: string
  to: Href
  accessibilityLabel?: string
  accessibilityHint?: string
}

export interface FilterOption {
  value: string
  label: string
  icon?: IconName
}

export interface FilterIntent {
  kind: 'filter'
  id?: string
  value: string
  options: FilterOption[]
  onChange: (value: string) => void
  accessibilityLabel?: string
  // 'segments' (default) — pill-shaped labels in a scroll row.
  // 'icons'             — compact icon-only buttons. Every option must have
  //                       an `icon`; the `label` becomes the a11y label.
  display?: 'segments' | 'icons'
}

export interface SearchIntent {
  kind: 'search'
  id?: string
  placeholder: string
  // 'inline' (default) — the search input lives in the bar itself. Used by
  //   finite client-side lists (blocked accounts, followed tags, ...) where
  //   filtering the rendered list is the user's purpose.
  // 'route'  — tapping the magnifier navigates to `routeTo` (or fires
  //   `onRouteTo`). Used everywhere else to funnel global search to
  //   `/search`.
  mode?: 'inline' | 'route'
  // Inline mode holds state in the bar; consumers wire the input to a local
  // query. Route mode ignores these.
  value?: string
  onChangeText?: (text: string) => void
  routeTo?: Href
  // Escape hatch when `routeTo` can't be expressed as a static Href (e.g.
  // must close the keyboard or build params imperatively).
  onRouteTo?: () => void
  // Keep the bar visible while the keyboard is up (the global /search screen
  // and long finite-list filters opt in).
  keepVisibleOnKeyboard?: boolean
  accessibilityLabel?: string
  accessibilityHint?: string
}

export interface FeedbackSpec {
  success?: string
  error?: string
}

export interface ActIntent {
  kind: 'act'
  id: string
  icon: IconName
  label: string
  onPress: () => void | Promise<void>
  weight?: IntentWeight
  state?: IntentState
  destructive?: boolean
  confirm?: string
  confirmTitle?: string
  confirmLabel?: string
  toggled?: boolean
  feedback?: FeedbackSpec
  accessibilityLabel?: string
  accessibilityHint?: string
}

export interface CommitIntent extends Omit<ActIntent, 'kind' | 'weight'> {
  kind: 'commit'
}

export interface MenuOverflowItemIntent {
  id: string
  label: string
  icon?: IconName
  onPress: () => void | Promise<void>
  destructive?: boolean
  disabled?: boolean
  toggled?: boolean
  confirm?: string
  confirmTitle?: string
  confirmLabel?: string
  feedback?: FeedbackSpec
}

export interface MenuIntent {
  kind: 'menu'
  id?: string
  items: MenuOverflowItemIntent[]
  accessibilityLabel?: string
}

export type Intent =
  | BackIntent
  | NavIntent
  | FilterIntent
  | SearchIntent
  | ActIntent
  | CommitIntent
  | MenuIntent

export interface IntentMenuSpec {
  title?: string
  intents: Intent[]
  // Opt in per-screen to scroll-minimize. Default matches Apple: OFF.
  scrollMinimize?: boolean
}

// ──────────────────────────────────────────────────────────────────────────
// Placement — a pure function the renderer consumes. Testable without JSX.
// ──────────────────────────────────────────────────────────────────────────

export interface PlacedIntents {
  back?: BackIntent
  filter?: FilterIntent
  search?: SearchIntent
  leading: (NavIntent | ActIntent)[]
  trailing: (NavIntent | ActIntent)[]
  commit?: CommitIntent | ActIntent
  menu?: MenuIntent
}

// How many non-commit trailing slots we try to show before demoting into the
// overflow menu. Narrower bars get fewer slots; commit+menu each take one.
function trailingCapacity(barWidthPt: number | undefined): number {
  if (barWidthPt == null) return 2
  if (barWidthPt < 320) return 1
  if (barWidthPt < 380) return 2
  if (barWidthPt < 480) return 3
  return 4
}

export function placeIntents(
  intents: Intent[],
  opts?: { barWidthPt?: number }
): PlacedIntents {
  let back: BackIntent | undefined
  let filter: FilterIntent | undefined
  let search: SearchIntent | undefined
  let commit: CommitIntent | ActIntent | undefined
  let menu: MenuIntent | undefined
  const leading: (NavIntent | ActIntent)[] = []
  const actsAndNavs: (NavIntent | ActIntent)[] = []
  const demotedFromActs: MenuOverflowItemIntent[] = []

  for (const intent of intents) {
    switch (intent.kind) {
      case 'back':
        if (!back) back = intent
        break
      case 'filter':
        if (!filter) filter = intent
        break
      case 'search':
        if (!search) search = intent
        break
      case 'commit':
        if (!commit) commit = intent
        break
      case 'menu':
        if (!menu) {
          menu = intent
        } else {
          menu = { ...menu, items: [...menu.items, ...intent.items] }
        }
        break
      case 'act':
        if (intent.weight === 'primary' && !commit) {
          commit = intent
        } else {
          actsAndNavs.push(intent)
        }
        break
      case 'nav':
        actsAndNavs.push(intent)
        break
    }
  }

  // Narrow-bar collapse: when both filter and search are present and the bar
  // is narrow, filter demotes into the overflow menu.
  const isNarrow = (opts?.barWidthPt ?? 400) < 380
  if (isNarrow && filter && search) {
    for (const opt of filter.options) {
      demotedFromActs.push({
        id: `filter:${opt.value}`,
        label: opt.label,
        icon: opt.icon,
        toggled: filter.value === opt.value,
        onPress: () => filter?.onChange(opt.value),
      })
    }
    filter = undefined
  }

  const capacity = trailingCapacity(opts?.barWidthPt)
  const trailing = actsAndNavs.slice(0, capacity)
  const overflowing = actsAndNavs.slice(capacity)
  for (const item of overflowing) {
    if (item.kind === 'act') {
      demotedFromActs.push({
        id: item.id,
        label: item.label,
        icon: item.icon,
        onPress: item.onPress,
        destructive: item.destructive,
        toggled: item.toggled,
        confirm: item.confirm,
        confirmTitle: item.confirmTitle,
        confirmLabel: item.confirmLabel,
        feedback: item.feedback,
        disabled: item.state === 'disabled' || item.state === 'busy',
      })
    } else {
      demotedFromActs.push({
        id: item.id ?? `nav:${item.label}`,
        label: item.label,
        icon: item.icon,
        onPress: () => {},
      })
    }
  }

  if (demotedFromActs.length > 0) {
    menu = menu
      ? { ...menu, items: [...menu.items, ...demotedFromActs] }
      : { kind: 'menu', items: demotedFromActs }
  }

  return { back, filter, search, leading, trailing, commit, menu }
}

// ──────────────────────────────────────────────────────────────────────────
// Canonical back — pure function so we can unit-test it without a Router.
// ──────────────────────────────────────────────────────────────────────────

export interface CanonicalBackDeps {
  canGoBack: () => boolean
  canDismiss: () => boolean
  back: () => void
  dismiss: () => void
  replace: (href: Href) => void
  fallbackHref: Href
}

export function canonicalBack(intent: BackIntent, deps: CanonicalBackDeps): void {
  if (intent.onPress) {
    intent.onPress()
    return
  }
  if (intent.dismissMode === 'close' && deps.canDismiss()) {
    deps.dismiss()
    return
  }
  if (deps.canGoBack()) {
    deps.back()
    return
  }
  deps.replace(intent.fallbackHref ?? deps.fallbackHref)
}
