import { memo, useCallback, useEffect, useMemo, useState } from 'react'
import ReactECharts from 'echarts-for-react'
import SimpleBar from 'simplebar-react'
import 'simplebar-react/dist/simplebar.min.css'
import {
    AppWindow,
    HeartPulse,
    Home,
    MessageCircle,
    Radio,
    Settings,
    ShieldCheck,
} from 'lucide-react'
import type { LucideIcon } from 'lucide-react'

import type {
    DevicePackage,
    DevicePackageUseEvent,
    DevicePackageUseEventsGroup,
    DeviceTelemetryItem,
    DeviceHistoryChartItem,
    DeviceTelemetryEventItem,
} from '../../../../entities/device/model/types'
import type { DeviceHistoryPeriodValue } from '../DeviceHistoryPeriodControl/DeviceHistoryPeriodControl'

import './DeviceHistoryChart.scss'

type DeviceHistoryChartProps = {
    period: DeviceHistoryPeriodValue | null
    zoomRange: {
        start: number
        end: number
    } | null
    telemetry: DeviceHistoryChartItem[]
    packageUseEvents: DevicePackageUseEventsGroup[]
    isLoading?: boolean
    isError?: boolean
}

type EventCategoryKey =
    | 'session'
    | 'charging'
    | 'screen'
    | 'network'
    | 'alerts'

type HistoryEvent = {
    category: EventCategoryKey
    name: string
    start: number
    end: number
    color: string
}

type HistoryEventPoint = {
    category: EventCategoryKey
    name: string
    date: number
    color: string
    symbol: string
}

type AppActivityState = 'active' | 'background'

type AppActivityInterval = {
    packageName: string
    state: AppActivityState
    name: string
    start: number
    end: number
    color: string
}

type NormalizedAppEvent = DevicePackageUseEvent & {
    label: string
    color: string
    symbol: string
}

type AppActivityRow = {
    packageName: string
    name: string
    category: string
    versionName: string | null
    app: DevicePackage | null
    events: NormalizedAppEvent[]
    intervals: AppActivityInterval[]
    activeDuration: number
    activePercent: number
}

type ChartPoint = [number, number]

type RectShape = {
    x: number
    y: number
    width: number
    height: number
}

type CustomRenderParams = {
    coordSys: RectShape
}

type CustomRenderApi = {
    value: (index: number) => number
    coord: (value: [number, number]) => [number, number]
    style: () => Record<string, unknown>
}

type AxisPointerLabelParams = {
    value: number | string
}

type TooltipFormatterParam = {
    axisValue: number | string
}

const FONT_FAMILY = 'Montserrat Variable, Montserrat, sans-serif'

const FALLBACK_PERIOD_MS = 24 * 60 * 60 * 1000

const APP_TITLE_TOP = 960
const APP_STATE_LEGEND_TOP = 1014
const APP_GRID_TOP = 1060
const APP_SELECTOR_GAP = 26
const APP_SELECTOR_HEIGHT = 82
const APP_MIN_GRID_HEIGHT = 150
const APP_ROW_HEIGHT = 34
const APP_ROW_GAP = 8
const MAX_VISIBLE_APP_ROWS = 8

const APP_EVENT_TYPES: Record<
    string,
    {
        label: string
        color: string
        symbol: string
    }
> = {
    ResumeActivity: {
        label: 'На экране',
        color: '#031d9a',
        symbol: 'circle',
    },
    PauseActivity: {
        label: 'Фон',
        color: '#ff8a34',
        symbol: 'roundRect',
    }
}

const APP_STATE_META: Record<
    AppActivityState,
    {
        label: string
        color: string
    }
> = {
    active: {
        label: 'На экране',
        color: '#031d9a',
    },
    background: {
        label: 'Фон',
        color: '#ff8a34',
    },
}

const EVENT_CATEGORIES: Array<{
    key: EventCategoryKey
    label: string
    color: string
}> = [
        {
            key: 'session',
            label: 'Питание',
            color: '#031d9a',
        },
        {
            key: 'charging',
            label: 'Зарядка',
            color: '#7a3fe8',
        },
        {
            key: 'screen',
            label: 'Экран вкл',
            color: '#2f80ed',
        },
        {
            key: 'network',
            label: 'Нет сети',
            color: '#ff8a34',
        },
        {
            key: 'alerts',
            label: 'Удары и перегревы',
            color: '#e04f5f',
        },
    ]

const LIGHT_CHART_COLORS = {
    axisLabel: '#738098',
    axisLine: '#dbe2ee',
    splitLine: '#edf0f5',
    pointerLine: '#cfd7e6',
    tooltipBg: '#20263a',
    tooltipText: '#ffffff',
    tooltipMuted: '#aeb8cc',
    tooltipShadow: '0 12px 32px rgba(16, 24, 40, 0.22)',
    pointBorder: '#ffffff',
    temperature: '#ff8a34',
    temperatureAreaStart: 'rgba(255, 138, 52, 0.24)',
    temperatureAreaEnd: 'rgba(255, 138, 52, 0.04)',
    battery: '#31b24a',
    batteryAreaStart: 'rgba(49, 178, 74, 0.28)',
    batteryAreaEnd: 'rgba(49, 178, 74, 0.04)',
    dangerArea: 'rgba(224, 0, 0, 0.1)',
    dangerMark: 'rgba(224, 0, 0, 0.045)',
    eventColors: {
        session: '#031d9a',
        charging: '#7a3fe8',
        screen: '#2f80ed',
        network: '#ff8a34',
        alerts: '#e04f5f',
    } satisfies Record<EventCategoryKey, string>,
    appActive: '#031d9a',
    appBackground: '#ff8a34',
    warning: '#ff8a34',
    danger: '#e04f5f',
}

