import { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import type { JourneySessionRow } from './SessionListTable';
import type { JourneyRealtimeSnapshot } from './JourneyOverviewSnapshot';
import styles from './journey-realtime-panel.module.css';

const POLL_MS = 10_000;
const ACTIVE_MS = 10_000;
const IDLE_AFTER_MS = 15_000 * 2;
const PATH_SKELETON_MS = 450;

export type RealtimeActiveUser = {
    visitor_id: string;
    visitor_ref?: string;
    session_id?: string;
    session_ref?: string;
    path: string;
    entry_page: string;
    last_seen_at: string;
    last_active_at?: string;
};

type FlashMap = Record<string, 'in' | 'out'>;
type PresenceStatus = 'active' | 'idle';

type Props = {
    onUserClick?: (
        row: Pick<JourneySessionRow, 'id' | 'visitor_id' | 'session_id'>,
        meta?: { sessionRef?: string }
    ) => void;
};

function engagementAgeMs(user: RealtimeActiveUser) {
    const iso = user.last_active_at || user.last_seen_at;
    if (!iso) return Number.POSITIVE_INFINITY;
    const ms = Date.now() - new Date(iso).getTime();
    return Number.isNaN(ms) ? Number.POSITIVE_INFINITY : Math.max(0, ms);
}

function resolveStatus(user: RealtimeActiveUser): PresenceStatus | null {
    const age = engagementAgeMs(user);
    if (age > IDLE_AFTER_MS * 2) return null;
    if (age > IDLE_AFTER_MS) return 'idle';
    return 'active';
}

function isFreshActive(user: RealtimeActiveUser) {
    return engagementAgeMs(user) <= ACTIVE_MS;
}

function shortId(id: string) {
    if (!id) return '—';
    if (id.length <= 14) return id;
    return `${id.slice(0, 8)}…${id.slice(-4)}`;
}

function minuteAgoLabel(offset: number) {
    const abs = Math.abs(Number(offset) || 0);
    if (abs === 0) return 'baru saja';
    return `${abs} menit yang lalu`;
}

function PathCell({ path, skeleton }: { path: string; skeleton: boolean }) {
    return (
        <td className={styles.path} title={path}>
            <span className={`${styles.pathInner} ${skeleton ? styles.pathSkeletonOn : ''}`}>
                {skeleton ? <span className={styles.pathSkeleton} aria-hidden /> : null}
                <span className={styles.pathText}>{path || '—'}</span>
            </span>
        </td>
    );
}

const JourneyRealtimePanel = ({ onUserClick }: Props) => {
    const [data, setData] = useState<JourneyRealtimeSnapshot | null>(null);
    const [error, setError] = useState('');
    const [updatedAt, setUpdatedAt] = useState<number | null>(null);
    const [tick, setTick] = useState(0);
    const [flash, setFlash] = useState<FlashMap>({});
    const [pathSkeleton, setPathSkeleton] = useState<Record<string, boolean>>({});
    const [refreshing, setRefreshing] = useState(false);
    const [initialLoading, setInitialLoading] = useState(true);
    const [activeMinuteIdx, setActiveMinuteIdx] = useState<number | null>(null);
    const prevIdsRef = useRef<Set<string>>(new Set());
    const prevPathsRef = useRef<Record<string, string>>({});
    const hydratedRef = useRef(false);
    const minuteChartRef = useRef<HTMLDivElement | null>(null);

    const fetchRealtime = useCallback(async () => {
        if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
        const isRefresh = hydratedRef.current;
        if (isRefresh) setRefreshing(true);
        try {
            const api_url = process.env.API_URL;
            const res = await fetch(`${api_url}api/journey/realtime`, {
                method: 'GET',
                headers: { 'Content-Type': 'application/json' },
                credentials: 'include',
            });
            const json = await res.json();
            if (!res.ok) throw new Error(json?.message || 'Gagal memuat realtime');

            const users: RealtimeActiveUser[] = Array.isArray(json?.active_users)
                ? json.active_users
                : [];
            const nextIds = new Set(
                users.map((u) => u.visitor_ref || u.visitor_id).filter(Boolean)
            );

            const changedPaths: Record<string, boolean> = {};
            users.forEach((u) => {
                const key = u.visitor_ref || u.visitor_id;
                const prev = prevPathsRef.current[key];
                if (prev && prev !== u.path) changedPaths[key] = true;
                prevPathsRef.current[key] = u.path;
            });
            Object.keys(prevPathsRef.current).forEach((id) => {
                if (!nextIds.has(id)) delete prevPathsRef.current[id];
            });

            if (Object.keys(changedPaths).length) {
                setPathSkeleton((p) => ({ ...p, ...changedPaths }));
                window.setTimeout(() => {
                    setPathSkeleton((p) => {
                        const next = { ...p };
                        Object.keys(changedPaths).forEach((id) => {
                            delete next[id];
                        });
                        return next;
                    });
                }, PATH_SKELETON_MS);
            }

            if (hydratedRef.current) {
                const nextFlash: FlashMap = {};
                nextIds.forEach((id) => {
                    if (!prevIdsRef.current.has(id)) nextFlash[id] = 'in';
                });
                prevIdsRef.current.forEach((id) => {
                    if (!nextIds.has(id)) nextFlash[id] = 'out';
                });
                if (Object.keys(nextFlash).length) {
                    setFlash(nextFlash);
                    window.setTimeout(() => setFlash({}), 1800);
                }
            } else {
                hydratedRef.current = true;
            }

            prevIdsRef.current = nextIds;
            setData({ ...json, active_users: users });
            setUpdatedAt(Date.now());
            setError('');
        } catch (err: any) {
            setError(err?.message || 'Realtime unavailable');
        } finally {
            setRefreshing(false);
            setInitialLoading(false);
        }
    }, []);

    useEffect(() => {
        fetchRealtime();
        const poll = window.setInterval(fetchRealtime, POLL_MS);
        const clock = window.setInterval(() => setTick((t) => t + 1), 1000);
        const onVis = () => {
            if (document.visibilityState === 'visible') fetchRealtime();
        };
        document.addEventListener('visibilitychange', onVis);
        return () => {
            window.clearInterval(poll);
            window.clearInterval(clock);
            document.removeEventListener('visibilitychange', onVis);
        };
    }, [fetchRealtime]);

    useEffect(() => {
        const onPointerDown = (e: PointerEvent) => {
            const root = minuteChartRef.current;
            if (!root || root.contains(e.target as Node)) return;
            setActiveMinuteIdx(null);
        };
        document.addEventListener('pointerdown', onPointerDown);
        return () => document.removeEventListener('pointerdown', onPointerDown);
    }, []);

    const ageSec = updatedAt ? Math.max(0, Math.floor((Date.now() - updatedAt) / 1000)) : null;
    void tick;

    const perMinute = data?.per_minute || [];
    const maxPerMin = Math.max(...perMinute.map((p) => p.active_users || 0), 1);
    const entryRows = data?.by_entry_path || [];
    const maxEntry = Math.max(...entryRows.map((r) => r.active_users || 0), 1);

    const visibleUsers = useMemo(() => {
        const raw = (data?.active_users || []) as RealtimeActiveUser[];
        return raw
            .map((u) => ({ user: u, status: resolveStatus(u) }))
            .filter((row): row is { user: RealtimeActiveUser; status: PresenceStatus } => row.status != null);
    }, [data?.active_users, tick]);

    const sourceLabel = useMemo(() => {
        if (data?.source === 'redis') return 'Redis';
        if (data?.source === 'mongo') return 'Mongo fallback';
        return '…';
    }, [data?.source]);

    return (
        <div className={`panel space-y-4 ${refreshing ? styles.panelRefreshing : ''}`}>
            <div className={styles.header}>
                <div>
                    <div className={styles.titleRow}>
                        <h3 className="text-lg font-semibold">Realtime</h3>
                        <span className={styles.liveBadge}>LIVE</span>
                        <span className={styles.sourcePill}>{sourceLabel}</span>
                        {refreshing ? <span className={styles.refreshHint}>updating…</span> : null}
                    </div>
                </div>
                <div className={styles.updated}>
                    {ageSec == null ? 'Memuat…' : `Updated ${ageSec}s ago`}
                    {error ? <span className={styles.errorHint}>{error}</span> : null}
                </div>
            </div>

            <div className={styles.kpiRow}>
                {initialLoading ? (
                    <>
                        <div className={styles.kpi}><div className={styles.skelBlock} /></div>
                        <div className={styles.kpi}><div className={styles.skelBlock} /></div>
                        <div className={styles.kpi}><div className={styles.skelBlock} /></div>
                    </>
                ) : (
                    <>
                        <div className={styles.kpi}>
                            <div className={styles.kpiLabel}>Active · 30 min</div>
                            <div className={`${styles.kpiValue} ${styles.valueFade}`}>
                                {(data?.active_users_30m ?? 0).toLocaleString('id-ID')}
                            </div>
                        </div>
                        <div className={styles.kpi}>
                            <div className={styles.kpiLabel}>Active · now</div>
                            <div className={`${styles.kpiValue} ${styles.valueFade}`}>
                                {(data?.active_users_now ?? visibleUsers.length).toLocaleString('id-ID')}
                            </div>
                        </div>
                        <div className={styles.kpi}>
                            <div className={styles.kpiLabel}>Listed users</div>
                            <div className={`${styles.kpiValue} ${styles.valueFade}`}>
                                {visibleUsers.length.toLocaleString('id-ID')}
                            </div>
                        </div>
                    </>
                )}
            </div>

            <div className={styles.split}>
                <section className={styles.block}>
                    <div className={styles.blockTitle}>Active users per minute</div>
                    <div
                        ref={minuteChartRef}
                        className={`${styles.minuteBars} ${refreshing ? styles.softPulse : ''}`}
                    >
                        {initialLoading
                            ? Array.from({ length: 24 }).map((_, i) => (
                                <div key={i} className={styles.minuteBarWrap}>
                                    <div
                                        className={`${styles.minuteBar} ${styles.skelBar}`}
                                        style={{ height: `${20 + ((i * 17) % 60)}%` }}
                                    />
                                </div>
                            ))
                            : perMinute.map((p, i) => {
                                const count = p.active_users || 0;
                                const offset =
                                    typeof p.minute_offset === 'number'
                                        ? p.minute_offset
                                        : i - (perMinute.length - 1);
                                const isActive = activeMinuteIdx === i;
                                return (
                                    <div
                                        key={i}
                                        className={`${styles.minuteBarWrap} ${isActive ? styles.minuteBarActive : ''
                                            }`}
                                        onMouseEnter={() => setActiveMinuteIdx(i)}
                                        onMouseLeave={() => setActiveMinuteIdx(null)}
                                        onClick={(e) => {
                                            e.stopPropagation();
                                            setActiveMinuteIdx((prev) => (prev === i ? null : i));
                                        }}
                                    >
                                        {count > 0 ? (
                                            <span className={styles.minuteValue}>{count}</span>
                                        ) : null}
                                        <div
                                            className={styles.minuteBar}
                                            style={{
                                                height: `${Math.max(4, (count / maxPerMin) * 100)}%`,
                                            }}
                                        />
                                        {isActive ? (
                                            <div className={styles.minuteTooltip} role="tooltip">
                                                {count} users · {minuteAgoLabel(offset)}
                                            </div>
                                        ) : null}
                                    </div>
                                );
                            })}
                        {!initialLoading && !perMinute.length ? (
                            <div className={styles.empty}>Belum ada data menit</div>
                        ) : null}
                    </div>
                </section>

                <section className={styles.block}>
                    <div className={styles.blockTitle}>Top entry path</div>
                    <div className={`${styles.entryList} ${refreshing ? styles.softPulse : ''}`}>
                        {initialLoading ? (
                            Array.from({ length: 4 }).map((_, i) => (
                                <div key={i} className={styles.skelLine} />
                            ))
                        ) : entryRows.length ? (
                            entryRows.map((row) => (
                                <div key={row.path} className={styles.entryRow}>
                                    <span className={styles.path} title={row.path}>
                                        {row.path}
                                    </span>
                                    <span className={styles.entryBarWrap}>
                                        <span
                                            className={styles.entryBar}
                                            style={{
                                                width: `${((row.active_users || 0) / maxEntry) * 100}%`,
                                            }}
                                        />
                                        <strong>{row.active_users}</strong>
                                    </span>
                                </div>
                            ))
                        ) : (
                            <div className={styles.empty}>Belum ada entry path</div>
                        )}
                    </div>
                </section>
            </div>

            <section className={styles.block}>
                <div className={styles.blockTitle}>Active users sekarang</div>
                <div className={styles.tableWrap}>
                    <table className={styles.table}>
                        <thead>
                            <tr>
                                <th>Visitor</th>
                                <th>Current path</th>
                                <th>Entry</th>
                                <th>Status</th>
                            </tr>
                        </thead>
                        <tbody>
                            {initialLoading ? (
                                Array.from({ length: 5 }).map((_, i) => (
                                    <tr key={i} className={styles.skelRow}>
                                        <td><span className={styles.skelLine} /></td>
                                        <td><span className={styles.skelLine} /></td>
                                        <td><span className={styles.skelLine} /></td>
                                        <td><span className={styles.skelLineShort} /></td>
                                    </tr>
                                ))
                            ) : visibleUsers.length ? (
                                visibleUsers.map(({ user: u, status }) => {
                                    const rowKey = u.visitor_ref || u.visitor_id;
                                    const flashState = flash[rowKey];
                                    const showPing = status === 'active' && isFreshActive(u);
                                    return (
                                        <tr
                                            key={rowKey}
                                            className={`${styles.trClickable} ${styles.rowEnter} ${flashState === 'in'
                                                    ? styles.flashIn
                                                    : flashState === 'out'
                                                        ? styles.flashOut
                                                        : ''
                                                }`}
                                            onClick={() =>
                                                onUserClick?.(
                                                    {
                                                        id: u.visitor_ref || u.visitor_id,
                                                        visitor_id: u.visitor_ref || u.visitor_id,
                                                        session_id: u.session_ref || u.session_id || '',
                                                    },
                                                    { sessionRef: u.session_ref || '' }
                                                )
                                            }
                                        >
                                            <td>
                                                <code className={styles.code}>{shortId(u.visitor_id)}</code>
                                            </td>
                                            <PathCell path={u.path} skeleton={Boolean(pathSkeleton[rowKey])} />
                                            <td className={styles.path} title={u.entry_page}>
                                                {u.entry_page || '—'}
                                            </td>
                                            <td>
                                                <span
                                                    className={`${styles.status} ${status === 'active' ? styles.statusActive : styles.statusIdle
                                                        }`}
                                                >
                                                    <span className={styles.statusDotWrap}>
                                                        <span className={styles.statusDot} />
                                                        {showPing ? <span className={styles.statusPing} /> : null}
                                                    </span>
                                                    {status === 'active' ? 'Active' : 'Idle'}
                                                </span>
                                            </td>
                                        </tr>
                                    );
                                })
                            ) : (
                                <tr>
                                    <td colSpan={4} className={styles.emptyCell}>
                                        {error || 'Belum ada user aktif'}
                                    </td>
                                </tr>
                            )}
                        </tbody>
                    </table>
                </div>
            </section>
        </div>
    );
};

export default JourneyRealtimePanel;
