import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:markdown/markdown.dart' as md;

import '../i18n/app_language.dart';
import '../runtime/assistant_artifacts.dart';
import '../runtime/runtime_models.dart';
import '../theme/app_palette.dart';
import '../theme/app_theme.dart';
import 'section_tabs.dart';
import 'surface_card.dart';

typedef AssistantArtifactSnapshotLoader =
    Future<AssistantArtifactSnapshot> Function();
typedef AssistantArtifactPreviewLoader =
    Future<AssistantArtifactPreview> Function(AssistantArtifactEntry entry);
typedef AssistantArtifactOpenWorkspace = Future<void> Function();
typedef AssistantArtifactOpenEntryLocation =
    Future<void> Function(AssistantArtifactEntry entry);

enum AssistantArtifactSidebarTab { files, preview }

class AssistantArtifactSidebar extends StatefulWidget {
  const AssistantArtifactSidebar({
    super.key,
    required this.sessionKey,
    required this.threadTitle,
    required this.workspacePath,
    required this.workspaceKind,
    required this.artifactSyncAtMs,
    required this.artifactSyncStatus,
    required this.taskContextMessageCount,
    required this.taskContextSelectedSkillKeys,
    required this.taskContextRemoteWorkingDirectory,
    required this.taskContextOpenClawRunId,
    required this.taskContextOpenClawStatus,
    required this.onCollapse,
    required this.loadSnapshot,
    required this.loadPreview,
    this.onOpenWorkspace,
    this.onOpenEntryLocation,
  });

  final String sessionKey;
  final String threadTitle;
  final String workspacePath;
  final WorkspaceRefKind workspaceKind;
  final double? artifactSyncAtMs;
  final String artifactSyncStatus;
  final int taskContextMessageCount;
  final List<String> taskContextSelectedSkillKeys;
  final String taskContextRemoteWorkingDirectory;
  final String taskContextOpenClawRunId;
  final String taskContextOpenClawStatus;
  final VoidCallback onCollapse;
  final AssistantArtifactSnapshotLoader loadSnapshot;
  final AssistantArtifactPreviewLoader loadPreview;
  final AssistantArtifactOpenWorkspace? onOpenWorkspace;
  final AssistantArtifactOpenEntryLocation? onOpenEntryLocation;

  @override
  State<AssistantArtifactSidebar> createState() =>
      _AssistantArtifactSidebarState();
}

class _AssistantArtifactSidebarState extends State<AssistantArtifactSidebar> {
  AssistantArtifactSidebarTab _activeTab = AssistantArtifactSidebarTab.files;
  AssistantArtifactSnapshot? _snapshot;
  AssistantArtifactEntry? _selectedEntry;
  AssistantArtifactPreview _preview = const AssistantArtifactPreview.empty();
  Object? _loadError;
  bool _loadingSnapshot = false;
  bool _loadingPreview = false;
  bool _taskContextExpanded = false;
  int _snapshotLoadGeneration = 0;
  int _previewLoadGeneration = 0;
  Timer? _refreshTimer;

  @override
  void initState() {
    super.initState();
    unawaited(_refreshSnapshot());
    _syncRefreshTimer();
  }

  @override
  void didUpdateWidget(covariant AssistantArtifactSidebar oldWidget) {
    super.didUpdateWidget(oldWidget);
    final workspaceChanged =
        oldWidget.sessionKey != widget.sessionKey ||
        oldWidget.workspacePath != widget.workspacePath ||
        oldWidget.workspaceKind != widget.workspaceKind;
    if (workspaceChanged ||
        oldWidget.artifactSyncAtMs != widget.artifactSyncAtMs ||
        oldWidget.artifactSyncStatus != widget.artifactSyncStatus) {
      if (workspaceChanged) {
        _activeTab = AssistantArtifactSidebarTab.files;
        _snapshot = null;
        _selectedEntry = null;
        _preview = const AssistantArtifactPreview.empty();
        _loadError = null;
        _loadingSnapshot = false;
        _loadingPreview = false;
        _previewLoadGeneration += 1;
      }
      unawaited(_refreshSnapshot());
    }
    _syncRefreshTimer();
  }

