import * as React from 'react'
import { cn } from './utils'

export type CronField = 'minute' | 'hour' | 'day' | 'month' | 'weekday'
export type CronFieldMode = 'every' | 'specific' | 'range' | 'interval'

export interface CronFieldConfig {
  mode: CronFieldMode
  specific?: number[]
  rangeStart?: number
  rangeEnd?: number
  interval?: number
  intervalStart?: number
}

export interface ParsedCron {
  minute: CronFieldConfig
  hour: CronFieldConfig
  day: CronFieldConfig
  month: CronFieldConfig
  weekday: CronFieldConfig
}

export interface CronEditorProps {
  /** Initial cron expression */
  value?: string
  /** Show human-readable preview */
  showPreview?: boolean
  /** Show next run times */
  showNextRuns?: boolean
  /** Number of next runs to display */
  nextRunCount?: number
  /** Additional class name */
  className?: string
  /** Callback when expression changes */
  onChange?: (expression: string) => void
}

interface FieldMeta {
  label: string
  min: number
  max: number
  labels?: string[]
}

const FIELD_META: Record<CronField, FieldMeta> = {
  minute: { label: 'Minute', min: 0, max: 59 },
  hour: { label: 'Hour', min: 0, max: 23 },
  day: { label: 'Day of Month', min: 1, max: 31 },
  month: {
    label: 'Month',
    min: 1,
    max: 12,
    labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
  },
  weekday: {
    label: 'Weekday',
    min: 0,
    max: 6,
    labels: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
  },
}

const FIELD_ORDER: CronField[] = ['minute', 'hour', 'day', 'month', 'weekday']
const MODES: CronFieldMode[] = ['every', 'specific', 'range', 'interval']
const MODE_LABELS: Record<CronFieldMode, string> = {
  every: 'Every',
  specific: 'Specific',
  range: 'Range',
  interval: 'Interval',
}

const FIELD_RANGES: Record<CronField, [number, number]> = {
  minute: [0, 59],
  hour: [0, 23],
  day: [1, 31],
  month: [1, 12],
  weekday: [0, 6],
}

// --- Parser / Formatter utilities ---

function parseField(token: string, field: CronField): CronFieldConfig {
  const [min, max] = FIELD_RANGES[field]
  if (token === '*') return { mode: 'every' }
  if (token.includes('/')) {
    const parts = token.split('/')
    const base = parts[0] ?? '*'
    const step = parseInt(parts[1] ?? '1', 10)
    const start = base === '*' ? min : parseInt(base, 10)
    return { mode: 'interval', interval: step, intervalStart: start }
  }
  if (token.includes('-') && !token.includes(',')) {
    const parts = token.split('-')
    return {
      mode: 'range',
      rangeStart: parseInt(parts[0] ?? String(min), 10),
      rangeEnd: parseInt(parts[1] ?? String(max), 10),
    }
  }
  const values = token
    .split(',')
    .map(v => parseInt(v.trim(), 10))
    .filter(v => !isNaN(v))
  if (values.length > 0) return { mode: 'specific', specific: values }
  return { mode: 'every' }
}

function formatField(config: CronFieldConfig, field: CronField): string {
  const [min] = FIELD_RANGES[field]
  switch (config.mode) {
    case 'every':
      return '*'
    case 'specific': {
      const values = config.specific ?? []
      return values.length === 0 ? '*' : values.sort((a, b) => a - b).join(',')
    }
    case 'range':
      return `${config.rangeStart ?? min}-${config.rangeEnd ?? min}`
    case 'interval': {
      const step = config.interval ?? 1
      const start = config.intervalStart ?? min
      const base = start === min ? '*' : String(start)
      return `${base}/${step}`
    }
    default:
      return '*'
  }
}

function parseCron(expression: string): ParsedCron {
  const tokens = expression.trim().split(/\s+/)
  const fields: CronFieldConfig[] = FIELD_ORDER.map((field, i) =>
    parseField(tokens[i] ?? '*', field)
  )
  return {
    minute: fields[0] ?? { mode: 'every' },
    hour: fields[1] ?? { mode: 'every' },
    day: fields[2] ?? { mode: 'every' },
    month: fields[3] ?? { mode: 'every' },
    weekday: fields[4] ?? { mode: 'every' },
  }
}

function formatCron(config: ParsedCron): string {
  return FIELD_ORDER.map(field => formatField(config[field], field)).join(' ')
}

function isValidCron(expression: string): boolean {
  const tokens = expression.trim().split(/\s+/)
  return tokens.length === 5
}

