package ru.forbion.f7cloud.feature.calendar

import android.content.Context
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.designsystem.AppForegroundTracker
import ru.forbion.f7cloud.core.network.UnauthorizedException
import java.time.Instant
import java.time.LocalDate
import java.time.YearMonth
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.Locale

data class CalendarUiState(
    val loading: Boolean = false,
    val viewMode: CalendarViewMode = CalendarViewMode.MONTH,
    val visibleMonth: YearMonth = YearMonth.now(),
    val visibleYear: Int = LocalDate.now().year,
    val selectedDay: LocalDate = LocalDate.now(),
    val events: List<CalendarEventItem> = emptyList(),
    val calendars: List<CalendarBookItem> = emptyList(),
    val datePickerOpen: Boolean = false,
    val createDialogOpen: Boolean = false,
    val syncSettingsOpen: Boolean = false,
    val settingsOpen: Boolean = false,
    val trashOpen: Boolean = false,
    val createBookOpen: Boolean = false,
    val tasksOpen: Boolean = false,
    val eventDetail: CalendarEventItem? = null,
    val editorMode: CalendarEditorMode = CalendarEditorMode.CREATE,
    val eventDraft: CalendarEventDraft = CalendarEventDraft(),
    val createBookDraft: CalendarCreateBookDraft = CalendarCreateBookDraft(),
    val trashItems: List<CalendarTrashItem> = emptyList(),
    val unscheduledTasks: List<CalendarTaskItem> = emptyList(),
    val attendeeSuggestions: List<CalendarAttendeeSuggestion> = emptyList(),
    val locationSuggestions: List<CalendarLocationSuggestion> = emptyList(),
    val userSettings: CalendarUserSettings = CalendarUserSettings(),
    val saving: Boolean = false,
    val deleting: Boolean = false,
    val syncing: Boolean = false,
    val syncEnabled: Boolean = false,
    val deviceCalendars: List<DeviceCalendarInfo> = emptyList(),
    val selectedDeviceCalendarId: Long = -1L,
    val lastSyncLabel: String? = null,
    val needsCalendarPermission: Boolean = false,
    val error: String? = null,
    val snackMessage: String? = null,
    val focusedEventUid: String? = null,
    val unauthorized: Boolean = false,
)

