'use client';

/* MobileEngagementPage — phase 07 (ENG-01..05, ENG-09).
 * Purpose: Phone-first refactor of /engagement — period chips + 4 stacked summary cards +
 *          compact sparkline + sortable/searchable per-employee list with infinite scroll.
 *          Real refactor, not a thin adaptation of the ~1300-line desktop page (ENG-01).
 *          Reachable from More drawer only — Engagement is NOT on the bottom nav (ENG-09).
 *          Per D-01..D-31. */

import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
import { Loader2, Users } from 'lucide-react';
import { toast } from 'sonner';

import type { MobileEngagementSummary } from '@/app/api/mobile/engagement/summary/route';
import type { SparklinePoint, EngagementTrendResponse } from '@/app/api/mobile/engagement/trend/route';
import { EngagementPeriodChips, type EngagementPeriod } from '@/components/mobile/EngagementPeriodChips';
import { EngagementSummaryCard } from '@/components/mobile/EngagementSummaryCard';
import { EngagementHoursSparkline } from '@/components/mobile/EngagementHoursSparkline';
import { EngagementSortChips, type EngagementSortKey } from '@/components/mobile/EngagementSortChips';
import { EngagementSearchInput } from '@/components/mobile/EngagementSearchInput';
import { EngagementUserRow } from '@/components/mobile/EngagementUserRow';
import { EngagementUserRowSkeleton } from '@/components/mobile/EngagementUserRowSkeleton';
import { Card, CardContent } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';

// ── Inline types for /api/engagement/users (existing endpoint, reused as-is per D-16) ──

interface EngagementUserApiRow {
  graphUserId: string;
  displayName: string;
  email: string;            // existing endpoint returns 'email', not 'userEmail'
  jobTitle: string | null;
  billableHours: number;
  hoursWorked: number;
}

interface EngagementUsersResponse {
  users: EngagementUserApiRow[];
  pagination: {
    page: number;
    pageSize: number;
    total: number;
    totalPages?: number;   // present when total > 0; existing endpoint returns this
  };
}

// Sort key → API param mapping (D-20)
// NOTE: EngagementSortKey values are lowercase ('hours'|'name'|'utilization') per Wave 2 component
const SORT_TO_API: Record<EngagementSortKey, { sort: string; order: 'asc' | 'desc' }> = {
  hours:       { sort: 'billable_hours', order: 'desc' },
  name:        { sort: 'display_name',   order: 'asc'  },
  utilization: { sort: 'billable_hours', order: 'desc' },
};

const SUMMARY_LABELS = {
  activeUsers:        'Active users',
  totalGraphHours:    'Total Graph hours',
  totalAutotaskHours: 'Total Autotask hours',
  hoursPerActiveUser: 'Hours / active user',
} as const;

