import * as React from 'react'
import {
  useInfiniteQuery,
  useMutation,
  useQuery,
  useQueryClient,
} from '@tanstack/react-query'
import { useConfig } from '@/state/config'
import { ipc } from '@/lib/api/desktop'
import { asEnvelope } from '@/lib/error'
import { qk } from '@/lib/queryKeys'
import type {
  DesktopErrorEnvelope,
  DesktopImportSessionResponse,
  DesktopSessionDetailResponse,
  DesktopSessionItem,
  DesktopSessionMessage,
  ImportCandidateDto,
} from '@/lib/types/desktop'
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Separator } from '@/components/ui/separator'
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import { ErrorBanner } from '@/components/error-banner'
import {
  Download,
  Filter,
  Loader2,
  MessageSquare,
  Play,
  Sparkles,
  Trash2,
} from 'lucide-react'

const PER_PAGE = 20
const MESSAGE_PAGE_SIZE = 50

const PROVIDER_OPTIONS = [
  { value: '', label: '全部' },
  { value: 'claude', label: 'Claude Code' },
  { value: 'codex', label: 'Codex' },
  { value: 'gemini', label: 'Gemini CLI' },
] as const

export function SessionsPage({ onImported }: { onImported?: () => void } = {}) {
  const cfg = useConfig()
  const qc = useQueryClient()
  const configPath = cfg.configPath

  const [providerFilter, setProviderFilter] = React.useState('')
  const [selectedId, setSelectedId] = React.useState<string | null>(null)
  const [preview, setPreview] = React.useState<DesktopImportSessionResponse | null>(null)
  const [actionError, setActionError] = React.useState<DesktopErrorEnvelope | null>(null)
  // Local detail state to support appending paginated messages without abandoning
  // the cached query data.
  const [detailMessages, setDetailMessages] = React.useState<DesktopSessionMessage[]>([])
  const [detailMessagesShown, setDetailMessagesShown] = React.useState(0)
  const [detailHasMore, setDetailHasMore] = React.useState(false)

  const listSentinelRef = React.useRef<HTMLDivElement>(null)
  const messageSentinelRef = React.useRef<HTMLDivElement>(null)

  const sessionsQuery = useInfiniteQuery({
    queryKey: qk.sessionsList(configPath, providerFilter),
    queryFn: ({ pageParam }) =>
      ipc.browseSessions({
        config_path: configPath,
        page: pageParam as number,
        per_page: PER_PAGE,
        provider: providerFilter || null,
      }),
    initialPageParam: 1,
    getNextPageParam: (lastPage) =>
      lastPage.has_more ? lastPage.page + 1 : undefined,
    enabled: configPath.trim().length > 0,
  })

  const allSessions: DesktopSessionItem[] = React.useMemo(
    () => sessionsQuery.data?.pages.flatMap((p) => p.sessions) ?? [],
    [sessionsQuery.data]
  )
  const totalCount = sessionsQuery.data?.pages[0]?.total ?? 0
  const hasMore = sessionsQuery.hasNextPage
  const loadingList = sessionsQuery.isFetching

  const sessionDetailQuery = useQuery({
    queryKey: selectedId
      ? qk.sessionDetail(configPath, selectedId)
      : ['sessionDetail', '__none__'],
    queryFn: () =>
      ipc.getSession({
        config_path: configPath,
        session_id: selectedId as string,
        message_offset: null,
        message_limit: MESSAGE_PAGE_SIZE,
      }),
    enabled: Boolean(selectedId) && configPath.trim().length > 0,
  })

  // Sync local pagination state when initial detail loads / changes.
  React.useEffect(() => {
    const data = sessionDetailQuery.data
    if (!data) {
      setDetailMessages([])
      setDetailMessagesShown(0)
      setDetailHasMore(false)
      return
    }
    setDetailMessages(data.messages)
    setDetailMessagesShown(data.showing_recent_messages)
    setDetailHasMore(data.has_more_messages)
  }, [sessionDetailQuery.data])

  const loadMoreMessagesMutation = useMutation({
    mutationFn: async () => {
      if (!selectedId) throw new Error('no session selected')
      return ipc.getSession({
        config_path: configPath,
        session_id: selectedId,
        message_offset: detailMessages.length,
        message_limit: MESSAGE_PAGE_SIZE,
      })
    },
    onSuccess: (res) => {
      if (!res) return
      setDetailMessages((prev) => [...prev, ...res.messages])
      setDetailMessagesShown((prev) => prev + res.messages.length)
      setDetailHasMore(res.has_more_messages)
    },
  })

  const continueMutation = useMutation({
    mutationFn: () =>
      ipc.continueSession({ config_path: configPath, session_id: selectedId as string }),
    onSuccess: () => {
      qc.invalidateQueries({ queryKey: qk.sessionsList(configPath, providerFilter) })
    },
  })

  const deleteMutation = useMutation({
    mutationFn: () =>
      ipc.deleteSession({ config_path: configPath, session_id: selectedId as string }),
    onSuccess: () => {
      setSelectedId(null)
      qc.invalidateQueries({ queryKey: qk.sessionsList(configPath, providerFilter) })
    },
  })

  const injectMutation = useMutation({
    mutationFn: async () => {
      const session = sessionDetailQuery.data?.session
      if (!session) throw new Error('no session detail')
      const cwd = session.cwd || ''
      const res = await ipc.runContext({
        config_path: configPath,
        vault_root_override: cfg.vaultRoot.trim() || null,
        cwd,
        task: session.title || 'session context',
        files: [],
        target: 'claude' as never,
        format: 'prompt' as never,
      })
      if (res.rendered && navigator.clipboard) {
        await navigator.clipboard.writeText(res.rendered)
      }
      return res
    },
  })

  const previewImportMutation = useMutation({
    mutationFn: async () => {
      const detail = sessionDetailQuery.data
      if (!selectedId || !detail) throw new Error('no session selected')
      return ipc.importSession({
        config_path: configPath,
        provider: detail.session.provider as 'claude' | 'codex',
        session_id: selectedId,
        apply: false,
        actor: 'desktop-ui',
      })
    },
    onSuccess: (res) => {
      setPreview(res)
    },
  })

  const confirmImportMutation = useMutation({
    mutationFn: async () => {
      const detail = sessionDetailQuery.data
      if (!selectedId || !detail || !preview) throw new Error('no preview')
      return ipc.importSession({
        config_path: configPath,
        provider: detail.session.provider as 'claude' | 'codex',
        session_id: selectedId,
        apply: true,
        actor: 'desktop-ui',
      })
    },
    onSuccess: () => {
      setPreview(null)
      qc.invalidateQueries({ queryKey: qk.workbench(configPath) })
      onImported?.()
    },
  })

  // IntersectionObserver for session list infinite scroll
  React.useEffect(() => {
    const sentinel = listSentinelRef.current
    if (!sentinel) return
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasMore && !loadingList) {
          void sessionsQuery.fetchNextPage()
        }
      },
      { threshold: 0.1 }
    )
    observer.observe(sentinel)
    return () => observer.disconnect()
  }, [hasMore, loadingList, sessionsQuery])

  function handleProviderChange(value: string) {
    setProviderFilter(value)
  }

  const acting: 'continue' | 'delete' | 'import' | 'inject' | null =
    continueMutation.isPending
      ? 'continue'
      : deleteMutation.isPending
        ? 'delete'
        : previewImportMutation.isPending
          ? 'import'
          : injectMutation.isPending
            ? 'inject'
            : null

  const queryError =
    sessionsQuery.error ??
    sessionDetailQuery.error ??
    continueMutation.error ??
    deleteMutation.error ??
    injectMutation.error ??
    previewImportMutation.error ??
    confirmImportMutation.error ??
    loadMoreMessagesMutation.error ??
    null
  const errorEnvelope = actionError ?? (queryError ? asEnvelope(queryError) : null)

  React.useEffect(() => {
    setActionError(null)
  }, [selectedId])

  const detail = sessionDetailQuery.data ?? null
  const loadingDetail = sessionDetailQuery.isFetching
  const loadingMore = loadMoreMessagesMutation.isPending
  const applying = confirmImportMutation.isPending

  return (
    <div className="grid gap-4 lg:grid-cols-[400px_1fr]">
      <Card className="h-[calc(100vh-13rem)] overflow-hidden flex flex-col">
        <CardHeader className="pb-3 shrink-0">
          <div className="flex items-center justify-between">
            <CardTitle className="text-base flex items-center gap-2">
              会话列表
              {loadingList && (
                <Loader2 className="h-3.5 w-3.5 animate-spin text-muted-foreground" />
              )}
            </CardTitle>
          </div>
          <CardDescription>
            {totalCount > 0
              ? `共 ${totalCount} 条 · 已加载 ${allSessions.length} 条`
              : loadingList
                ? '加载中...'
                : '暂无会话'}
          </CardDescription>
          <div className="flex items-center gap-1 pt-1">
            <Filter className="h-3 w-3 text-muted-foreground" />
            <select
              className="h-7 rounded-md border bg-background px-2 text-xs"
              value={providerFilter}
              onChange={(e) => handleProviderChange(e.target.value)}
              aria-label="Provider filter"
            >
              {PROVIDER_OPTIONS.map((opt) => (
                <option key={opt.value} value={opt.value}>
                  {opt.label}
                </option>
              ))}
            </select>
          </div>
        </CardHeader>
        <CardContent className="overflow-y-auto px-3 pb-3 flex-1">
          {allSessions.length === 0 && !loadingList && (
            <div className="px-3 py-8 text-center">
              <p className="text-sm text-muted-foreground">暂无会话</p>
              <p className="mt-2 text-xs text-muted-foreground">
                在 AI 客户端开启会话后会自动出现
              </p>
            </div>
          )}
          <div className="space-y-1">
            {allSessions.map((s) => (
              <SessionRow
                key={`${s.provider}:${s.session_id}`}
                item={s}
                selected={s.session_id === selectedId}
                onSelect={() => setSelectedId(s.session_id)}
              />
            ))}
          </div>
          {hasMore && (
            <div ref={listSentinelRef} className="flex justify-center py-3">
              {loadingList && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
            </div>
          )}
        </CardContent>
      </Card>

      <div className="space-y-4">
        {errorEnvelope && <ErrorBanner envelope={errorEnvelope} />}
        {!selectedId && !errorEnvelope && (
          <Card>
            <CardContent className="p-10 text-center text-sm text-muted-foreground">
              选择左侧的会话查看对话记录与关联的记忆记录。
            </CardContent>
          </Card>
        )}
        {selectedId && detail && (
          <SessionDetail
            detail={detail}
            messages={detailMessages}
            messagesShown={detailMessagesShown}
            hasMoreMessages={detailHasMore}
            loading={loadingDetail}
            loadingMore={loadingMore}
            acting={acting}
            onContinue={() => continueMutation.mutate()}
            onDelete={() => deleteMutation.mutate()}
            onImport={() => previewImportMutation.mutate()}
            onInjectContext={() => injectMutation.mutate()}
            onLoadMore={() => loadMoreMessagesMutation.mutate()}
            messageSentinelRef={messageSentinelRef}
          />
        )}
      </div>

      <Dialog
        open={preview !== null}
        onOpenChange={(open) => {
          if (!open) setPreview(null)
        }}
      >
        <DialogContent>
          <DialogHeader>
            <DialogTitle>导入预览 · {preview?.session_ref}</DialogTitle>
            <DialogDescription>
              扫描 {preview?.total_messages} 条消息，识别 {preview?.candidate_count} 条候选记忆。
              确认后以 candidate 状态写入 ledger，需在"记忆审核"tab 再做接受/归档。
            </DialogDescription>
          </DialogHeader>
          <div className="max-h-[50vh] space-y-2 overflow-y-auto">
            {preview?.candidates.length === 0 && (
              <p className="py-8 text-center text-sm text-muted-foreground">
                未识别到候选。可以关闭本窗口换条会话再试。
              </p>
            )}
            {preview?.candidates.map((c, idx) => (
              <CandidateRow key={`${c.source_ref}-${idx}`} candidate={c} />
            ))}
          </div>
          <DialogFooter>
            <DialogClose asChild>
              <Button variant="outline" disabled={applying}>
                取消
              </Button>
            </DialogClose>
            <Button
              onClick={() => confirmImportMutation.mutate()}
              disabled={applying || !preview || preview.candidate_count === 0}
            >
              {applying && <Loader2 className="mr-1 h-3.5 w-3.5 animate-spin" />}
              确认导入 {preview?.candidate_count ?? 0} 条候选
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  )
}