  @override
  void dispose() {
    _refreshTimer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final palette = context.palette;
    final theme = Theme.of(context);
    final snapshot = _snapshot;
    final entries = _artifactEntries(snapshot);
    final entriesForPreview = _previewCandidates(snapshot);
    final selectedEntry = _selectedEntry;
    final workspacePath = widget.workspacePath.trim();
    final canCopyWorkspace = workspacePath.isNotEmpty;
    final canOpenWorkspace =
        canCopyWorkspace &&
        widget.workspaceKind == WorkspaceRefKind.localPath &&
        widget.onOpenWorkspace != null;

    return SurfaceCard(
      key: const Key('assistant-artifact-pane'),
      tone: SurfaceCardTone.chrome,
      padding: EdgeInsets.zero,
      borderRadius: AppRadius.sidebar,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Padding(
            padding: const EdgeInsets.fromLTRB(
              AppSpacing.md,
              AppSpacing.md,
              AppSpacing.sm,
              AppSpacing.sm,
            ),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        widget.threadTitle.trim().isEmpty
                            ? appText('当前线程', 'Current thread')
                            : widget.threadTitle.trim(),
                        maxLines: 1,
                        overflow: TextOverflow.ellipsis,
                        style: theme.textTheme.titleSmall?.copyWith(
                          fontWeight: FontWeight.w700,
                        ),
                      ),
                      const SizedBox(height: AppSpacing.xxs),
                      Text(
                        appText('当前任务工作路径', 'Current task workspace'),
                        style: theme.textTheme.labelSmall?.copyWith(
                          color: palette.textMuted,
                          fontWeight: FontWeight.w600,
                        ),
                      ),
                      const SizedBox(height: AppSpacing.xxs),
                      Tooltip(
                        message: workspacePath,
                        child: GestureDetector(
                          key: const Key(
                            'assistant-artifact-pane-workspace-ref-container',
                          ),
                          onDoubleTap: canOpenWorkspace
                              ? () => unawaited(_openWorkspace())
                              : null,
                          child: Container(
                            width: double.infinity,
                            padding: const EdgeInsets.symmetric(
                              horizontal: AppSpacing.xs,
                              vertical: AppSpacing.xxs,
                            ),
                            decoration: BoxDecoration(
                              color: palette.chromeSurface.withValues(
                                alpha: 0.72,
                              ),
                              borderRadius: BorderRadius.circular(
                                AppRadius.button,
                              ),
                              border: Border.all(color: palette.chromeStroke),
                            ),
                            child: Row(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Padding(
                                  padding: const EdgeInsets.only(top: 1),
                                  child: Icon(
                                    widget.workspaceKind ==
                                            WorkspaceRefKind.localPath
                                        ? Icons.folder_open_rounded
                                        : Icons.cloud_queue_rounded,
                                    size: 14,
                                    color: palette.textSecondary,
                                  ),
                                ),
                                const SizedBox(width: AppSpacing.xxs),
                                Expanded(
                                  child: Text(
                                    _workspaceSummary(
                                      widget.workspacePath,
                                      widget.workspaceKind,
                                    ),
                                    key: const Key(
                                      'assistant-artifact-pane-workspace-ref',
                                    ),
                                    maxLines: 2,
                                    overflow: TextOverflow.ellipsis,
                                    style: theme.textTheme.bodySmall?.copyWith(
                                      color: palette.textSecondary,
                                      height: 1.25,
                                    ),
                                  ),
                                ),
                                const SizedBox(width: AppSpacing.xxs),
                                IconButton(
                                  key: const Key(
                                    'assistant-artifact-pane-copy-workspace-ref',
                                  ),
                                  tooltip: appText(
                                    '复制工作路径',
                                    'Copy workspace path',
                                  ),
                                  visualDensity: VisualDensity.compact,
                                  padding: EdgeInsets.zero,
                                  constraints: const BoxConstraints(
                                    minWidth: 24,
                                    minHeight: 24,
                                  ),
                                  onPressed: canCopyWorkspace
                                      ? _copyWorkspace
                                      : null,
                                  icon: const Icon(
                                    Icons.content_copy_rounded,
                                    size: 14,
                                  ),
                                ),
                              ],
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                ),
                IconButton(
                  key: const Key('assistant-artifact-pane-refresh'),
                  tooltip: appText('刷新产物', 'Refresh artifacts'),
                  style: IconButton.styleFrom(
                    minimumSize: const Size(40, 40),
                    maximumSize: const Size(40, 40),
                  ),
                  onPressed: _loadingSnapshot ? null : _refreshSnapshot,
                  icon: _loadingSnapshot
                      ? const SizedBox(
                          width: 18,
                          height: 18,
                          child: CircularProgressIndicator(strokeWidth: 2),
                        )
                      : const Icon(Icons.refresh_rounded, size: 18),
                ),
                IconButton(
                  key: const Key('assistant-artifact-pane-collapse'),
                  tooltip: appText('收起右侧栏', 'Collapse sidebar'),
                  style: IconButton.styleFrom(
                    minimumSize: const Size(40, 40),
                    maximumSize: const Size(40, 40),
                  ),
                  onPressed: widget.onCollapse,
                  icon: const Icon(Icons.keyboard_double_arrow_right_rounded),
                ),
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
            child: _TaskContextSummaryCard(
              messageCount: widget.taskContextMessageCount,
              selectedSkillKeys: widget.taskContextSelectedSkillKeys,
              remoteWorkingDirectory: widget.taskContextRemoteWorkingDirectory,
              openClawRunId: widget.taskContextOpenClawRunId,
              openClawStatus: widget.taskContextOpenClawStatus,
              artifactSyncStatus: widget.artifactSyncStatus,
              expanded: _taskContextExpanded,
              onToggle: () {
                setState(() {
                  _taskContextExpanded = !_taskContextExpanded;
                });
              },
            ),
          ),
          const SizedBox(height: AppSpacing.sm),
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: AppSpacing.md),
            child: SectionTabs(
              size: SectionTabsSize.small,
              items: AssistantArtifactSidebarTab.values
                  .map(_labelForTab)
                  .toList(growable: false),
              value: _labelForTab(_activeTab),
              onChanged: (value) {
                final nextTab = AssistantArtifactSidebarTab.values.firstWhere(
                  (item) => _labelForTab(item) == value,
                  orElse: () => AssistantArtifactSidebarTab.files,
                );
                setState(() {
                  _activeTab = nextTab;
                });
                if (nextTab == AssistantArtifactSidebarTab.preview &&
                    (selectedEntry == null || !selectedEntry.previewable) &&
                    entriesForPreview.isNotEmpty) {
                  unawaited(_selectEntry(entriesForPreview.first));
                }
              },
            ),
          ),
          const SizedBox(height: AppSpacing.sm),
          Expanded(
            child: AnimatedSwitcher(
              duration: const Duration(milliseconds: 160),
              switchInCurve: Curves.easeOutCubic,
              switchOutCurve: Curves.easeInCubic,
              child: _buildTabBody(
                context,
                snapshot: snapshot,
                entries: entries,
                previewCandidates: entriesForPreview,
              ),
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _copyWorkspace() async {
    final workspacePath = widget.workspacePath.trim();
    if (workspacePath.isEmpty) {
      return;
    }
    await Clipboard.setData(ClipboardData(text: workspacePath));
    if (!mounted) {
      return;
    }
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(appText('工作路径已复制', 'Workspace path copied')),
        duration: const Duration(milliseconds: 1200),
      ),
    );
  }

  Future<void> _openWorkspace() async {
    if (widget.onOpenWorkspace == null) {
      return;
    }
    await widget.onOpenWorkspace!.call();
  }

  Widget _buildTabBody(
    BuildContext context, {
    required AssistantArtifactSnapshot? snapshot,
    required List<AssistantArtifactEntry> entries,
    required List<AssistantArtifactEntry> previewCandidates,
  }) {
    if (_loadError != null) {
      return _SidebarEmptyState(
        key: const Key('assistant-artifact-pane-empty'),
        icon: Icons.error_outline_rounded,
        title: appText('产物载入失败', 'Artifacts failed to load'),
        message: _loadError.toString(),
      );
    }
    if (snapshot == null && _loadingSnapshot) {
      return const Center(child: CircularProgressIndicator());
    }
    if (snapshot == null) {
      return _SidebarEmptyState(
        key: const Key('assistant-artifact-pane-empty'),
        icon: Icons.inbox_outlined,
        title: appText('暂无产物', 'No artifacts yet'),
        message: appText(
          '展开右侧栏后会按需加载当前线程的工作目录内容。',
          'Open the sidebar to load the current thread workspace on demand.',
        ),
      );
    }
    return switch (_activeTab) {
      AssistantArtifactSidebarTab.files => _ArtifactEntryList(
        key: const Key('assistant-artifact-tab-files'),
        entries: entries,
        emptyMessage: _filesEmptyMessage(snapshot),
        onSelectEntry: _selectEntry,
        onOpenEntryLocation: widget.onOpenEntryLocation,
        selectedEntry: _selectedEntry,
      ),
      AssistantArtifactSidebarTab.preview => _ArtifactPreviewPanel(
        key: const Key('assistant-artifact-tab-preview'),
        entry: _selectedEntry,
        preview: _preview,
        loading: _loadingPreview,
        fallbackEntries: previewCandidates,
        onSelectEntry: _selectEntry,
      ),
    };
  }

  List<AssistantArtifactEntry> _previewCandidates(
    AssistantArtifactSnapshot? snapshot,
  ) {
    return _artifactEntries(
      snapshot,
    ).where((item) => item.previewable).toList(growable: false);
  }

  List<AssistantArtifactEntry> _artifactEntries(
    AssistantArtifactSnapshot? snapshot,
  ) {
    if (snapshot == null) {
      return const <AssistantArtifactEntry>[];
    }
    final seen = <String>{};
    final merged = <AssistantArtifactEntry>[
      ...snapshot.resultEntries,
      ...snapshot.fileEntries,
    ];
    return merged
        .where((item) => seen.add(item.relativePath))
        .toList(growable: false);
  }

  String _filesEmptyMessage(AssistantArtifactSnapshot snapshot) {
    if (widget.artifactSyncStatus.trim().toLowerCase() == 'failed') {
      return appText(
        '本轮没有检测到实际生成的文件。请重新执行，并要求 OpenClaw 在当前 workspace 中创建文件。',
        'No files were generated for this run. Try again and ask OpenClaw to create files in the current workspace.',
      );
    }
    final filesMessage = snapshot.filesMessage.trim();
    if (filesMessage.isNotEmpty) {
      return filesMessage;
    }
    final resultsMessage = snapshot.resultMessage.trim();
    if (resultsMessage.isNotEmpty) {
      return resultsMessage;
    }
    return appText(
      '当前线程里还没有可展示的文件。',
      'No files are available for this thread yet.',
    );
  }

  Future<void> _refreshSnapshot() async {
    if (_loadingSnapshot) {
      return;
    }
    final generation = ++_snapshotLoadGeneration;
    final sessionKey = widget.sessionKey;
    setState(() {
      _loadingSnapshot = true;
      _loadError = null;
    });
    try {
      final snapshot = await widget.loadSnapshot();
      if (!mounted ||
          generation != _snapshotLoadGeneration ||
          sessionKey != widget.sessionKey) {
        return;
      }
      final nextSelected = _reconcileSelection(
        snapshot,
        previous: _selectedEntry,
      );
      setState(() {
        _snapshot = snapshot;
        _selectedEntry = nextSelected;
        _loadingSnapshot = false;
      });
      if (_activeTab == AssistantArtifactSidebarTab.preview &&
          nextSelected != null) {
        await _loadPreview(nextSelected);
      }
    } catch (error) {
      if (!mounted ||
          generation != _snapshotLoadGeneration ||
          sessionKey != widget.sessionKey) {
        return;
      }
      setState(() {
        _loadingSnapshot = false;
        _loadError = error;
      });
    }
  }

  void _syncRefreshTimer() {
    final shouldPoll = _shouldPollArtifactSnapshot(widget.artifactSyncStatus);
    if (!shouldPoll) {
      _refreshTimer?.cancel();
      _refreshTimer = null;
      return;
    }
    if (_refreshTimer?.isActive == true) {
      return;
    }
    _refreshTimer = Timer.periodic(const Duration(seconds: 3), (_) {
      if (!mounted || _loadingSnapshot) {
        return;
      }
      unawaited(_refreshSnapshot());
    });
  }

  bool _shouldPollArtifactSnapshot(String status) {
    switch (status.trim().toLowerCase()) {
      case 'partial':
      case 'syncing':
      case 'running':
      case 'queued':
        return true;
      default:
        return false;
    }
  }

  AssistantArtifactEntry? _reconcileSelection(
    AssistantArtifactSnapshot snapshot, {
    AssistantArtifactEntry? previous,
  }) {
    final candidates = _previewCandidates(snapshot);
    if (previous == null) {
      return candidates.isEmpty ? null : candidates.first;
    }
    for (final item in candidates) {
      if (item.relativePath == previous.relativePath) {
        return item;
      }
    }
    return candidates.isEmpty ? null : candidates.first;
  }

  Future<void> _selectEntry(AssistantArtifactEntry entry) async {
    if (!entry.previewable) {
      setState(() {
        _selectedEntry = entry;
      });
      return;
    }
    setState(() {
      _selectedEntry = entry;
      _activeTab = AssistantArtifactSidebarTab.preview;
    });
    await _loadPreview(entry);
  }

  Future<void> _loadPreview(AssistantArtifactEntry entry) async {
    final generation = ++_previewLoadGeneration;
    final sessionKey = widget.sessionKey;
    final relativePath = entry.relativePath;
    setState(() {
      _loadingPreview = true;
      _preview = const AssistantArtifactPreview.empty();
    });
    try {
      final preview = await widget.loadPreview(entry);
      if (!mounted ||
          generation != _previewLoadGeneration ||
          sessionKey != widget.sessionKey ||
          relativePath != _selectedEntry?.relativePath) {
        return;
      }
      setState(() {
        _preview = preview;
        _loadingPreview = false;
      });
    } catch (error) {
      if (!mounted ||
          generation != _previewLoadGeneration ||
          sessionKey != widget.sessionKey ||
          relativePath != _selectedEntry?.relativePath) {
        return;
      }
      setState(() {
        _preview = AssistantArtifactPreview.empty(message: error.toString());
        _loadingPreview = false;
      });
    }
  }

  String _labelForTab(AssistantArtifactSidebarTab tab) {
    return switch (tab) {
      AssistantArtifactSidebarTab.files => appText('全部文件', 'All files'),
      AssistantArtifactSidebarTab.preview => appText('预览', 'Preview'),
    };
  }

  static String _workspaceSummary(String workspacePath, WorkspaceRefKind kind) {
    final trimmed = workspacePath.trim();
    if (trimmed.isEmpty) {
      return appText('未设置', 'Not set');
    }
    if (kind == WorkspaceRefKind.remotePath) {
      return trimmed;
    }
    final normalized = trimmed.replaceAll('\\', '/');
    if (normalized.length <= 56) {
      return normalized;
    }
    final segments = normalized
        .split('/')
        .where((item) => item.isNotEmpty)
        .toList();
    if (segments.length <= 4) {
      return normalized;
    }
    return '.../${segments.sublist(segments.length - 4).join('/')}';
  }
}

class AssistantArtifactSidebarRevealButton extends StatelessWidget {
  const AssistantArtifactSidebarRevealButton({super.key, required this.onTap});

