import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../shared/widgets/loading_dialog.dart';
import '../../core/utils/date_utils.dart' as du;
import '../../shared/widgets/form_section.dart';
import '../../shared/widgets/form_field_row.dart';
import '../../shared/widgets/app_skeletons.dart';
import '../../shared/widgets/attachment_download_helper.dart';
import '../../shared/widgets/action_bar.dart';
import '../../core/navigation/host_app_channel.dart';
import 'expense_model.dart';
import '../../core/i18n/app_localizations.dart';
import '../../shared/models/bill_attachment.dart';
import '../../shared/models/bill_file_rights.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_colors_extension.dart';
import 'expense_api.dart';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:open_filex/open_filex.dart';
import '../../shared/widgets/attachment_preview_page.dart';
import 'widgets/expense_detail_view_dialog.dart';
import '../../core/utils/amount_utils.dart';

class ExpenseDetailPage extends ConsumerStatefulWidget {
  final String billNo;
  final int queryId;
  const ExpenseDetailPage({super.key, required this.billNo, this.queryId = 0});

  @override
  ConsumerState<ExpenseDetailPage> createState() => _ExpenseDetailPageState();
}

class _ExpenseDetailPageState extends ConsumerState<ExpenseDetailPage> {
  ExpenseModel? _expense;
  List<BillAttachment> _attachments = [];
  bool _attachAvailable = false;
  BillFileRights _billFileRights = BillFileRights.none;
  bool _isLoading = true;
  String? _error;
  Map<String, dynamic>? _billStatus;
  bool _statusLoading = false;
  bool _canEdit = false;

  @override
  void initState() {
    super.initState();
    _loadData();
  }

  @override
  void dispose() {
    super.dispose();
  }