const DARK_CHART_COLORS = {
    axisLabel: '#7e8ba0',
    axisLine: 'rgba(146, 155, 170, 0.24)',
    splitLine: 'rgba(146, 155, 170, 0.18)',
    pointerLine: 'rgba(174, 183, 197, 0.34)',
    tooltipBg: '#1f242d',
    tooltipText: '#f7f8fb',
    tooltipMuted: '#9ba5b3',
    tooltipShadow: '0 18px 42px rgba(0, 0, 0, 0.42)',
    pointBorder: '#171a20',
    temperature: '#ff9f43',
    temperatureAreaStart: 'rgba(255, 159, 67, 0.14)',
    temperatureAreaEnd: 'rgba(255, 159, 67, 0.02)',
    battery: '#3fd26c',
    batteryAreaStart: 'rgba(63, 210, 108, 0.14)',
    batteryAreaEnd: 'rgba(63, 210, 108, 0.02)',
    dangerArea: 'rgba(255, 79, 94, 0.055)',
    dangerMark: 'rgba(255, 79, 94, 0.04)',
    eventColors: {
        session: '#2d8cff',
        charging: '#8b5cf6',
        screen: '#55a6ff',
        network: '#ff9f43',
        alerts: '#ff4f5e',
    } satisfies Record<EventCategoryKey, string>,
    appActive: '#2d8cff',
    appBackground: '#ff9f43',
    warning: '#ff9f43',
    danger: '#ff4f5e',
}

function formatTooltipDate(value: number) {
    return new Intl.DateTimeFormat('ru-RU', {
        day: '2-digit',
        month: '2-digit',
        hour: '2-digit',
        minute: '2-digit',
    }).format(new Date(value))
}

function formatDuration(value: number) {
    const totalMinutes = Math.max(0, Math.round(value / 60000))
    const hours = Math.floor(totalMinutes / 60)
    const minutes = totalMinutes % 60

    if (!hours) return `${minutes} мин`
    if (!minutes) return `${hours} ч`

    return `${hours} ч ${minutes} мин`
}

function formatUsagePercent(value: number) {
    if (value <= 0) return '0%'
    if (value < 1) return '<1%'

    return `${Math.round(value)}%`
}

function escapeHtml(value: string) {
    return value
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll("'", '&#039;')
}

function getLineValueAtTime(data: ChartPoint[], hoverTime: number) {
    if (!data.length) return null

    const firstPoint = data[0]
    const lastPoint = data[data.length - 1]

    if (hoverTime <= firstPoint[0]) return firstPoint[1]
    if (hoverTime >= lastPoint[0]) return lastPoint[1]

    let left = 0
    let right = data.length - 1

    while (right - left > 1) {
        const middle = Math.floor((left + right) / 2)

        if (data[middle][0] <= hoverTime) {
            left = middle
        } else {
            right = middle
        }
    }

    const currentPoint = data[left]
    const nextPoint = data[right]
    const currentTime = currentPoint[0]
    const currentValue = currentPoint[1]
    const nextTime = nextPoint[0]
    const nextValue = nextPoint[1]

    if (nextTime === currentTime) return currentValue

    const progress = (hoverTime - currentTime) / (nextTime - currentTime)
    const interpolatedValue =
        currentValue + (nextValue - currentValue) * progress

    return Math.round(interpolatedValue)
}

function formatTooltipRange(start: number, end: number) {
    return `${formatTooltipDate(start)} — ${formatTooltipDate(end)}`
}

function isTelemetryEvent(
    item: DeviceHistoryChartItem,
): item is DeviceTelemetryEventItem {
    return 'type' in item && item.type === 'event'
}

function isTelemetryPoint(
    item: DeviceHistoryChartItem,
): item is DeviceTelemetryItem {
    return !isTelemetryEvent(item)
}

function sortTelemetry(telemetry: DeviceTelemetryItem[]) {
    return [...telemetry].sort((a, b) => a.date - b.date)
}

function createTelemetryLines(telemetry: DeviceTelemetryItem[]): {
    battery: ChartPoint[]
    temperature: ChartPoint[]
} {
    const sortedTelemetry = sortTelemetry(telemetry)

    return {
        battery: sortedTelemetry
            .filter((item) => typeof item.batteryLevel === 'number')
            .map((item) => [item.date, item.batteryLevel as number]),

        temperature: sortedTelemetry
            .filter((item) => typeof item.temperature === 'number')
            .map((item) => [item.date, item.temperature as number]),
    }
}

function createTelemetryEvents(
    telemetryEvents: DeviceTelemetryEventItem[],
    periodEnd: number,
): HistoryEvent[] {
    const sortedEvents = [...telemetryEvents].sort(
        (a, b) => a.date - b.date,
    )

    const events: HistoryEvent[] = []

    let screenStart: number | null = null
    let networkLostStart: number | null = null
    let sessionStart: number | null = null

    let chargingStart: number | null = null
    let chargingLabel: string | null = null

    sortedEvents.forEach((item) => {
        switch (item.eventType) {
            case 'PowerOn': {
                if (sessionStart === null) {
                    sessionStart = item.date
                }

                break
            }

            case 'PowerOff': {
                if (sessionStart !== null) {
                    events.push({
                        category: 'session',
                        name: 'Устройство включено',
                        start: sessionStart,
                        end: item.date,
                        color: '#031d9a',
                    })

                    sessionStart = null
                }

                break
            }

            case 'ScreenOn': {
                if (screenStart === null) {
                    screenStart = item.date
                }

                break
            }

            case 'ScreenOff': {
                if (screenStart !== null) {
                    events.push({
                        category: 'screen',
                        name: 'Экран включен',
                        start: screenStart,
                        end: item.date,
                        color: '#2f80ed',
                    })

                    screenStart = null
                }

                break
            }

            case 'LeaveNetwork': {
                if (networkLostStart === null) {
                    networkLostStart = item.date
                }

                break
            }

            case 'EnterNetwork': {
                if (networkLostStart !== null) {
                    events.push({
                        category: 'network',
                        name: 'Нет сети',
                        start: networkLostStart,
                        end: item.date,
                        color: '#ff8a34',
                    })

                    networkLostStart = null
                }

                break
            }

            case 'PowerAC':
            case 'PowerUSB':
            case 'PowerWireless': {
                if (
                    chargingStart !== null &&
                    chargingLabel !== null
                ) {
                    events.push({
                        category: 'charging',
                        name: chargingLabel,
                        start: chargingStart,
                        end: item.date,
                        color: '#7a3fe8',
                    })
                }

                chargingStart = item.date
                chargingLabel = item.label

                break
            }

            case 'PowerDisconnected': {
                if (
                    chargingStart !== null &&
                    chargingLabel !== null
                ) {
                    events.push({
                        category: 'charging',
                        name: chargingLabel,
                        start: chargingStart,
                        end: item.date,
                        color: '#7a3fe8',
                    })

                    chargingStart = null
                    chargingLabel = null
                }

                break
            }
        }
    })

    if (sessionStart !== null && periodEnd > sessionStart) {
        events.push({
            category: 'session',
            name: 'Устройство включено',
            start: sessionStart,
            end: periodEnd,
            color: '#031d9a',
        })
    }

    if (screenStart !== null && periodEnd > screenStart) {
        events.push({
            category: 'screen',
            name: 'Экран включен',
            start: screenStart,
            end: periodEnd,
            color: '#2f80ed',
        })
    }

    if (networkLostStart !== null && periodEnd > networkLostStart) {
        events.push({
            category: 'network',
            name: 'Нет сети',
            start: networkLostStart,
            end: periodEnd,
            color: '#ff8a34',
        })
    }

    if (
        chargingStart !== null &&
        chargingLabel !== null &&
        periodEnd > chargingStart
    ) {
        events.push({
            category: 'charging',
            name: chargingLabel,
            start: chargingStart,
            end: periodEnd,
            color: '#7a3fe8',
        })
    }

    return events.filter((event) => event.end > event.start)
}

