import { createContext, useContext, useEffect, useLayoutEffect, useState, type ComponentType, type ReactNode } from 'react'
import { motion } from 'framer-motion'
import { AlertCircle, Check } from '../../lib/icons'
import type { AppSettings } from '../../api/types'
import { usePreferencesStore } from '../../stores/preferences-store'

/* Shared types ───────────────────────────────────────────── */

export type PrefsLike = ReturnType<typeof usePreferencesStore.getState>

export type BoolPrefKey = {
  [K in keyof AppSettings]: AppSettings[K] extends boolean ? K : never
}[keyof AppSettings]

/* Search context ─────────────────────────────────────────── */

export interface SettingsSearchValue {
  query: string
  matches: (haystack: string) => boolean
}

export const SettingsSearchContext = createContext<SettingsSearchValue>({
  query: '',
  matches: () => true,
})

export function useSettingsSearch() {
  return useContext(SettingsSearchContext)
}

/* Section + SubHeading ───────────────────────────────────── */

export function Section({
  id,
  title,
  description,
  children,
}: {
  id: string
  title: string
  description?: string
  children: ReactNode
}) {
  const { query, matches } = useSettingsSearch()
  const titleMatches = matches(`${title} ${description || ''}`)
  const [hasVisibleRow, setHasVisibleRow] = useState(true)
  // useLayoutEffect runs after DOM mutations but before paint, so we read
  // the up-to-date count of visible rows in the same frame the query
  // changed - no flash where the section appears or stays hidden
  // incorrectly while a deferred rAF catches up.
  useLayoutEffect(() => {
    if (!query) {
      setHasVisibleRow(true)
      return
    }
    const root = document.querySelector(`[data-settings-section="${title}"]`)
    if (!root) return
    const visible = root.querySelectorAll('[data-settings-row]').length
    setHasVisibleRow(visible > 0)
  }, [query, title])
  const hidden = !!query && !titleMatches && !hasVisibleRow
  return (
    <section
      id={id}
      data-settings-section={title}
      className={`scroll-mt-4 ${hidden ? 'hidden' : ''}`}
    >
      <div className="mb-3.5">
        <div className="flex items-center gap-2 mb-1">
          <span className="w-1 h-3.5 rounded-full bg-accent" />
          <h2 className="text-[11px] font-semibold text-text-2 uppercase tracking-[0.14em]">{title}</h2>
        </div>
        {description && <p className="text-[11.5px] text-text-3 ml-3">{description}</p>}
      </div>
      <div className="rounded-xl bg-elevated/30 border border-border overflow-hidden">
        <div className="divide-y divide-border/50 px-4">{children}</div>
      </div>
    </section>
  )
}

/**
 * Small uppercase divider for chunking long toggle lists. Returns null
 * during search so the divide-y separator cascade collapses cleanly
 * between visible Rows.
 */
export function SubHeading({ label }: { label: string }) {
  const { query } = useSettingsSearch()
  if (query) return null
  return (
    <div className="pt-5 pb-1">
      <p className="text-[10px] uppercase tracking-[0.18em] font-semibold text-text-4">
        {label}
      </p>
    </div>
  )
}

/* Row primitive ──────────────────────────────────────────── */

export function Row({
  label,
  hint,
  danger,
  children,
}: {
  label: string
  hint?: string
  danger?: boolean
  children: ReactNode
}) {
  const { query, matches } = useSettingsSearch()
  const haystack = `${label} ${hint || ''}`
  const visible = matches(haystack)
  if (query && !visible) return null
  return (
    <div data-settings-row className="flex items-center justify-between gap-6 py-4 min-h-[60px]">
      <div className="min-w-0">
        <p className={`text-[13px] font-medium tracking-tight ${danger ? 'text-text-1' : 'text-text-1'}`}>
          {label}
        </p>
        {hint && <p className="text-[11.5px] text-text-3 mt-0.5 leading-snug">{hint}</p>}
      </div>
      <div className="shrink-0 flex items-center">{children}</div>
    </div>
  )
}

/* Controls ───────────────────────────────────────────────── */

export function Input({
  value,
  onChange,
  placeholder,
  type = 'text',
  width = 'w-32',
}: {
  value: string
  onChange: (v: string) => void
  placeholder?: string
  type?: string
  width?: string
}) {
  return (
    <input
      type={type}
      value={value}
      onChange={e => onChange(e.target.value)}
      placeholder={placeholder}
      className={`h-9 px-3 ${width} bg-void/50 hover:bg-void/70 rounded-md text-[12.5px] text-text-1 placeholder:text-text-4 border border-border focus:outline-none focus:border-accent/50 focus:ring-2 focus:ring-accent/15 transition-all duration-200 font-mono tabular-nums`}
    />
  )
}