const MONTH_NAMES = [
  '',
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December',
]
const WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']

function cronToHuman(expression: string): string {
  const tokens = expression.trim().split(/\s+/)
  if (tokens.length < 5) return 'Invalid cron expression'
  const [minuteToken, hourToken, dayToken, monthToken, weekdayToken] = tokens as [
    string,
    string,
    string,
    string,
    string,
  ]
  const parts: string[] = []

  if (
    minuteToken === '*' &&
    hourToken === '*' &&
    dayToken === '*' &&
    monthToken === '*' &&
    weekdayToken === '*'
  ) {
    return 'Every minute'
  }

  if (minuteToken.includes('/')) {
    parts.push(`Every ${minuteToken.split('/')[1] ?? '1'} minutes`)
  } else if (minuteToken !== '*' && minuteToken !== '0') {
    parts.push(`At minute ${minuteToken}`)
  }

  if (hourToken.includes('/')) {
    parts.push(`every ${hourToken.split('/')[1] ?? '1'} hours`)
  } else if (hourToken !== '*') {
    const h = parseInt(hourToken, 10)
    const ampm = h >= 12 ? 'PM' : 'AM'
    const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h
    if (minuteToken === '0') {
      parts.push(`At ${h12}:00 ${ampm}`)
    } else {
      parts.push(`at hour ${hourToken}`)
    }
  }

  if (dayToken !== '*') {
    if (dayToken.includes('/')) parts.push(`every ${dayToken.split('/')[1] ?? '1'} days`)
    else parts.push(`on day ${dayToken}`)
  }

  if (monthToken !== '*') {
    const months = monthToken.split(',').map(m => MONTH_NAMES[parseInt(m, 10)] ?? m)
    parts.push(`in ${months.join(', ')}`)
  }

  if (weekdayToken !== '*') {
    const days = weekdayToken.split(',').map(d => WEEKDAY_NAMES[parseInt(d, 10)] ?? d)
    parts.push(`on ${days.join(', ')}`)
  }

  if (parts.length === 0) return 'Every minute'
  const result = parts.join(', ')
  return result.charAt(0).toUpperCase() + result.slice(1)
}

function matchesField(value: number, token: string, field: CronField): boolean {
  const [min] = FIELD_RANGES[field]
  if (token === '*') return true
  const subTokens = token.split(',')
  if (subTokens.length > 1) return subTokens.some(sub => matchesField(value, sub.trim(), field))
  if (token.includes('/')) {
    const parts = token.split('/')
    const base = parts[0] ?? '*'
    const step = parseInt(parts[1] ?? '1', 10)
    const start = base === '*' ? min : parseInt(base, 10)
    if (step <= 0) return false
    return value >= start && (value - start) % step === 0
  }
  if (token.includes('-')) {
    const parts = token.split('-')
    return value >= parseInt(parts[0] ?? '0', 10) && value <= parseInt(parts[1] ?? '0', 10)
  }
  return value === parseInt(token, 10)
}

function getNextRuns(expression: string, count: number): Date[] {
  const tokens = expression.trim().split(/\s+/)
  if (tokens.length < 5) return []
  const [mt, ht, dt, mot, wt] = tokens as [string, string, string, string, string]
  const results: Date[] = []
  const current = new Date()
  current.setSeconds(0, 0)
  current.setMinutes(current.getMinutes() + 1)

  for (let i = 0; i < 525600 && results.length < count; i++) {
    if (
      matchesField(current.getMinutes(), mt, 'minute') &&
      matchesField(current.getHours(), ht, 'hour') &&
      matchesField(current.getDate(), dt, 'day') &&
      matchesField(current.getMonth() + 1, mot, 'month') &&
      matchesField(current.getDay(), wt, 'weekday')
    ) {
      results.push(new Date(current.getTime()))
    }
    current.setMinutes(current.getMinutes() + 1)
  }
  return results
}

function formatRunDate(date: Date): string {
  const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
  const months = [
    'Jan',
    'Feb',
    'Mar',
    'Apr',
    'May',
    'Jun',
    'Jul',
    'Aug',
    'Sep',
    'Oct',
    'Nov',
    'Dec',
  ]
  const h = date.getHours()
  const m = String(date.getMinutes()).padStart(2, '0')
  const ampm = h >= 12 ? 'PM' : 'AM'
  const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h
  return `${days[date.getDay()]}, ${months[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()} at ${h12}:${m} ${ampm}`
}