function createTelemetryEventPoints(
    telemetryEvents: DeviceTelemetryEventItem[],
): HistoryEventPoint[] {
    return telemetryEvents
        .filter((item) => {
            return (
                item.eventType === 'Hit' ||
                item.eventType === 'Overheat'
            )
        })
        .map((item) => {
            if (item.eventType === 'Overheat') {
                return {
                    category: 'alerts',
                    name: item.label,
                    date: item.date,
                    color: '#e04f5f',
                    symbol: 'circle',
                }
            }

            return {
                category: 'alerts',
                name: item.label,
                date: item.date,
                color: '#ff8a34',
                symbol: 'circle',
            }
        })
}

function clamp(value: number, min: number, max: number) {
    return Math.min(Math.max(value, min), max)
}

function getAppEventMeta(type: string) {
    return (
        APP_EVENT_TYPES[type] ?? {
            label: type,
            color: '#738098',
            symbol: 'circle',
        }
    )
}

function getAppName(app: DevicePackage | null, fallbackPackageName: string) {
    return app?.name || app?.packageName || fallbackPackageName || 'Неизвестное приложение'
}

function normalizeAppEvents(
    events: DevicePackageUseEvent[],
    periodStart: number,
    periodEnd: number,
): NormalizedAppEvent[] {
    return events
        .filter((event) => event.date >= periodStart && event.date <= periodEnd)
        .sort((a, b) => a.date - b.date)
        .map((event) => {
            const meta = getAppEventMeta(event.type)

            return {
                ...event,
                label: meta.label,
                color: meta.color,
                symbol: meta.symbol,
            }
        })
}

function createAppActivityRows(
    packageUseEvents: DevicePackageUseEventsGroup[],
    periodStart: number,
    periodEnd: number,
): AppActivityRow[] {
    const periodDuration = Math.max(periodEnd - periodStart, 1)

    return packageUseEvents
        .map((group) => {
            const app = group.phonePackage ?? null
            const packageName =
                app?.packageName || group.events[0]?.packageName || 'unknown-package'

            const events = normalizeAppEvents(group.events ?? [], periodStart, periodEnd)
            const intervals: AppActivityInterval[] = []

            let currentState: AppActivityState | null = null
            let currentStart: number | null = null

            for (const event of events) {
                const eventDate = clamp(event.date, periodStart, periodEnd)

                if (
                    currentState !== null &&
                    currentStart !== null &&
                    eventDate > currentStart
                ) {
                    const stateMeta = APP_STATE_META[currentState]

                    intervals.push({
                        packageName,
                        state: currentState,
                        name: stateMeta.label,
                        start: currentStart,
                        end: eventDate,
                        color: stateMeta.color,
                    })
                }

                if (event.type === 'ResumeActivity') {
                    currentState = 'active'
                    currentStart = eventDate
                    continue
                }

                if (event.type === 'PauseActivity') {
                    currentState = 'background'
                    currentStart = eventDate
                    continue
                }

                if (event.type === 'StopActivity') {
                    currentState = null
                    currentStart = null
                }
            }

            const openState = currentState
            const openStart = currentStart

            if (
                openState !== null &&
                openStart !== null &&
                periodEnd > openStart
            ) {
                const stateMeta = APP_STATE_META[openState]

                intervals.push({
                    packageName,
                    state: openState,
                    name: stateMeta.label,
                    start: openStart,
                    end: periodEnd,
                    color: stateMeta.color,
                })
            }

            const activeDuration = intervals.reduce((sum, interval) => {
                if (interval.state !== 'active') return sum

                return sum + Math.max(interval.end - interval.start, 0)
            }, 0)

            const activePercent = Math.min(
                (activeDuration / periodDuration) * 100,
                100,
            )

            return {
                packageName,
                name: getAppName(app, packageName),
                category: app?.category || 'Не указана',
                versionName: app?.versionName ?? null,
                app,
                events,
                intervals,
                activeDuration,
                activePercent,
            }
        })
        .filter((row) => row.events.length > 0 || row.app)
        .sort((a, b) => {
            if (b.activeDuration !== a.activeDuration) {
                return b.activeDuration - a.activeDuration
            }

            return a.name.localeCompare(b.name, 'ru')
        })
}

function getAppIcon(row: AppActivityRow): {
    Icon: LucideIcon
    modifier: string
} {
    const value = `${row.name} ${row.packageName}`.toLowerCase()

    if (value.includes('radio') || value.includes('fm')) {
        return { Icon: Radio, modifier: 'is-radio' }
    }

    if (value.includes('sms') || value.includes('message') || value.includes('ims')) {
        return { Icon: MessageCircle, modifier: 'is-message' }
    }

    if (value.includes('health') || value.includes('здоров')) {
        return { Icon: HeartPulse, modifier: 'is-health' }
    }

    if (value.includes('launcher') || value.includes('home')) {
        return { Icon: Home, modifier: 'is-home' }
    }

    if (value.includes('security') || value.includes('cts')) {
        return { Icon: ShieldCheck, modifier: 'is-security' }
    }

    if (value.includes('android') || value.includes('system')) {
        return { Icon: Settings, modifier: 'is-system' }
    }

    return { Icon: AppWindow, modifier: 'is-default' }
}

