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 '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/form_section.dart';
import '../../shared/widgets/form_field_row.dart';
import '../../shared/widgets/app_skeletons.dart';
import '../../shared/widgets/nav_bar_config.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 '../../shared/widgets/searchable_picker_sheet.dart';
import '../../core/utils/amount_utils.dart';

class ExpenseApplyEditPage extends ConsumerStatefulWidget {
  final String billNo;
  const ExpenseApplyEditPage({super.key, required this.billNo});

  @override
  ConsumerState<ExpenseApplyEditPage> createState() =>
      _ExpenseApplyEditPageState();
}

class _ExpenseApplyEditPageState extends ConsumerState<ExpenseApplyEditPage> {
  // ── 原单数据 ──
  String _billNo = '';
  String _applyDate = '';

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

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

  // ── 参考数据（从 API 加载） ──
  bool _firstBuild = true;
  bool _refDataLoading = true;
  bool _loadingBill = true;
  String? _loadingError;
  bool _addingDetail = false;
  dynamic _acctTree;

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

  @override
  void initState() {
    super.initState();
    SystemChrome.setSystemUIOverlayStyle(
      const SystemUiOverlayStyle(
        statusBarColor: Colors.transparent,
        statusBarIconBrightness: Brightness.dark,
      ),
    );
    _purposeFocus.addListener(() => _ensureVisible(_purposeFocus));
    _remarkFocus.addListener(() => _ensureVisible(_remarkFocus));
    _acctTree = null;
    _refDataLoading = true;
    _loadingBill = true;
    _loadRefData();
    _loadBillData();
    WidgetsBinding.instance.addPostFrameCallback((_) => _checkDataReady());
  }