  static const double _buttonSize = 28;

  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    final palette = context.palette;
    return Tooltip(
      message: appText('展开右侧栏', 'Expand sidebar'),
      child: IconButton(
        key: const Key('assistant-artifact-pane-toggle'),
        onPressed: onTap,
        visualDensity: VisualDensity.compact,
        splashRadius: 18,
        padding: EdgeInsets.zero,
        constraints: const BoxConstraints.tightFor(
          width: _buttonSize,
          height: _buttonSize,
        ),
        style: IconButton.styleFrom(
          padding: EdgeInsets.zero,
          minimumSize: const Size(_buttonSize, _buttonSize),
          maximumSize: const Size(_buttonSize, _buttonSize),
          tapTargetSize: MaterialTapTargetSize.shrinkWrap,
          backgroundColor: Colors.transparent,
          foregroundColor: palette.textSecondary,
          overlayColor: palette.chromeSurfacePressed,
          side: BorderSide.none,
          shape: const CircleBorder(),
        ),
        icon: const Icon(Icons.keyboard_double_arrow_left_rounded, size: 20),
      ),
    );
  }
}

class _TaskContextSummaryCard extends StatelessWidget {
  const _TaskContextSummaryCard({
    required this.messageCount,
    required this.selectedSkillKeys,
    required this.remoteWorkingDirectory,
    required this.openClawRunId,
    required this.openClawStatus,
    required this.artifactSyncStatus,
    required this.expanded,
    required this.onToggle,
  });

