/**
 * API-функции конференций: предустановленных комнат нет, конференции
 * создаются динамически.
 *
 * Все даты — UTC ISO-8601 со 'Z' (в БД и API только UTC).
 */
import { apiRequest } from '@/api/client'

/** Жизненный цикл конференции (см. ADR-001; `draft` не используется). */
export type ConferenceStatus = 'scheduled' | 'active' | 'ended'

/** Тип повторения закреплённой конференции. */
export type RecurrenceType = 'weekly' | 'biweekly' | 'monthly' | 'every_n_days'

/**
 * Режим рассылки саммари конференции — переопределение дефолта
 * инстанса (`null` = «по умолчанию», см. `SettingsOut.summary_recipients`
 * в `src/api/admin.ts`).
 */
export type SummaryRecipientsMode = 'all' | 'owner'

/**
 * Потолок качества исходящего видео участника (`instance_settings.media_limits`,
 * см. `SettingsOut`/`SettingsUpdateIn` в `src/api/admin.ts`). `off` — без
 * ограничения. Применяется на клиенте через `publishDefaults`
 * (`lib/publishQualityCap.ts`) — режет битрейт верхнего слоя симулкаста, а не
 * жёсткое разрешение захвата камеры.
 */
export type PublishQualityCap = 'off' | '180p' | '360p' | '720p'

/**
 * Правило повторения закреплённой конференции — форма 1:1 с pydantic-моделью
 * `backend/services/recurrence.py::RecurrenceRule` (истина о форме — там).
 * Поля, специфичные для типа (`weekdays` для weekly/biweekly, `day_of_month`
 * для monthly, `interval_days` для every_n_days), обязательны только для
 * своего типа — см. валидацию на backend.
 */
export interface ConferenceRecurrence {
  type: RecurrenceType
  /** 0 (понедельник) .. 6 (воскресенье) — обязателен для weekly/biweekly. */
  weekdays?: number[]
  /** 1..31 — обязателен для monthly (переносится на последний день короткого месяца). */
  day_of_month?: number
  /** >= 1 — обязателен для every_n_days. */
  interval_days?: number
  /** Дата первого вхождения серии (YYYY-MM-DD) — якорь отсчёта для biweekly/every_n_days. */
  anchor_date: string
  /** Локальное время начала вхождения, формат HH:MM. */
  time_local: string
  /** IANA-таймзона (браузера пользователя на момент создания/редактирования). */
  timezone: string
  /** Длительность вхождения в минутах — дублирует верхнеуровневое поле конференции (того требует модель RecurrenceRule). */
  duration_minutes: number
}

/** Данные для входа в LiveKit-комнату конференции. */
export interface ConferenceJoinData {
  livekit_url: string
  token: string
  room_name: string
  conference_id: string
  /** Включён ли чат для этой конференции — при `false` панель/кнопка чата не рендерятся. */
  chat_enabled: boolean
  /** Включён ли модуль «поднятие руки» — при `false` кнопка «Рука» и очередь не рендерятся. */
  hand_queue_enabled: boolean
  /** Потолок качества публикации видео на момент входа — см. `PublishQualityCap`. */
  publish_quality_cap: PublishQualityCap
  /** Максимум одновременно видимых плиток сцены (`StageGrid`) на момент входа. */
  stage_max_tiles: number
  /** Включён ли модуль «замена фона» — при `false` кнопка «Фон» в тулбаре не рендерится. */
  virtual_background_enabled: boolean
}

/**
 * Приглашённый участник конференции — либо
 * зарегистрированный пользователь (`user_id` заполнен), либо внешний гость
 * по email (`user_id === null`). Организатор всегда присутствует первым
 * элементом (`is_organizer: true`), добавляется backend'ом автоматически.
 */
export interface InviteeOut {
  user_id: string | null
  email: string
  name: string | null
  avatar_url: string | null
  is_organizer: boolean
}

/**
 * Приглашаемый участник в теле создания/обновления — либо `user_id`
 * зарегистрированного пользователя, либо `email` внешнего гостя (ровно одно
 * из полей). Организатора указывать не нужно — backend добавляет его
 * автоматически и обязательно.
 */
export type InviteeIn = { user_id: string } | { email: string }

/** Конференция (ответ API). */
export interface ConferenceOut {
  id: string
  number: string
  slug: string
  title: string | null
  status: ConferenceStatus
  is_pinned: boolean
  is_closed: boolean
  scheduled_at: string | null
  duration_minutes: number | null
  recurrence: ConferenceRecurrence | null
  next_occurrence: string | null
  created_at: string
  /** `null` — используется дефолт инстанса (см. `SettingsOut.summary_recipients`). */
  summary_recipients: SummaryRecipientsMode | null
  /** Присутствует только у мгновенной конференции — сразу входим, не дожидаясь отдельного join. */
  join?: ConferenceJoinData
  /** Владелец (организатор) конференции. */
  owner_id: string
  /** Организатор ли конференции текущий пользователь — относительно него же скрываются кнопки правки/удаления. */
  is_owner: boolean
  organizer_name: string | null
  /** Присутствует ТОЛЬКО в ответе детального эндпоинта (`GET /conferences/{id}`, owner|admin) — списковые эндпоинты (`/conferences/my`, календарь) его не возвращают. */
  participants?: InviteeOut[]
}

/** Тело запроса на создание конференции. Без `scheduled_at` — мгновенная (ответ сразу содержит `join`). */
export interface ConferenceCreatePayload {
  title?: string
  /** UTC ISO-8601. */
  scheduled_at?: string
  duration_minutes?: number
  is_pinned?: boolean
  recurrence?: ConferenceRecurrence
  is_closed?: boolean
  password?: string
  /** `null`/не передано — дефолт инстанса; `'all'`/`'owner'` — явное переопределение. */
  summary_recipients?: SummaryRecipientsMode | null
  /** `undefined` — состав не менялся (не отправлять поле); `null`/массив — полная замена состава (кроме организатора, добавляется backend'ом сам). */
  participants?: InviteeIn[] | null
}