  void _checkDataReady() {
    if (!_refDataLoading && !_loadingBill && mounted) {
      setState(() => _firstBuild = false);
      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.getAcctSubjects()]);
      if (!mounted) return;
      setState(() {
        _acctTree = _convertAcctTree(results[0]);
        _refDataLoading = false;
      });
      completer.complete();
    } catch (_) {
      if (!mounted) {
        completer.complete();
        return;
      }
      setState(() => _refDataLoading = false);
      completer.complete();
    } finally {
      if (showLoading && mounted) LoadingDialog.hide(context);
      _refDataFuture = null;
    }
  }

  Future<void> _loadBillData() async {
    try {
      final api = ref.read(expenseApplyApiProvider);
      final detail = await api.fetchDetail(widget.billNo);
      if (!mounted) return;

      final n = detail.createTime;
      final applyDate =
          '${n.year}-${n.month.toString().padLeft(2, '0')}-${n.day.toString().padLeft(2, '0')}';

      setState(() {
        _billNo = detail.expenseApplyNo;
        _applyDate = applyDate;

        _selectedDeptId = detail.deptId;
        _selectedDeptName = detail.deptName;

        // 紧急程度映射（兼容代码值 '1'/'2'/'3' 和文字值）
        final urg = detail.urgency;
        if (urg == '3' || urg == 'critical') {
          _urgency = Urgency.critical.value;
        } else if (urg == '2' || urg == 'urgent') {
          _urgency = Urgency.urgent.value;
        } else {
          _urgency = Urgency.normal.value;
        }

        _purposeController.text = detail.purpose;
        _remarkController.text = detail.remark;

        // 费用明细回填
        _details.clear();
        _detailIdCounter = 1;
        for (final d in detail.details) {
          String startDateStr = '';
          String endDateStr = '';
          if (d.estimatedStartDate != null) {
            final s = d.estimatedStartDate!;
            startDateStr =
                '${s.year}-${s.month.toString().padLeft(2, '0')}-${s.day.toString().padLeft(2, '0')}';
          }
          if (d.estimatedEndDate != null) {
            final e = d.estimatedEndDate!;
            endDateStr =
                '${e.year}-${e.month.toString().padLeft(2, '0')}-${e.day.toString().padLeft(2, '0')}';
          }
          _details.add(
            _DetailItem(
              id: _detailIdCounter++,
              category: d.expenseCategory,
              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: startDateStr,
              endDate: endDateStr,
              estimatedAmount: d.estimatedAmount,
              remark: d.remark,
              preItm: d.preItm,
              sqMan: d.sqMan,
              sqManName: d.sqName,
            ),
          );
        }

        _loadingBill = false;
      });
    } catch (e) {
      if (!mounted) return;
      setState(() {
        _loadingBill = false;
        _loadingError = e.toString();
      });
    }
  }

  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();
    _remarkController.dispose();
    _remarkFocus.dispose();
    _scrollCtrl.dispose();
    super.dispose();
  }

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

    if (_loadingError != null) {
      return Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.error_outline, size: 48, color: colors.danger),
            const SizedBox(height: 16),
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 32),
              child: Text(
                _loadingError!,
                textAlign: TextAlign.center,
                style: TextStyle(
                  fontSize: AppFontSizes.body,
                  color: colors.textSecondary,
                ),
              ),
            ),
            const SizedBox(height: 16),
            TDButton(
              text: l10n.get('retry'),
              size: TDButtonSize.medium,
              onTap: () {
                setState(() {
                  _loadingError = null;
                  _loadingBill = true;
                });
                _loadBillData();
              },
            ),
          ],
        ),
      );
    }

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

    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: 24),
                    _buildPageFooter(),
                  ],
                ),
              ),
            ),
          ),
          _buildBottomBar(l10n),
        ],
      ),
    );
  }

  // ═══ 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('expenseApplyNo'),
          value: _billNo,
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('date'),
          value: _applyDate,
          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,
        ),
        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.isNotEmpty ? '/${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) ...[
                            const SizedBox(height: 4),
                            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.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) ...[
                            const SizedBox(height: 4),
                            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) ...[
                            const SizedBox(height: 4),
                            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.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,
      );
      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,
            preItm: editIndex != null ? _details[editIndex].preItm : null,
            sqMan: result.sqMan,
            sqManName: result.sqManName,
          );
          if (editIndex != null) {
            _details[editIndex] = item;
          } else {
            _details.add(item);
          }
        });
      }
    } finally {
      _addingDetail = false;
    }
  }

  // ═══ 类型转换 ═══

  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();
  }

  // ═══ 3. 底部操作栏 ═══
  Widget _buildBottomBar(AppLocalizations l10n) {
    return ActionBar(
      showLeft: false,
      showCenter: false,
      rightLabel: l10n.get('submit'),
      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);
          await api.submit(data);
          if (mounted) {
            LoadingDialog.hide(context);
            TDToast.showSuccess(l10n.get('submitSuccess'), context: context);
            WidgetsBinding.instance.addPostFrameCallback((_) {
              if (mounted) GoRouter.of(context).pop(true);
            });
          }
        } 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() {
    String priority;
    switch (_urgency) {
      case 'urgent':
        priority = '2';
        break;
      case 'critical':
        priority = '3';
        break;
      default:
        priority = '1';
    }

    return {
      'HeadData': {
        'AE_NO': _billNo,
        '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;
        final item = <String, dynamic>{
          'AE_NO': _billNo,
          'SQ_MAN': d.sqMan,
          'TYPE_NO': d.category,
          'AMTN_YJ': d.estimatedAmount,
          'ACC_NO': d.acctSubjectId,
          '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,
        };
        if (d.preItm != null) {
          item['PRE_ITM'] = d.preItm;
        }
        return item;
      }).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() {
    _forcePop();
  }

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

  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;
      });
    }
  }

  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,
              ),
            ),
          ],
        ),
      ),
    );
  }

  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,
              ),
            ),
        ],
      ),
    );
  }

  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 int? preItm;
  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.preItm,
    this.sqMan = '',
    this.sqManName = '',
  });
}