  Future<void> _loadData() async {
    setState(() {
      _isLoading = true;
      _error = null;
    });
    try {
      final api = ref.read(expenseApiProvider);
      // 1. 加载报销详情（主表 + 明细）
      final expense = await api.fetchDetail(widget.billNo);
      setState(() => _expense = expense);

      // 2. 加载附件权限（非致命）
      BillFileRights billFileRights = BillFileRights.none;
      try {
        billFileRights = await api.getBillFileRights('BX');
      } catch (_) {}

      // 3. 加载附件（非致命）
      try {
        _attachAvailable = await api.checkAttachHealth();
      } catch (_) {
        _attachAvailable = false;
      }
      _billFileRights = billFileRights;
      if (_attachAvailable && billFileRights.canBrowseAttachments) {
        try {
          _attachments = await api.getAttachments('BX', widget.billNo);
        } catch (_) {
          _attachments = [];
        }
      }

      // 3. 获取单据状态（非致命）
      setState(() => _statusLoading = true);
      try {
        final status = await api.getBillStatus(widget.billNo);
        if (mounted) {
          setState(() {
            _billStatus = status;
            _canEdit = status['canEdit'] == true;
            _statusLoading = false;
          });
        }
      } catch (_) {
        if (mounted) setState(() => _statusLoading = false);
      }
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    final l10n = AppLocalizations.of(context);

    if (_isLoading) {
      return const SkeletonDetailPage();
    }

    if (_error != null) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.error_outline, size: 48, color: colors.danger),
            const SizedBox(height: 12),
            Text(
              _error!,
              style: TextStyle(
                fontSize: AppFontSizes.body,
                color: colors.danger,
              ),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 16),
            TDButton(
              text: l10n.get('retry'),
              theme: TDButtonTheme.primary,
              onTap: _loadData,
            ),
          ],
        ),
      );
    }

    final expense = _expense!;

    return Column(
      children: [
        Expanded(
          child: SingleChildScrollView(
            physics: const AlwaysScrollableScrollPhysics(),
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [
                _buildBasicInfoSection(expense, l10n, colors),
                const SizedBox(height: 16),
                _buildExpenseDetailSection(expense, l10n, colors),
                const SizedBox(height: 16),
                _buildAttachmentSection(l10n, colors),
                const SizedBox(height: 24),
                _buildPageFooter(colors),
              ],
            ),
          ),
        ),
        // TODO: 等 ERP 提供"能否修改单据"接口后，恢复为 if (_canEdit)
        if (false)
          ActionBar(
            showLeft: false,
            showCenter: false,
            rightLabel: l10n.get('editExpense'),
            onRightTap: () async {
              final result = await GoRouter.of(
                context,
              ).push('/expense/edit/${widget.billNo}');
              if (result == true && mounted) _loadData();
            },
          ),
      ],
    );
  }

  // ═══ 状态 tag（标题行右侧） ═══
  Widget? _buildStatusTag(AppLocalizations l10n) {
    final isClosed = _billStatus?['isClosed'] == true;
    final isTransferred = _billStatus?['isTransferred'] == true;
    final approvalStatus = _billStatus?['approvalStatus'] as String?;
    final approvalText = _billStatus?['approvalText'] as String? ?? '';

    IconData icon;
    String text;
    Color color;

    if (isClosed) {
      icon = Icons.lock_outline;
      text = l10n.get('statusClosed');
      color = Colors.blue;
    } else if (isTransferred) {
      icon = Icons.swap_horiz;
      text = l10n.get('statusTransferred');
      color = Colors.blue;
    } else if (approvalStatus == null || approvalStatus.isEmpty) {
      return null;
    } else {
      switch (approvalStatus) {
        case 'SHCOUNT0':
          icon = Icons.edit_note;
          color = Colors.teal;
          break;
        case 'SHCOUNT1':
          icon = Icons.hourglass_empty;
          color = Colors.blueGrey;
          break;
        case 'SHCOUNT2':
          icon = Icons.cancel_outlined;
          color = Colors.red;
          break;
        case 'SHCOUNT3':
          icon = Icons.undo;
          color = Colors.deepOrange;
          break;
        case 'SHCOUNT4':
          icon = Icons.check_circle_outline;
          color = Colors.green;
          break;
        default:
          return null;
      }
      text = approvalText;
    }

    final tag = Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.1),
        borderRadius: BorderRadius.circular(6),
        border: Border.all(color: color.withValues(alpha: 0.3), width: 0.5),
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icon, size: 18, color: color),
          const SizedBox(width: 4),
          Text(
            text,
            style: TextStyle(
              fontSize: 13,
              fontWeight: FontWeight.w600,
              color: color,
            ),
          ),
        ],
      ),
    );
    final canTap =
        approvalStatus != null &&
        approvalStatus.isNotEmpty &&
        approvalText.isNotEmpty;
    if (canTap) {
      return GestureDetector(onTap: () => _showAuditTrail('BX'), child: tag);
    }
    return tag;
  }

  Future<void> _showAuditTrail(String billId) async {
    await HostAppChannel.showAuditTrail(billId, widget.billNo);
  }

  // ═══ 基本信息 ═══
  Widget _buildBasicInfoSection(
    ExpenseModel expense,
    AppLocalizations l10n,
    AppColorsExtension colors,
  ) {
    return FormSection(
      title: l10n.get('basicInfo'),
      leadingIcon: Icons.info_outline,
      trailing: _buildStatusTag(l10n),
      children: [
        FormFieldRow(
          label: l10n.get('expenseNo'),
          value: expense.expenseNo,
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('date'),
          value: du.DateUtils.formatDate(expense.createTime),
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('expensePersonnel'),
          value: expense.applicantId.isNotEmpty
              ? '${expense.applicantId}${expense.applicantName.isNotEmpty ? '/${expense.applicantName}' : ''}'
              : '-',
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('expenseDept'),
          value: expense.deptId.isNotEmpty
              ? '${expense.deptId}${expense.deptName.isNotEmpty ? '/${expense.deptName}' : ''}'
              : '-',
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('expenseReason'),
          value: expense.purpose.isNotEmpty ? expense.purpose : '-',
          readOnly: true,
          showArrow: false,
          bold: true,
          showMoreOnOverflow: true,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('voucherNo'),
          value: expense.voucherNo.isNotEmpty ? expense.voucherNo : '-',
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('currency'),
          value: expense.currencyCode.isNotEmpty ? expense.currencyCode : '-',
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('paymentMethod'),
          value: expense.paymentMethod.isNotEmpty ? expense.paymentMethod : '-',
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('remark'),
          value: expense.remark.isNotEmpty ? expense.remark : '-',
          readOnly: true,
          showArrow: false,
        ),
      ],
    );
  }

  // ═══ 费用明细 ═══
  Widget _buildExpenseDetailSection(
    ExpenseModel expense,
    AppLocalizations l10n,
    AppColorsExtension colors,
  ) {
    final totalAmount = expense.details.fold<double>(
      0,
      (sum, d) => sum + d.totalAmount,
    );
    final totalApproved = expense.details.fold<double>(
      0,
      (sum, d) => sum + d.approvedAmount,
    );
    return FormSection(
      title: l10n.get('expenseDetails'),
      leadingIcon: Icons.receipt_long_outlined,
      children: [
        if (expense.details.isEmpty)
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 8),
            child: Text(
              l10n.get('noDetailData'),
              style: TextStyle(
                fontSize: AppFontSizes.body,
                color: colors.textPlaceholder,
              ),
            ),
          )
        else
          ...expense.details.asMap().entries.map((e) {
            final d = e.value;
            final title = d.categoryName.isNotEmpty
                ? '${d.expenseCategory}/${d.categoryName}'
                : d.expenseCategory;
            return GestureDetector(
              onTap: () => _showExpenseDetailDialog(context, d),
              child: Container(
                margin: const EdgeInsets.symmetric(vertical: 6),
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: colors.bgPage,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  children: [
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Row(
                            children: [
                              Expanded(
                                child: Text(
                                  title,
                                  style: TextStyle(
                                    fontSize: AppFontSizes.body,
                                    fontWeight: FontWeight.w500,
                                    color: colors.textPrimary,
                                  ),
                                ),
                              ),
                              Column(
                                crossAxisAlignment: CrossAxisAlignment.end,
                                children: [
                                  Text(
                                    formatAmount(d.totalAmount),
                                    style: TextStyle(
                                      fontSize: AppFontSizes.body,
                                      fontWeight: FontWeight.w600,
                                      color: colors.amountPrimary,
                                    ),
                                  ),
                                  if (d.approvedAmount > 0)
                                    Text(
                                      formatAmount(d.approvedAmount),
                                      style: TextStyle(
                                        fontSize: AppFontSizes.body,
                                        fontWeight: FontWeight.w600,
                                        color: colors.success,
                                      ),
                                    ),
                                ],
                              ),
                            ],
                          ),
                          const SizedBox(height: 2),
                          Text(
                            '${l10n.get('amountExcludingTax')}: ${formatAmount(d.amount)}',
                            style: TextStyle(
                              fontSize: AppFontSizes.caption,
                              color: colors.textSecondary,
                            ),
                          ),
                          if (d.taxAmount > 0)
                            Text(
                              '${l10n.get('taxAmount')}: ${formatAmount(d.taxAmount)}',
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.taxRate > 0)
                            Text(
                              '${l10n.get('taxRate')}: ${d.taxRate.toStringAsFixed(0)}%',
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.acctSubjectId.isNotEmpty)
                            Text(
                              '${l10n.get('acctSubject')}: ${d.acctSubjectId}${d.acctSubjectName.isNotEmpty ? '/${d.acctSubjectName}' : ''}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.aeNo.isNotEmpty)
                            Text(
                              '${l10n.get('expenseApplyNo')}: ${d.aeNo}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.aeDd.isNotEmpty)
                            Text(
                              '${l10n.get('applyDate')}: ${d.aeDd.length >= 10 ? d.aeDd.substring(0, 10) : d.aeDd}',
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.projectId.isNotEmpty)
                            Text(
                              '${l10n.get('project')}: ${d.projectId}${d.projectName.isNotEmpty ? '/${d.projectName}' : ''}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.costDeptId.isNotEmpty)
                            Text(
                              '${l10n.get('costDept')}: ${d.costDeptId}${d.costDeptName.isNotEmpty ? '/${d.costDeptName}' : ''}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.customerVendorId.isNotEmpty)
                            Text(
                              '${l10n.get('customerVendor')}: ${d.customerVendorId}${d.customerVendorName.isNotEmpty ? '/${d.customerVendorName}' : ''}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.sqMan.isNotEmpty)
                            Text(
                              '${l10n.get('applicant')}: ${d.sqMan}${d.sqManName.isNotEmpty ? '/${d.sqManName}' : ''}',
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.bankAccountName.isNotEmpty)
                            Text(
                              '${l10n.get('bankAccountName')}: ${d.bankAccountName}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.bankName.isNotEmpty)
                            Text(
                              '${l10n.get('bankName')}: ${d.bankName}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.bankAccount.isNotEmpty)
                            Text(
                              '${l10n.get('bankAccount')}: ${d.bankAccount}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          if (d.remark.isNotEmpty)
                            Text(
                              '${l10n.get('remark')}: ${d.remark}',
                              maxLines: 2,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            );
          }),
        if (expense.details.isNotEmpty) ...[
          const SizedBox(height: 8),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                l10n.get('totalExpense'),
                style: TextStyle(
                  fontSize: AppFontSizes.body,
                  fontWeight: FontWeight.w600,
                  color: colors.textPrimary,
                ),
              ),
              Text(
                formatAmount(totalAmount),
                style: TextStyle(
                  fontSize: AppFontSizes.subtitle,
                  fontWeight: FontWeight.w700,
                  color: colors.amountPrimary,
                ),
              ),
            ],
          ),
          const SizedBox(height: 4),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                l10n.get('approvedTotal'),
                style: TextStyle(
                  fontSize: AppFontSizes.body,
                  fontWeight: FontWeight.w600,
                  color: colors.textPrimary,
                ),
              ),
              Text(
                formatAmount(totalApproved),
                style: TextStyle(
                  fontSize: AppFontSizes.subtitle,
                  fontWeight: FontWeight.w700,
                  color: totalApproved > 0
                      ? colors.success
                      : colors.textPrimary,
                ),
              ),
            ],
          ),
        ],
      ],
    );
  }

  void _showExpenseDetailDialog(BuildContext context, ExpenseDetailModel d) {
    ExpenseDetailViewDialog.show(context, d);
  }

  // ═══ 附件 ═══
  Widget _buildAttachmentSection(
    AppLocalizations l10n,
    AppColorsExtension colors,
  ) {
    if (!_attachAvailable) {
      return FormSection(
        title: l10n.get('attachments'),
        leadingIcon: Icons.attach_file_outlined,
        children: [
          Text(
            l10n.get('attachServiceUnavailable'),
            style: TextStyle(
              fontSize: AppFontSizes.body,
              color: colors.textPlaceholder,
            ),
          ),
        ],
      );
    }
    if (!_billFileRights.canBrowseAttachments) {
      return FormSection(
        title: l10n.get('attachments'),
        leadingIcon: Icons.attach_file_outlined,
        children: [
          Text(
            l10n.get('noAttachmentPermission'),
            style: TextStyle(
              fontSize: AppFontSizes.body,
              color: colors.textPlaceholder,
            ),
          ),
        ],
      );
    }

    final headerAtts = _attachments.where((a) => a.isHeader).toList();
    final bodyGroups = <int, List<BillAttachment>>{};
    for (final a in _attachments.where((a) => a.isBody)) {
      bodyGroups.putIfAbsent(a.srcItm, () => []).add(a);
    }

    final children = <Widget>[];
    if (_attachments.isEmpty) {
      children.add(
        Text(
          l10n.get('noAttachment'),
          style: TextStyle(
            fontSize: AppFontSizes.body,
            color: colors.textPlaceholder,
          ),
        ),
      );
    } else {
      // 表头附件
      if (headerAtts.isNotEmpty) {
        children.add(
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Text(
              l10n.get('headerAttachments'),
              style: TextStyle(
                fontSize: AppFontSizes.caption,
                fontWeight: FontWeight.w600,
                color: colors.textSecondary,
              ),
            ),
          ),
        );
        for (final a in headerAtts) {
          children.add(_buildAttachmentRow(a, colors));
        }
      }
      // 表身附件（按明细行分组）
      for (final entry in bodyGroups.entries) {
        children.add(const SizedBox(height: 8));
        children.add(
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Text(
              '${l10n.get('detailLine')} ${entry.key}',
              style: TextStyle(
                fontSize: AppFontSizes.caption,
                fontWeight: FontWeight.w600,
                color: colors.textSecondary,
              ),
            ),
          ),
        );
        for (final a in entry.value) {
          children.add(_buildAttachmentRow(a, colors));
        }
      }
    }

    return FormSection(
      title: l10n.get('attachments'),
      leadingIcon: Icons.attach_file_outlined,
      children: children,
    );
  }

  Widget _buildAttachmentRow(BillAttachment a, AppColorsExtension colors) {
    final isImage = [
      'jpg',
      'jpeg',
      'png',
      'gif',
      'bmp',
      'webp',
    ].contains(a.ext.toLowerCase());
    return GestureDetector(
      onTap: () => _openAttachment(a),
      child: Container(
        margin: const EdgeInsets.symmetric(vertical: 4),
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        decoration: BoxDecoration(
          color: colors.bgPage,
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          children: [
            if (isImage)
              _ExpAttachmentThumbnail(
                api: ref.read(expenseApiProvider),
                attachment: a,
                size: 40,
              )
            else
              Icon(_fileTypeIcon(a.ext), size: 40, color: colors.primary),
            const SizedBox(width: 10),
            Expanded(
              child: Text(
                a.fileName,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontSize: AppFontSizes.body,
                  color: colors.textPrimary,
                ),
              ),
            ),
            const SizedBox(width: 8),
            GestureDetector(
              onTap: () => AttachmentDownloadHelper.downloadAndSave(
                context,
                a,
                ref.read(expenseApiProvider).downloadAttachment,
              ),
              child: Icon(
                Icons.download_outlined,
                size: 22,
                color: colors.primary,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _openAttachment(BillAttachment a) async {
    final l10n = AppLocalizations.of(context);
    final ext = a.ext.toLowerCase();
    final isImage = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].contains(ext);

    if (isImage) {
      // 图片 → 弹窗预览，内部自动下载并显示 loading
      final api = ref.read(expenseApiProvider);
      AttachmentPreview.show(
        context,
        loader: api.downloadAttachment(a.id),
        fileName: a.fileName,
        loadingText: l10n.get('loading'),
      );
      return;
    }

    // 非图片 → 下载后调用系统工具打开
    try {
      LoadingDialog.show(context, text: l10n.get('downloading'));
      final api = ref.read(expenseApiProvider);
      final bytes = await api.downloadAttachment(a.id);
      if (!mounted) return;
      LoadingDialog.hide(context);
      if (bytes == null) {
        TDToast.showText(l10n.get('downloadFailed'), context: context);
        return;
      }
      final dir = await getTemporaryDirectory();
      final file = File('${dir.path}/${a.fileName}');
      await file.writeAsBytes(bytes);
      await OpenFilex.open(file.path);
    } catch (_) {
      if (mounted) LoadingDialog.hide(context);
      if (mounted) TDToast.showText(l10n.get('openFailed'), context: context);
    }
  }

  IconData _fileTypeIcon(String ext) {
    switch (ext.toLowerCase()) {
      case 'pdf':
        return Icons.picture_as_pdf;
      case 'doc':
      case 'docx':
        return Icons.description;
      case 'xls':
      case 'xlsx':
        return Icons.table_chart;
      case 'jpg':
      case 'jpeg':
      case 'png':
      case 'gif':
      case 'bmp':
        return Icons.image_outlined;
      default:
        return Icons.insert_drive_file;
    }
  }

  Widget _buildPageFooter(AppColorsExtension colors) {
    final l10n = AppLocalizations.of(context);
    return Center(
      child: Padding(
        padding: const EdgeInsets.only(bottom: 16),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              Icons.rocket_launch_outlined,
              size: 16,
              color: colors.textPlaceholder,
            ),
            const SizedBox(width: 6),
            Text(
              l10n.get('pageFooter'),
              style: TextStyle(
                fontSize: AppFontSizes.caption,
                color: colors.textPlaceholder,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// 附件缩略图 — 自动调用 DownloadAttachment 加载图片
class _ExpAttachmentThumbnail extends StatefulWidget {
  final ExpenseApi api;
  final BillAttachment attachment;
  final double size;
  const _ExpAttachmentThumbnail({
    required this.api,
    required this.attachment,
    required this.size,
  });
  @override
  State<_ExpAttachmentThumbnail> createState() =>
      _ExpAttachmentThumbnailState();
}

class _ExpAttachmentThumbnailState extends State<_ExpAttachmentThumbnail> {
  Uint8List? _bytes;
  bool _loading = true;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    try {
      final bytes = await widget.api.downloadAttachment(widget.attachment.id);
      if (mounted) {
        setState(() {
          _bytes = bytes;
          _loading = false;
        });
      }
    } catch (_) {
      if (mounted) setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_loading) {
      return SizedBox(
        width: widget.size,
        height: widget.size,
        child: const Center(
          child: SizedBox(
            width: 16,
            height: 16,
            child: CircularProgressIndicator(strokeWidth: 2),
          ),
        ),
      );
    }
    if (_bytes != null) {
      return ClipRRect(
        borderRadius: BorderRadius.circular(4),
        child: Image.memory(
          _bytes!,
          width: widget.size,
          height: widget.size,
          fit: BoxFit.cover,
        ),
      );
    }
    return Icon(
      Icons.broken_image,
      size: widget.size * 0.6,
      color: Colors.grey,
    );
  }
}