/**
 * Краткие сведения о конференции по ссылке/номеру — для экрана подключения
 * (JoinPage). Для `status === 'ended'` (ADR-001) backend отдаёт минимальный
 * ответ — `is_closed`/`requires_password` отсутствуют (незачем: повторный
 * вход недоступен независимо от закрытости).
 */
export interface ConferenceResolveOut {
  id: string
  title: string | null
  status: ConferenceStatus
  is_closed?: boolean
  requires_password?: boolean
}

/** Тело входа авторизованного пользователя в конференцию. */
export interface ConferenceJoinPayload {
  password?: string
}

/** Тело гостевого входа — представление обязательно, email факультативен (для рассылки саммари). */
export interface ConferenceGuestJoinPayload {
  display_name: string
  email?: string
  password?: string
}

/** Источник трека, который организатор может принудительно выключить (задача B2). */
export type MuteSource = 'microphone' | 'camera'

/** Ответ на принудительный мьют — `false`, если трек и так не был опубликован (нечего было мьютить). */
export interface MuteParticipantResult {
  muted: boolean
}

/** Тело частичного обновления конференции — те же поля, что и при создании, все опциональны. */
export type ConferenceUpdatePayload = Partial<ConferenceCreatePayload>

/** Одно вхождение (развёрнутое по recurrence или разовое) в календарной выборке. */
export interface OccurrenceOut {
  conference_id: string
  title: string | null
  /** UTC ISO-8601. */
  starts_at: string
  /** UTC ISO-8601. */
  ends_at: string
  number: string
  slug: string
  is_pinned: boolean
  is_closed: boolean
}

/**
 * Создать конференцию. Без `scheduled_at` тела — мгновенная: создатель сразу
 * входит (см. `ConferenceOut.join`).
 */
export async function createConference(payload: ConferenceCreatePayload = {}): Promise<ConferenceOut> {
  return apiRequest<ConferenceOut>('/conferences', { method: 'POST', body: payload })
}

/**
 * Резолв конференции по ссылке (slug) или номеру — публичный эндпоинт,
 * доступен без авторизации (нужен и гостю до входа). 404 — не найдена/недоступна
 * (в т.ч. незакреплённая, уже завершившаяся).
 */
export async function resolveConference(query: string): Promise<ConferenceResolveOut> {
  const params = new URLSearchParams({ q: query })
  return apiRequest<ConferenceResolveOut>(`/conferences/resolve?${params.toString()}`, {
    skipAuthRefresh: true,
  })
}

/**
 * Вход авторизованного пользователя в конференцию.
 * 403 (`password_required`/`invalid_password`), 410 (`conference_ended`).
 */
export async function joinConference(id: string, payload?: ConferenceJoinPayload): Promise<ConferenceJoinData> {
  return apiRequest<ConferenceJoinData>(`/conferences/${id}/join`, { method: 'POST', body: payload })
}

/** Гостевой вход без аккаунта — те же коды ошибок, что и `joinConference`. */
export async function guestJoinConference(
  id: string,
  payload: ConferenceGuestJoinPayload,
): Promise<ConferenceJoinData> {
  return apiRequest<ConferenceJoinData>(`/conferences/${id}/guest-join`, {
    method: 'POST',
    body: payload,
    skipAuthRefresh: true,
  })
}

/**
 * Принудительно выключить микрофон/камеру участника (задача B2) — только
 * владелец конференции/администратор, иначе 403 (`not_owner`). 404
 * (`participant_not_in_room`) — участника с таким `identity` сейчас нет в
 * комнате LiveKit.
 */
export async function muteParticipant(
  conferenceId: string,
  identity: string,
  source: MuteSource,
): Promise<MuteParticipantResult> {
  return apiRequest<MuteParticipantResult>(`/conferences/${conferenceId}/mute-participant`, {
    method: 'POST',
    body: { identity, source },
  })
}

/** Список «моих» конференций — закреплённые (повторяющиеся) и предстоящие разовые владельца. */
export async function getMyConferences(): Promise<ConferenceOut[]> {
  return apiRequest<ConferenceOut[]>('/conferences/my')
}

/**
 * Вхождения конференций в календарном диапазоне `[from, to]` (обе границы —
 * UTC ISO, диапазон не длиннее 62 дней — ограничение backend).
 */
export async function getCalendarOccurrences(fromIso: string, toIso: string): Promise<OccurrenceOut[]> {
  const params = new URLSearchParams({ from: fromIso, to: toIso })
  return apiRequest<OccurrenceOut[]>(`/conferences/calendar?${params.toString()}`)
}

/** Частично обновить конференцию (название, время, повторение, закрытость и т.п.). */
export async function updateConference(id: string, payload: ConferenceUpdatePayload): Promise<ConferenceOut> {
  return apiRequest<ConferenceOut>(`/conferences/${id}`, { method: 'PATCH', body: payload })
}

/** Удалить конференцию навсегда. 409 — конференция сейчас идёт (`status: 'active'`). */
export async function deleteConference(id: string): Promise<void> {
  await apiRequest(`/conferences/${id}`, { method: 'DELETE' })
}

/**
 * Детали одной конференции с полным составом участников (`participants`).
 * Доступно только владельцу или admin — иначе 403.
 * Используется ховер-карточкой (§F2) и формой редактирования для подгрузки
 * состава участников.
 */
export async function getConference(id: string): Promise<ConferenceOut> {
  return apiRequest<ConferenceOut>(`/conferences/${id}`)
}