export function Toggle({ value, onChange }: { value: boolean; onChange: (v: boolean) => void }) {
  return (
    <button
      onClick={() => onChange(!value)}
      role="switch"
      aria-checked={value}
      className={`relative w-10 h-6 rounded-full transition-colors duration-200 focus-ring ${
        value ? 'bg-accent' : 'bg-elevated border border-border'
      }`}
    >
      <span
        className={`absolute top-0.5 left-0.5 w-5 h-5 rounded-full bg-white shadow-md transition-transform duration-200 ${
          value ? 'translate-x-4' : 'translate-x-0'
        }`}
      />
    </button>
  )
}

/**
 * Zoom range slider - the preview tracks the thumb but the actual page
 * zoom is only applied when the user releases the slider, so users can
 * preview without applying.
 */
export function ZoomSlider({
  value,
  onCommit,
}: {
  value: number
  onCommit: (v: number) => void
}) {
  const min = 0.75
  const max = 1.5
  const step = 0.05
  const safeValue = Number.isFinite(value) && value > 0 ? value : 1
  const [draft, setDraft] = useState(safeValue)

  useEffect(() => {
    setDraft(safeValue)
  }, [safeValue])

  function commit() {
    const next = Number(draft.toFixed(2))
    if (Math.abs(next - safeValue) > 0.001) onCommit(next)
  }

  return (
    <div className="flex items-center gap-3 min-w-[260px]">
      <input
        type="range"
        min={min}
        max={max}
        step={step}
        value={draft}
        onChange={e => setDraft(Number(e.target.value))}
        onPointerUp={commit}
        onKeyUp={commit}
        onBlur={commit}
        aria-label="Interface zoom"
        className="slider w-44"
      />
      <span className="inline-flex items-center justify-center min-w-[44px] h-7 px-2 rounded-md bg-void/60 border border-border text-[11.5px] text-text-1 font-medium tabular-nums">
        {Math.round(draft * 100)}%
      </span>
    </div>
  )
}

export function Segmented<T extends string>({
  value,
  options,
  onChange,
}: {
  value: T
  options: { value: T; label: string }[]
  onChange: (v: T) => void
}) {
  return (
    <div className="flex p-0.5 bg-void/60 rounded-md border border-border">
      {options.map(opt => {
        const isActive = value === opt.value
        return (
          <button
            key={opt.value}
            onClick={() => onChange(opt.value)}
            className={`relative h-7 px-3 text-[11.5px] font-medium tracking-tight transition-colors duration-150 rounded focus-ring ${
              isActive ? 'text-void' : 'text-text-3 hover:text-text-1'
            }`}
          >
            {isActive && (
              <motion.span
                layoutId={`seg-${opt.label}-${options.length}-${options.map(o => o.value).join('|')}`}
                className="absolute inset-0 bg-accent rounded"
                transition={{ type: 'spring', stiffness: 380, damping: 30 }}
              />
            )}
            <span className="relative flex items-center gap-1">
              {isActive && <Check size={10} stroke={3} />}
              {opt.label}
            </span>
          </button>
        )
      })}
    </div>
  )
}

/**
 * Two-tap confirm button for destructive actions. First click arms it
 * for 3 seconds; second click commits. Resets if the user leaves it
 * armed without confirming.
 */
export function DangerButton({
  icon: Icon,
  label,
  onConfirm,
}: {
  icon: ComponentType<{ size?: number; stroke?: number; className?: string }>
  label: string
  onConfirm: () => void
}) {
  const [armed, setArmed] = useState(false)
  useEffect(() => {
    if (!armed) return
    const t = setTimeout(() => setArmed(false), 3000)
    return () => clearTimeout(t)
  }, [armed])

  return (
    <button
      onClick={() => {
        if (!armed) {
          setArmed(true)
        } else {
          onConfirm()
          setArmed(false)
        }
      }}
      className={`group inline-flex items-center gap-1.5 h-9 px-3 rounded-md text-[12px] font-medium transition-all duration-150 focus-ring ${
        armed
          ? 'bg-error/15 text-error border border-error/35 hover:bg-error/25'
          : 'bg-elevated/60 text-text-2 border border-border hover:border-border-hover hover:text-text-1'
      }`}
    >
      {armed ? <AlertCircle size={13} stroke={2} /> : <Icon size={13} stroke={2} />}
      {armed ? 'Click again to confirm' : label}
    </button>
  )
}