export default function MobileEngagementPage() {
  // ── State ─────────────────────────────────────────────────────────────
  const [period, setPeriod] = useState<EngagementPeriod>('D30');               // D-04 default
  const [sortKey, setSortKey] = useState<EngagementSortKey>('hours');          // D-20 default (lowercase)
  const [searchQuery, setSearchQuery] = useState<string>('');                  // D-21

  const [summary, setSummary] = useState<MobileEngagementSummary | null>(null);
  const [summaryLoading, setSummaryLoading] = useState<boolean>(true);

  const [trendPoints, setTrendPoints] = useState<SparklinePoint[]>([]);
  const [trendLoading, setTrendLoading] = useState<boolean>(true);

  const [users, setUsers] = useState<EngagementUserApiRow[]>([]);
  const [usersLoading, setUsersLoading] = useState<boolean>(true);
  const [currentPage, setCurrentPage] = useState<number>(1);
  const [hasMore, setHasMore] = useState<boolean>(false);
  const [loadingMore, setLoadingMore] = useState<boolean>(false);
  const [loadMoreError, setLoadMoreError] = useState<boolean>(false);

  // ── Fetchers ──────────────────────────────────────────────────────────
  const loadSummary = useCallback(async (p: EngagementPeriod) => {
    setSummaryLoading(true);
    try {
      const r = await fetch(`/api/mobile/engagement/summary?period=${p}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const data: MobileEngagementSummary = await r.json();
      setSummary(data);
    } catch (e) {
      console.error('Engagement summary fetch failed:', e);
      toast.error('Failed to load engagement summary');     // D-25
    } finally {
      setSummaryLoading(false);
    }
  }, []);

  const loadTrend = useCallback(async (p: EngagementPeriod) => {
    setTrendLoading(true);
    try {
      const r = await fetch(`/api/mobile/engagement/trend?period=${p}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const data: EngagementTrendResponse = await r.json();
      setTrendPoints(data.points);
    } catch (e) {
      console.error('Engagement trend fetch failed:', e);
      toast.error('Failed to load hours trend');             // D-25
    } finally {
      setTrendLoading(false);
    }
  }, []);

  const loadUsersPage1 = useCallback(async (p: EngagementPeriod, sk: EngagementSortKey) => {
    setUsersLoading(true);
    setLoadMoreError(false);
    try {
      const { sort, order } = SORT_TO_API[sk];
      const sp = new URLSearchParams({ period: p, sort, order, page: '1' });
      const r = await fetch(`/api/engagement/users?${sp.toString()}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const data: EngagementUsersResponse = await r.json();
      setUsers(data.users);
      setCurrentPage(1);
      const totalPages = data.pagination.totalPages
        ?? Math.ceil(data.pagination.total / data.pagination.pageSize);
      setHasMore(1 < totalPages);
    } catch (e) {
      console.error('Engagement users fetch failed:', e);
      toast.error('Failed to load engagement users');        // D-25
    } finally {
      setUsersLoading(false);
    }
  }, []);

  const loadMoreUsers = useCallback(async () => {
    if (loadingMore || !hasMore) return;
    setLoadingMore(true);
    setLoadMoreError(false);
    try {
      const next = currentPage + 1;
      const { sort, order } = SORT_TO_API[sortKey];
      const sp = new URLSearchParams({ period, sort, order, page: String(next) });
      const r = await fetch(`/api/engagement/users?${sp.toString()}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const data: EngagementUsersResponse = await r.json();
      setUsers(prev => [...prev, ...data.users]);
      setCurrentPage(next);
      const totalPages = data.pagination.totalPages
        ?? Math.ceil(data.pagination.total / data.pagination.pageSize);
      setHasMore(next < totalPages);
    } catch (e) {
      console.error('Engagement users load-more fetch failed:', e);
      toast.error('Failed to load more team members');       // D-25
      setLoadMoreError(true);
    } finally {
      setLoadingMore(false);
    }
  }, [loadingMore, hasMore, currentPage, sortKey, period]);

  // ── Effects ───────────────────────────────────────────────────────────

  // Period change: refetch all three (summary + trend + users page 1) — D-04
  useEffect(() => {
    void loadSummary(period);
    void loadTrend(period);
    void loadUsersPage1(period, sortKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [period]);

  // Sort change: refetch users only (D-20: summary/trend are period-scoped, not sort-scoped)
  useEffect(() => {
    void loadUsersPage1(period, sortKey);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sortKey]);

  // D-04 fallback: scroll restoration via sessionStorage. The actual scroll
  // container differs by viewport — `<main>` has overflow-y-auto, but in some
  // configurations the document scrolls instead. Listen on both, save the one
  // that's non-zero, and restore both.
  const restoredScrollRef = useRef(false);
  useEffect(() => {
    let raf = 0;
    const save = () => {
      const main = document.querySelector('main');
      const mainTop = main?.scrollTop ?? 0;
      const winTop  = window.scrollY || document.documentElement.scrollTop || 0;
      sessionStorage.setItem('mobile-engagement-scroll', JSON.stringify({ main: mainTop, win: winTop }));
    };
    const onScroll = () => {
      if (raf) return;
      raf = requestAnimationFrame(() => { raf = 0; save(); });
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    const main = document.querySelector('main');
    main?.addEventListener('scroll', onScroll, { passive: true });
    return () => {
      window.removeEventListener('scroll', onScroll);
      main?.removeEventListener('scroll', onScroll);
      if (raf) cancelAnimationFrame(raf);
    };
  }, []);
  useEffect(() => {
    if (restoredScrollRef.current) return;
    if (usersLoading || users.length === 0) return;
    const raw = sessionStorage.getItem('mobile-engagement-scroll');
    if (!raw) { restoredScrollRef.current = true; return; }
    try {
      const { main: mainTop, win: winTop } = JSON.parse(raw) as { main: number; win: number };
      // Retry across frames until document height supports the target scroll;
      // initial render may compute heights lazily and clamp scrollTo to a small
      // maxScroll. Cap attempts so we never loop forever.
      let attempts = 0;
      const tryRestore = () => {
        const main = document.querySelector('main');
        if (main && mainTop) main.scrollTop = mainTop;
        if (winTop) window.scrollTo(0, winTop);
        const winNow  = window.scrollY;
        const mainNow = main?.scrollTop ?? 0;
        const winOk   = !winTop  || Math.abs(winNow  - winTop)  <= 4;
        const mainOk  = !mainTop || Math.abs(mainNow - mainTop) <= 4;
        if ((!winOk || !mainOk) && attempts < 30) {
          attempts++;
          requestAnimationFrame(tryRestore);
        }
      };
      requestAnimationFrame(tryRestore);
    } catch {
      /* corrupt entry — ignore */
    }
    restoredScrollRef.current = true;
  }, [usersLoading, users.length]);

  // IntersectionObserver — D-18 (mirrors Phase 4/6 pattern, rootMargin '200px')
  const sentinelRef = useRef<HTMLDivElement | null>(null);
  useEffect(() => {
    const node = sentinelRef.current;
    if (!node) return;
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0]?.isIntersecting && hasMore && !loadingMore && !usersLoading) {
          void loadMoreUsers();
        }
      },
      { rootMargin: '200px' },
    );
    observer.observe(node);
    return () => observer.disconnect();
  }, [hasMore, loadingMore, usersLoading, loadMoreUsers]);

  // ── Derived data ──────────────────────────────────────────────────────

  // Client-side search filter (D-21): applies on displayName + email of loaded users
  const filteredUsers = useMemo(() => {
    const q = searchQuery.trim().toLowerCase();
    if (!q) return users;
    return users.filter(u =>
      u.displayName.toLowerCase().includes(q) || u.email.toLowerCase().includes(q),
    );
  }, [users, searchQuery]);

  // maxHours for hours bar normalization (UI-SPEC: largest billableHours in current page set)
  const maxHours = useMemo(() => {
    return filteredUsers.reduce((m, u) => Math.max(m, u.billableHours), 0);
  }, [filteredUsers]);

  // Empty-state predicate (D-26): both summary.activeUsers AND users.length === 0
  const showEmptyState = !summaryLoading && !usersLoading
    && summary !== null && summary.activeUsers === 0 && users.length === 0;

  // Not-configured banner predicate (D-27)
  const showNotConfiguredBanner = !summaryLoading && summary !== null && summary.configured === false;

  // Summary card values (formatted strings — Plan 02's SummaryCard accepts pre-formatted)
  const summaryCard1 = summary ? String(summary.activeUsers)                       : '0';
  const summaryCard2 = summary ? `${summary.totalGraphHours.toFixed(1)}h`          : '0.0h';
  const summaryCard3 = summary ? `${summary.totalAutotaskHours.toFixed(1)}h`       : '0.0h';
  const summaryCard4 = summary
    ? (summary.activeUsers === 0 ? '—' : `${summary.hoursPerActiveUser.toFixed(1)}h`)  // D-08
    : '—';

  // ── Render ────────────────────────────────────────────────────────────

  return (
    <div className="px-4 py-4 space-y-4">                                          {/* D-30 */}
      {/* H1 — D-31, scrolls away under the sticky chips */}
      <h1 className="text-sm font-semibold">Engagement</h1>                        {/* D-29 */}

      {/* Sticky period chips — D-05 */}
      <EngagementPeriodChips period={period} onPeriodChange={setPeriod} />

      {showNotConfiguredBanner ? (
        // Not-configured banner (D-27) — replaces all data sections
        <div className="rounded-xl border bg-card px-4 py-4 space-y-1">
          <p className="text-sm font-semibold">Engagement sync not configured</p>
          <p className="text-xs text-muted-foreground">
            Set MSGRAPH_* environment variables and restart.
            <a
              href="/admin"
              target="_blank"
              rel="noopener noreferrer"
              aria-label="Open Admin on desktop"
              className="underline ml-1"
            >
              Open Admin
            </a>
          </p>
        </div>
      ) : (
        <>
          {/* Section 3 — Summary cards (D-08, D-23) */}
          {summaryLoading ? (
            <div className="space-y-3">
              {Array.from({ length: 4 }).map((_, i) => (
                <Card key={i} className="py-0 shadow-none">
                  <CardContent className="px-4 py-4 space-y-2">
                    <Skeleton className="h-8 w-20" />
                    <Skeleton className="h-3 w-28" />
                  </CardContent>
                </Card>
              ))}
            </div>
          ) : (
            <div className="space-y-3">
              <EngagementSummaryCard value={summaryCard1} label={SUMMARY_LABELS.activeUsers} />
              <EngagementSummaryCard value={summaryCard2} label={SUMMARY_LABELS.totalGraphHours} />
              <EngagementSummaryCard value={summaryCard3} label={SUMMARY_LABELS.totalAutotaskHours} />
              <EngagementSummaryCard value={summaryCard4} label={SUMMARY_LABELS.hoursPerActiveUser} />
            </div>
          )}

          {/* Section 4 — Sparkline (D-11..D-15, D-23) */}
          {trendLoading ? (
            <Card className="py-0 shadow-none">
              <CardContent className="px-4 py-3 space-y-2">
                <div className="flex justify-between gap-2">
                  <Skeleton className="h-3 w-32" />
                  <Skeleton className="h-3 w-16" />
                </div>
                <Skeleton className="h-12 w-full mt-2" />
              </CardContent>
            </Card>
          ) : (
            <EngagementHoursSparkline points={trendPoints} period={period} />
          )}

          {/* Section 5 — Sort + search (D-20, D-21) */}
          <div className="space-y-2">
            <EngagementSortChips value={sortKey} onChange={setSortKey} />
            <EngagementSearchInput value={searchQuery} onChange={setSearchQuery} />
          </div>

          {/* Section 6 — User list (D-19, D-22, D-23, D-26) */}
          {showEmptyState ? (
            // Empty state (D-26)
            <div className="flex flex-col items-center justify-center py-12 text-center space-y-3">
              <Users className="h-8 w-8 text-muted-foreground/50" aria-hidden="true" />
              <div className="space-y-1">
                <p className="text-sm font-semibold">No engagement data for this period</p>
                <p className="text-xs text-muted-foreground">
                  Try a different period or trigger a sync from
                  <a
                    href="/admin"
                    target="_blank"
                    rel="noopener noreferrer"
                    aria-label="Open Admin on desktop"
                    className="underline ml-1"
                  >Admin</a>
                </p>
              </div>
            </div>
          ) : usersLoading ? (
            <div className="divide-y border rounded-xl overflow-hidden">
              {Array.from({ length: 5 }).map((_, i) => <EngagementUserRowSkeleton key={i} />)}
            </div>
          ) : filteredUsers.length === 0 && searchQuery.trim() !== '' ? (
            // No-matches inline state (D-22) — preserves the list border + rounding
            <div className="border rounded-xl overflow-hidden">
              <div className="px-4 py-6 text-center space-y-2">
                <p className="text-sm text-muted-foreground">{`No matches for "${searchQuery}"`}</p>
                <button
                  type="button"
                  onClick={() => setSearchQuery('')}
                  className="text-xs font-semibold text-primary underline"
                >
                  Clear search
                </button>
              </div>
            </div>
          ) : (
            <>
              <div className="divide-y border rounded-xl overflow-hidden">
                {filteredUsers.map(u => (
                  <EngagementUserRow
                    key={u.graphUserId}
                    user={{
                      graphUserId: u.graphUserId,
                      displayName: u.displayName,
                      userEmail: u.email,       // existing endpoint uses 'email'; component expects 'userEmail'
                      jobTitle: u.jobTitle,
                      billableHours: u.billableHours,
                      hoursWorked: u.hoursWorked,
                    }}
                    maxHours={maxHours}
                  />
                ))}
              </div>

              {/* Sentinel — D-18 */}
              <div ref={sentinelRef} aria-hidden="true" />

              {/* Loading-more spinner — D-24 */}
              {loadingMore && (
                <div className="flex justify-center py-2">
                  <Loader2 className="w-4 h-4 animate-spin text-muted-foreground" aria-hidden="true" />
                </div>
              )}

              {/* Load more fallback button — D-18, D-25 */}
              {hasMore && (
                <button
                  type="button"
                  onClick={() => void loadMoreUsers()}
                  disabled={loadingMore}
                  aria-label="Load more team members"
                  className="w-full py-3 rounded-xl border text-sm font-semibold hover:bg-muted/50 transition-colors disabled:opacity-50 min-h-[44px]"
                >
                  {loadMoreError ? 'Retry' : loadingMore ? 'Loading…' : 'Load more'}
                </button>
              )}
            </>
          )}
        </>
      )}
    </div>
  );
}
