import { useCallback, useMemo, useState } from 'react';
import { Archive, ChevronRight, Pin, Plus, RotateCcw, Settings as SettingsIcon, Smartphone, Trash2 } from 'lucide-react';
import { toast } from 'sonner';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { ConfirmDialog } from '@/components/ui/confirm-dialog';
import { AgentCreationDialog, type AgentCreationInput } from '@/components/opencode/AgentCreationDialog';
import { getAgentAvatarSrc } from '@/lib/agent-avatars';
import type { ConfiguredModelOption } from '@/lib/model-options';
import { extractText } from '@/pages/Chat/message-utils';
import type { ProjectAgentConfig } from '../../../shared/project-config';
import type { ProjectSessionMetadata } from '../../../shared/project-conversations';
import type { RawMessage } from '@/types/chat';
import type { OpencodeSession, OpencodeSessionStatusMap } from '@/types/opencode';
import { cn } from '@/lib/utils';

type ArchiveTarget =
  | { kind: 'agent'; agent: ProjectAgentConfig }
  | { kind: 'session'; sessionId: string; title: string }
  | null;

type DeleteTarget =
  | { kind: 'agent'; agent: ProjectAgentConfig; sessionCount: number }
  | { kind: 'session'; sessionId: string; title: string }
  | null;

type AgentConversationSidebarProps = {
  agents: ProjectAgentConfig[];
  sessions: OpencodeSession[];
  sessionMessagesBySessionId: Record<string, RawMessage[]>;
  conversationSessions: ProjectSessionMetadata[];
  selectedAgentId: string | null;
  selectedSessionId: string | null;
  sessionStatuses: OpencodeSessionStatusMap;
  pendingQuestionCountBySession: Record<string, number>;
  pendingPermissionCountBySession: Record<string, number>;
  startingAgentId: string | null;
  modelOptions: ConfiguredModelOption[];
  onSelectAgent: (agentId: string) => void;
  onSelectSession: (sessionId: string) => void;
  onNewSession: (agentId: string) => void;
  onCreateAgent: (input: AgentCreationInput) => Promise<void>;
  onArchiveAgent: (agent: ProjectAgentConfig) => Promise<void>;
  onRestoreAgent: (agent: ProjectAgentConfig) => Promise<void>;
  onDeleteAgent: (agent: ProjectAgentConfig) => Promise<void>;
  onArchiveSession: (sessionId: string) => Promise<void>;
  onRestoreSession: (sessionId: string) => Promise<void>;
  onDeleteSession: (sessionId: string) => Promise<void>;
  onTogglePinAgent: (agent: ProjectAgentConfig) => Promise<void>;
  onOpenDevicePreview?: () => void;
  onOpenProjectSettings?: () => void;
};

function getSessionId(session: OpencodeSession): string {
  return String(session.id ?? session.sessionID ?? '');
}

function sessionTimestamp(session: OpencodeSession | undefined, metadata: ProjectSessionMetadata | undefined): number {
  const runtimeTime = session?.time?.updated;
  if (typeof runtimeTime === 'number') return runtimeTime;
  const runtimeUpdated = Date.parse(session?.updatedAt ?? '');
  if (Number.isFinite(runtimeUpdated)) return runtimeUpdated;
  const metadataUpdated = Date.parse(metadata?.updatedAt ?? '');
  return Number.isFinite(metadataUpdated) ? metadataUpdated : 0;
}

function formatSessionTime(timestamp: number): string {
  if (!timestamp) return '';
  const date = new Date(timestamp);
  const now = new Date();
  if (date.toDateString() === now.toDateString()) {
    return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  }
  return date.toLocaleDateString([], { month: 'numeric', day: 'numeric' });
}

function sessionTitle(session: OpencodeSession | undefined): string {
  const title = session?.title?.trim() || session?.name?.trim();
  return title || '新对话';
}

