import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import '../../core/i18n/app_localizations.dart';
import '../../core/navigation/host_app_channel.dart';
import '../../core/storage/draft_storage.dart';
import 'package:dio/dio.dart';
import '../../core/network/api_exception.dart';
import '../../shared/widgets/action_bar.dart';
import '../../shared/widgets/loading_dialog.dart';
import '../../shared/widgets/searchable_picker_sheet.dart';
import '../../shared/models/bill_file_rights.dart';
import '../../shared/widgets/form_section.dart';
import '../../shared/widgets/form_field_row.dart';
import '../../shared/widgets/app_skeletons.dart';
import '../../shared/widgets/nav_bar_config.dart';
import '../../shared/widgets/attachment_picker.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_colors_extension.dart';
import '../../core/constants/enums.dart';
import 'expense_apply_api.dart';
import 'widgets/expense_apply_detail_dialog.dart';
import '../../core/utils/amount_utils.dart';

class ExpenseApplyCreatePage extends ConsumerStatefulWidget {
  final String? id;
  const ExpenseApplyCreatePage({super.key, this.id});

  @override
  ConsumerState<ExpenseApplyCreatePage> createState() =>
      _ExpenseApplyCreatePageState();
}

class _ExpenseApplyCreatePageState
    extends ConsumerState<ExpenseApplyCreatePage> {
  static const _draftKey = 'expense_apply';

  // ── 基本信息 ──
  String _urgency = Urgency.normal.value;
  final _purposeController = TextEditingController();
  final _purposeFocus = FocusNode();
  String _validUntil = '';
  final _referenceNoController = TextEditingController();
  final _remarkController = TextEditingController();
  final _remarkFocus = FocusNode();
  final _scrollCtrl = ScrollController();

  // ── 费用明细 ──
  final List<_DetailItem> _details = [];
  int _detailIdCounter = 1;

  // ── 附件 ──
  late final AttachmentPickerController _attachmentController;
  bool _attachAvailable = false;
  BillFileRights _billFileRights = BillFileRights.none;
  EmployeeItem? _currentUserAsEmployee;

  // ── 草稿 ──
  late Future<bool> _draftFuture;
  bool _draftHandled = false;

  // ── 参考数据（从 API 加载） ──
  List<DepartmentItem> _departments = [];
  bool _firstBuild = true;
  bool _refDataLoading = true;
  bool _addingDetail = false;
  dynamic _acctTree;

  // ── 申请部门 ──
  String _selectedDeptId = '';
  String _selectedDeptName = '';

  @override
  void initState() {
    super.initState();
    SystemChrome.setSystemUIOverlayStyle(
      const SystemUiOverlayStyle(
        statusBarColor: Colors.transparent,
        statusBarIconBrightness: Brightness.dark,
      ),
    );
    _attachmentController = AttachmentPickerController(maxCount: 9)
      ..addListener(() => setState(() {}));
    _checkAttachHealth();
    _checkBillFileRights();
    _purposeFocus.addListener(() => _ensureVisible(_purposeFocus));
    _remarkFocus.addListener(() => _ensureVisible(_remarkFocus));
    _departments = [];
    _acctTree = null;
    _refDataLoading = true;
    _refDataFuture = null;
    _draftFuture = DraftStorage.has(_draftKey);
    _loadRefData();
    WidgetsBinding.instance.addPostFrameCallback((_) => _checkDataReady());
  }

  void _checkDataReady() {
    if (!_refDataLoading && mounted) {
      setState(() => _firstBuild = false);
      // 延迟一帧确保 ModalRoute.isCurrent 为 true，nav bar 配置正确生效
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) setState(() {});
      });
    } else if (mounted) {
      WidgetsBinding.instance.addPostFrameCallback((_) => _checkDataReady());
    }
  }

  Future<void>? _refDataFuture;

  Future<void> _loadRefData({bool showLoading = false}) async {
    if (_refDataFuture != null) return _refDataFuture!;
    final completer = Completer<void>();
    _refDataFuture = completer.future;
    if (showLoading) {
      LoadingDialog.show(
        context,
        text: AppLocalizations.of(context).get('dataLoading'),
      );
    }
    try {
      final api = ref.read(expenseApplyApiProvider);
      final results = await Future.wait([
        api.getDepartments(),
        api.getAcctSubjects(),
        api.getEmployees(salNo: HostAppChannel.usr, size: 1),
      ]);
      if (!mounted) return;
      final employeeCheck = results[2] as List<EmployeeItem>;
      setState(() {
        _departments = results[0] as List<DepartmentItem>;
        _acctTree = _convertAcctTree(results[1]);
        _currentUserAsEmployee = employeeCheck.isNotEmpty
            ? employeeCheck.first
            : null;
        _refDataLoading = false;
        _autoSelectDept();
      });
      completer.complete();
    } catch (_) {
      if (!mounted) {
        completer.complete();
        return;
      }
      setState(() => _refDataLoading = false);
      completer.complete();
    } finally {
      if (showLoading && mounted) LoadingDialog.hide(context);
      _refDataFuture = null;
    }
  }

  void _autoSelectDept() {
    if (_selectedDeptId.isNotEmpty) return; // 已选中则不覆盖
    final dep = HostAppChannel.dep;
    if (dep.isEmpty) return;
    final match = _departments.where((d) => d.dep == dep);
    if (match.isNotEmpty) {
      _selectedDeptId = match.first.dep;
      _selectedDeptName = match.first.name;
    }
  }

  void _ensureVisible(FocusNode node) {
    if (!node.hasFocus) return;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (node.hasFocus && _scrollCtrl.hasClients) {
        final ctx = node.context;
        if (ctx != null) {
          Scrollable.ensureVisible(
            ctx,
            alignment: 0.3,
            duration: const Duration(milliseconds: 300),
          );
        }
      }
    });
  }

  @override
  void dispose() {
    _purposeController.dispose();
    _purposeFocus.dispose();
    _referenceNoController.dispose();
    _remarkController.dispose();
    _remarkFocus.dispose();
    _attachmentController.dispose();
    _scrollCtrl.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final l10n = AppLocalizations.of(context);

    if (_firstBuild) {
      return const SkeletonFormPage();
    }

    Future.microtask(
      () => ref.read(pageBackProvider.notifier).state = () => _doPop(),
    );

    return FutureBuilder<bool>(
      future: _draftFuture,
      builder: (ctx, snapshot) {
        final hasDraft = snapshot.hasData && snapshot.data == true;

        if (hasDraft && !_draftHandled) {
          _draftHandled = true;
          WidgetsBinding.instance.addPostFrameCallback((_) {
            if (mounted) _showDraftDialog();
          });
        }

        Future.microtask(
          () => ref.read(pageBackProvider.notifier).state = () => _doPop(),
        );
        return PopScope(
          canPop: false,
          onPopInvokedWithResult: (didPop, _) {
            if (didPop) return;
            _doPop();
          },
          child: Column(
            children: [
              Expanded(
                child: GestureDetector(
                  onTap: () => FocusScope.of(context).unfocus(),
                  child: SingleChildScrollView(
                    controller: _scrollCtrl,
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      children: [
                        _buildBasicInfo(l10n),
                        const SizedBox(height: 16),
                        _buildDetailsSection(l10n),
                        const SizedBox(height: 16),
                        _buildAttachmentSection(l10n),
                        const SizedBox(height: 24),
                        _buildPageFooter(),
                      ],
                    ),
                  ),
                ),
              ),
              _buildBottomBar(l10n),
            ],
          ),
        );
      },
    );
  }

  // ═══ 草稿持久化 ═══

  Future<void> _restoreDraft() async {
    final data = await DraftStorage.load(_draftKey);
    if (data == null) return;
    final attData = data['attachments'] as List<dynamic>?;
    if (attData != null) {
      await _attachmentController.restoreFromPaths(attData.cast<String>());
    }
    setState(() {
      _urgency = data['urgency'] as String? ?? Urgency.normal.value;
      _purposeController.text = data['purpose'] as String? ?? '';
      _validUntil = data['validUntil'] as String? ?? '';
      _referenceNoController.text = data['referenceNo'] as String? ?? '';
      _remarkController.text = data['remark'] as String? ?? '';
      _selectedDeptId = data['deptId'] as String? ?? '';
      _selectedDeptName = data['deptName'] as String? ?? '';
      _details.clear();
      final detailList = data['details'] as List<dynamic>?;
      if (detailList != null) {
        for (final d in detailList) {
          final m = d as Map<String, dynamic>;
          _details.add(
            _DetailItem(
              id: m['id'] as int? ?? _detailIdCounter++,
              category: m['category'] as String? ?? '',
              categoryName: m['categoryName'] as String? ?? '',
              acctSubjectId: m['acctSubjectId'] as String? ?? '',
              acctSubjectName: m['acctSubjectName'] as String? ?? '',
              purpose: m['purpose'] as String? ?? '',
              projectId: m['projectId'] as String? ?? '',
              projectName: m['projectName'] as String? ?? '',
              costDeptId: m['costDeptId'] as String? ?? '',
              costDeptName: m['costDeptName'] as String? ?? '',
              startDate: m['startDate'] as String? ?? '',
              endDate: m['endDate'] as String? ?? '',
              estimatedAmount: (m['estimatedAmount'] as num?)?.toDouble() ?? 0,
              remark: m['remark'] as String? ?? '',
              sqMan: m['sqMan'] as String? ?? '',
              sqManName: m['sqManName'] as String? ?? '',
            ),
          );
        }
      }
      _detailIdCounter = _details.isEmpty
          ? 1
          : _details.map((d) => d.id).reduce((a, b) => a > b ? a : b) + 1;
    });
  }

  Future<void> _saveDraftToStorage() async {
    final detailList = _details
        .map(
          (d) => {
            'id': d.id,
            'category': d.category,
            'categoryName': d.categoryName,
            'acctSubjectId': d.acctSubjectId,
            'acctSubjectName': d.acctSubjectName,
            'purpose': d.purpose,
            'projectId': d.projectId,
            'projectName': d.projectName,
            'costDeptId': d.costDeptId,
            'costDeptName': d.costDeptName,
            'startDate': d.startDate,
            'endDate': d.endDate,
            'estimatedAmount': d.estimatedAmount,
            'remark': d.remark,
            'sqMan': d.sqMan,
            'sqManName': d.sqManName,
          },
        )
        .toList();
    await DraftStorage.save(_draftKey, {
      'urgency': _urgency,
      'purpose': _purposeController.text,
      'deptId': _selectedDeptId,
      'deptName': _selectedDeptName,
      'validUntil': _validUntil,
      'referenceNo': _referenceNoController.text,
      'remark': _remarkController.text,
      'attachments': _attachmentController.toPathList(),
      'details': detailList,
    });
  }

  // ═══ 草稿弹窗 ═══
  // 使用 showDialog 而非内联渲染，确保 TDAlertDialog 获取正确的主题上下文
  void _showDraftDialog() {
    final l10n = AppLocalizations.of(context);
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    FocusManager.instance.primaryFocus?.unfocus();
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (ctx) => TDAlertDialog(
        title: l10n.get('draftFound'),
        content: l10n.get('draftRestorePrompt'),
        leftBtn: TDDialogButtonOptions(
          title: l10n.get('discard'),
          titleColor: colors.textSecondary,
          action: () {
            Navigator.pop(ctx);
            DraftStorage.delete(_draftKey);
          },
        ),
        rightBtn: TDDialogButtonOptions(
          title: l10n.get('restore'),
          titleColor: colors.primary,
          action: () {
            Navigator.pop(ctx);
            _restoreDraft();
          },
        ),
      ),
    );
  }

  // ═══ 1. 基本信息 ═══
  Widget _buildBasicInfo(AppLocalizations l10n) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    return FormSection(
      title: l10n.get('basicInfo'),
      leadingIcon: Icons.info_outline,
      children: [
        FormFieldRow(
          label: l10n.get('date'),
          value: _today(),
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('applyDept'),
          value: _selectedDeptId.isNotEmpty
              ? '$_selectedDeptId/$_selectedDeptName'
              : '',
          hint: l10n.get('pleaseSelect'),
          onTap: _refDataLoading ? null : () => _showDeptPicker(),
        ),
        const SizedBox(height: 16),
        _buildUrgencyRow(l10n),
        const SizedBox(height: 16),
        _label(l10n.get('applyReason'), required: true),
        const SizedBox(height: 8),
        TDTextarea(
          controller: _purposeController,
          focusNode: _purposeFocus,
          hintText: l10n.get('enterApplyReason'),
          maxLines: 4,
          minLines: 1,
          maxLength: 500,
          indicator: true,
          padding: EdgeInsets.zero,
          bordered: true,
          backgroundColor: colors.bgPage,
        ),
        // TODO: 暂不支持录入，后续开放
        // const SizedBox(height: 16),
        // FormFieldRow(
        //   label: l10n.get('validUntil'),
        //   value: _validUntil,
        //   hint: l10n.get('pleaseSelect'),
        //   onTap: () => _pickDate((d) => setState(() => _validUntil = d)),
        // ),
        // const SizedBox(height: 16),
        // FormFieldRow(
        //   label: l10n.get('relatedContractNo'),
        //   value: _referenceNoController.text,
        //   hint: l10n.get('optional'),
        //   onTap: () => _showTextInput(
        //     l10n.get('relatedContractNo'),
        //     (v) => setState(() {
        //       _referenceNoController.text = v;
        //       _referenceNoController.selection = TextSelection.fromPosition(
        //         TextPosition(offset: v.length),
        //       );
        //     }),
        //     initialText: _referenceNoController.text,
        //   ),
        // ),
        const SizedBox(height: 16),
        _label(l10n.get('remark')),
        const SizedBox(height: 8),
        TDTextarea(
          controller: _remarkController,
          focusNode: _remarkFocus,
          hintText: l10n.get('enterRemark'),
          maxLines: 3,
          minLines: 1,
          maxLength: 500,
          indicator: true,
          padding: EdgeInsets.zero,
          bordered: true,
          backgroundColor: colors.bgPage,
        ),
      ],
    );
  }

  Widget _buildUrgencyRow(AppLocalizations l10n) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    return Row(
      children: [
        Text.rich(
          TextSpan(
            children: [
              TextSpan(
                text: l10n.get('emergencyLevel'),
                style: TextStyle(
                  fontSize: AppFontSizes.subtitle,
                  color: colors.textSecondary,
                ),
              ),
              TextSpan(
                text: ' *',
                style: TextStyle(
                  fontSize: AppFontSizes.subtitle,
                  color: colors.danger,
                ),
              ),
            ],
          ),
        ),
        const SizedBox(width: 8),
        Expanded(
          child: Wrap(
            alignment: WrapAlignment.end,
            crossAxisAlignment: WrapCrossAlignment.center,
            spacing: 18,
            runSpacing: 8,
            children: Urgency.values.asMap().entries.map((e) {
              final sel = _urgency == e.value.value;
              final isCritical = e.value.value == Urgency.critical.value;
              final isUrgent = e.value.value == Urgency.urgent.value;
              final activeColor = isCritical
                  ? colors.danger
                  : isUrgent
                  ? colors.warning
                  : colors.primary;
              return GestureDetector(
                behavior: HitTestBehavior.opaque,
                onTap: () => setState(() => _urgency = e.value.value),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Container(
                      width: 18,
                      height: 18,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        border: Border.all(
                          color: sel ? activeColor : colors.textPlaceholder,
                          width: 2,
                        ),
                      ),
                      child: sel
                          ? Center(
                              child: Container(
                                width: 8,
                                height: 8,
                                decoration: BoxDecoration(
                                  shape: BoxShape.circle,
                                  color: activeColor,
                                ),
                              ),
                            )
                          : null,
                    ),
                    const SizedBox(width: 5),
                    Text(
                      l10n.get(e.value.labelKey),
                      style: TextStyle(
                        fontSize: AppFontSizes.subtitle,
                        color: sel ? activeColor : colors.textPrimary,
                      ),
                    ),
                  ],
                ),
              );
            }).toList(),
          ),
        ),
      ],
    );
  }

  // ═══ 2. 费用明细 ═══
  Widget _buildDetailsSection(AppLocalizations l10n) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    return FormSection(
      title: l10n.get('expenseDetails'),
      leadingIcon: Icons.receipt_long_outlined,
      showAction: true,
      actionText: l10n.get('add'),
      onActionTap: _showDetailDialog,
      children: [
        if (_details.isEmpty)
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 8),
            child: Text(
              l10n.get('noDetailHint'),
              style: TextStyle(
                fontSize: AppFontSizes.subtitle,
                color: colors.textPlaceholder,
              ),
            ),
          )
        else
          ..._details.asMap().entries.map((e) {
            final d = e.value;
            return GestureDetector(
              onTap: () => _showDetailDialog(editIndex: e.key),
              child: Container(
                margin: const EdgeInsets.symmetric(vertical: 8),
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: colors.bgPage,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.center,
                  children: [
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              Expanded(
                                child: Text(
                                  '${d.category}/${d.categoryName}',
                                  maxLines: 1,
                                  overflow: TextOverflow.ellipsis,
                                  style: TextStyle(
                                    fontSize: AppFontSizes.subtitle,
                                    color: colors.textPrimary,
                                  ),
                                ),
                              ),
                              const SizedBox(width: 12),
                              Text(
                                formatAmount(d.estimatedAmount),
                                style: TextStyle(
                                  fontSize: AppFontSizes.caption,
                                  fontWeight: FontWeight.w600,
                                  color: colors.amountPrimary,
                                ),
                              ),
                            ],
                          ),
                          if (d.acctSubjectId.isNotEmpty &&
                              d.acctSubjectName.isNotEmpty) ...[
                            const SizedBox(height: 4),
                            Text(
                              '${l10n.get('acctSubject')}: ${d.acctSubjectId}/${d.acctSubjectName}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          ],
                          if (d.sqMan.isNotEmpty) ...[
                            const SizedBox(height: 4),
                            Text(
                              '${l10n.get('applicant')}: ${d.sqMan}${d.sqManName.isNotEmpty ? '/${d.sqManName}' : ''}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          ],
                          if (d.projectId.isNotEmpty &&
                              d.projectName.isNotEmpty) ...[
                            const SizedBox(height: 4),
                            Text(
                              '${l10n.get('project')}: ${d.projectId}/${d.projectName}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          ],
                          if (d.costDeptId.isNotEmpty &&
                              d.costDeptName.isNotEmpty) ...[
                            const SizedBox(height: 4),
                            Text(
                              '${l10n.get('costDept')}: ${d.costDeptId}/${d.costDeptName}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          ],
                          if (d.startDate.isNotEmpty &&
                              d.endDate.isNotEmpty) ...[
                            const SizedBox(height: 4),
                            Text(
                              '${l10n.get('estimatedDate')}: ${d.startDate} ~ ${d.endDate}',
                              maxLines: 1,
                              overflow: TextOverflow.ellipsis,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          ],
                          if (d.remark.isNotEmpty) ...[
                            const SizedBox(height: 4),
                            Text(
                              d.remark,
                              style: TextStyle(
                                fontSize: AppFontSizes.caption,
                                color: colors.textSecondary,
                              ),
                            ),
                          ],
                        ],
                      ),
                    ),
                    const SizedBox(width: 8),
                    GestureDetector(
                      onTap: () => setState(() => _details.removeAt(e.key)),
                      child: Icon(
                        Icons.close,
                        size: 18,
                        color: colors.textSecondary,
                      ),
                    ),
                  ],
                ),
              ),
            );
          }),
        const SizedBox(height: 8),
        Container(
          padding: const EdgeInsets.symmetric(vertical: 8),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                l10n.get('total'),
                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,
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  double _totalAmount() => _details.fold(0, (s, d) => s + d.estimatedAmount);

  Future<void> _showDetailDialog({int? editIndex}) async {
    if (_addingDetail) return;
    _addingDetail = true;
    try {
      final l10n = AppLocalizations.of(context);
      ExpenseDetailData? initialData;
      if (editIndex != null) {
        final d = _details[editIndex];
        initialData = ExpenseDetailData(
          category: d.category,
          categoryName: d.categoryName,
          acctSubjectId: d.acctSubjectId,
          acctSubjectName: d.acctSubjectName,
          purpose: d.purpose,
          projectId: d.projectId,
          projectName: d.projectName,
          costDeptId: d.costDeptId,
          costDeptName: d.costDeptName,
          startDate: d.startDate,
          endDate: d.endDate,
          estimatedAmount: d.estimatedAmount,
          remark: d.remark,
          sqMan: d.sqMan,
          sqManName: d.sqManName,
        );
      }
      FocusManager.instance.primaryFocus?.unfocus();
      final result = await ExpenseApplyDetailDialog.show(
        // ignore: use_build_context_synchronously
        context,
        api: ref.read(expenseApplyApiProvider),
        l10n: l10n,
        acctTree: _acctTree,
        initialData: initialData,
        defaultSqMan: _currentUserAsEmployee?.salNo,
        defaultSqManName: _currentUserAsEmployee?.name,
      );
      if (result != null && mounted) {
        setState(() {
          final item = _DetailItem(
            id: editIndex != null ? _details[editIndex].id : _detailIdCounter++,
            category: result.category,
            categoryName: result.categoryName,
            acctSubjectId: result.acctSubjectId,
            acctSubjectName: result.acctSubjectName,
            purpose: result.purpose,
            projectId: result.projectId,
            projectName: result.projectName,
            costDeptId: result.costDeptId,
            costDeptName: result.costDeptName,
            startDate: result.startDate,
            endDate: result.endDate,
            estimatedAmount: result.estimatedAmount,
            remark: result.remark,
            sqMan: result.sqMan,
            sqManName: result.sqManName,
          );
          if (editIndex != null) {
            _details[editIndex] = item;
          } else {
            _details.add(item);
          }
        });
      }
    } finally {
      _addingDetail = false;
    }
  }

  // ═══ 3. 附件上传 ═══

  // 深度转换 JSON 解析的 List<dynamic> 为 List<Map>，确保 TDCascader 类型匹配
  List<Map<String, dynamic>> _convertAcctTree(dynamic tree) {
    if (tree is! List) return [];
    return tree.map<Map<String, dynamic>>((e) {
      final map = Map<String, dynamic>.from(e as Map);
      if (map['children'] != null) {
        map['children'] = _convertAcctTree(map['children']);
      }
      return map;
    }).toList();
  }

  Future<void> _checkAttachHealth() async {
    // 立即设为 false，等待 API 返回后再更新，避免缓存旧值
    if (mounted) setState(() => _attachAvailable = false);
    try {
      final api = ref.read(expenseApplyApiProvider);
      final ok = await api.checkAttachHealth();
      if (mounted) setState(() => _attachAvailable = ok);
    } catch (_) {
      if (mounted) setState(() => _attachAvailable = false);
    }
  }

  Future<void> _checkBillFileRights() async {
    try {
      final api = ref.read(expenseApplyApiProvider);
      final rights = await api.getBillFileRights('AE');
      if (mounted) setState(() => _billFileRights = rights);
    } catch (_) {
      if (mounted) setState(() => _billFileRights = BillFileRights.none);
    }
  }

  Widget _buildAttachmentSection(AppLocalizations l10n) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    final children = <Widget>[];

    if (!_attachAvailable) {
      children.add(
        Text(
          l10n.get('attachServiceUnavailable'),
          style: TextStyle(
            fontSize: AppFontSizes.caption,
            color: colors.textPlaceholder,
          ),
        ),
      );
    } else if (!_billFileRights.canManageAttachments) {
      children.add(
        Text(
          l10n.get('noAttachmentPermission'),
          style: TextStyle(
            fontSize: AppFontSizes.caption,
            color: colors.textPlaceholder,
          ),
        ),
      );
    } else {
      children.addAll([
        Text(
          l10n.get('maxAttachment'),
          style: TextStyle(
            fontSize: AppFontSizes.caption,
            color: colors.textPlaceholder,
          ),
        ),
        const SizedBox(height: 8),
      ]);
    }
    return FormSection(
      title: l10n.get('attachmentUpload'),
      leadingIcon: Icons.attach_file_outlined,
      children: [
        ...children,
        if (_attachAvailable && _billFileRights.canManageAttachments)
          AttachmentPicker(
            controller: _attachmentController,
            maxImageSizeMB: 10,
            maxFileSizeMB: 20,
            allowedExtensions: const [
              'pdf',
              'doc',
              'docx',
              'xls',
              'xlsx',
              'ppt',
              'pptx',
              'txt',
            ],
            onFileRejected: (file, reason) {
              if (context.mounted) {
                TDToast.showText(reason, context: context);
              }
            },
          ),
      ],
    );
  }

  Future<void> _showDeptPicker() async {
    FocusManager.instance.primaryFocus?.unfocus();
    final l10n = AppLocalizations.of(context);
    final api = ref.read(expenseApplyApiProvider);
    final result = await showSearchablePicker<DepartmentItem>(
      context,
      title: '${l10n.get('select')}${l10n.get('applyDept')}',
      searchHint: l10n.get('search'),
      loader: (keyword, page) =>
          api.getDepartments(keyword: keyword, page: page, size: 20),
      labelBuilder: (d) => d.name.isEmpty ? d.dep : '${d.dep} ${d.name}',
      onRefresh: () => api.clearRefCache(),
    );
    if (result != null && mounted) {
      setState(() {
        _selectedDeptId = result.dep;
        _selectedDeptName = result.name;
      });
    }
  }

  // ═══ 4. 底部操作栏 ═══
  Widget _buildBottomBar(AppLocalizations l10n) {
    return ActionBar(
      showLeft: false,
      centerLabel: l10n.get('saveDraft'),
      rightLabel: l10n.get('submit'),
      centerTextOnly: true,
      onCenterTap: () async {
        FocusScope.of(context).unfocus();
        try {
          await _saveDraftToStorage();
          if (mounted) _forcePop();
        } catch (_) {
          if (mounted) {
            TDToast.showFail(l10n.get('saveFailed'), context: context);
          }
        }
      },
      onRightTap: () async {
        final err = _validate(l10n);
        if (err.isNotEmpty) {
          TDToast.showText(err.first, context: context);
          return;
        }
        FocusScope.of(context).unfocus();
        LoadingDialog.show(context, text: l10n.get('submitting'));
        try {
          final data = _buildSubmitData();
          final api = ref.read(expenseApplyApiProvider);
          final billNo = await api.submit(data);
          // 上传表头附件（billNo 提取失败则跳过，不影响主流程）
          if (billNo != null && _attachmentController.files.isNotEmpty) {
            final now = DateTime.now();
            final effDd =
                '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')} '
                '${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}.'
                '${now.millisecond.toString().padLeft(3, '0')}';
            final usr = HostAppChannel.usr;
            for (var i = 0; i < _attachmentController.files.length; i++) {
              final file = _attachmentController.files[i];
              try {
                await api.uploadAttachment(file.path, {
                  'BIL_ID': 'AE',
                  'BIL_NO': billNo,
                  'SRCITM': 0,
                  'ITM': i + 1,
                  'TAG': 1,
                  'EFF_DD': effDd,
                  'USR': usr,
                  'FILENAME': file.name,
                  'EXT': file.name.split('.').last,
                });
              } catch (_) {
                // Attachment upload failure is non-fatal
              }
            }
          }
          await DraftStorage.delete(_draftKey);
          if (mounted) {
            LoadingDialog.hide(context);
            TDToast.showSuccess(l10n.get('submitSuccess'), context: context);
            GoRouter.of(context).go('/expense-apply/list');
          }
        } catch (e) {
          if (mounted) {
            LoadingDialog.hide(context);
            WidgetsBinding.instance.addPostFrameCallback((_) {
              if (mounted) _showSubmitError(e, l10n);
            });
          }
        }
      },
    );
  }

  void _showSubmitError(Object e, AppLocalizations l10n) {
    final message = _extractErrorMessage(e) ?? l10n.get('submitFailedRetry');
    showGeneralDialog(
      context: context,
      pageBuilder: (ctx, animation, secondaryAnimation) => TDConfirmDialog(
        title: l10n.get('submitFailed'),
        content: message,
        buttonStyle: TDDialogButtonStyle.text,
      ),
    );
  }

  String? _extractErrorMessage(Object e) {
    if (e is DioException) {
      if (e.error is ApiException) return (e.error as ApiException).message;
      if (e.error is NetworkException) {
        return (e.error as NetworkException).message;
      }
    }
    return null;
  }

  Map<String, dynamic> _buildSubmitData() {
    // 紧急程度映射：normal→1, urgent→2, critical→3
    String priority;
    switch (_urgency) {
      case 'urgent':
        priority = '2';
        break;
      case 'critical':
        priority = '3';
        break;
      default:
        priority = '1';
    }

    return {
      'HeadData': {
        'AE_DD': _today(),
        'PRIORITY': priority,
        'AMTN_YJ': _totalAmount(),
        'REASON': _purposeController.text.trim(),
        'REM': _remarkController.text,
        'DEP': _selectedDeptId,
        'USR': HostAppChannel.usr,
      },
      'BodyData1': _details.asMap().entries.map((e) {
        final d = e.value;
        return {
          'SQ_MAN': d.sqMan,
          'TYPE_NO': d.category,
          'AMTN_YJ': d.estimatedAmount,
          'ACC_NO': d.acctSubjectId,
          //'ACC_NAME': d.acctSubjectName,
          'DEP': d.costDeptId,
          'OBJ_NO': d.projectId.isNotEmpty ? d.projectId : '',
          'START_DD': d.startDate,
          'END_DD': d.endDate,
          'REM': d.remark.isNotEmpty ? d.remark : d.purpose,
        };
      }).toList(),
    };
  }

  List<String> _validate(AppLocalizations l10n) {
    final e = <String>[];
    if (_purposeController.text.trim().isEmpty) {
      e.add(l10n.get('enterApplyReason'));
    }
    if (_details.isEmpty) e.add(l10n.get('addAtLeastOneDetail'));
    return e;
  }

  void _doPop() {
    if (_hasUnsaved()) {
      final l10n = AppLocalizations.of(context);
      _showConfirmDialog(
        l10n.get('confirmExit'),
        l10n.get('unsavedContentWarning'),
        l10n.get('continueEditing'),
        l10n.get('discardAndExit'),
        () async {
          await DraftStorage.delete(_draftKey);
          if (!mounted) return;
          setState(() => _clearLocalState());
          _forcePop();
        },
      );
    } else {
      _forcePop();
    }
  }

  void _forcePop() {
    FocusManager.instance.primaryFocus?.unfocus();
    final router = GoRouter.of(context);
    if (router.canPop()) {
      router.pop();
    } else {
      SystemNavigator.pop();
    }
  }

  bool _hasUnsaved() =>
      _purposeController.text.isNotEmpty ||
      _details.isNotEmpty ||
      _attachmentController.files.isNotEmpty ||
      _referenceNoController.text.isNotEmpty ||
      _remarkController.text.isNotEmpty ||
      _urgency != Urgency.normal.value ||
      _validUntil.isNotEmpty;

  void _clearLocalState() {
    _urgency = Urgency.normal.value;
    _purposeController.clear();
    _validUntil = '';
    _referenceNoController.clear();
    _remarkController.clear();
    _details.clear();
    _detailIdCounter = 1;
    _attachmentController.clear();
    _selectedDeptId = '';
    _selectedDeptName = '';
  }

  void _unfocus() => FocusScope.of(context).unfocus();

  // ═══ 通用弹窗方法 ═══
  void _showConfirmDialog(
    String title,
    String content,
    String leftText,
    String rightText,
    VoidCallback onConfirm,
  ) {
    _unfocus();
    FocusManager.instance.primaryFocus?.unfocus();
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    showDialog(
      context: context,
      useRootNavigator: true,
      builder: (ctx) => TDAlertDialog(
        title: title,
        content: content,
        buttonStyle: TDDialogButtonStyle.text,
        leftBtn: TDDialogButtonOptions(
          title: leftText,
          titleColor: colors.primary,
          action: () => Navigator.pop(ctx),
        ),
        rightBtn: TDDialogButtonOptions(
          title: rightText,
          titleColor: colors.danger,
          action: () {
            Navigator.pop(ctx);
            onConfirm();
          },
        ),
      ),
    );
  }

  // TODO: 有效期至 / 关联合同号 暂不支持，方法暂时注释
  // void _showTextInput(
  //   String title,
  //   Function(String) onConfirm, {
  //   String initialText = '',
  // }) {
  //   ...
  // }

  // void _pickDate(Function(String) onPick) {
  //   ...
  // }

  Widget _label(String t, {bool required = false}) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    return Text.rich(
      TextSpan(
        children: [
          TextSpan(
            text: t,
            style: TextStyle(
              fontSize: AppFontSizes.subtitle,
              color: colors.textSecondary,
            ),
          ),
          if (required)
            TextSpan(
              text: ' *',
              style: TextStyle(
                fontSize: AppFontSizes.subtitle,
                color: colors.danger,
              ),
            ),
        ],
      ),
    );
  }

  Widget _buildPageFooter() {
    final l10n = AppLocalizations.of(context);
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    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,
              ),
            ),
          ],
        ),
      ),
    );
  }

  String _today() {
    final n = DateTime.now();
    return '${n.year}-${n.month.toString().padLeft(2, '0')}-${n.day.toString().padLeft(2, '0')}';
  }
}

class _DetailItem {
  final int id;
  final String category;
  final String categoryName;
  final String acctSubjectId;
  final String acctSubjectName;
  final String purpose;
  final String projectId;
  final String projectName;
  final String costDeptId;
  final String costDeptName;
  final String startDate;
  final String endDate;
  final double estimatedAmount;
  final String remark;
  final String sqMan;
  final String sqManName;
  const _DetailItem({
    required this.id,
    required this.category,
    required this.categoryName,
    required this.acctSubjectId,
    required this.acctSubjectName,
    required this.purpose,
    required this.projectId,
    required this.projectName,
    required this.costDeptId,
    required this.costDeptName,
    required this.startDate,
    required this.endDate,
    required this.estimatedAmount,
    required this.remark,
    this.sqMan = '',
    this.sqManName = '',
  });
}