function CandidateRow({ candidate }: { candidate: ImportCandidateDto }) {
  return (
    <div className="rounded-md border px-3 py-2 text-sm">
      <div className="mb-1 flex items-center gap-2 text-xs">
        <Badge variant="default">{candidate.signal}</Badge>
        <Badge variant="outline">{candidate.memory_type}</Badge>
        <Badge variant="muted">{candidate.scope}</Badge>
      </div>
      <p className="font-medium">{candidate.title}</p>
      <p className="mt-1 text-xs leading-relaxed text-muted-foreground line-clamp-3">
        {candidate.summary}
      </p>
    </div>
  )
}

function SessionRow({
  item,
  selected,
  onSelect,
}: {
  item: DesktopSessionItem
  selected: boolean
  onSelect: () => void
}) {
  return (
    <button
      type="button"
      onClick={onSelect}
      className={`w-full rounded-md border px-3 py-2 text-left transition-colors ${
        selected ? 'border-primary bg-accent' : 'border-transparent hover:bg-accent/60'
      }`}
    >
      <div className="flex items-center justify-between gap-2">
        <span className="line-clamp-1 text-sm font-medium">{item.title}</span>
        <Badge variant="outline" className="shrink-0 text-[10px]">
          {item.provider}
        </Badge>
      </div>
      {item.summary && (
        <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">{item.summary}</p>
      )}
      <div className="mt-1 flex items-center gap-2 text-xs text-muted-foreground">
        <MessageSquare className="h-3 w-3" />
        <span>{item.record_count} 条记忆</span>
        {item.pending_review_count > 0 && (
          <Badge variant="secondary" className="text-[10px]">
            待审 {item.pending_review_count}
          </Badge>
        )}
        <span className="ml-auto truncate">{item.updated_at.slice(0, 19)}</span>
      </div>
      {item.project_path && (
        <p className="mt-0.5 truncate text-[10px] text-muted-foreground/70 font-mono">
          {item.project_path}
        </p>
      )}
    </button>
  )
}