function getActiveDurationUntil(
    row: AppActivityRow,
    hoverTime: number,
    periodStart: number,
) {
    return row.intervals.reduce((sum, interval) => {
        if (interval.state !== 'active') return sum

        const start = Math.max(interval.start, periodStart)
        const end = Math.min(interval.end, hoverTime)

        if (end <= start) return sum

        return sum + end - start
    }, 0)
}

function getAppStateAtTime(
    row: AppActivityRow,
    hoverTime: number,
    chartColors: typeof LIGHT_CHART_COLORS,
) {
    const interval = row.intervals.find((item) => {
        return item.start <= hoverTime && item.end >= hoverTime
    })

    if (interval) {
        return {
            label: interval.name,
            color:
                interval.state === 'active'
                    ? chartColors.appActive
                    : chartColors.appBackground,
        }
    }

    const lastEvent = [...row.events]
        .filter((event) => event.date <= hoverTime)
        .at(-1)

    if (!lastEvent) {
        return null
    }

    if (
        lastEvent.type !== 'ResumeActivity' &&
        lastEvent.type !== 'PauseActivity'
    ) {
        return null
    }

    const meta = getAppEventMeta(lastEvent.type)

    return {
        label: meta.label,
        color:
            lastEvent.type === 'ResumeActivity'
                ? chartColors.appActive
                : chartColors.appBackground,
    }
}

function getIsDarkTheme() {
    if (typeof document === 'undefined') return false

    return document.documentElement.dataset.theme === 'dark'
}

function useIsDarkTheme() {
    const [isDarkTheme, setIsDarkTheme] = useState(getIsDarkTheme)

    useEffect(() => {
        if (typeof document === 'undefined') return

        const root = document.documentElement
        const observer = new MutationObserver(() => {
            setIsDarkTheme(getIsDarkTheme())
        })

        observer.observe(root, {
            attributes: true,
            attributeFilter: ['data-theme'],
        })

        return () => {
            observer.disconnect()
        }
    }, [])

    return isDarkTheme
}

function clipRectByRect(shape: RectShape, rect: RectShape) {
    const x = Math.max(shape.x, rect.x)
    const y = Math.max(shape.y, rect.y)
    const x2 = Math.min(shape.x + shape.width, rect.x + rect.width)
    const y2 = Math.min(shape.y + shape.height, rect.y + rect.height)

    if (x2 < x || y2 < y) return null

    return {
        x,
        y,
        width: x2 - x,
        height: y2 - y,
    }
}

function renderEventItem(params: CustomRenderParams, api: CustomRenderApi) {
    const categoryIndex = api.value(0)
    const start = api.coord([api.value(1), categoryIndex])
    const end = api.coord([api.value(2), categoryIndex])

    const height = 10

    const rectShape = clipRectByRect(
        {
            x: start[0],
            y: start[1] - height / 2,
            width: end[0] - start[0],
            height,
        },
        {
            x: params.coordSys.x,
            y: params.coordSys.y,
            width: params.coordSys.width,
            height: params.coordSys.height,
        },
    )

    if (!rectShape) return null

    return {
        type: 'rect',
        transition: ['shape', 'style'],
        shape: {
            ...rectShape,
            r: 999,
        },
        style: api.style(),
    }
}

function renderAppIntervalItem(
    params: CustomRenderParams,
    api: CustomRenderApi,
) {
    const rowIndex = api.value(0)
    const start = api.coord([api.value(1), rowIndex])
    const end = api.coord([api.value(2), rowIndex])

    const height = 16
    const minWidth = 12

    const rectShape = clipRectByRect(
        {
            x: start[0],
            y: start[1] - height / 2,
            width: Math.max(end[0] - start[0], minWidth),
            height,
        },
        {
            x: params.coordSys.x,
            y: params.coordSys.y,
            width: params.coordSys.width,
            height: params.coordSys.height,
        },
    )

    if (!rectShape) return null

    return {
        type: 'rect',
        shape: {
            ...rectShape,
            r: 999,
        },
        style: api.style(),
    }
}