function latestMessagePreview(messages: RawMessage[] | undefined, fallback: string): string {
  for (let index = (messages?.length ?? 0) - 1; index >= 0; index -= 1) {
    const message = messages?.[index];
    if (!message) continue;
    const preview = extractText(message).replace(/\s+/gu, ' ').trim();
    if (preview) return preview;
  }
  return fallback;
}

const SESSION_PREVIEW_LIMIT = 24;

function compactSessionPreview(preview: string): string {
  const characters = Array.from(preview);
  return characters.length > SESSION_PREVIEW_LIMIT
    ? `${characters.slice(0, SESSION_PREVIEW_LIMIT - 1).join('')}…`
    : preview;
}

export function AgentConversationSidebar({
  agents,
  sessions,
  sessionMessagesBySessionId,
  conversationSessions,
  selectedAgentId,
  selectedSessionId,
  sessionStatuses,
  pendingQuestionCountBySession,
  pendingPermissionCountBySession,
  startingAgentId,
  modelOptions,
  onSelectAgent,
  onSelectSession,
  onNewSession,
  onCreateAgent,
  onArchiveAgent,
  onRestoreAgent,
  onDeleteAgent,
  onArchiveSession,
  onRestoreSession,
  onDeleteSession,
  onTogglePinAgent,
  onOpenDevicePreview,
  onOpenProjectSettings,
}: AgentConversationSidebarProps) {
  const [createOpen, setCreateOpen] = useState(false);
  const [archiveTarget, setArchiveTarget] = useState<ArchiveTarget>(null);
  const [deleteTarget, setDeleteTarget] = useState<DeleteTarget>(null);
  const [showArchived, setShowArchived] = useState(false);
  const [expandedSessionLists, setExpandedSessionLists] = useState<Record<string, boolean>>({});

  const runtimeBySessionId = useMemo(
    () => new Map(sessions.map((session) => [getSessionId(session), session])),
    [sessions],
  );
  const sessionsByAgentId = useMemo(() => {
    const grouped = new Map<string, ProjectSessionMetadata[]>();
    for (const metadata of conversationSessions) {
      const current = grouped.get(metadata.agentId) ?? [];
      current.push(metadata);
      grouped.set(metadata.agentId, current);
    }
    return grouped;
  }, [conversationSessions]);

  const getAgentSessions = useCallback((agentId: string) => {
    const metadata = sessionsByAgentId.get(agentId) ?? [];
    return [...metadata].sort((left, right) => {
      const leftArchived = Boolean(left.archivedAt);
      const rightArchived = Boolean(right.archivedAt);
      if (leftArchived !== rightArchived) return leftArchived ? 1 : -1;
      return sessionTimestamp(runtimeBySessionId.get(right.sessionId), right)
        - sessionTimestamp(runtimeBySessionId.get(left.sessionId), left);
    });
  }, [runtimeBySessionId, sessionsByAgentId]);

  const agentActivity = useCallback((agent: ProjectAgentConfig) => {
    const agentSessions = getAgentSessions(agent.id);
    const unread = agentSessions.reduce((total, item) => total + item.unreadCount, 0);
    const running = agentSessions.some((item) => {
      const type = sessionStatuses[item.sessionId]?.type;
      return type === 'busy' || type === 'retry';
    });
    const pending = agentSessions.reduce(
      (total, item) => total + (pendingQuestionCountBySession[item.sessionId] ?? 0) + (pendingPermissionCountBySession[item.sessionId] ?? 0),
      0,
    );
    const latest = agentSessions[0];
    return { unread, running, pending, latestAt: latest ? sessionTimestamp(runtimeBySessionId.get(latest.sessionId), latest) : 0 };
  }, [getAgentSessions, pendingPermissionCountBySession, pendingQuestionCountBySession, runtimeBySessionId, sessionStatuses]);

  const activeAgents = useMemo(
    () => agents
      .filter((agent) => !agent.archivedAt)
      .sort((left, right) => {
        if (Boolean(left.pinned) !== Boolean(right.pinned)) return left.pinned ? -1 : 1;
        return agentActivity(right).latestAt - agentActivity(left).latestAt;
      }),
    [agentActivity, agents],
  );
  const archivedAgents = useMemo(() => agents.filter((agent) => Boolean(agent.archivedAt)), [agents]);

  const getAgentStatus = (activity: ReturnType<typeof agentActivity>) => {
    if (activity.pending > 0) return { label: '处理中', dot: 'bg-amber-500 animate-pulse' };
    if (activity.running) return { label: '处理中', dot: 'bg-emerald-500 animate-pulse' };
    if (activity.unread > 0) return { label: '有新消息', dot: 'bg-brand' };
    return null;
  };

  const renderAgentSessions = (agent: ProjectAgentConfig) => {
    const agentSessions = getAgentSessions(agent.id);
    const activeSessions = agentSessions.filter((item) => !item.archivedAt);
    const visibleSessions = activeSessions.slice(0, 5);
    const hiddenSessions = activeSessions.slice(5);
    const archivedSessions = agentSessions.filter((item) => Boolean(item.archivedAt));
    const renderSession = (metadata: ProjectSessionMetadata) => {
      const runtimeSession = runtimeBySessionId.get(metadata.sessionId);
      const title = sessionTitle(runtimeSession);
      const preview = compactSessionPreview(latestMessagePreview(sessionMessagesBySessionId[metadata.sessionId], title));
      const selected = metadata.sessionId === selectedSessionId;
      const pending = (pendingQuestionCountBySession[metadata.sessionId] ?? 0) + (pendingPermissionCountBySession[metadata.sessionId] ?? 0);
      return (
        <div
          key={metadata.sessionId}
          className={cn(
            'group/session-card flex w-full items-center gap-1 rounded-lg px-1 py-0.5',
            selected ? 'bg-background shadow-soft' : 'hover:bg-background/70',
          )}
        >
          <button
            type="button"
            aria-current={selected ? 'page' : undefined}
            className="flex min-w-0 flex-1 items-center gap-1.5 px-1.5 py-1 text-left"
            onClick={() => onSelectSession(metadata.sessionId)}
          >
            <span className="min-w-0 flex-1 truncate text-[11px] font-semibold" title={latestMessagePreview(sessionMessagesBySessionId[metadata.sessionId], title)}>
              {preview}
            </span>
            {metadata.unreadCount > 0 ? <Badge className="shrink-0 rounded-full border-0 bg-brand px-1.5 text-[10px] text-primary-foreground">{metadata.unreadCount > 99 ? '99+' : metadata.unreadCount}</Badge> : pending > 0 ? <Badge className="shrink-0 rounded-full border-0 bg-amber-500 px-1.5 text-[10px] text-white">{pending}</Badge> : null}
            <span className="shrink-0 whitespace-nowrap text-[10px] text-muted-foreground">
              {formatSessionTime(sessionTimestamp(runtimeSession, metadata)) || '刚刚'}
            </span>
          </button>
          <button
            type="button"
            className="pointer-events-none shrink-0 rounded p-1 text-muted-foreground opacity-0 transition-opacity hover:bg-surface-subtle hover:text-destructive group-hover/session-card:pointer-events-auto group-hover/session-card:opacity-100"
            aria-label={`归档会话 ${title}`}
            title="归档会话"
            onClick={() => setArchiveTarget({ kind: 'session', sessionId: metadata.sessionId, title })}
          >
            <Archive className="h-3 w-3" />
          </button>
        </div>
      );
    };
    return (
      <section className="border-t border-brand/15 px-2 pb-1.5 pt-1" aria-label={`${agent.name || '联系人'}的会话列表`}>
        <div className="flex items-center justify-end">
          <button type="button" className="rounded p-1 text-muted-foreground hover:bg-background hover:text-brand" aria-label="新建会话" title="新建会话" onClick={() => onNewSession(agent.id)}><Plus className="h-3.5 w-3.5" /></button>
        </div>
        <div className="space-y-0.5">
          {visibleSessions.map(renderSession)}
          {activeSessions.length === 0 ? <button type="button" className="flex w-full items-center gap-2 rounded-lg border border-dashed border-border/80 px-2.5 py-2.5 text-left text-xs text-muted-foreground hover:bg-background" onClick={() => onNewSession(agent.id)}><Plus className="h-3.5 w-3.5" />开始一条新对话</button> : null}
          {hiddenSessions.length > 0 ? <details className="pt-1" open={expandedSessionLists[agent.id] ?? hiddenSessions.some((item) => item.sessionId === selectedSessionId)} onToggle={(event) => setExpandedSessionLists((current) => ({ ...current, [agent.id]: event.currentTarget.open }))}><summary className="cursor-pointer px-2.5 py-1 text-[10px] font-semibold text-muted-foreground">更多会话（{hiddenSessions.length}）</summary><div className="mt-0.5 space-y-0.5">{hiddenSessions.map(renderSession)}</div></details> : null}
          {archivedSessions.length > 0 ? <details className="pt-2"><summary className="cursor-pointer px-2.5 text-[10px] font-semibold text-muted-foreground">已归档会话（{archivedSessions.length}）</summary><div className="mt-1 space-y-1">{archivedSessions.map((metadata) => { const title = sessionTitle(runtimeBySessionId.get(metadata.sessionId)); return <div key={metadata.sessionId} className="flex items-center gap-2 rounded-lg px-2.5 py-2 text-xs text-muted-foreground"><span className="min-w-0 flex-1 truncate" title={title}>{compactSessionPreview(title)}</span><button type="button" className="rounded p-1 text-muted-foreground hover:bg-background hover:text-brand" aria-label={`恢复会话 ${title}`} onClick={() => void onRestoreSession(metadata.sessionId)}><RotateCcw className="h-3 w-3" /></button><button type="button" className="rounded p-1 text-muted-foreground hover:bg-accent-soft hover:text-destructive" aria-label={`永久删除会话 ${title}`} title="永久删除会话" onClick={() => setDeleteTarget({ kind: 'session', sessionId: metadata.sessionId, title })}><Trash2 className="h-3 w-3" /></button></div>; })}</div></details> : null}
        </div>
      </section>
    );
  };

  const handleArchiveConfirm = async () => {
    if (!archiveTarget) return;
    if (archiveTarget.kind === 'agent') await onArchiveAgent(archiveTarget.agent);
    else await onArchiveSession(archiveTarget.sessionId);
    setArchiveTarget(null);
  };

  const handleDeleteConfirm = async () => {
    if (!deleteTarget) return;
    if (deleteTarget.kind === 'agent') await onDeleteAgent(deleteTarget.agent);
    else await onDeleteSession(deleteTarget.sessionId);
    setDeleteTarget(null);
  };

  return (
    <aside className="flex h-full min-h-0 w-full shrink-0 flex-col overflow-hidden rounded-none border-r border-border/80 bg-surface-tertiary text-foreground lg:w-[19rem]" data-testid="agent-conversation-sidebar">
      <div className="relative flex shrink-0 items-center border-b border-border/70 bg-background/70 px-3 py-3 pr-28">
        <h2 className="min-w-0 truncate text-left text-base font-semibold">我的项目空间</h2>
        <div className="absolute right-3 flex items-center gap-1">
          {onOpenDevicePreview ? (
            <Button type="button" size="icon" variant="ghost" className="h-10 w-10 rounded-full text-muted-foreground hover:bg-surface-subtle hover:text-foreground active:scale-[0.96]" aria-label="真机预览" title="真机预览" onClick={onOpenDevicePreview}>
              <Smartphone className="h-4 w-4" />
            </Button>
          ) : null}
          {onOpenProjectSettings ? (
            <Button type="button" size="icon" variant="ghost" className="h-10 w-10 rounded-full text-muted-foreground hover:bg-surface-subtle hover:text-foreground active:scale-[0.96]" aria-label="项目设置" title="项目设置" onClick={onOpenProjectSettings}>
              <SettingsIcon className="h-4 w-4" />
            </Button>
          ) : null}
          <Button type="button" size="icon" className="h-8 w-8 rounded-full border border-brand/20 bg-brand-soft text-brand" aria-label="创建联系人" onClick={() => { if (modelOptions.length === 0) { toast.error('请先配置至少一个可用模型。'); return; } setCreateOpen(true); }}>
            <Plus className="h-4 w-4" />
          </Button>
        </div>
      </div>

      <div className="min-h-0 flex-1 overflow-y-auto p-0" data-testid="agent-conversation-sidebar-content">
        <div className="space-y-1">
          {activeAgents.map((agent) => {
            const activity = agentActivity(agent);
            const selected = agent.id === selectedAgentId;
            const status = getAgentStatus(activity);
            return (
              <div key={agent.id} className={cn('group/agent-card relative overflow-hidden rounded-none border transition-colors', selected ? 'border-brand/45 bg-brand-soft shadow-soft before:absolute before:inset-y-2 before:left-0 before:w-0.5 before:rounded-full before:bg-brand' : 'border-transparent hover:border-border/70 hover:bg-background')}>
                <div className="relative">
                  <button type="button" className="flex w-full min-w-0 items-center gap-2.5 px-2.5 py-2.5 pr-12 text-left" aria-pressed={selected} aria-expanded={selected} aria-busy={startingAgentId === agent.id} title={selected ? '收起最近会话' : '展开最近会话'} data-testid={`project-agent-chat-${agent.id}`} disabled={startingAgentId === agent.id} onClick={() => onSelectAgent(agent.id)}>
                    <span className="relative shrink-0"><img src={getAgentAvatarSrc(agent.avatarId)} alt="" className="h-11 w-11 rounded-xl border border-border object-cover [image-rendering:pixelated]" /></span>
                    <span className="min-w-0 flex-1"><span className="flex items-center gap-1.5"><span className="truncate text-sm font-semibold">{agent.name || '未命名联系人'}</span>{agent.pinned ? <Pin className="h-3 w-3 shrink-0 text-brand" /> : null}</span><span className="mt-0.5 block truncate text-[11px] text-muted-foreground">{agent.responsibility.mission || '还没有填写职责'}</span></span>
                    <span className="flex shrink-0 flex-col items-end gap-1">
                      <span data-testid={`agent-status-row-${agent.id}`} className="inline-flex items-center gap-1">
                        {status ? <span className="inline-flex max-w-24 items-center gap-1 text-[10px] font-medium text-muted-foreground" title={status.label}><span aria-hidden="true" className={cn('h-2 w-2 shrink-0 rounded-full', status.dot)} /><span className="truncate">{status.label}</span></span> : null}
                        {activity.unread > 0 ? <Badge className="min-w-5 justify-center rounded-full border-0 bg-brand px-1.5 text-[10px] text-primary-foreground">{activity.unread > 99 ? '99+' : activity.unread}</Badge> : activity.pending > 0 ? <Badge className="min-w-5 justify-center rounded-full border-0 bg-amber-500 px-1.5 text-[10px] text-white">{activity.pending}</Badge> : null}
                      </span>
                      {activity.latestAt ? <span className="text-[10px] text-muted-foreground">{formatSessionTime(activity.latestAt)}</span> : null}
                    </span>
                  </button>
                  <div className="absolute right-1 top-1/2 hidden -translate-y-1/2 flex-col gap-0.5 rounded-md bg-background/90 p-0.5 shadow-soft group-hover/agent-card:flex">
                    <button type="button" className="rounded p-1.5 text-muted-foreground hover:bg-surface-subtle hover:text-brand" aria-label={agent.pinned ? '取消置顶联系人' : '置顶联系人'} title={agent.pinned ? '取消置顶' : '置顶'} onClick={() => void onTogglePinAgent(agent)}><Pin className={cn('h-3.5 w-3.5', agent.pinned && 'fill-current text-brand')} /></button>
                    <button type="button" className="rounded p-1.5 text-muted-foreground hover:bg-surface-subtle hover:text-destructive" aria-label={`归档联系人 ${agent.name || '未命名联系人'}`} title="归档联系人" onClick={() => setArchiveTarget({ kind: 'agent', agent })}><Archive className="h-3.5 w-3.5" /></button>
                  </div>
                </div>
                {selected ? renderAgentSessions(agent) : null}
              </div>
            );
          })}
          {activeAgents.length === 0 ? <div className="rounded-xl border border-dashed border-border/80 bg-background/60 p-5 text-center text-xs leading-5 text-muted-foreground">还没有联系人<br />点击右上角的 + 创建第一个</div> : null}
        </div>

        {archivedAgents.length > 0 ? <section className="mt-3 border-t border-border/70 pt-3"><button type="button" className="flex w-full items-center justify-between px-1 text-xs font-semibold text-muted-foreground" onClick={() => setShowArchived((current) => !current)}><span>已归档联系人（{archivedAgents.length}）</span><ChevronRight className={cn('h-3.5 w-3.5 transition-transform', showArchived && 'rotate-90')} /></button>{showArchived ? <div className="mt-1 space-y-1">{archivedAgents.map((agent) => <div key={agent.id} className="flex items-center gap-2 rounded-lg px-2.5 py-2 text-xs text-muted-foreground"><img src={getAgentAvatarSrc(agent.avatarId)} alt="" className="h-7 w-7 rounded-lg object-cover [image-rendering:pixelated]" /><span className="min-w-0 flex-1 truncate">{agent.name || '未命名联系人'}</span><button type="button" className="rounded p-1.5 hover:bg-background hover:text-brand" aria-label={`恢复联系人 ${agent.name || '未命名联系人'}`} onClick={() => void onRestoreAgent(agent)}><RotateCcw className="h-3.5 w-3.5" /></button>{!agent.builtIn ? <button type="button" className="rounded p-1.5 text-muted-foreground hover:bg-accent-soft hover:text-destructive" aria-label={`永久删除联系人 ${agent.name || '未命名联系人'}`} title="永久删除联系人" onClick={() => setDeleteTarget({ kind: 'agent', agent, sessionCount: getAgentSessions(agent.id).length })}><Trash2 className="h-3.5 w-3.5" /></button> : null}</div>)}</div> : null}</section> : null}
      </div>

      <AgentCreationDialog
        open={createOpen}
        onOpenChange={setCreateOpen}
        modelOptions={modelOptions}
        existingAgentNames={agents.map((agent) => agent.name)}
        onCreate={onCreateAgent}
      />

      <ConfirmDialog
        open={Boolean(archiveTarget)}
        title={archiveTarget?.kind === 'agent' ? `归档联系人“${archiveTarget.agent.name || '未命名联系人'}”？` : `归档会话“${archiveTarget?.title ?? ''}”？`}
        message={archiveTarget?.kind === 'agent' ? '归档会停止这个联系人下正在运行的所有会话，但会保留联系人、会话记录和未完成内容，之后可以在底部恢复。' : '归档会停止这个会话并保留全部历史，不会影响同一联系人的其他会话。'}
        confirmLabel="确认归档"
        cancelLabel="取消"
        onCancel={() => setArchiveTarget(null)}
        onConfirm={() => void handleArchiveConfirm()}
        onError={(error) => toast.error(error instanceof Error ? error.message : String(error))}
      />
      <ConfirmDialog
        open={Boolean(deleteTarget)}
        title={deleteTarget?.kind === 'agent' ? `永久删除联系人“${deleteTarget.agent.name || '未命名联系人'}”？` : `永久删除会话“${deleteTarget?.title ?? ''}”？`}
        message={deleteTarget?.kind === 'agent'
          ? `这会永久删除联系人及其 ${deleteTarget.sessionCount} 条会话记录，删除后无法恢复。`
          : '这会永久删除会话及其全部历史记录，删除后无法恢复。'}
        confirmLabel="永久删除"
        cancelLabel="取消"
        variant="destructive"
        onCancel={() => setDeleteTarget(null)}
        onConfirm={() => void handleDeleteConfirm()}
        onError={(error) => toast.error(error instanceof Error ? error.message : String(error))}
      />
    </aside>
  );
}