class CalendarViewModel(
    context: Context,
    private val repository: CalendarRepository = CalendarRepository(
        eventsCache = CalendarEventsCache(context.applicationContext),
    ),
) : ViewModel() {
    private val appContext = context.applicationContext
    private val deviceClient = DeviceCalendarClient(appContext)
    private val syncStore = CalendarSyncStore(appContext)
    private val prefsStore = CalendarPrefsStore(appContext)
    private val syncEngine = CalendarSyncEngine(repository, deviceClient, syncStore)

    private val _state = MutableStateFlow(CalendarUiState())
    val state: StateFlow<CalendarUiState> = _state.asStateFlow()

    private var session: AuthSession? = null
    private var visibleCalendarHrefs: Set<String> = emptySet()
    private var periodicRefreshJob: Job? = null

    fun bind(session: AuthSession) {
        this.session = session
        periodicRefreshJob?.cancel()
        val accountKey = syncStore.accountKey(session.serverUrl, session.username)
        visibleCalendarHrefs = prefsStore.getVisibleCalendarHrefs(accountKey)
        _state.value = _state.value.copy(
            viewMode = prefsStore.getViewMode(accountKey),
            userSettings = prefsStore.getUserSettings(accountKey),
            syncEnabled = syncStore.isEnabled(accountKey),
            selectedDeviceCalendarId = syncStore.getDeviceCalendarId(accountKey),
            lastSyncLabel = formatLastSync(syncStore.getLastSyncAt(accountKey)),
        )
        refreshDeviceCalendars()
        loadCalendars(session)
        if (syncStore.isEnabled(accountKey) && syncStore.getDeviceCalendarId(accountKey) > 0) {
            runSyncInternal(session, syncStore.getDeviceCalendarId(accountKey), false)
        } else {
            reloadEvents(session)
        }
        startPeriodicRefresh()
    }

    fun refresh() {
        val s = session ?: return
        val calId = _state.value.selectedDeviceCalendarId
        if (_state.value.syncEnabled && calId > 0) runSyncInternal(s, calId, true) else reloadEvents(s)
    }

    private fun refreshSilent() {
        val s = session ?: return
        if (!AppForegroundTracker.isForeground) return
        reloadEvents(s, showLoading = false)
    }

    private fun startPeriodicRefresh() {
        periodicRefreshJob = viewModelScope.launch {
            while (isActive) {
                delay(REFRESH_INTERVAL_MS)
                if (AppForegroundTracker.isForeground) {
                    refreshSilent()
                }
            }
        }
    }

    override fun onCleared() {
        periodicRefreshJob?.cancel()
        super.onCleared()
    }

    fun onCalendarPermissionResult(granted: Boolean) {
        _state.value = _state.value.copy(needsCalendarPermission = !granted)
        if (granted) {
            refreshDeviceCalendars()
            session?.let { s ->
                if (_state.value.syncEnabled && _state.value.selectedDeviceCalendarId > 0) {
                    runSyncInternal(s, _state.value.selectedDeviceCalendarId, true)
                }
            }
        }
    }

    fun refreshDeviceCalendars() {
        runCatching { deviceClient.listWritableCalendars() }
            .onSuccess { _state.value = _state.value.copy(deviceCalendars = it) }
    }

    fun setViewMode(mode: CalendarViewMode) {
        val s = session ?: return
        prefsStore.setViewMode(syncStore.accountKey(s.serverUrl, s.username), mode)
        _state.value = _state.value.copy(viewMode = mode)
        reloadEvents(s)
    }

    fun goToToday() {
        val s = session ?: return
        val today = LocalDate.now()
        _state.value = _state.value.copy(selectedDay = today, visibleMonth = YearMonth.from(today), visibleYear = today.year)
        reloadEvents(s)
    }

    fun previousPeriod() {
        val s = session ?: return
        val st = _state.value
        _state.value = when (st.viewMode) {
            CalendarViewMode.DAY -> st.copy(selectedDay = st.selectedDay.minusDays(1), visibleMonth = YearMonth.from(st.selectedDay.minusDays(1)))
            CalendarViewMode.WEEK -> st.copy(selectedDay = st.selectedDay.minusWeeks(1), visibleMonth = YearMonth.from(st.selectedDay.minusWeeks(1)))
            CalendarViewMode.MONTH -> st.copy(visibleMonth = st.visibleMonth.minusMonths(1), selectedDay = st.visibleMonth.minusMonths(1).atDay(1))
            CalendarViewMode.YEAR -> st.copy(visibleYear = st.visibleYear - 1)
            CalendarViewMode.LIST -> st.copy(selectedDay = st.selectedDay.minusDays(14), visibleMonth = YearMonth.from(st.selectedDay.minusDays(14)))
        }
        reloadEvents(s)
    }

    fun nextPeriod() {
        val s = session ?: return
        val st = _state.value
        _state.value = when (st.viewMode) {
            CalendarViewMode.DAY -> st.copy(selectedDay = st.selectedDay.plusDays(1), visibleMonth = YearMonth.from(st.selectedDay.plusDays(1)))
            CalendarViewMode.WEEK -> st.copy(selectedDay = st.selectedDay.plusWeeks(1), visibleMonth = YearMonth.from(st.selectedDay.plusWeeks(1)))
            CalendarViewMode.MONTH -> st.copy(visibleMonth = st.visibleMonth.plusMonths(1), selectedDay = st.visibleMonth.plusMonths(1).atDay(1))
            CalendarViewMode.YEAR -> st.copy(visibleYear = st.visibleYear + 1)
            CalendarViewMode.LIST -> st.copy(selectedDay = st.selectedDay.plusDays(14), visibleMonth = YearMonth.from(st.selectedDay.plusDays(14)))
        }
        reloadEvents(s)
    }

    fun openDatePicker() { _state.value = _state.value.copy(datePickerOpen = true) }
    fun closeDatePicker() { _state.value = _state.value.copy(datePickerOpen = false) }

    fun pickDate(day: LocalDate) {
        val s = session ?: return
        _state.value = _state.value.copy(selectedDay = day, visibleMonth = YearMonth.from(day), visibleYear = day.year, datePickerOpen = false)
        reloadEvents(s)
    }

    fun selectDay(day: LocalDate) {
        _state.value = _state.value.copy(selectedDay = day, visibleMonth = YearMonth.from(day), visibleYear = day.year)
        if (_state.value.viewMode != CalendarViewMode.MONTH && _state.value.viewMode != CalendarViewMode.YEAR) {
            session?.let { reloadEvents(it) }
        }
    }

    fun selectYearMonth(month: YearMonth) {
        val s = session ?: return
        _state.value = _state.value.copy(viewMode = CalendarViewMode.MONTH, visibleMonth = month, selectedDay = month.atDay(1))
        reloadEvents(s)
    }

    fun openTrash() {
        val s = session ?: return
        _state.value = _state.value.copy(trashOpen = true)
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.loadTrash(s) }
                .onSuccess { _state.value = _state.value.copy(trashItems = it) }
                .onFailure { _state.value = _state.value.copy(error = it.message) }
        }
    }

    fun closeTrash() { _state.value = _state.value.copy(trashOpen = false) }

    fun restoreTrash(item: CalendarTrashItem) {
        val s = session ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.restoreTrashItem(s, item) }
                .onSuccess {
                    openTrash()
                    reloadEvents(s)
                    _state.value = _state.value.copy(snackMessage = "Событие восстановлено")
                }
                .onFailure { _state.value = _state.value.copy(error = it.message) }
        }
    }

    fun purgeTrash(item: CalendarTrashItem) {
        val s = session ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.purgeTrashItem(s, item) }
                .onSuccess { openTrash() }
                .onFailure { _state.value = _state.value.copy(error = it.message) }
        }
    }

    fun openTasks() {
        val s = session ?: return
        _state.value = _state.value.copy(tasksOpen = true)
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.loadUnscheduledTasks(s) }
                .onSuccess { _state.value = _state.value.copy(unscheduledTasks = it) }
        }
    }

    fun closeTasks() { _state.value = _state.value.copy(tasksOpen = false) }

    fun openCreateBook() { _state.value = _state.value.copy(createBookOpen = true) }
    fun closeCreateBook() { _state.value = _state.value.copy(createBookOpen = false, createBookDraft = CalendarCreateBookDraft()) }
    fun updateCreateBookDraft(draft: CalendarCreateBookDraft) { _state.value = _state.value.copy(createBookDraft = draft) }

    fun saveCreateBook() {
        val s = session ?: return
        val draft = _state.value.createBookDraft
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(saving = true)
            runCatching {
                when (draft.mode) {
                    CreateBookMode.SUBSCRIBE -> repository.subscribeCalendar(s, draft.name, draft.subscriptionUrl)
                    CreateBookMode.WITH_TASKS -> repository.createCalendar(s, draft.name, withTasks = true)
                    CreateBookMode.NEW -> repository.createCalendar(s, draft.name, withTasks = false)
                }
            }.onSuccess {
                loadCalendars(s)
                _state.value = _state.value.copy(saving = false, createBookOpen = false, snackMessage = "Календарь добавлен")
                reloadEvents(s)
            }.onFailure { t ->
                _state.value = _state.value.copy(saving = false, error = t.message, unauthorized = t is UnauthorizedException)
            }
        }
    }

    fun importIcs(content: String) {
        val s = session ?: return
        val href = _state.value.calendars.firstOrNull { it.visible }?.href
            ?: _state.value.calendars.firstOrNull()?.href ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.importIcs(s, href, content) }
                .onSuccess { count ->
                    reloadEvents(s)
                    _state.value = _state.value.copy(snackMessage = "Импортировано событий: $count")
                }
                .onFailure { _state.value = _state.value.copy(error = it.message) }
        }
    }

    fun toggleCalendarVisibility(href: String) {
        val s = session ?: return
        val accountKey = syncStore.accountKey(s.serverUrl, s.username)
        val updated = _state.value.calendars.map { if (it.href == href) it.copy(visible = !it.visible) else it }
        visibleCalendarHrefs = updated.filter { it.visible }.map { it.href }.toSet()
        prefsStore.setVisibleCalendarHrefs(accountKey, visibleCalendarHrefs)
        _state.value = _state.value.copy(calendars = updated)
        reloadEvents(s)
    }

    fun openSyncSettings() { refreshDeviceCalendars(); _state.value = _state.value.copy(syncSettingsOpen = true) }
    fun closeSyncSettings() { _state.value = _state.value.copy(syncSettingsOpen = false) }

    fun openAppSettings() { _state.value = _state.value.copy(settingsOpen = true) }
    fun closeAppSettings() { _state.value = _state.value.copy(settingsOpen = false) }

    fun updateUserSettings(settings: CalendarUserSettings) {
        val s = session ?: return
        val accountKey = syncStore.accountKey(s.serverUrl, s.username)
        prefsStore.setUserSettings(accountKey, settings)
        _state.value = _state.value.copy(userSettings = settings)
        viewModelScope.launch(Dispatchers.IO) {
            runCatching {
                repository.saveUserSetting(s, "timezone", settings.timezone)
                repository.saveUserSetting(s, "showWeekends", if (settings.showWeekends) "yes" else "no")
                repository.saveUserSetting(s, "showWeekNr", if (settings.showWeekNumbers) "yes" else "no")
                repository.saveUserSetting(s, "defaultReminder", settings.defaultReminderMinutes.toString())
                repository.saveUserSetting(s, "showTasks", if (settings.showTasks) "yes" else "no")
                repository.saveUserSetting(s, "tasksSidebar", if (settings.tasksSidebar) "yes" else "no")
            }
        }
    }

    fun setSyncEnabled(enabled: Boolean) {
        val s = session ?: return
        val accountKey = syncStore.accountKey(s.serverUrl, s.username)
        syncStore.setEnabled(accountKey, enabled)
        _state.value = _state.value.copy(syncEnabled = enabled, needsCalendarPermission = enabled)
        if (enabled && _state.value.selectedDeviceCalendarId <= 0 && _state.value.deviceCalendars.isNotEmpty()) {
            selectDeviceCalendar(_state.value.deviceCalendars.first().id)
        } else if (!enabled) reloadEvents(s)
    }

    fun selectDeviceCalendar(calendarId: Long) {
        val s = session ?: return
        syncStore.setDeviceCalendarId(syncStore.accountKey(s.serverUrl, s.username), calendarId)
        _state.value = _state.value.copy(selectedDeviceCalendarId = calendarId)
        if (_state.value.syncEnabled) runSyncInternal(s, calendarId, true)
    }

    fun runSyncNow() {
        val s = session ?: return
        val calId = _state.value.selectedDeviceCalendarId
        if (calId <= 0) { _state.value = _state.value.copy(error = "Выберите календарь телефона"); return }
        runSyncInternal(s, calId, true)
    }

    fun openCreateDialog(day: LocalDate? = null) {
        val s = session ?: return
        val accountKey = syncStore.accountKey(s.serverUrl, s.username)
        val targetDay = day ?: _state.value.selectedDay
        val createHref = prefsStore.getCreateCalendarHref(accountKey)
            ?: _state.value.calendars.firstOrNull { it.visible }?.href.orEmpty()
        _state.value = _state.value.copy(
            createDialogOpen = true,
            editorMode = CalendarEditorMode.CREATE,
            eventDetail = null,
            eventDraft = CalendarEventDraft(
                date = targetDay,
                calendarHref = createHref,
                reminderMinutes = _state.value.userSettings.defaultReminderMinutes,
            ),
        )
    }

    fun closeCreateDialog() { _state.value = _state.value.copy(createDialogOpen = false) }
    fun updateEventDraft(transform: (CalendarEventDraft) -> CalendarEventDraft) {
        _state.value = _state.value.copy(eventDraft = transform(_state.value.eventDraft))
    }

    fun searchAttendees(query: String) {
        val s = session ?: return
        updateEventDraft { it.copy(attendeeQuery = query) }
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.searchAttendees(s, query) }
                .onSuccess { _state.value = _state.value.copy(attendeeSuggestions = it) }
        }
    }

    fun addAttendee(suggestion: CalendarAttendeeSuggestion) {
        updateEventDraft { draft ->
            if (draft.attendees.any { it.email.equals(suggestion.email, true) }) return@updateEventDraft draft
            draft.copy(
                attendees = draft.attendees + CalendarAttendeeItem(suggestion.email, suggestion.name),
                attendeeQuery = "",
            )
        }
        _state.value = _state.value.copy(attendeeSuggestions = emptyList())
    }

    fun removeAttendee(email: String) {
        updateEventDraft { it.copy(attendees = it.attendees.filterNot { a -> a.email == email }) }
    }

    fun searchLocations(query: String) {
        val s = session ?: return
        updateEventDraft { it.copy(location = query) }
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.searchLocations(s, query) }
                .onSuccess { _state.value = _state.value.copy(locationSuggestions = it) }
        }
    }

    fun applyLocationSuggestion(suggestion: CalendarLocationSuggestion) {
        updateEventDraft { it.copy(location = suggestion.address.ifBlank { suggestion.name }) }
        _state.value = _state.value.copy(locationSuggestions = emptyList())
    }

    fun openEventDetail(event: CalendarEventItem) {
        _state.value = _state.value.copy(eventDetail = event, focusedEventUid = event.uid)
    }

    fun closeEventDetail() { _state.value = _state.value.copy(eventDetail = null, focusedEventUid = null) }

    fun openEditEvent(event: CalendarEventItem) {
        val zone = ZoneId.systemDefault()
        val start = Instant.ofEpochMilli(event.startEpochMilli).atZone(zone)
        val end = Instant.ofEpochMilli(event.endEpochMilli).atZone(zone)
        val recurrence = RecurrencePreset.entries.firstOrNull { it.rrule == event.rrule } ?: RecurrencePreset.NONE
        _state.value = _state.value.copy(
            createDialogOpen = true,
            editorMode = CalendarEditorMode.EDIT,
            eventDetail = null,
            eventDraft = CalendarEventDraft(
                title = event.summary,
                date = start.toLocalDate(),
                startTime = start.format(DateTimeFormatter.ofPattern("HH:mm")),
                endTime = end.format(DateTimeFormatter.ofPattern("HH:mm")),
                allDay = event.allDay,
                description = event.description,
                location = event.location,
                calendarHref = event.calendarHref,
                recurrence = recurrence,
                customRrule = if (recurrence == RecurrencePreset.NONE) event.rrule else "",
                categories = event.categories.joinToString(", "),
                status = EventStatus.entries.firstOrNull { it.icsValue == event.status } ?: EventStatus.CONFIRMED,
                classification = EventClassification.entries.firstOrNull { it.icsValue == event.classification } ?: EventClassification.PUBLIC,
                attendees = event.attendees,
                reminderMinutes = event.alarms.firstOrNull()?.minutesBefore ?: 15,
                talkRoomUrl = event.talkRoomUrl,
                addTalkRoom = event.talkRoomUrl.isNotBlank(),
            ),
            focusedEventUid = event.uid,
        )
    }

    fun duplicateEvent(event: CalendarEventItem) {
        val s = session ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.duplicateEvent(s, event, _state.value.selectedDay) }
                .onSuccess {
                    reloadEvents(s)
                    _state.value = _state.value.copy(snackMessage = "Событие скопировано", eventDetail = null)
                }
                .onFailure { _state.value = _state.value.copy(error = it.message) }
        }
    }

    fun respondToInvite(partStat: String) {
        val s = session ?: return
        val event = _state.value.eventDetail ?: return
        val email = s.username
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.respondToInvite(s, event, partStat, email) }
                .onSuccess {
                    reloadEvents(s)
                    _state.value = _state.value.copy(snackMessage = "Ответ отправлен", eventDetail = null)
                }
                .onFailure { _state.value = _state.value.copy(error = it.message) }
        }
    }

    fun saveEventDraft() {
        val s = session ?: return
        val draft = _state.value.eventDraft
        if (draft.title.trim().isBlank()) { _state.value = _state.value.copy(error = "Введите название"); return }
        if (!draft.allDay) {
            val start = parseDateTime(draft.date, draft.startTime)
            val end = parseDateTime(draft.date, draft.endTime)
            if (start == null || end == null) { _state.value = _state.value.copy(error = "Некорректное время"); return }
            if (!end.isAfter(start)) { _state.value = _state.value.copy(error = "Окончание должно быть позже начала"); return }
        }
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(saving = true, error = null)
            val existing = findEventByUid(_state.value.focusedEventUid.orEmpty())
            runCatching { repository.saveEvent(s, draft, existing) }
                .onSuccess {
                    prefsStore.setCreateCalendarHref(syncStore.accountKey(s.serverUrl, s.username), draft.calendarHref)
                    _state.value = _state.value.copy(saving = false, createDialogOpen = false, snackMessage = "Сохранено")
                    afterMutation(s)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(saving = false, error = t.message, unauthorized = t is UnauthorizedException)
                }
        }
    }

    fun deleteSelectedEvent() {
        val s = session ?: return
        val event = findEventByUid(_state.value.focusedEventUid.orEmpty()) ?: _state.value.eventDetail ?: return
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(deleting = true)
            runCatching { repository.deleteEvent(s, event) }
                .onSuccess {
                    _state.value = _state.value.copy(deleting = false, createDialogOpen = false, eventDetail = null, focusedEventUid = null, snackMessage = "Удалено")
                    afterMutation(s)
                }
                .onFailure { t -> _state.value = _state.value.copy(deleting = false, error = t.message) }
        }
    }

    fun clearSnack() { _state.value = _state.value.copy(snackMessage = null) }

    fun focusEventByUid(session: AuthSession, uid: String) {
        viewModelScope.launch(Dispatchers.IO) {
            findEventByUid(uid.trim())?.let { applyFocusedEvent(it) } ?: run {
                reloadEvents(session)
                findEventByUid(uid.trim())?.let { applyFocusedEvent(it) }
            }
        }
    }

    fun visibleEvents(): List<CalendarEventItem> {
        val visible = _state.value.calendars.filter { it.visible }.map { it.href }.toSet()
        return if (visible.isEmpty()) _state.value.events else _state.value.events.filter { it.calendarHref in visible || it.calendarHref.isBlank() }
    }

    fun eventsForDay(day: LocalDate) = visibleEvents().filter { CalendarRepository.eventOccursOnDay(it, day) }

    fun daysWithEvents(): Set<LocalDate> = CalendarRepository.monthGridDays(_state.value.visibleMonth)
        .filter { day -> visibleEvents().any { CalendarRepository.eventOccursOnDay(it, day) } }.toSet()

    fun periodTitle(): String {
        val st = _state.value
        return when (st.viewMode) {
            CalendarViewMode.YEAR -> st.visibleYear.toString()
            CalendarViewMode.LIST -> "Предстоящие события"
            CalendarViewMode.WEEK -> {
                val (start, end) = CalendarRepository.weekRange(st.selectedDay)
                "${start.format(DateTimeFormatter.ofPattern("MMM d", Locale.ENGLISH))} – ${end.format(DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.ENGLISH))}"
            }
            CalendarViewMode.DAY, CalendarViewMode.MONTH ->
                st.selectedDay.format(DateTimeFormatter.ofPattern("MMM d, yyyy", Locale.ENGLISH))
        }
    }

    private fun loadCalendars(session: AuthSession) {
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.listCalendars(session) }.onSuccess { cals ->
                val accountKey = syncStore.accountKey(session.serverUrl, session.username)
                val visible = prefsStore.getVisibleCalendarHrefs(accountKey)
                val items = cals.map { cal ->
                    CalendarBookItem(
                        href = cal.href,
                        displayName = cal.displayName.ifBlank { "Календарь" },
                        visible = visible.isEmpty() || cal.href in visible,
                        color = cal.color,
                    )
                }
                if (visible.isEmpty()) visibleCalendarHrefs = items.map { it.href }.toSet()
                _state.value = _state.value.copy(calendars = items)
            }
        }
    }

    private fun reloadEvents(session: AuthSession, showLoading: Boolean = true) {
        viewModelScope.launch(Dispatchers.IO) {
            if (showLoading) {
                _state.value = _state.value.copy(loading = true, error = null)
            }
            val st = _state.value
            runCatching {
                when (st.viewMode) {
                    CalendarViewMode.MONTH -> repository.loadMonth(session, st.visibleMonth, visibleCalendarHrefs)
                    CalendarViewMode.YEAR -> repository.loadRange(session, LocalDate.of(st.visibleYear, 1, 1), LocalDate.of(st.visibleYear, 12, 31), visibleCalendarHrefs)
                    CalendarViewMode.DAY -> repository.loadRange(session, st.selectedDay, st.selectedDay, visibleCalendarHrefs)
                    CalendarViewMode.WEEK -> {
                        val (a, b) = CalendarRepository.weekRange(st.selectedDay)
                        repository.loadRange(session, a, b, visibleCalendarHrefs)
                    }
                    CalendarViewMode.LIST -> {
                        val (a, b) = CalendarRepository.listRange(st.selectedDay)
                        repository.loadRange(session, a, b, visibleCalendarHrefs)
                    }
                }
            }.onSuccess { _state.value = _state.value.copy(loading = false, events = it) }
                .onFailure { _state.value = _state.value.copy(loading = false, error = it.message, unauthorized = it is UnauthorizedException) }
        }
    }

    private fun afterMutation(session: AuthSession) {
        val calId = _state.value.selectedDeviceCalendarId
        if (_state.value.syncEnabled && calId > 0) runSyncInternal(session, calId, false) else reloadEvents(session)
    }

    private fun runSyncInternal(session: AuthSession, deviceCalendarId: Long, showSnack: Boolean) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(syncing = true, loading = true)
            runCatching { syncEngine.run(session, deviceCalendarId) }
                .onSuccess { result ->
                    reloadEvents(session)
                    _state.value = _state.value.copy(
                        syncing = false,
                        snackMessage = if (showSnack) "Синхронизация завершена (${result.pushedToDevice + result.pushedToServer + result.updated + result.linked})" else _state.value.snackMessage,
                        lastSyncLabel = formatLastSync(syncStore.getLastSyncAt(syncStore.accountKey(session.serverUrl, session.username))),
                    )
                }
                .onFailure { t -> _state.value = _state.value.copy(syncing = false, loading = false, error = t.message) }
        }
    }

    private fun findEventByUid(uid: String): CalendarEventItem? {
        if (uid.isBlank()) return null
        return visibleEvents().find { it.uid == uid || it.uid.endsWith(uid) || uid.endsWith(it.uid) }
    }

    private fun applyFocusedEvent(event: CalendarEventItem) {
        val day = Instant.ofEpochMilli(event.startEpochMilli).atZone(ZoneId.systemDefault()).toLocalDate()
        _state.value = _state.value.copy(selectedDay = day, visibleMonth = YearMonth.from(day), eventDetail = event, focusedEventUid = event.uid)
    }

    private fun formatLastSync(epoch: Long) = if (epoch <= 0) null else "Синхр.: ${Instant.ofEpochMilli(epoch).atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))}"

    private fun parseDateTime(day: LocalDate, time: String): java.time.LocalDateTime? {
        val p = time.trim().split(':')
        if (p.size != 2) return null
        val h = p[0].toIntOrNull() ?: return null
        val m = p[1].toIntOrNull() ?: return null
        if (h !in 0..23 || m !in 0..59) return null
        return day.atTime(h, m)
    }

    class Factory(private val context: Context) : ViewModelProvider.Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel> create(modelClass: Class<T>): T =
            CalendarViewModel(context.applicationContext) as T
    }

    companion object {
        private const val REFRESH_INTERVAL_MS = 5 * 60 * 1000L
        val monthTitleFormatter = DateTimeFormatter.ofPattern("LLLL yyyy", Locale.forLanguageTag("ru"))
    }
}