export const DeviceHistoryChart = memo(function DeviceHistoryChart({
    period,
    zoomRange,
    telemetry,
    packageUseEvents,
    isLoading = false,
    isError = false,
}: DeviceHistoryChartProps) {
    const isDarkTheme = useIsDarkTheme()
    const chartColors = useMemo(
        () => (isDarkTheme ? DARK_CHART_COLORS : LIGHT_CHART_COLORS),
        [isDarkTheme],
    )

    const [enabledCategories, setEnabledCategories] = useState<EventCategoryKey[]>(
        EVENT_CATEGORIES.map((category) => category.key),
    )

    const [enabledPackages, setEnabledPackages] = useState<string[]>([])
    const [fallbackPeriod] = useState(() => {
        const end = Date.now()

        return {
            start: end - FALLBACK_PERIOD_MS,
            end,
        }
    })

    const periodStart =
        period?.periodStart.getTime() ?? fallbackPeriod.start

    const periodEnd = period?.periodEnd.getTime() ?? fallbackPeriod.end
    const visibleStart = zoomRange?.start ?? periodStart
    const visibleEnd = zoomRange?.end ?? periodEnd

    const telemetryPoints = useMemo(
        () => telemetry.filter(isTelemetryPoint),
        [telemetry],
    )

    const telemetryEvents = useMemo(
        () => telemetry.filter(isTelemetryEvent),
        [telemetry],
    )

    const allEvents = useMemo(
        () => createTelemetryEvents(telemetryEvents, periodEnd),
        [telemetryEvents, periodEnd],
    )

    const allEventPoints = useMemo(
        () => createTelemetryEventPoints(telemetryEvents),
        [telemetryEvents],
    )

    const { battery, temperature } = useMemo(
        () => createTelemetryLines(telemetryPoints),
        [telemetryPoints],
    )

    const appRows = useMemo(
        () => createAppActivityRows(packageUseEvents, periodStart, periodEnd),
        [packageUseEvents, periodEnd, periodStart],
    )

    const appPackageNames = useMemo(
        () => appRows.map((row) => row.packageName),
        [appRows],
    )

    const enabledCategoriesSet = useMemo(
        () => new Set(enabledCategories),
        [enabledCategories],
    )

    const visibleCategories = useMemo(
        () =>
            EVENT_CATEGORIES
                .filter((category) => enabledCategoriesSet.has(category.key))
                .map((category) => ({
                    ...category,
                    color: chartColors.eventColors[category.key],
                })),
        [chartColors, enabledCategoriesSet],
    )

    const visibleCategoryIndexMap = useMemo(() => {
        return new Map(
            visibleCategories.map((category, index) => [
                category.key,
                index,
            ]),
        )
    }, [visibleCategories])

    const visibleEvents = useMemo(
        () =>
            allEvents
                .filter((event) => {
                    return (
                        enabledCategoriesSet.has(event.category) &&
                        event.end >= visibleStart &&
                        event.start <= visibleEnd
                    )
                })
                .map((event) => ({
                    ...event,
                    color: chartColors.eventColors[event.category],
                })),
        [
            allEvents,
            chartColors,
            enabledCategoriesSet,
            visibleEnd,
            visibleStart,
        ],
    )

    const visibleEventPoints = useMemo(
        () =>
            allEventPoints
                .filter((event) => {
                    return (
                        enabledCategoriesSet.has(event.category) &&
                        event.date >= visibleStart &&
                        event.date <= visibleEnd
                    )
                })
                .map((event) => ({
                    ...event,
                    color:
                        event.color === '#ff8a34'
                            ? chartColors.warning
                            : chartColors.danger,
                })),
        [
            allEventPoints,
            chartColors,
            enabledCategoriesSet,
            visibleEnd,
            visibleStart,
        ],
    )

    const effectiveEnabledPackages = useMemo(() => {
        const packageNamesSet = new Set(appPackageNames)
        const existingEnabledPackages = enabledPackages.filter((packageName) => {
            return packageNamesSet.has(packageName)
        })

        if (existingEnabledPackages.length > 0) {
            return existingEnabledPackages.slice(0, MAX_VISIBLE_APP_ROWS)
        }

        return appPackageNames.slice(0, MAX_VISIBLE_APP_ROWS)
    }, [appPackageNames, enabledPackages])

    const enabledPackagesSet = useMemo(
        () => new Set(effectiveEnabledPackages),
        [effectiveEnabledPackages],
    )

    const enabledAppRows = useMemo(
        () =>
            appRows.filter((row) =>
                enabledPackagesSet.has(row.packageName),
            ),
        [appRows, enabledPackagesSet],
    )

    const appGridHeight = Math.max(
        APP_MIN_GRID_HEIGHT,
        enabledAppRows.length * (APP_ROW_HEIGHT + APP_ROW_GAP),
    )

    const appSelectorTop = APP_GRID_TOP + appGridHeight + APP_SELECTOR_GAP
    const chartHeight =
        appSelectorTop + (appRows.length > 0 ? APP_SELECTOR_HEIGHT : 90)
    const appRowBand =
        enabledAppRows.length > 0 ? appGridHeight / enabledAppRows.length : 0

    const eventSeriesData = useMemo(
        () =>
            visibleEvents
                .map((event) => {
                    const categoryIndex =
                        visibleCategoryIndexMap.get(event.category) ?? -1

                    if (categoryIndex < 0) return null

                    return {
                        name: event.name,
                        value: [categoryIndex, event.start, event.end],
                        itemStyle: {
                            color: event.color,
                        },
                        meta: event,
                    }
                })
                .filter(Boolean),
        [visibleCategoryIndexMap, visibleEvents],
    )

    const eventPointSeriesData = useMemo(
        () =>
            visibleEventPoints
                .map((event) => {
                    const categoryIndex =
                        visibleCategoryIndexMap.get(event.category) ?? -1

                    if (categoryIndex < 0) return null

                    return {
                        name: event.name,
                        value: [event.date, categoryIndex],
                        symbol: event.symbol,
                        symbolSize: 14,
                        itemStyle: {
                            color: event.color,
                            borderColor: chartColors.pointBorder,
                            borderWidth: 2,
                        },
                        meta: event,
                    }
                })
                .filter(Boolean),
        [chartColors.pointBorder, visibleCategoryIndexMap, visibleEventPoints],
    )

    const appIntervalSeriesData = useMemo(
        () =>
            enabledAppRows.flatMap((row, rowIndex) => {
                return row.intervals
                    .filter((interval) => {
                        return (
                            interval.end >= visibleStart &&
                            interval.start <= visibleEnd
                        )
                    })
                    .map((interval) => ({
                        name: `${row.name}: ${interval.name}`,
                        value: [rowIndex, interval.start, interval.end],
                        itemStyle: {
                            color:
                                interval.state === 'active'
                                    ? chartColors.appActive
                                    : chartColors.appBackground,
                        },
                        meta: interval,
                    }))
            }),
        [
            chartColors.appActive,
            chartColors.appBackground,
            enabledAppRows,
            visibleEnd,
            visibleStart,
        ],
    )

    const toggleCategory = useCallback((categoryKey: EventCategoryKey) => {
        setEnabledCategories((prev) => {
            if (prev.includes(categoryKey)) {
                return prev.filter((item) => item !== categoryKey)
            }

            return [...prev, categoryKey]
        })
    }, [])

    const togglePackage = useCallback((packageName: string) => {
        setEnabledPackages((prev) => {
            const basePackages = prev.length > 0 ? prev : effectiveEnabledPackages

            if (basePackages.includes(packageName)) {
                return basePackages.filter((item) => item !== packageName)
            }

            return [...basePackages, packageName].slice(-MAX_VISIBLE_APP_ROWS)
        })
    }, [effectiveEnabledPackages])

    const option = useMemo(() => ({
        animation: false,

        textStyle: {
            fontFamily: FONT_FAMILY,
            fontWeight: 500,
        },

        tooltip: {
            trigger: 'axis',
            axisPointer: {
                type: 'line',
                axis: 'x',
                lineStyle: {
                    color: chartColors.pointerLine,
                    width: 1,
                },
                label: {
                    show: true,
                    backgroundColor: chartColors.tooltipBg,
                    color: chartColors.tooltipText,
                    fontFamily: FONT_FAMILY,
                    formatter: (params: AxisPointerLabelParams) => {
                        return formatTooltipDate(Number(params.value))
                    },
                },
            },
            borderWidth: 0,
            backgroundColor: chartColors.tooltipBg,
            padding: 14,
            textStyle: {
                color: chartColors.tooltipText,
                fontSize: 13,
                fontFamily: FONT_FAMILY,
            },
            extraCssText:
                `border-radius: 14px; box-shadow: ${chartColors.tooltipShadow};`,
            transitionDuration: 0,
            formatter: (params: TooltipFormatterParam[]) => {
                if (!params?.length) return ''

                const hoverTime = Number(params[0].axisValue)

                if (!Number.isFinite(hoverTime)) return ''

                const eventsAtTime = visibleEvents.filter((event) => {
                    return event.start <= hoverTime && event.end >= hoverTime
                })
                const pointEventsAtTime = visibleEventPoints.filter((event) => {
                    return Math.abs(event.date - hoverTime) <= 60 * 1000
                })
                const batteryValue = getLineValueAtTime(battery, hoverTime)
                const temperatureValue = getLineValueAtTime(temperature, hoverTime)
                const visibleAppRowsAtTime = enabledAppRows.slice(0, 8)

                if (
                    !eventsAtTime.length &&
                    !pointEventsAtTime.length &&
                    !visibleAppRowsAtTime.length &&
                    temperatureValue === null &&
                    batteryValue === null
                ) {
                    return ''
                }

                const rows = [
                    ...eventsAtTime.map((event) => {
                        return `
          <div style="display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:7px;">
            <div style="display:flex;align-items:center;gap:8px;min-width:0;">
              <span style="width:10px;height:10px;border-radius:999px;background:${event.color};display:inline-block;flex:0 0 auto;"></span>
              <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;">
                ${escapeHtml(event.name)}
              </span>
            </div>

            <span style="color:${chartColors.tooltipText};font-weight:600;white-space:nowrap;">
              ${formatTooltipRange(event.start, event.end)}
            </span>
          </div>
        `
                    }),

                    ...pointEventsAtTime.map((event) => {
                        return `
          <div style="display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:7px;">
            <div style="display:flex;align-items:center;gap:8px;min-width:0;">
              <span style="width:10px;height:10px;border-radius:999px;background:${event.color};display:inline-block;flex:0 0 auto;"></span>
              <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;">
                ${escapeHtml(event.name)}
              </span>
            </div>

            <span style="color:${chartColors.tooltipText};font-weight:600;white-space:nowrap;">
              ${formatTooltipDate(event.date)}
            </span>
          </div>
        `
                    }),
                ].join('')

                const temperatureRow =
                    temperatureValue !== null
                        ? `
      <div style="display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:7px;">
        <div style="display:flex;align-items:center;gap:8px;min-width:0;">
          <span style="width:10px;height:10px;border-radius:999px;background:${chartColors.temperature};display:inline-block;flex:0 0 auto;"></span>
          <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;">
            Температура
          </span>
        </div>

        <span style="color:${chartColors.tooltipText};font-weight:600;white-space:nowrap;">
          ${temperatureValue}°C
        </span>
      </div>
    `
                        : ''

                const batteryRow =
                    batteryValue !== null
                        ? `
      <div style="display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:7px;">
        <div style="display:flex;align-items:center;gap:8px;min-width:0;">
          <span style="width:10px;height:10px;border-radius:999px;background:${chartColors.battery};display:inline-block;flex:0 0 auto;"></span>
          <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;">
            Аккумулятор
          </span>
        </div>

        <span style="color:${chartColors.tooltipText};font-weight:600;white-space:nowrap;">
          ${batteryValue}%
        </span>
      </div>
    `
                        : ''

                const appRowsHtml = visibleAppRowsAtTime
    .map((row) => {
        const appState = getAppStateAtTime(row, hoverTime, chartColors)

        if (!appState) {
            return ''
        }

        const activeDurationUntil = getActiveDurationUntil(
            row,
            hoverTime,
            periodStart,
        )

        const elapsedTime = Math.max(hoverTime - periodStart, 1)

        const activePercentUntil = Math.min(
            Math.round((activeDurationUntil / elapsedTime) * 100),
            100,
        )

        const shortName = escapeHtml(row.name)

        return `
      <div style="display:flex;align-items:center;justify-content:space-between;gap:20px;margin-top:8px;">
        <div style="display:flex;align-items:center;gap:8px;min-width:0;">
          <span style="width:22px;height:22px;border-radius:8px;background:${appState.color};display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;color:#fff;font-size:11px;font-weight:700;">
            ${shortName.slice(0, 1).toUpperCase()}
          </span>

          <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:205px;">
            ${shortName}
          </span>
        </div>

        <span style="color:${chartColors.tooltipText};font-weight:600;white-space:nowrap;">
          ${escapeHtml(appState.label)} · ${formatDuration(activeDurationUntil)} / ${activePercentUntil}%
        </span>
      </div>
    `
    })
    .join('')

                const appsBlock = appRowsHtml
                    ? `
    <div style="margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,0.12);">
      <div style="color:${chartColors.tooltipMuted};font-size:12px;margin-bottom:2px;">
        Приложения к этому моменту
      </div>

      ${appRowsHtml}
    </div>
  `
                    : ''

                return `
      <div style="min-width:360px;">
        <div style="color:${chartColors.tooltipMuted};font-size:12px;margin-bottom:8px;">
          ${formatTooltipDate(hoverTime)}
        </div>
        ${rows}
        ${temperatureRow}
        ${batteryRow}
        ${appsBlock}
      </div>
    `
            },
        },

        axisPointer: {
            link: [
                {
                    xAxisIndex: [0, 1, 2, 3],
                },
            ],
        },

        /* dataZoom: [
            {
                type: 'slider',
                xAxisIndex: [0, 1, 2, 3],

                top: 0,
                height: 28,
                start: 0,
                end: 100,

                borderColor: 'transparent',
                backgroundColor: '#eff2f7',
                fillerColor: 'rgba(3, 28, 154, 0.15)',

                showDataShadow: false,
                showDetail: true,
                brushSelect: false,

                handleIcon:
                    'path://M2,0 H4 Q6,0 6,2 V22 Q6,24 4,24 H2 Q0,24 0,22 V2 Q0,0 2,0 Z',
                handleSize: 24,
                handleStyle: {
                    color: '#031d9a',
                    borderColor: '#031d9a',
                    borderWidth: 0,
                },

                moveHandleSize: 0,

                textStyle: {
                    color: chartColors.axisLabel,
                    fontSize: 12,
                    fontFamily: FONT_FAMILY,
                },

                labelFormatter: (value: number) => {
                    return formatTooltipDate(value)
                },
            }
        ], */

        grid: [
            {
                id: 'events',
                left: 90,
                right: 90,
                top: 140,
                height: 170,
            },
            {
                id: 'temperature',
                left: 90,
                right: 90,
                top: 470,
                height: 150,
            },
            {
                id: 'battery',
                left: 90,
                right: 90,
                top: 750,
                height: 150,
            },
            {
                id: 'apps',
                left: 270,
                right: 90,
                top: APP_GRID_TOP,
                height: appGridHeight,
            },
        ],

        xAxis: [
            {
                type: 'time',
                gridIndex: 0,
                min: visibleStart,
                max: visibleEnd,
                axisLabel: {
                    color: chartColors.axisLabel,
                    fontSize: 12,
                    fontFamily: FONT_FAMILY,
                    hideOverlap: true,
                },
                axisLine: {
                    lineStyle: {
                        color: chartColors.axisLine,
                    },
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
            {
                type: 'time',
                gridIndex: 1,
                min: visibleStart,
                max: visibleEnd,
                axisLabel: {
                    color: chartColors.axisLabel,
                    fontSize: 12,
                    fontFamily: FONT_FAMILY,
                    hideOverlap: true,
                },
                axisLine: {
                    lineStyle: {
                        color: chartColors.axisLine,
                    },
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
            {
                type: 'time',
                gridIndex: 2,
                min: visibleStart,
                max: visibleEnd,
                axisLabel: {
                    color: chartColors.axisLabel,
                    fontSize: 12,
                    fontFamily: FONT_FAMILY,
                    hideOverlap: true,
                },
                axisLine: {
                    lineStyle: {
                        color: chartColors.axisLine,
                    },
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
            {
                type: 'time',
                gridIndex: 3,
                min: visibleStart,
                max: visibleEnd,
                axisLabel: {
                    color: chartColors.axisLabel,
                    fontSize: 12,
                    fontFamily: FONT_FAMILY,
                    hideOverlap: true,
                },
                axisLine: {
                    lineStyle: {
                        color: chartColors.axisLine,
                    },
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
        ],

        yAxis: [
            {
                type: 'category',
                gridIndex: 0,
                data: visibleCategories.map((category) => category.label),
                inverse: true,
                axisLabel: {
                    show: false,
                },
                axisLine: {
                    show: false,
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
            {
                type: 'value',
                gridIndex: 1,
                min: 0,
                max: 60,
                interval: 15,
                position: 'right',
                axisLabel: {
                    formatter: '{value}°C',
                    color: chartColors.axisLabel,
                    fontSize: 12,
                    fontFamily: FONT_FAMILY,
                },
                axisLine: {
                    show: false,
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
            {
                type: 'value',
                gridIndex: 2,
                min: 0,
                max: 100,
                interval: 25,
                position: 'right',
                axisLabel: {
                    formatter: '{value}%',
                    color: chartColors.axisLabel,
                    fontSize: 12,
                    fontFamily: FONT_FAMILY,
                },
                axisLine: {
                    show: false,
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
            {
                type: 'category',
                gridIndex: 3,
                data: enabledAppRows.map((row) => row.packageName),
                inverse: true,
                axisLabel: {
                    show: false,
                },
                axisLine: {
                    show: false,
                },
                axisTick: {
                    show: false,
                },
                splitLine: {
                    show: true,
                    lineStyle: {
                        color: chartColors.splitLine,
                    },
                },
            },
        ],

        series: [
            {
                type: 'custom',
                name: 'События',
                xAxisIndex: 0,
                yAxisIndex: 0,
                renderItem: renderEventItem,
                encode: {
                    x: [1, 2],
                    y: 0,
                },
                data: eventSeriesData,
                //universalTransition: true,
            },
            {
                type: 'scatter',
                name: 'Точки событий',
                xAxisIndex: 0,
                yAxisIndex: 0,
                encode: {
                    x: 0,
                    y: 1,
                },
                data: eventPointSeriesData,
                z: 10,
            },
            {
                type: 'line',
                name: 'Температура',
                xAxisIndex: 1,
                yAxisIndex: 1,
                data: temperature,
                symbol: 'none',
                showSymbol: false,
                sampling: 'lttb',
                smooth: true,
                lineStyle: {
                    width: isDarkTheme ? 1.6 : 2,
                    color: chartColors.temperature,
                },
                itemStyle: {
                    color: chartColors.temperature,
                },
                areaStyle: {
                    color: {
                        type: 'linear',
                        x: 0,
                        y: 0,
                        x2: 0,
                        y2: 1,
                        colorStops: [
                            {
                                offset: 0,
                                color: chartColors.temperatureAreaStart,
                            },
                            {
                                offset: 1,
                                color: chartColors.temperatureAreaEnd,
                            },
                        ],
                    },
                },
                markArea: {
                    silent: true,
                    itemStyle: {
                        color: chartColors.dangerMark,
                    },
                    data: [[{ yAxis: 45 }, { yAxis: 60 }]],
                },
            },
            {
                type: 'line',
                name: 'Аккумулятор',
                xAxisIndex: 2,
                yAxisIndex: 2,
                data: battery,
                symbol: 'none',
                showSymbol: false,
                sampling: 'lttb',
                smooth: true,
                lineStyle: {
                    width: isDarkTheme ? 1.6 : 2,
                    color: chartColors.battery,
                },
                itemStyle: {
                    color: chartColors.battery,
                },
                areaStyle: {
                    color: {
                        type: 'linear',
                        x: 0,
                        y: 0,
                        x2: 0,
                        y2: 1,
                        colorStops: [
                            {
                                offset: 0,
                                color: chartColors.batteryAreaStart,
                            },
                            {
                                offset: 1,
                                color: chartColors.batteryAreaEnd,
                            },
                        ],
                    },
                },
                markArea: {
                    silent: true,
                    itemStyle: {
                        color: {
                            type: 'linear',
                            x: 0,
                            y: 0,
                            x2: 0,
                            y2: 1,
                            colorStops: [
                                {
                                    offset: 0,
                                    color: chartColors.dangerArea,
                                },
                                {
                                    offset: 1,
                                    color: chartColors.dangerArea,
                                },
                            ],
                        },
                    },
                    data: [[{ yAxis: 0 }, { yAxis: 20 }]],
                },
            },
            {
                type: 'custom',
                name: 'Активность приложений',
                xAxisIndex: 3,
                yAxisIndex: 3,
                renderItem: renderAppIntervalItem,
                encode: {
                    x: [1, 2],
                    y: 0,
                },
                data: appIntervalSeriesData,
                //universalTransition: true,
                z: 4,
            },
        ],
    }), [
        appGridHeight,
        appIntervalSeriesData,
        battery,
        chartColors,
        enabledAppRows,
        eventPointSeriesData,
        eventSeriesData,
        isDarkTheme,
        periodStart,
        temperature,
        visibleCategories,
        visibleEnd,
        visibleEventPoints,
        visibleEvents,
        visibleStart,
    ])

    return (
        <div
            className="device-history-chart"
            style={{ height: chartHeight, minHeight: chartHeight }}
        >
            {isLoading && (
                <div className="device-history-chart__state">
                    Загрузка телеметрии...
                </div>
            )}

            {isError && (
                <div className="device-history-chart__state device-history-chart__state--error">
                    Не удалось загрузить телеметрию
                </div>
            )}
            <ReactECharts
                option={option}
                lazyUpdate
                style={{ height: chartHeight }}
            />

            <div className="device-history-chart__events-title">
                <h3>События</h3>
                <p>Хронология активности устройства за выбранный период</p>
            </div>

            <div className="device-history-events-legend">
                {EVENT_CATEGORIES.map((category) => {
                    const checked = enabledCategories.includes(category.key)

                    return (
                        <button
                            key={category.key}
                            className={
                                checked
                                    ? 'device-history-events-legend__item is-active'
                                    : 'device-history-events-legend__item'
                            }
                            type="button"
                            onClick={() => toggleCategory(category.key)}
                        >
                            <span
                                className="device-history-events-legend__checkbox"
                                style={{
                                    backgroundColor: checked ? category.color : 'transparent',
                                    borderColor: checked ? category.color : chartColors.axisLine,
                                }}
                            >
                                {checked && (
                                    <svg className="device-history-events-legend__check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><title>Check SVG Icon</title><path fill="currentColor" d="M10.97 4.97a.75.75 0 0 1 1.07 1.05l-3.99 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093l3.473-4.425z" /></svg>
                                )}
                            </span>

                            <span>{category.label}</span>
                        </button>
                    )
                })}
            </div>

            <div className="device-history-chart__temperature-title">
                <h3>Температура</h3>
                <p>Динамика температуры устройства за выбранный период</p>
            </div>

            <div className="device-history-chart__battery-title">
                <h3>Аккумулятор</h3>
                <p>Динамика уровня заряда за выбранный период</p>
            </div>
            <div
                className="device-history-chart__apps-title"
                style={{ top: APP_TITLE_TOP }}
            >
                <h3>Приложения</h3>
                <p>События жизненного цикла приложений за выбранный период</p>
            </div>

            <div
                className="device-history-app-events-legend"
                style={{ top: APP_STATE_LEGEND_TOP }}
            >
                <span>
                    <i style={{ backgroundColor: chartColors.appActive }} />
                    На экране
                </span>

                <span>
                    <i style={{ backgroundColor: chartColors.appBackground }} />
                    Фон
                </span>
            </div>

            {enabledAppRows.map((row, index) => {
                const checked = enabledPackagesSet.has(row.packageName)
                const { Icon, modifier } = getAppIcon(row)
                const top =
                    APP_GRID_TOP +
                    appRowBand * index +
                    appRowBand / 2 -
                    APP_ROW_HEIGHT / 2

                return (
                    <button
                        key={row.packageName}
                        className={
                            checked
                                ? 'device-history-app-legend__item is-active'
                                : 'device-history-app-legend__item'
                        }
                        style={{
                            top,
                            height: APP_ROW_HEIGHT,
                        }}
                        type="button"
                        title={`${row.name} · ${row.packageName}`}
                        onClick={() => togglePackage(row.packageName)}
                    >

                        {row.app?.iconUrl ? (
                            <img
                                src={`${row.app.iconUrl}`}
                                alt={row.name}
                                className="device-history-app-legend__image"
                            />
                        ) : (
                            <span className={`device-history-app-legend__icon ${modifier}`}>
                                <Icon size={16} />
                            </span>
                        )}

                        <span className="device-history-app-legend__text">
                            <strong>{row.name}</strong>
                            <small>
                                {formatDuration(row.activeDuration)} · {formatUsagePercent(row.activePercent)}
                            </small>
                        </span>
                    </button>
                )
            })}

            {!enabledAppRows.length && !isLoading && (
                <div
                    className="device-history-apps-empty"
                    style={{ top: APP_GRID_TOP, height: appGridHeight }}
                >
                    <strong>
                        {appRows.length
                            ? 'Приложения не выбраны'
                            : 'Нет событий по приложениям'}
                    </strong>

                    <span>
                        {appRows.length
                            ? 'Выберите приложения, которые нужно показать на графике.'
                            : 'За выбранный период не найдено событий жизненного цикла приложений.'}
                    </span>
                </div>
            )}

            {appRows.length > 0 && (
                <div
                    className="device-history-app-selector"
                    style={{ top: appSelectorTop }}
                >
                    <span className="device-history-app-selector__count">
                        Выбрано {enabledAppRows.length} из {appRows.length}
                    </span>

                    <SimpleBar className="device-history-app-selector__scroll">
                        <div className="device-history-app-selector__list">
                            {appRows.map((row) => {
                                const checked = enabledPackagesSet.has(row.packageName)

                                return (
                                    <button
                                        className={
                                            checked
                                                ? 'device-history-app-selector__item is-active'
                                                : 'device-history-app-selector__item'
                                        }
                                        key={row.packageName}
                                        type="button"
                                        title={`${row.name} · ${row.packageName}`}
                                        onClick={() => togglePackage(row.packageName)}
                                    >
                                        <span>{row.name}</span>
                                        <small>
                                            {formatDuration(row.activeDuration)}
                                        </small>
                                    </button>
                                )
                            })}
                        </div>
                    </SimpleBar>
                </div>
            )}
        </div>
    )
})