function SessionDetail({
  detail,
  messages,
  messagesShown,
  hasMoreMessages,
  loading,
  loadingMore,
  acting,
  onContinue,
  onDelete,
  onImport,
  onInjectContext,
  onLoadMore,
  messageSentinelRef,
}: {
  detail: DesktopSessionDetailResponse
  messages: DesktopSessionMessage[]
  messagesShown: number
  hasMoreMessages: boolean
  loading: boolean
  loadingMore: boolean
  acting: 'continue' | 'delete' | 'import' | 'inject' | null
  onContinue: () => void
  onDelete: () => void
  onImport: () => void
  onInjectContext: () => void
  onLoadMore: () => void
  messageSentinelRef: React.RefObject<HTMLDivElement | null>
}) {
  const { session } = detail

  // Auto-load more messages when sentinel is visible
  React.useEffect(() => {
    const sentinel = messageSentinelRef.current
    if (!sentinel || !hasMoreMessages) return
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasMoreMessages && !loadingMore) {
          onLoadMore()
        }
      },
      { threshold: 0.1 }
    )
    observer.observe(sentinel)
    return () => observer.disconnect()
  }, [hasMoreMessages, loadingMore, onLoadMore, messageSentinelRef])

  return (
    <>
      <Card>
        <CardHeader className="pb-3">
          <div className="flex items-start justify-between gap-3">
            <div>
              <CardTitle className="text-base">{session.title}</CardTitle>
              <CardDescription className="flex flex-wrap items-center gap-2 pt-1 text-xs">
                <Badge variant="outline">{session.provider}</Badge>
                <span>{session.session_id}</span>
                <span className="text-muted-foreground">{session.updated_at}</span>
              </CardDescription>
            </div>
            {loading && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
          </div>
        </CardHeader>
        <CardContent className="space-y-3">
          {session.summary && (
            <p className="text-sm leading-relaxed text-muted-foreground">{session.summary}</p>
          )}
          {session.cwd && (
            <p className="text-xs">
              cwd: <span className="font-mono">{session.cwd}</span>
            </p>
          )}
          {session.project_path && session.project_path !== session.cwd && (
            <p className="text-xs">
              project: <span className="font-mono">{session.project_path}</span>
            </p>
          )}
          {session.source_path && (
            <p className="text-xs">
              source: <span className="font-mono">{session.source_path}</span>
            </p>
          )}
          <Separator />
          <div className="flex flex-wrap gap-2">
            <Button size="sm" onClick={onContinue} disabled={acting !== null}>
              {acting === 'continue' ? (
                <Loader2 className="h-3.5 w-3.5 animate-spin" />
              ) : (
                <Play className="h-3.5 w-3.5" />
              )}
              继续会话
            </Button>
            <Button size="sm" variant="outline" onClick={onInjectContext} disabled={acting !== null}>
              {acting === 'inject' ? (
                <Loader2 className="h-3.5 w-3.5 animate-spin" />
              ) : (
                <Sparkles className="h-3.5 w-3.5" />
              )}
              生成上下文
            </Button>
            <Button
              size="sm"
              variant="secondary"
              onClick={onImport}
              disabled={acting !== null}
            >
              {acting === 'import' ? (
                <Loader2 className="h-3.5 w-3.5 animate-spin" />
              ) : (
                <Download className="h-3.5 w-3.5" />
              )}
              导入为候选
            </Button>
            <Button
              size="sm"
              variant="destructive"
              onClick={onDelete}
              disabled={acting !== null}
            >
              {acting === 'delete' ? (
                <Loader2 className="h-3.5 w-3.5 animate-spin" />
              ) : (
                <Trash2 className="h-3.5 w-3.5" />
              )}
              删除
            </Button>
          </div>
        </CardContent>
      </Card>

      <Card>
        <CardHeader className="pb-3">
          <CardTitle className="text-base">消息</CardTitle>
          <CardDescription>
            共 {detail.total_messages} 条 · 已加载 {messagesShown} 条
            {hasMoreMessages && '（滚动加载更多）'}
          </CardDescription>
        </CardHeader>
        <CardContent className="space-y-3">
          {messages.map((m, idx) => (
            <MessageCard key={idx} message={m} />
          ))}
          {hasMoreMessages && (
            <div ref={messageSentinelRef as React.LegacyRef<HTMLDivElement>} className="flex justify-center py-3">
              {loadingMore && <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />}
            </div>
          )}
        </CardContent>
      </Card>
    </>
  )
}

function MessageCard({ message }: { message: DesktopSessionMessage }) {
  const isUser = message.role === 'user'
  return (
    <div
      className={`rounded-md border px-3 py-2 text-sm ${
        isUser ? 'bg-accent/40' : 'bg-muted/20'
      }`}
    >
      <div className="mb-1 flex items-center gap-2 text-xs text-muted-foreground">
        <Badge variant={isUser ? 'default' : 'outline'}>{message.role}</Badge>
        <span>{message.timestamp}</span>
        {message.truncated && <Badge variant="muted">truncated</Badge>}
      </div>
      <p className="whitespace-pre-wrap leading-relaxed">{message.content}</p>
    </div>
  )
}
