'use client';

import { useEffect, useState, useCallback, useRef, useMemo, Suspense } from 'react';
import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import { ChevronRight, Clock, Loader2, RefreshCw } from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import { TicketFilterStrip, type TicketFilterValue, type QueueOption } from '@/components/mobile/TicketFilterStrip';
import { TicketRowSkeleton } from '@/components/mobile/TicketRowSkeleton';
import type { MobileTicket, MobileTicketListResponse } from '@/app/api/mobile/tickets/route';

const PRIORITY_BORDER: Record<number, string> = {
  1: 'border-red-500',
  2: 'border-orange-400',
  3: 'border-amber-400',
  4: 'border-slate-300',
};

const DEFAULT_STATUS: number[] = [1, 8, 7]; // Open + In Progress + Waiting (matches API default)

function relTime(ts: string | null): string {
  if (!ts) return '—';
  const diff = Date.now() - new Date(ts).getTime();
  const m = Math.floor(diff / 60000);
  if (m < 60) return `${m}m ago`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h}h ago`;
  return `${Math.floor(h / 24)}d ago`;
}

function parseFilterFromSearch(sp: URLSearchParams): TicketFilterValue {
  const parseIntList = (raw: string | null): number[] => {
    if (raw === null) return [];
    if (raw === '') return [];
    return raw.split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
  };
  const statusParam = sp.get('status');
  return {
    q: sp.get('q') ?? '',
    // when 'status' is absent entirely, fall through to DEFAULT_STATUS so the visible state matches what the API will return
    status: statusParam === null ? [...DEFAULT_STATUS] : parseIntList(statusParam),
    priority: parseIntList(sp.get('priority')),
    queue: sp.get('queue') ? parseInt(sp.get('queue')!, 10) : null,
    mine: sp.get('mine') === '1',
  };
}

function filterToSearch(value: TicketFilterValue): URLSearchParams {
  const sp = new URLSearchParams();
  if (value.q) sp.set('q', value.q);
  // Only include status param when it differs from default — keeps URL clean for unfiltered visits (D-06)
  const isDefaultStatus = value.status.length === DEFAULT_STATUS.length
    && DEFAULT_STATUS.every(s => value.status.includes(s));
  if (!isDefaultStatus && value.status.length > 0) sp.set('status', value.status.join(','));
  if (value.status.length === 0) sp.set('status', ''); // explicit "no status filter"
  if (value.priority.length > 0) sp.set('priority', value.priority.join(','));
  if (value.queue !== null) sp.set('queue', String(value.queue));
  if (value.mine) sp.set('mine', '1');
  return sp;
}

function isFilterModified(value: TicketFilterValue): boolean {
  const isDefaultStatus = value.status.length === DEFAULT_STATUS.length
    && DEFAULT_STATUS.every(s => value.status.includes(s));
  return Boolean(value.q)
    || !isDefaultStatus
    || value.priority.length > 0
    || value.queue !== null
    || value.mine;
}

export default function MobileTicketsPage() {
  return (
    <Suspense fallback={<div className="flex items-center justify-center h-40"><Loader2 className="w-5 h-5 animate-spin text-muted-foreground" /></div>}>
      <MobileTicketsInner />
    </Suspense>
  );
}

function MobileTicketsInner() {
  const router = useRouter();
  const searchParams = useSearchParams();

  // Filter state — initial value from URL (deep-link hydration per D-06)
  const initialFilter = useMemo(() => parseFilterFromSearch(new URLSearchParams(searchParams.toString())), []);
  const [filter, setFilter] = useState<TicketFilterValue>(initialFilter);

  // Debounced search — separate from filter so other filters update immediately
  const [debouncedQ, setDebouncedQ] = useState(initialFilter.q);
  useEffect(() => {
    const t = setTimeout(() => setDebouncedQ(filter.q), 400);
    return () => clearTimeout(t);
  }, [filter.q]);

  // List state
  const [tickets, setTickets] = useState<MobileTicket[]>([]);
  const [nextCursor, setNextCursor] = useState<string | null>(null);
  const [hasMore, setHasMore] = useState(false);
  const [openTotal, setOpenTotal] = useState(0);
  const [loading, setLoading] = useState(true);
  const [loadingMore, setLoadingMore] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [queueOptions, setQueueOptions] = useState<QueueOption[]>([]);

  // Build URL search params for the API call given a filter and optional cursor
  const buildApiParams = useCallback((f: TicketFilterValue, q: string, cursor: string | null): URLSearchParams => {
    const sp = new URLSearchParams();
    if (q) sp.set('q', q);
    if (f.status.length > 0) sp.set('status', f.status.join(','));
    else sp.set('status', ''); // explicit no-status (vs. omit = use default on server)
    if (f.priority.length > 0) sp.set('priority', f.priority.join(','));
    if (f.queue !== null) sp.set('queue', String(f.queue));
    if (f.mine) sp.set('mine', '1');
    if (cursor) sp.set('cursor', cursor);
    sp.set('limit', '25');
    return sp;
  }, []);

  // Fetch first page (filters changed)
  const loadFirst = useCallback(async (f: TicketFilterValue, q: string) => {
    setLoading(true);
    setError(null);
    try {
      const sp = buildApiParams(f, q, null);
      const r = await fetch(`/api/mobile/tickets?${sp.toString()}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const data: MobileTicketListResponse = await r.json();
      setTickets(data.tickets);
      setNextCursor(data.nextCursor);
      setHasMore(data.hasMore);
      // Approximate "open total" from first page until a count endpoint exists; keep tickets.length when hasMore=false
      setOpenTotal(data.tickets.length + (data.hasMore ? 1 : 0));
      // Derive queue options from the first page so the Select shows real labels (best-effort; deduped by id)
      setQueueOptions(prev => {
        const seen = new Map<number, QueueOption>();
        for (const opt of prev) seen.set(opt.id, opt);
        for (const t of data.tickets) {
          if (t.queue_id && t.queue_label && !seen.has(t.queue_id)) {
            seen.set(t.queue_id, { id: t.queue_id, label: t.queue_label });
          }
        }
        return Array.from(seen.values()).sort((a, b) => a.label.localeCompare(b.label));
      });
    } catch (e) {
      const msg = e instanceof Error ? e.message : 'Failed to load tickets';
      setError(msg);
      toast.error(msg);
    } finally {
      setLoading(false);
    }
  }, [buildApiParams]);

  // Fetch next page (cursor advance)
  const loadMore = useCallback(async () => {
    if (loadingMore || !hasMore || !nextCursor) return;
    setLoadingMore(true);
    setError(null);
    try {
      const sp = buildApiParams(filter, debouncedQ, nextCursor);
      const r = await fetch(`/api/mobile/tickets?${sp.toString()}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const data: MobileTicketListResponse = await r.json();
      setTickets(prev => [...prev, ...data.tickets]);
      setNextCursor(data.nextCursor);
      setHasMore(data.hasMore);
    } catch (e) {
      const msg = e instanceof Error ? e.message : 'Failed to load more tickets';
      setError(msg);
      toast.error(msg);
    } finally {
      setLoadingMore(false);
    }
  }, [loadingMore, hasMore, nextCursor, filter, debouncedQ, buildApiParams]);

  // Reload first page when filter or debounced search changes (D-05/D-06: also push URL)
  useEffect(() => {
    const next = filterToSearch({ ...filter, q: debouncedQ });
    const nextStr = next.toString();
    if (nextStr !== searchParams.toString()) {
      router.replace(`/mobile/tickets${nextStr ? `?${nextStr}` : ''}`, { scroll: false });
    }
    void loadFirst(filter, debouncedQ);
    // Intentionally exclude searchParams from deps to prevent loop with router.replace
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedQ, filter.status, filter.priority, filter.queue, filter.mine, loadFirst, router]);

  // IntersectionObserver — infinite scroll trigger (D-12, D-13)
  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 && !loading) {
          void loadMore();
        }
      },
      { rootMargin: '200px' },
    );
    observer.observe(node);
    return () => observer.disconnect();
  }, [hasMore, loadingMore, loading, loadMore]);

  // Clear all (D-04, D-20 empty-state CTA)
  const clearAll = useCallback(() => {
    setFilter({ q: '', status: [...DEFAULT_STATUS], priority: [], queue: null, mine: false });
  }, []);

  const filtered = isFilterModified(filter);

  // ───── Render ─────
  return (
    <div className="flex flex-col h-full">
      <TicketFilterStrip
        value={filter}
        onChange={setFilter}
        queueOptions={queueOptions}
        openTotal={openTotal}
        isFiltered={filtered}
        onClearAll={clearAll}
      />

      <div className="flex-1 overflow-y-auto">
        {loading ? (
          <div className="divide-y">
            {Array.from({ length: 5 }).map((_, i) => <TicketRowSkeleton key={i} />)}
          </div>
        ) : tickets.length === 0 ? (
          // Empty state (D-20)
          <div className="text-center py-12 px-4 space-y-3">
            {filtered ? (
              <>
                <p className="text-sm text-muted-foreground">No tickets match your filters</p>
                <Button variant="outline" size="sm" onClick={clearAll}>Clear filters</Button>
              </>
            ) : (
              <>
                <p className="text-sm text-muted-foreground">No tickets to triage right now</p>
                <Button variant="ghost" size="sm" onClick={() => loadFirst(filter, debouncedQ)} aria-label="Refresh ticket list">
                  <RefreshCw className="w-4 h-4" aria-hidden="true" />
                </Button>
              </>
            )}
          </div>
        ) : (
          <>
            <div className="divide-y">
              {tickets.map((t) => (
                <Link
                  key={t.id}
                  href={`/mobile/tickets/${t.id}`}
                  className={`flex items-start border-l-4 ${PRIORITY_BORDER[t.priority] ?? 'border-slate-300'} px-4 py-4 hover:bg-muted/50 active:bg-muted/50 transition-colors`}
                >
                  <div className="flex-1 min-w-0">
                    <div className="flex items-start justify-between gap-2">
                      <p className="text-sm font-semibold leading-snug truncate">{t.title}</p>
                      <ChevronRight className="w-4 h-4 text-muted-foreground shrink-0 mt-0.5" aria-hidden="true" />
                    </div>
                    <p className="text-xs text-muted-foreground truncate mt-0.5">{t.company_name}</p>
                    <div className="flex items-center gap-2 mt-1.5 flex-wrap">
                      <span className="text-[10px] bg-muted rounded px-1.5 py-0.5 font-mono">{t.ticket_number}</span>
                      {t.queue_label && (
                        <span className="text-[10px] text-muted-foreground">{t.queue_label}</span>
                      )}
                      {t.assigned_to && (
                        <span className="inline-flex items-center justify-center h-5 w-5 rounded-full bg-primary/15 text-primary text-[10px] font-semibold">
                          {t.assigned_to.split(' ').map((s: string) => s[0]).filter(Boolean).slice(0, 2).join('').toUpperCase() || '·'}
                        </span>
                      )}
                      <span className="text-[10px] text-muted-foreground flex items-center gap-0.5 ml-auto">
                        <Clock className="w-3 h-3" aria-hidden="true" />
                        {relTime(t.last_activity_date)}
                      </span>
                    </div>
                  </div>
                </Link>
              ))}
            </div>

            {/* Sentinel — IntersectionObserver target (D-12) */}
            <div ref={sentinelRef} aria-hidden="true" />

            {/* Loading-more spinner (D-21) */}
            {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-14, TICK-06) */}
            {hasMore && (
              <div className="p-4">
                <button
                  type="button"
                  onClick={() => void loadMore()}
                  disabled={loadingMore}
                  aria-label="Load more tickets"
                  className="w-full py-3 rounded-xl border text-sm font-semibold hover:bg-muted/50 transition-colors disabled:opacity-50"
                >
                  {error ? 'Retry' : loadingMore ? 'Loading…' : 'Load more'}
                </button>
              </div>
            )}
          </>
        )}
      </div>
    </div>
  );
}