// --- React Component ---

function CronEditor({
  value = '* * * * *',
  showPreview = true,
  showNextRuns = true,
  nextRunCount = 5,
  className,
  onChange,
}: CronEditorProps) {
  const [parsed, setParsed] = React.useState<ParsedCron>(() => parseCron(value))
  const [activeField, setActiveField] = React.useState<CronField>('minute')
  const [expressionInput, setExpressionInput] = React.useState(value)

  // Sync from prop
  React.useEffect(() => {
    const p = parseCron(value)
    setParsed(p)
    setExpressionInput(formatCron(p))
  }, [value])

  const expression = React.useMemo(() => formatCron(parsed), [parsed])
  const humanReadable = React.useMemo(() => cronToHuman(expression), [expression])
  const nextRuns = React.useMemo(
    () => (showNextRuns ? getNextRuns(expression, nextRunCount) : []),
    [expression, showNextRuns, nextRunCount]
  )

  function updateParsed(newParsed: ParsedCron) {
    setParsed(newParsed)
    const expr = formatCron(newParsed)
    setExpressionInput(expr)
    onChange?.(expr)
  }

  function handleExpressionCommit() {
    const val = expressionInput.trim()
    if (isValidCron(val)) {
      const p = parseCron(val)
      setParsed(p)
      onChange?.(formatCron(p))
    }
  }

  function setFieldMode(mode: CronFieldMode) {
    const meta = FIELD_META[activeField]
    const config: CronFieldConfig = { mode }
    if (mode === 'specific') config.specific = []
    if (mode === 'range') {
      config.rangeStart = meta.min
      config.rangeEnd = meta.max
    }
    if (mode === 'interval') {
      config.interval = 1
      config.intervalStart = meta.min
    }
    updateParsed({ ...parsed, [activeField]: config })
  }

  function toggleSpecificValue(val: number) {
    const config = parsed[activeField]
    if (config.mode !== 'specific') return
    const values = [...(config.specific ?? [])]
    const idx = values.indexOf(val)
    if (idx >= 0) values.splice(idx, 1)
    else values.push(val)
    updateParsed({ ...parsed, [activeField]: { ...config, specific: values } })
  }

  const fieldConfig = parsed[activeField]
  const meta = FIELD_META[activeField]

  const tabBtnClass = (field: CronField) =>
    cn(
      'px-3 py-2 text-[12px] font-medium rounded-lg transition-all duration-200',
      field === activeField
        ? 'bg-primary text-white shadow-glow'
        : 'text-foreground-secondary hover:bg-glass-hover hover:text-foreground'
    )

  const modeBtnClass = (mode: CronFieldMode) =>
    cn(
      'px-3 py-1.5 text-[12px] rounded-lg transition-all duration-200',
      mode === fieldConfig.mode
        ? 'bg-glass border border-primary text-primary'
        : 'text-foreground-muted hover:bg-glass-hover hover:text-foreground'
    )

  const fieldInputClass = cn(
    'border-border-glass bg-glass h-8 w-20 rounded-lg border px-2 text-[12px] text-foreground',
    'outline-none transition-all duration-200 focus:border-primary focus:shadow-glow backdrop-blur-[20px]'
  )

  return (
    <div
      data-slot="cron-editor"
      className={cn(
        'w-full rounded-xl border border-border-glass bg-glass p-4 backdrop-blur-[20px]',
        className
      )}
    >
      {/* Field tabs */}
      <div role="tablist" className="mb-4 flex flex-wrap gap-1">
        {FIELD_ORDER.map(field => (
          <button
            key={field}
            type="button"
            role="tab"
            aria-selected={field === activeField}
            className={tabBtnClass(field)}
            onClick={() => setActiveField(field)}
          >
            {FIELD_META[field].label}
          </button>
        ))}
      </div>

      {/* Mode selector */}
      <div role="tabpanel" className="mb-4">
        <div className="mb-3 flex flex-wrap gap-1">
          {MODES.map(mode => (
            <button
              key={mode}
              type="button"
              className={modeBtnClass(mode)}
              onClick={() => setFieldMode(mode)}
            >
              {MODE_LABELS[mode]}
            </button>
          ))}
        </div>

        {/* Editor body */}
        <div className="min-h-[60px]">
          {fieldConfig.mode === 'every' && (
            <p className="text-[13px] text-foreground-muted">
              Every {meta.label.toLowerCase()} (* wildcard)
            </p>
          )}

          {fieldConfig.mode === 'specific' && (
            <div className="flex flex-wrap gap-1">
              {Array.from({ length: meta.max - meta.min + 1 }, (_, i) => {
                const val = meta.min + i
                const isActive = (fieldConfig.specific ?? []).includes(val)
                const label = meta.labels ? (meta.labels[i] ?? String(val)) : String(val)
                return (
                  <button
                    key={val}
                    type="button"
                    className={cn(
                      'inline-flex h-8 min-w-[2rem] items-center justify-center rounded-lg px-2 text-[12px] transition-all duration-200',
                      isActive
                        ? 'bg-primary text-white shadow-glow'
                        : 'border border-border-glass text-foreground-secondary hover:bg-glass-hover'
                    )}
                    onClick={() => toggleSpecificValue(val)}
                  >
                    {label}
                  </button>
                )
              })}
            </div>
          )}

          {fieldConfig.mode === 'range' && (
            <div className="flex items-center gap-2">
              <label className="text-[12px] text-foreground-muted">From</label>
              <input
                type="number"
                className={fieldInputClass}
                min={meta.min}
                max={meta.max}
                value={fieldConfig.rangeStart ?? meta.min}
                onChange={e => {
                  const v = parseInt(e.target.value, 10)
                  if (!isNaN(v) && v >= meta.min && v <= meta.max) {
                    updateParsed({ ...parsed, [activeField]: { ...fieldConfig, rangeStart: v } })
                  }
                }}
              />
              <label className="text-[12px] text-foreground-muted">To</label>
              <input
                type="number"
                className={fieldInputClass}
                min={meta.min}
                max={meta.max}
                value={fieldConfig.rangeEnd ?? meta.max}
                onChange={e => {
                  const v = parseInt(e.target.value, 10)
                  if (!isNaN(v) && v >= meta.min && v <= meta.max) {
                    updateParsed({ ...parsed, [activeField]: { ...fieldConfig, rangeEnd: v } })
                  }
                }}
              />
            </div>
          )}

          {fieldConfig.mode === 'interval' && (
            <div className="flex items-center gap-2">
              <label className="text-[12px] text-foreground-muted">Every</label>
              <input
                type="number"
                className={fieldInputClass}
                min={1}
                max={meta.max - meta.min + 1}
                value={fieldConfig.interval ?? 1}
                onChange={e => {
                  const v = parseInt(e.target.value, 10)
                  if (!isNaN(v) && v > 0) {
                    updateParsed({ ...parsed, [activeField]: { ...fieldConfig, interval: v } })
                  }
                }}
              />
              <label className="text-[12px] text-foreground-muted">Starting at</label>
              <input
                type="number"
                className={fieldInputClass}
                min={meta.min}
                max={meta.max}
                value={fieldConfig.intervalStart ?? meta.min}
                onChange={e => {
                  const v = parseInt(e.target.value, 10)
                  if (!isNaN(v) && v >= meta.min && v <= meta.max) {
                    updateParsed({ ...parsed, [activeField]: { ...fieldConfig, intervalStart: v } })
                  }
                }}
              />
            </div>
          )}
        </div>
      </div>

      {/* Expression input */}
      <div className="mb-3">
        <label className="mb-1 block text-[11px] font-medium text-foreground-muted">
          Cron Expression
        </label>
        <input
          type="text"
          aria-label="Cron expression"
          className={cn(
            'border-border-glass bg-glass h-10 w-full rounded-xl border px-4 py-2 font-mono text-[13px] text-foreground backdrop-blur-[20px]',
            'outline-none transition-all duration-200 focus:border-primary focus:shadow-glow'
          )}
          value={expressionInput}
          onChange={e => setExpressionInput(e.target.value)}
          onBlur={handleExpressionCommit}
          onKeyDown={e => e.key === 'Enter' && handleExpressionCommit()}
        />
      </div>

      {/* Human-readable preview */}
      {showPreview && (
        <div className="mb-3 text-[13px] text-foreground-secondary">{humanReadable}</div>
      )}

      {/* Next runs */}
      {showNextRuns && nextRuns.length > 0 && (
        <div>
          <div className="mb-1 text-[11px] font-medium text-foreground-muted">
            Next {nextRuns.length} runs
          </div>
          <ul className="space-y-0.5">
            {nextRuns.map((run, i) => (
              <li key={i} className="text-[12px] text-foreground-secondary">
                {formatRunDate(run)}
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  )
}

export { CronEditor, parseCron, formatCron, cronToHuman, isValidCron, getNextRuns }
