import { useMemo, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { AlertTriangle, Calendar as CalendarIcon, Check, Clock, Lock } from 'lucide-react'
import {
  createConference,
  deleteConference,
  getConference,
  updateConference,
  type ConferenceCreatePayload,
  type ConferenceOut,
  type ConferenceRecurrence,
  type InviteeIn,
  type RecurrenceType,
  type SummaryRecipientsMode,
} from '@/api/conferences'
import { ApiError } from '@/api/client'
import { useAuth } from '@/auth/useAuth'
import { Select } from '@/components/ui/Select'
import { ParticipantsPicker, type SelectedParticipant } from '@/components/calendar/ParticipantsPicker'
import { useToast } from '@/components/ui/ToastProvider'
import { browserTimeZone, localDateTimeToIso, toLocalDateTimeParts } from '@/lib/localTime'
import { WEEKDAY_SHORT, formatPreviewHint, previewNextDates } from '@/lib/recurrenceFormat'

const SUMMARY_RECIPIENTS_OPTIONS = [
  { value: '', label: 'По умолчанию' },
  { value: 'all', label: 'Всем участникам' },
  { value: 'owner', label: 'Только организатору' },
]

interface ConferenceFormCardProps {
  /** `undefined` — карточка в режиме создания; конференция — режим редактирования (см. design/mockups/calendar.html). */
  conference?: ConferenceOut
  onSaved: (conference: ConferenceOut) => void
  onDeleted?: () => void
  /** Только для режима редактирования — вернуться к пустой форме создания. */
  onCancelEdit?: () => void
}

function defaultTimeSlot(): { date: string; time: string } {
  const now = new Date()
  now.setMinutes(0, 0, 0)
  now.setHours(now.getHours() + 1)
  return toLocalDateTimeParts(now.toISOString())
}

function weekdayOf(dateStr: string): number {
  if (!dateStr) return 0
  const [y, m, d] = dateStr.split('-').map(Number)
  return (new Date(y, m - 1, d).getDay() + 6) % 7
}

/**
 * Сайдбар-карточка создания/редактирования запланированной конференции —
 * 1:1 `.create-card` из design/mockups/calendar.html. В отличие от статичного
 * макета (раскрытие блоков через CSS `:has()`), состояние — управляемое
 * (`useState`), как и предписано DESIGN_SYSTEM.md §4.13 для frontend.
 */
export function ConferenceFormCard({ conference, onSaved, onDeleted, onCancelEdit }: ConferenceFormCardProps) {
  const isEdit = !!conference
  const queryClient = useQueryClient()
  const toast = useToast()
  const { user: currentUser } = useAuth()

  // Не-организатору форма недоступна вообще (кнопки правки/удаления скрыты
  // везде выше по дереву), но проверяем и здесь как
  // защиту в глубину: список конференции (`/conferences/my`) уже содержит
  // `is_owner`, детальный запрос ниже не нужен для этой проверки.
  const isOwner = conference?.is_owner ?? true

  const initialSlot = useMemo(
    () => (conference?.scheduled_at ? toLocalDateTimeParts(conference.scheduled_at) : defaultTimeSlot()),
    [conference],
  )

  const [title, setTitle] = useState(conference?.title ?? '')
  const [dateStr, setDateStr] = useState(initialSlot.date)
  const [timeStr, setTimeStr] = useState(initialSlot.time)
  // Строкой, а не числом: контролируемый number-инпут с числовым состоянием
  // не даёт очистить поле (`Number('') || 0` тут же возвращает 0, значение
  // "0" вспыхивает обратно) — строка хранит именно то, что ввёл пользователь,
  // включая пустоту. Вторая половина жалобы («ввод дописывается после 0», а
  // не заменяет его) — не про хранение, а про то, что при фокусе курсор
  // просто вставал рядом с текущим значением; чинится `onFocus={select()}`
  // у самого `<input>` ниже. `durationMinutes` — производное число для
  // валидации и отправки.
  const [durationMinutesInput, setDurationMinutesInput] = useState(String(conference?.duration_minutes ?? 30))
  const durationMinutes = Number(durationMinutesInput) || 0

  const [isClosed, setIsClosed] = useState(conference?.is_closed ?? false)
  const [password, setPassword] = useState('')
  const [summaryRecipients, setSummaryRecipients] = useState<'' | SummaryRecipientsMode>(
    conference?.summary_recipients ?? '',
  )

  const [isPinned, setIsPinned] = useState(conference?.is_pinned ?? false)
  const [recurrenceType, setRecurrenceType] = useState<RecurrenceType>(conference?.recurrence?.type ?? 'weekly')
  const [weekdays, setWeekdays] = useState<number[]>(
    conference?.recurrence?.weekdays ?? (conference ? [] : [weekdayOf(initialSlot.date)]),
  )
  const [dayOfMonth, setDayOfMonth] = useState(conference?.recurrence?.day_of_month ?? 1)
  const [intervalDays, setIntervalDays] = useState(conference?.recurrence?.interval_days ?? 7)

  const [formError, setFormError] = useState<string | null>(null)
  const [confirmingDelete, setConfirmingDelete] = useState(false)

  // Участники. Списковый `/conferences/my` не содержит
  // `participants` (только детальный `GET /conferences/{id}`, owner|admin) —
  // при редактировании подгружаем состав отдельным запросом.
  const [participants, setParticipants] = useState<SelectedParticipant[]>([])
  const [participantsTouched, setParticipantsTouched] = useState(false)

  const participantsQuery = useQuery({
    queryKey: ['conferences', 'detail', conference?.id],
    queryFn: () => getConference(conference!.id),
    enabled: isEdit,
  })

  // Сидируем состояние формы из загруженной детали конференции — паттерн
  // «подстройка состояния при изменении пропса/данных запроса во время
  // рендера» (react.dev/learn/you-might-not-need-an-effect), а не эффект:
  // setState вызывается один раз для каждого нового объекта `participants`
  // из query (`loadedParticipants` меняет identity только на новый ответ),
  // не на каждый ре-рендер.
  const loadedParticipants = participantsQuery.data?.participants
  const [seededFrom, setSeededFrom] = useState<typeof loadedParticipants>(undefined)
  if (isEdit && loadedParticipants && loadedParticipants !== seededFrom) {
    setSeededFrom(loadedParticipants)
    setParticipants(
      loadedParticipants
        .filter((p) => !p.is_organizer)
        .map((p) => ({
          key: p.user_id ?? p.email,
          userId: p.user_id ?? undefined,
          email: p.email,
          displayName: p.name ?? p.email,
          avatarUrl: p.avatar_url,
        })),
    )
  }

  const organizerChip = isEdit
    ? conference?.organizer_name
      ? { name: conference.organizer_name, avatarUrl: participantsQuery.data?.participants?.find((p) => p.is_organizer)?.avatar_url ?? null }
      : null
    : currentUser
      ? { name: currentUser.name_user, email: currentUser.email, avatarUrl: currentUser.avatar_url }
      : null

  function invalidateConferenceQueries() {
    queryClient.invalidateQueries({ queryKey: ['conferences'] })
  }

  const saveMutation = useMutation({
    mutationFn: (payload: ConferenceCreatePayload) =>
      isEdit ? updateConference(conference!.id, payload) : createConference(payload),
    onSuccess: (result) => {
      invalidateConferenceQueries()
      toast.show(isEdit ? 'Изменения сохранены' : 'Конференция запланирована', 'success')
      onSaved(result)
    },
    onError: (err: unknown) => {
      if (err instanceof ApiError && err.status === 422) {
        setFormError('Проверьте правильность заполнения полей')
      } else {
        setFormError(isEdit ? 'Не удалось сохранить изменения. Попробуйте ещё раз' : 'Не удалось создать конференцию. Попробуйте ещё раз')
      }
    },
  })

  const unpinMutation = useMutation({
    mutationFn: () => updateConference(conference!.id, { is_pinned: false }),
    onSuccess: (result) => {
      invalidateConferenceQueries()
      toast.show('Конференция откреплена', 'success')
      onSaved(result)
    },
    onError: () => toast.show('Не удалось открепить конференцию', 'error'),
  })

  const deleteMutation = useMutation({
    mutationFn: () => deleteConference(conference!.id),
    onSuccess: () => {
      invalidateConferenceQueries()
      toast.show('Конференция удалена', 'success')
      onDeleted?.()
    },
    onError: (err: unknown) => {
      if (err instanceof ApiError && err.status === 409) {
        toast.show('Нельзя удалить — конференция сейчас идёт', 'error')
      } else {
        toast.show('Не удалось удалить конференцию', 'error')
      }
      setConfirmingDelete(false)
    },
  })

  function toggleWeekday(day: number) {
    setWeekdays((prev) => (prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day].sort((a, b) => a - b)))
  }

  const isRecurrenceValid =
    !isPinned ||
    (recurrenceType === 'weekly' && weekdays.length > 0) ||
    (recurrenceType === 'biweekly' && weekdays.length > 0) ||
    (recurrenceType === 'monthly' && dayOfMonth >= 1 && dayOfMonth <= 31) ||
    (recurrenceType === 'every_n_days' && intervalDays >= 1)
  const isPasswordValid = !isClosed || password.length >= 4
  const canSubmit = !!dateStr && !!timeStr && durationMinutes > 0 && isPasswordValid && isRecurrenceValid && !saveMutation.isPending

  const previewHint = useMemo(() => {
    if (!isPinned) return ''
    const dates = previewNextDates(
      { type: recurrenceType, weekdays, day_of_month: dayOfMonth, interval_days: intervalDays, anchor_date: dateStr || initialSlot.date },
      2,
    )
    return formatPreviewHint(dates)
  }, [isPinned, recurrenceType, weekdays, dayOfMonth, intervalDays, dateStr, initialSlot.date])

  function handleSubmit() {
    setFormError(null)
    const recurrence: ConferenceRecurrence | undefined = isPinned
      ? {
          type: recurrenceType,
          weekdays: recurrenceType === 'weekly' || recurrenceType === 'biweekly' ? weekdays : undefined,
          day_of_month: recurrenceType === 'monthly' ? dayOfMonth : undefined,
          interval_days: recurrenceType === 'every_n_days' ? intervalDays : undefined,
          anchor_date: dateStr,
          time_local: timeStr,
          timezone: browserTimeZone(),
          duration_minutes: durationMinutes,
        }
      : undefined
    // Состав участников отправляем только если его трогали — иначе поле не
    // передаём вовсе (не отправляем `participants`), чтобы backend не
    // заменил состав пустым списком (см. контракт `InviteeIn`). `p.email!` —
    // безопасно: внешний участник (без `userId`) в UI добавляется только
    // через `addExternal`, которая всегда заполняет `email` (ParticipantsPicker.tsx).
    const participantsPayload: InviteeIn[] | undefined = participantsTouched
      ? participants.map((p) => (p.userId ? { user_id: p.userId } : { email: p.email! }))
      : undefined

    saveMutation.mutate({
      title: title.trim() || undefined,
      scheduled_at: localDateTimeToIso(dateStr, timeStr),
      duration_minutes: durationMinutes,
      is_pinned: isPinned,
      recurrence,
      is_closed: isClosed,
      password: isClosed ? password : undefined,
      summary_recipients: summaryRecipients === '' ? null : summaryRecipients,
      participants: participantsPayload,
    })
  }

  return (
    <aside className="create-card">
      <h2>
        <CalendarIcon className="lucide" style={{ width: 20, height: 20 }} aria-hidden="true" />
        {isEdit ? 'Редактирование конференции' : 'Новая конференция'}
      </h2>

      {isEdit && conference?.next_occurrence && (
        <div className="next-occurrence">
          <Clock style={{ width: 16, height: 16 }} aria-hidden="true" />
          Следующее вхождение: {new Date(conference.next_occurrence).toLocaleString('ru-RU', {
            weekday: 'long',
            day: 'numeric',
            month: 'long',
            hour: '2-digit',
            minute: '2-digit',
          })}
        </div>
      )}

      {formError && (
        <div className="error-banner">
          <AlertTriangle style={{ width: 18, height: 18 }} aria-hidden="true" />
          <div>{formError}</div>
        </div>
      )}

      <form
        onSubmit={(e) => {
          e.preventDefault()
          handleSubmit()
        }}
      >
        <div className="field">
          <label htmlFor="conf-title">Название</label>
          <input
            id="conf-title"
            type="text"
            placeholder="Например, Синк по релизу 2.4"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
            maxLength={255}
          />
        </div>

        <div className="field-row">
          <div className="field">
            <label htmlFor="conf-date">Дата</label>
            <input id="conf-date" type="date" value={dateStr} onChange={(e) => setDateStr(e.target.value)} />
          </div>
          <div className="field">
            <label htmlFor="conf-time">Время</label>
            <input id="conf-time" type="time" value={timeStr} onChange={(e) => setTimeStr(e.target.value)} />
          </div>
        </div>

        <div className="field">
          <label htmlFor="conf-duration">Длительность, мин</label>
          <input
            id="conf-duration"
            type="number"
            min={5}
            step={5}
            value={durationMinutesInput}
            onChange={(e) => setDurationMinutesInput(e.target.value)}
            onFocus={(e) => e.target.select()}
          />
        </div>

        <div className="field">
          <label id="conf-summary-recipients-label" htmlFor="conf-summary-recipients">
            Рассылка саммари
          </label>
          <Select
            id="conf-summary-recipients"
            aria-labelledby="conf-summary-recipients-label"
            value={summaryRecipients}
            onChange={(v) => setSummaryRecipients(v as '' | SummaryRecipientsMode)}
            options={SUMMARY_RECIPIENTS_OPTIONS}
          />
        </div>

        <div className="field">
          <label id="conf-participants-label">Участники</label>
          <ParticipantsPicker
            participants={participants}
            onChange={(next) => {
              setParticipants(next)
              setParticipantsTouched(true)
            }}
            organizer={organizerChip}
            disabled={isEdit && participantsQuery.isPending}
          />
          {isEdit && participantsQuery.isPending && <p className="field-hint">Загрузка состава участников…</p>}
        </div>

        <div className="toggle-block">
          <div className="toggle-row" style={{ borderTop: 'none', paddingTop: 0 }}>
            <div className="toggle-copy">
              <strong>Закрытая по паролю</strong>
              <span>Вход только по паролю, включая гостей</span>
            </div>
            <label className="switch">
              <input type="checkbox" checked={isClosed} onChange={(e) => setIsClosed(e.target.checked)} />
              <span className="slider" />
            </label>
          </div>
          {isClosed && (
            <div className="password-block field">
              <label htmlFor="conf-password">
                <Lock style={{ width: 14, height: 14 }} aria-hidden="true" />
                Пароль конференции
              </label>
              <input
                id="conf-password"
                type="password"
                placeholder="Минимум 4 символа"
                value={password}
                onChange={(e) => setPassword(e.target.value)}
              />
            </div>
          )}
        </div>

        <div className="pin-block">
          <div className="toggle-row">
            <div className="toggle-copy">
              <strong>{isEdit ? 'Закреплена' : 'Закрепить постоянную конференцию'}</strong>
              <span>Сохранится и попадёт в «Мои конференции», не исчезнет после встречи</span>
            </div>
            <label className="switch">
              <input type="checkbox" checked={isPinned} onChange={(e) => setIsPinned(e.target.checked)} />
              <span className="slider" />
            </label>
          </div>

          {isPinned && (
            <div className="recurrence-panel">
              <label className="recurrence-option-row">
                <input type="radio" name="recurrence-type" checked={recurrenceType === 'weekly'} onChange={() => setRecurrenceType('weekly')} />
                По дням недели, еженедельно
              </label>
              {recurrenceType === 'weekly' && (
                <div className="recurrence-sub">
                  <WeekdayToggles selected={weekdays} onToggle={toggleWeekday} />
                  {previewHint && <p className="recurrence-hint">{previewHint}</p>}
                </div>
              )}

              <label className="recurrence-option-row">
                <input type="radio" name="recurrence-type" checked={recurrenceType === 'biweekly'} onChange={() => setRecurrenceType('biweekly')} />
                Раз в 2 недели
              </label>
              {recurrenceType === 'biweekly' && (
                <div className="recurrence-sub">
                  <WeekdayToggles selected={weekdays} onToggle={toggleWeekday} />
                  {previewHint && <p className="recurrence-hint">{previewHint}</p>}
                </div>
              )}

              <label className="recurrence-option-row">
                <input type="radio" name="recurrence-type" checked={recurrenceType === 'monthly'} onChange={() => setRecurrenceType('monthly')} />
                Раз в месяц
              </label>
              {recurrenceType === 'monthly' && (
                <div className="recurrence-sub">
                  <div className="field" style={{ marginBottom: 0 }}>
                    <label htmlFor="conf-day-of-month">Число месяца</label>
                    <input
                      id="conf-day-of-month"
                      type="number"
                      min={1}
                      max={31}
                      value={dayOfMonth}
                      onChange={(e) => setDayOfMonth(Number(e.target.value) || 1)}
                    />
                  </div>
                  <p className="recurrence-hint">
                    Если в месяце нет такого числа — переносится на последний день месяца (напр. 28/29 февраля)
                  </p>
                </div>
              )}

              <label className="recurrence-option-row">
                <input
                  type="radio"
                  name="recurrence-type"
                  checked={recurrenceType === 'every_n_days'}
                  onChange={() => setRecurrenceType('every_n_days')}
                />
                Каждые N дней
              </label>
              {recurrenceType === 'every_n_days' && (
                <div className="recurrence-sub">
                  <div className="field" style={{ marginBottom: 0 }}>
                    <label htmlFor="conf-interval-days">N (дней)</label>
                    <input
                      id="conf-interval-days"
                      type="number"
                      min={1}
                      value={intervalDays}
                      onChange={(e) => setIntervalDays(Number(e.target.value) || 1)}
                    />
                  </div>
                  {previewHint && <p className="recurrence-hint">{previewHint}</p>}
                </div>
              )}
            </div>
          )}
        </div>

        {!isOwner ? (
          <p className="field-hint" style={{ marginTop: 'var(--space-3)' }}>
            Редактирование доступно только организатору конференции.
          </p>
        ) : isEdit && conference?.is_pinned ? (
          <div className="btn-row">
            <button type="submit" className="btn btn-primary" disabled={!canSubmit}>
              {saveMutation.isPending ? 'Сохраняем…' : 'Сохранить изменения'}
            </button>
            <button type="button" className="btn btn-secondary" onClick={() => unpinMutation.mutate()} disabled={unpinMutation.isPending}>
              Открепить
            </button>
          </div>
        ) : (
          <button type="submit" className="btn btn-primary full" disabled={!canSubmit}>
            {saveMutation.isPending ? 'Сохраняем…' : isEdit ? 'Сохранить изменения' : 'Создать конференцию'}
          </button>
        )}
      </form>

      {isEdit && (
        <>
          {isOwner && (
            <div className="danger-link-row">
              {confirmingDelete ? (
                <div className="error-banner" style={{ alignItems: 'center', marginTop: 'var(--space-4)' }}>
                  <AlertTriangle style={{ width: 18, height: 18 }} aria-hidden="true" />
                  <div style={{ flex: 1, textAlign: 'left' }}>Удалить конференцию навсегда? Действие необратимо.</div>
                  <button
                    type="button"
                    className="btn btn-danger"
                    style={{ width: 'auto', padding: '6px 14px' }}
                    disabled={deleteMutation.isPending}
                    onClick={() => deleteMutation.mutate()}
                  >
                    <Check style={{ width: 14, height: 14 }} aria-hidden="true" />
                    Да, удалить
                  </button>
                </div>
              ) : (
                <button type="button" className="btn-text" onClick={() => setConfirmingDelete(true)}>
                  Удалить конференцию навсегда
                </button>
              )}
            </div>
          )}
          <div className="danger-link-row">
            <button type="button" className="btn-text" style={{ color: 'var(--color-ink-500)' }} onClick={onCancelEdit}>
              Отмена редактирования
            </button>
          </div>
        </>
      )}
    </aside>
  )
}

function WeekdayToggles({ selected, onToggle }: { selected: number[]; onToggle: (day: number) => void }) {
  return (
    <div className="weekday-toggles">
      {WEEKDAY_SHORT.map((label, day) => (
        <button
          type="button"
          key={label}
          className={`weekday-chip${selected.includes(day) ? ' is-selected' : ''}`}
          onClick={() => onToggle(day)}
          aria-pressed={selected.includes(day)}
        >
          {label}
        </button>
      ))}
    </div>
  )
}