  final int messageCount;
  final List<String> selectedSkillKeys;
  final String remoteWorkingDirectory;
  final String openClawRunId;
  final String openClawStatus;
  final String artifactSyncStatus;
  final bool expanded;
  final VoidCallback onToggle;

  @override
  Widget build(BuildContext context) {
    final palette = context.palette;
    final theme = Theme.of(context);
    final skills = selectedSkillKeys
        .map((item) => item.trim())
        .where((item) => item.isNotEmpty)
        .toList(growable: false);
    final status = artifactSyncStatus.trim().isEmpty
        ? appText('未同步', 'Not synced')
        : artifactSyncStatus.trim();
    return Container(
      decoration: BoxDecoration(
        color: palette.chromeSurface.withValues(alpha: 0.72),
        borderRadius: BorderRadius.circular(AppRadius.button),
        border: Border.all(color: palette.chromeStroke),
      ),
      child: Column(
        children: [
          InkWell(
            key: const Key('assistant-artifact-task-context-toggle'),
            onTap: onToggle,
            borderRadius: BorderRadius.circular(AppRadius.button),
            child: Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: AppSpacing.sm,
                vertical: AppSpacing.xs,
              ),
              child: Row(
                children: [
                  Icon(
                    Icons.forum_outlined,
                    size: 16,
                    color: palette.textSecondary,
                  ),
                  const SizedBox(width: AppSpacing.xs),
                  Expanded(
                    child: Text(
                      appText('任务上下文会话', 'Task context session'),
                      style: theme.textTheme.labelMedium?.copyWith(
                        fontWeight: FontWeight.w700,
                      ),
                    ),
                  ),
                  Text(
                    '$messageCount msg · $status',
                    style: theme.textTheme.labelSmall?.copyWith(
                      color: palette.textMuted,
                    ),
                  ),
                  const SizedBox(width: AppSpacing.xxs),
                  Icon(
                    expanded
                        ? Icons.expand_less_rounded
                        : Icons.expand_more_rounded,
                    size: 18,
                    color: palette.textSecondary,
                  ),
                ],
              ),
            ),
          ),
          AnimatedCrossFade(
            duration: const Duration(milliseconds: 140),
            crossFadeState: expanded
                ? CrossFadeState.showSecond
                : CrossFadeState.showFirst,
            firstChild: const SizedBox(width: double.infinity),
            secondChild: Padding(
              padding: const EdgeInsets.fromLTRB(
                AppSpacing.sm,
                0,
                AppSpacing.sm,
                AppSpacing.sm,
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: [
                  _TaskContextLine(
                    label: appText('消息', 'Messages'),
                    value: '$messageCount',
                  ),
                  _TaskContextLine(
                    label: appText('技能', 'Skills'),
                    value: skills.isEmpty
                        ? appText('未选择', 'None')
                        : skills.join(', '),
                  ),
                  _TaskContextLine(
                    label: appText('远端路径', 'Remote path'),
                    value: remoteWorkingDirectory.trim().isEmpty
                        ? appText('未记录', 'Not recorded')
                        : remoteWorkingDirectory.trim(),
                  ),
                  _TaskContextLine(
                    label: appText('OpenClaw run', 'OpenClaw run'),
                    value: openClawRunId.trim().isEmpty
                        ? appText('未绑定', 'Not bound')
                        : openClawRunId.trim(),
                  ),
                  _TaskContextLine(
                    label: appText('OpenClaw 状态', 'OpenClaw status'),
                    value: openClawStatus.trim().isEmpty
                        ? appText('未记录', 'Not recorded')
                        : openClawStatus.trim(),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _TaskContextLine extends StatelessWidget {
  const _TaskContextLine({required this.label, required this.value});

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    final palette = context.palette;
    final theme = Theme.of(context);
    return Padding(
      padding: const EdgeInsets.only(top: AppSpacing.xxs),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 86,
            child: Text(
              label,
              style: theme.textTheme.labelSmall?.copyWith(
                color: palette.textMuted,
                fontWeight: FontWeight.w600,
              ),
            ),
          ),
          Expanded(
            child: Text(
              value,
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
              style: theme.textTheme.labelSmall?.copyWith(
                color: palette.textSecondary,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _ArtifactEntryList extends StatelessWidget {
  const _ArtifactEntryList({
    super.key,
    required this.entries,
    required this.emptyMessage,
    required this.onSelectEntry,
    required this.onOpenEntryLocation,
    required this.selectedEntry,
  });

  final List<AssistantArtifactEntry> entries;
  final String emptyMessage;
  final ValueChanged<AssistantArtifactEntry> onSelectEntry;
  final AssistantArtifactOpenEntryLocation? onOpenEntryLocation;
  final AssistantArtifactEntry? selectedEntry;

  @override
  Widget build(BuildContext context) {
    if (entries.isEmpty) {
      return _SidebarEmptyState(
        key: const Key('assistant-artifact-pane-empty'),
        icon: Icons.folder_open_outlined,
        title: appText('暂无文件', 'No files'),
        message: emptyMessage,
      );
    }
    final palette = context.palette;
    final theme = Theme.of(context);
    return ListView.separated(
      padding: const EdgeInsets.fromLTRB(
        AppSpacing.md,
        0,
        AppSpacing.md,
        AppSpacing.md,
      ),
      itemCount: entries.length,
      separatorBuilder: (_, _) => const SizedBox(height: AppSpacing.xs),
      itemBuilder: (context, index) {
        final entry = entries[index];
        final selected =
            selectedEntry?.relativePath == entry.relativePath &&
            selectedEntry?.workspacePath == entry.workspacePath;
        return Material(
          color: Colors.transparent,
          child: InkWell(
            key: ValueKey<String>(
              'assistant-artifact-entry-${entry.relativePath}',
            ),
            onTap: entry.previewable ? () => onSelectEntry(entry) : null,
            borderRadius: BorderRadius.circular(AppRadius.button),
            child: Container(
              padding: const EdgeInsets.all(AppSpacing.sm),
              decoration: BoxDecoration(
                color: selected
                    ? palette.accentMuted.withValues(alpha: 0.88)
                    : palette.chromeSurface.withValues(alpha: 0.72),
                borderRadius: BorderRadius.circular(AppRadius.button),
                border: Border.all(
                  color: selected ? palette.accent : palette.chromeStroke,
                ),
              ),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Icon(
                    _iconForEntry(entry),
                    size: 18,
                    color: selected ? palette.accent : palette.textSecondary,
                  ),
                  const SizedBox(width: AppSpacing.sm),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          entry.label,
                          maxLines: 1,
                          overflow: TextOverflow.ellipsis,
                          style: theme.textTheme.labelLarge?.copyWith(
                            fontWeight: FontWeight.w600,
                          ),
                        ),
                        const SizedBox(height: AppSpacing.xxs),
                        Text(
                          entry.relativePath,
                          maxLines: 2,
                          overflow: TextOverflow.ellipsis,
                          style: theme.textTheme.bodySmall?.copyWith(
                            color: palette.textSecondary,
                          ),
                        ),
                        const SizedBox(height: AppSpacing.xxs),
                        Text(
                          _metaLabel(entry),
                          style: theme.textTheme.labelSmall?.copyWith(
                            color: palette.textMuted,
                          ),
                        ),
                      ],
                    ),
                  ),
                  Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Tooltip(
                        message: entry.previewable
                            ? appText('预览文件', 'Preview file')
                            : appText(
                                '此类型不可预览',
                                'Preview is unavailable for this type',
                              ),
                        child: IconButton(
                          key: ValueKey<String>(
                            'assistant-artifact-preview-${entry.relativePath}',
                          ),
                          visualDensity: VisualDensity.compact,
                          padding: EdgeInsets.zero,
                          constraints: const BoxConstraints.tightFor(
                            width: 28,
                            height: 28,
                          ),
                          onPressed: entry.previewable
                              ? () => onSelectEntry(entry)
                              : null,
                          icon: const Icon(Icons.visibility_outlined, size: 16),
                        ),
                      ),
                      Tooltip(
                        message: appText('打开文件所在位置', 'Open file location'),
                        child: IconButton(
                          key: ValueKey<String>(
                            'assistant-artifact-open-location-${entry.relativePath}',
                          ),
                          visualDensity: VisualDensity.compact,
                          padding: EdgeInsets.zero,
                          constraints: const BoxConstraints.tightFor(
                            width: 28,
                            height: 28,
                          ),
                          onPressed: onOpenEntryLocation == null
                              ? null
                              : () => onOpenEntryLocation!(entry),
                          icon: const Icon(Icons.folder_open_rounded, size: 16),
                        ),
                      ),
                    ],
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }

  static IconData _iconForEntry(AssistantArtifactEntry entry) {
    if (entry.mimeType.startsWith('image/')) {
      return Icons.image_outlined;
    }
    if (entry.mimeType == 'text/markdown') {
      return Icons.description_outlined;
    }
    if (entry.mimeType == 'text/html') {
      return Icons.language_rounded;
    }
    return Icons.insert_drive_file_outlined;
  }

  static String _metaLabel(AssistantArtifactEntry entry) {
    final parts = <String>[
      if (entry.mimeType.trim().isNotEmpty) entry.mimeType,
      if (entry.sizeBytes != null) _formatBytes(entry.sizeBytes!),
      if (entry.updatedAtMs != null)
        _formatTimestamp(entry.updatedAtMs!.toInt()),
    ];
    return parts.join(' · ');
  }

  static String _formatBytes(int bytes) {
    if (bytes < 1024) {
      return '$bytes B';
    }
    if (bytes < 1024 * 1024) {
      return '${(bytes / 1024).toStringAsFixed(1)} KB';
    }
    return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
  }

  static String _formatTimestamp(int millis) {
    final date = DateTime.fromMillisecondsSinceEpoch(millis);
    final month = date.month.toString().padLeft(2, '0');
    final day = date.day.toString().padLeft(2, '0');
    final hour = date.hour.toString().padLeft(2, '0');
    final minute = date.minute.toString().padLeft(2, '0');
    return '${date.year}-$month-$day $hour:$minute';
  }
}

class _ArtifactPreviewPanel extends StatelessWidget {
  const _ArtifactPreviewPanel({
    super.key,
    required this.entry,
    required this.preview,
    required this.loading,
    required this.fallbackEntries,
    required this.onSelectEntry,
  });

  final AssistantArtifactEntry? entry;
  final AssistantArtifactPreview preview;
  final bool loading;
  final List<AssistantArtifactEntry> fallbackEntries;
  final ValueChanged<AssistantArtifactEntry> onSelectEntry;

  @override
  Widget build(BuildContext context) {
    final resolvedEntry = entry;
    final theme = Theme.of(context);
    final palette = context.palette;
    if (loading) {
      return const Center(child: CircularProgressIndicator());
    }
    if (resolvedEntry == null) {
      return _SidebarEmptyState(
        key: const Key('assistant-artifact-pane-empty'),
        icon: Icons.preview_outlined,
        title: appText('暂无预览对象', 'No preview target'),
        message: appText(
          '从全部文件里选择一个文件后，会在这里轻量预览。',
          'Select a file from all files to preview it here.',
        ),
      );
    }
    if (preview.kind == AssistantArtifactPreviewKind.empty &&
        preview.message.trim().isNotEmpty) {
      return _SidebarEmptyState(
        key: const Key('assistant-artifact-pane-empty'),
        icon: Icons.preview_outlined,
        title: resolvedEntry.label,
        message: preview.message,
      );
    }

    final body = switch (preview.kind) {
      AssistantArtifactPreviewKind.markdown => MarkdownBody(
        key: const Key('assistant-artifact-preview-markdown'),
        data: preview.content,
        selectable: true,
        extensionSet: md.ExtensionSet.gitHubWeb,
      ),
      AssistantArtifactPreviewKind.html => Html(
        key: const Key('assistant-artifact-preview-html'),
        data: preview.content,
        style: <String, Style>{
          'body': Style(
            margin: Margins.zero,
            padding: HtmlPaddings.zero,
            fontSize: FontSize(13),
            color: palette.textPrimary,
          ),
        },
      ),
      AssistantArtifactPreviewKind.text => SelectableText(
        preview.content,
        key: const Key('assistant-artifact-preview-text'),
        style: theme.textTheme.bodyMedium?.copyWith(
          fontFamily: 'Menlo',
          height: 1.4,
        ),
      ),
      AssistantArtifactPreviewKind.unsupported => Column(
        key: const Key('assistant-artifact-preview-unsupported'),
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            preview.message.trim().isEmpty
                ? appText(
                    '当前文件类型不支持轻量预览。',
                    'Lightweight preview is unavailable for this file type.',
                  )
                : preview.message,
            style: theme.textTheme.bodyMedium?.copyWith(
              color: palette.textSecondary,
            ),
          ),
          if (fallbackEntries.isNotEmpty) ...[
            const SizedBox(height: AppSpacing.md),
            Text(
              appText('可继续查看的文件', 'Other files you can preview'),
              style: theme.textTheme.labelLarge?.copyWith(
                color: palette.textSecondary,
              ),
            ),
            const SizedBox(height: AppSpacing.xs),
            ...fallbackEntries.take(6).map((item) {
              return Padding(
                padding: const EdgeInsets.only(bottom: AppSpacing.xs),
                child: InkWell(
                  onTap: () => onSelectEntry(item),
                  child: Text(
                    item.relativePath,
                    style: theme.textTheme.bodyMedium?.copyWith(
                      color: palette.accent,
                    ),
                  ),
                ),
              );
            }),
          ],
        ],
      ),
      AssistantArtifactPreviewKind.empty => const SizedBox.shrink(),
    };

    return SingleChildScrollView(
      padding: const EdgeInsets.fromLTRB(
        AppSpacing.md,
        0,
        AppSpacing.md,
        AppSpacing.md,
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            resolvedEntry.label,
            style: theme.textTheme.titleSmall?.copyWith(
              fontWeight: FontWeight.w700,
            ),
          ),
          const SizedBox(height: AppSpacing.xxs),
          Text(
            resolvedEntry.relativePath,
            style: theme.textTheme.bodySmall?.copyWith(
              color: palette.textSecondary,
            ),
          ),
          const SizedBox(height: AppSpacing.md),
          body,
        ],
      ),
    );
  }
}

class _SidebarEmptyState extends StatelessWidget {
  const _SidebarEmptyState({
    super.key,
    required this.icon,
    required this.title,
    required this.message,
  });

  final IconData icon;
  final String title;
  final String message;

  @override
  Widget build(BuildContext context) {
    final palette = context.palette;
    final theme = Theme.of(context);
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(AppSpacing.lg),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, size: 28, color: palette.textMuted),
            const SizedBox(height: AppSpacing.sm),
            Text(
              title,
              textAlign: TextAlign.center,
              style: theme.textTheme.titleSmall?.copyWith(
                fontWeight: FontWeight.w700,
              ),
            ),
            const SizedBox(height: AppSpacing.xs),
            Text(
              message,
              textAlign: TextAlign.center,
              style: theme.textTheme.bodyMedium?.copyWith(
                color: palette.textSecondary,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
