// ignore_for_file: use_build_context_synchronously

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import '../../../core/i18n/app_localizations.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_colors_extension.dart';
import '../../../shared/widgets/searchable_picker_sheet.dart';
import '../expense_apply_api.dart';
import '../../../core/utils/amount_utils.dart';

/// 费用明细输入数据。
class ExpenseDetailData {
  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 ExpenseDetailData({
    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 = '',
  });
}

/// 添加费用明细弹窗。
///
/// 使用 [TDSlidePopupRoute] 从底部滑出，卡片化展示表单字段。
/// 费用项目、关联项目、费用承担部门均从接口实时加载，无需预传数据。
class ExpenseApplyDetailDialog extends StatefulWidget {
  final ExpenseApplyApi api;
  final AppLocalizations l10n;
  final ExpenseDetailData? initialData;
  final dynamic acctTree;
  final String? defaultSqMan;
  final String? defaultSqManName;

  const ExpenseApplyDetailDialog({
    super.key,
    required this.api,
    required this.l10n,
    required this.acctTree,
    this.initialData,
    this.defaultSqMan,
    this.defaultSqManName,
  });

  /// 显示弹窗，返回 [ExpenseDetailData] 或 `null`（取消时）。
  static Future<ExpenseDetailData?> show(
    BuildContext context, {
    required ExpenseApplyApi api,
    required AppLocalizations l10n,
    required dynamic acctTree,
    ExpenseDetailData? initialData,
    String? defaultSqMan,
    String? defaultSqManName,
  }) {
    FocusScope.of(context).unfocus();
    return Navigator.push<ExpenseDetailData>(
      context,
      TDSlidePopupRoute<ExpenseDetailData>(
        slideTransitionFrom: SlideTransitionFrom.bottom,
        isDismissible: true,
        builder: (_) => ExpenseApplyDetailDialog(
          api: api,
          l10n: l10n,
          acctTree: acctTree,
          initialData: initialData,
          defaultSqMan: defaultSqMan,
          defaultSqManName: defaultSqManName,
        ),
      ),
    );
  }

  @override
  State<ExpenseApplyDetailDialog> createState() =>
      _ExpenseApplyDetailDialogState();
}

class _ExpenseApplyDetailDialogState extends State<ExpenseApplyDetailDialog> {
  // 费用项目
  late CostProjectItem _selCat;
  // 关联项目
  ProjectCodeItem? _selProject;
  // 费用承担部门
  DepartmentItem? _selDept;
  // 申请人
  EmployeeItem? _selEmployee;
  // 预计开始/结束日期
  String _startDate = '';
  String _endDate = '';
  // 费用事由
  final _purposeCtrl = TextEditingController();
  final _purposeFocus = FocusNode();
  // 预估金额
  final _amountCtrl = TextEditingController();
  final _amountFocus = FocusNode();
  // 备注
  final _remarkCtrl = TextEditingController();
  final _remarkFocus = FocusNode();
  // 滚动控制
  final _scrollCtrl = ScrollController();

  AppLocalizations get _l10n => widget.l10n;

  bool get _isEdit => widget.initialData != null;

  @override
  void initState() {
    super.initState();
    final d = widget.initialData;
    _selCat = CostProjectItem(
      idx1: d?.category ?? '',
      name: d?.categoryName ?? '',
      accNo: d?.acctSubjectId ?? '',
      accName: d?.acctSubjectName ?? '',
    );
    _selProject = (d != null && d.projectId.isNotEmpty)
        ? ProjectCodeItem(objNo: d.projectId, name: d.projectName)
        : null;
    _selDept = (d != null && d.costDeptId.isNotEmpty)
        ? DepartmentItem(dep: d.costDeptId, name: d.costDeptName)
        : null;
    _selEmployee = (d != null && d.sqMan.isNotEmpty)
        ? EmployeeItem(salNo: d.sqMan, name: d.sqManName)
        : (widget.defaultSqMan != null
            ? EmployeeItem(salNo: widget.defaultSqMan!, name: widget.defaultSqManName ?? '')
            : null);
    _startDate = d?.startDate ?? '';
    _endDate = d?.endDate ?? '';
    if (d != null) {
      _purposeCtrl.text = d.purpose;
      _amountCtrl.text = d.estimatedAmount > 0
          ? formatAmountNumeric(d.estimatedAmount)
          : '';
      _remarkCtrl.text = d.remark;
    }
    _purposeFocus.addListener(() => _ensureVisible(_purposeFocus));
    _amountFocus.addListener(() => _ensureVisible(_amountFocus));
    _remarkFocus.addListener(() => _ensureVisible(_remarkFocus));
  }

  void _ensureVisible(FocusNode node) {
    if (!node.hasFocus) return;
    _doEnsureVisible(node, 0, -1);
  }

  void _doEnsureVisible(FocusNode node, int attempt, double lastInsets) {
    if (attempt >= 15) return;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted || !node.hasFocus || !_scrollCtrl.hasClients) return;
      final insets = MediaQuery.of(context).viewInsets.bottom;
      if (insets != lastInsets) {
        _doEnsureVisible(node, attempt + 1, insets);
        return;
      }
      // viewInsets 已稳定，但 AnimatedPadding 动画（200ms）可能还在进行
      // 等 250ms 确保 dialog 位置完全就位再滚动
      final ctx = node.context;
      if (ctx == null) return;
      Future.delayed(const Duration(milliseconds: 500), () {
        if (!mounted || !node.hasFocus || !_scrollCtrl.hasClients) return;
        Scrollable.ensureVisible(
          ctx,
          alignment: 0.5,
          duration: const Duration(milliseconds: 300),
        );
      });
    });
  }

  @override
  void dispose() {
    _purposeCtrl.dispose();
    _purposeFocus.dispose();
    _amountCtrl.dispose();
    _amountFocus.dispose();
    _remarkCtrl.dispose();
    _remarkFocus.dispose();
    _scrollCtrl.dispose();
    super.dispose();
  }

  void _confirm() {
    if (_selCat.idx1.isEmpty) {
      TDToast.showText(
        '${_l10n.get('pleaseSelect')}${_l10n.get('expenseCategory')}',
        context: context,
      );
      return;
    }
    if (_selEmployee == null) {
      TDToast.showText(
        '${_l10n.get('pleaseSelect')}${_l10n.get('applicant')}',
        context: context,
      );
      return;
    }
    final amt = double.tryParse(_amountCtrl.text) ?? 0;
    if (amt <= 0) {
      TDToast.showText(_l10n.get('estimatedAmountPositive'), context: context);
      return;
    }
    if (_startDate.isNotEmpty &&
        _endDate.isNotEmpty &&
        _startDate.compareTo(_endDate) > 0) {
      TDToast.showText(_l10n.get('startDateNotAfterEndDate'), context: context);
      return;
    }
    Navigator.pop(
      context,
      ExpenseDetailData(
        category: _selCat.idx1,
        categoryName: _selCat.name,
        acctSubjectId: _selCat.accNo,
        acctSubjectName: _selCat.accName,
        purpose: _purposeCtrl.text.trim(),
        projectId: _selProject != null ? _selProject!.objNo.toString() : '',
        projectName: _selProject?.name ?? '',
        costDeptId: _selDept?.dep ?? '',
        costDeptName: _selDept?.name ?? '',
        sqMan: _selEmployee?.salNo ?? '',
        sqManName: _selEmployee?.name ?? '',
        startDate: _startDate,
        endDate: _endDate,
        estimatedAmount: amt,
        remark: _remarkCtrl.text,
      ),
    );
  }

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

    return AnimatedPadding(
      padding: EdgeInsets.only(
        bottom: MediaQuery.of(context).viewInsets.bottom,
      ),
      duration: const Duration(milliseconds: 200),
      child: SafeArea(
        child: ConstrainedBox(
          constraints: BoxConstraints(
            maxHeight: MediaQuery.of(context).size.height * 0.8,
          ),
          child: Container(
            decoration: BoxDecoration(
              color: colors.bgPage,
              borderRadius: const BorderRadius.vertical(
                top: Radius.circular(16),
              ),
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                _buildHeader(colors),
                Flexible(
                  child: GestureDetector(
                    onTap: () => FocusScope.of(context).unfocus(),
                    behavior: HitTestBehavior.translucent,
                    child: SingleChildScrollView(
                      controller: _scrollCtrl,
                      keyboardDismissBehavior:
                          ScrollViewKeyboardDismissBehavior.manual,
                      padding: const EdgeInsets.fromLTRB(16, 0, 16, 12),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.stretch,
                        children: [
                          _buildCategoryPicker(colors),
                          const SizedBox(height: 12),
                          _buildAcctSubjectCard(colors),
                          const SizedBox(height: 12),
                          _buildEmployeePicker(colors),
                          const SizedBox(height: 12),
                          _buildAmountInput(colors),
                          const SizedBox(height: 12),
                          _buildProjectPicker(colors),
                          const SizedBox(height: 12),
                          _buildCostDeptPicker(colors),
                          const SizedBox(height: 12),
                          _buildStartDatePicker(colors),
                          const SizedBox(height: 12),
                          _buildEndDatePicker(colors),
                          const SizedBox(height: 12),
                          _buildRemarkInput(colors),
                        ],
                      ),
                    ),
                  ),
                ),
                Container(
                  padding: const EdgeInsets.fromLTRB(16, 12, 16, 16),
                  decoration: BoxDecoration(
                    color: colors.bgCard,
                    border: Border(
                      top: BorderSide(color: colors.border, width: 0.5),
                    ),
                  ),
                  child: _buildActions(),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  // ── 标题栏 ──
  Widget _buildHeader(AppColorsExtension colors) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Center(
          child: Container(
            margin: const EdgeInsets.only(top: 8, bottom: 4),
            width: 36,
            height: 4,
            decoration: BoxDecoration(
              color: colors.border,
              borderRadius: BorderRadius.circular(2),
            ),
          ),
        ),
        Padding(
          padding: const EdgeInsets.fromLTRB(20, 8, 12, 16),
          child: Row(
            children: [
              const SizedBox(width: 24),
              Expanded(
                child: Center(
                  child: Text(
                    _l10n.get('addDetail'),
                    style: TextStyle(
                      fontSize: AppFontSizes.title,
                      fontWeight: FontWeight.w600,
                      color: colors.textPrimary,
                    ),
                  ),
                ),
              ),
              GestureDetector(
                onTap: () => Navigator.pop(context),
                child: Padding(
                  padding: const EdgeInsets.all(4),
                  child: Icon(
                    Icons.close,
                    size: 20,
                    color: colors.textSecondary,
                  ),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  // ── 通用 Picker 卡片 ──
  Widget _pickerCard({
    required String label,
    required bool required,
    required String currentLabel,
    required bool hasValue,
    required VoidCallback onTap,
    VoidCallback? onClear,
  }) {
    final tdTheme = TDTheme.of(context);
    return GestureDetector(
      onTap: () {
        FocusManager.instance.primaryFocus?.unfocus();
        onTap();
      },
      child: Container(
        padding: const EdgeInsets.only(
          left: 16,
          right: 10,
          top: 12,
          bottom: 12,
        ),
        decoration: BoxDecoration(
          color: tdTheme.bgColorContainer,
          borderRadius: BorderRadius.circular(tdTheme.radiusDefault),
          border: Border.all(color: tdTheme.componentStrokeColor),
        ),
        child: Row(
          children: [
            TDText(
              label,
              maxLines: 1,
              overflow: TextOverflow.visible,
              font: tdTheme.fontBodyLarge,
              fontWeight: FontWeight.w400,
              style: const TextStyle(letterSpacing: 0),
            ),
            if (required)
              Padding(
                padding: const EdgeInsets.only(left: 4),
                child: TDText(
                  '*',
                  font: tdTheme.fontBodyLarge,
                  fontWeight: FontWeight.w400,
                  style: TextStyle(color: tdTheme.errorColor6),
                ),
              ),
            const SizedBox(width: 12),
            Expanded(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.end,
                mainAxisSize: MainAxisSize.max,
                children: [
                  Flexible(
                    child: TDText(
                      currentLabel,
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      font: tdTheme.fontBodyLarge,
                      fontWeight: FontWeight.w400,
                      textColor: hasValue
                          ? tdTheme.textColorPrimary
                          : tdTheme.textColorPlaceholder,
                      textAlign: TextAlign.end,
                    ),
                  ),
                  const SizedBox(width: 4),
                  SizedBox(
                    width: 18,
                    height: 18,
                    child: onClear != null
                        ? GestureDetector(
                            onTap: onClear,
                            child: Icon(
                              Icons.close,
                              size: 18,
                              color: tdTheme.textColorPlaceholder,
                            ),
                          )
                        : Icon(
                            Icons.chevron_right,
                            size: 18,
                            color: tdTheme.textColorPlaceholder,
                          ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  // ── 1. 费用项目 ──
  Widget _buildCategoryPicker(AppColorsExtension colors) {
    return _pickerCard(
      label: _l10n.get('expenseCategory'),
      required: true,
      hasValue: _selCat.idx1.isNotEmpty,
      currentLabel: _selCat.idx1.isNotEmpty
          ? '${_selCat.idx1}/${_selCat.name}'
          : _l10n.get('pleaseSelect'),
      onTap: () async {
        final result = await showSearchablePicker<CostProjectItem>(
          context,
          title: '${_l10n.get('select')}${_l10n.get('expenseCategory')}',
          searchHint: _l10n.get('search'),
          loader: (keyword, page) => widget.api
              .getCostProjects(keyword: keyword, page: page, size: 20)
              .then((list) => list.cast<CostProjectItem>()),
          labelBuilder: (c) => '${c.idx1}/${c.name}',
          onRefresh: () => widget.api.clearRefCache(),
        );
        if (result != null && mounted) {
          setState(() => _selCat = result);
        }
      },
    );
  }

  // ── 2. 会计科目（级联选择器） ──
  Widget _buildAcctSubjectCard(AppColorsExtension colors) {
    final tdTheme = TDTheme.of(context);
    final hasValue = _selCat.accNo.isNotEmpty;
    final displayValue = hasValue
        ? (_selCat.accName.startsWith(_selCat.accNo)
              ? _selCat.accName
              : (_selCat.accName.toString().isNotEmpty
                    ? '${_selCat.accNo}/${_selCat.accName}'
                    : _selCat.accNo))
        : _l10n.get('pleaseSelect');
    return GestureDetector(
      onTap: () {
        debugPrint(
          '[AcctSubject] onTap fired, acctTree isNull: ${widget.acctTree == null}',
        );
        if (widget.acctTree == null) {
          TDToast.showText(_l10n.get('loading'), context: context);
          return;
        }
        FocusManager.instance.primaryFocus?.unfocus();
        TDCascader.showMultiCascader(
          context,
          title: _l10n.get('acctSubject'),
          data: widget.acctTree as List<Map>,
          theme: 'step',
          onChange: (List<MultiCascaderListModel> selected) {
            setState(() {
              _selCat = CostProjectItem(
                idx1: _selCat.idx1,
                name: _selCat.name,
                accNo: (selected.last.value ?? '').replaceFirst('self:', ''),
                accName: (selected.last.label ?? '')
                    .replaceFirst(RegExp(r'^> '), '')
                    .replaceFirst('${selected.last.value ?? ''}/', ''),
              );
            });
          },
        );
      },
      child: Container(
        padding: const EdgeInsets.only(
          left: 16,
          right: 10,
          top: 12,
          bottom: 12,
        ),
        decoration: BoxDecoration(
          color: tdTheme.bgColorContainer,
          borderRadius: BorderRadius.circular(tdTheme.radiusDefault),
          border: Border.all(color: tdTheme.componentStrokeColor),
        ),
        child: Row(
          children: [
            TDText(
              _l10n.get('acctSubject'),
              maxLines: 1,
              overflow: TextOverflow.visible,
              font: tdTheme.fontBodyLarge,
              fontWeight: FontWeight.w400,
              style: const TextStyle(letterSpacing: 0),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.end,
                mainAxisSize: MainAxisSize.max,
                children: [
                  Flexible(
                    child: TDText(
                      displayValue,
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      font: tdTheme.fontBodyLarge,
                      fontWeight: FontWeight.w400,
                      textColor: hasValue
                          ? tdTheme.textColorPrimary
                          : tdTheme.textColorPlaceholder,
                      textAlign: TextAlign.end,
                    ),
                  ),
                  const SizedBox(width: 4),
                  SizedBox(
                    width: 18,
                    height: 18,
                    child: Icon(
                      Icons.chevron_right,
                      size: 18,
                      color: tdTheme.textColorPlaceholder,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  // ── 3. 关联项目 ──
  Widget _buildProjectPicker(AppColorsExtension colors) {
    return _pickerCard(
      label: _l10n.get('relatedProject'),
      required: false,
      hasValue: _selProject != null,
      currentLabel: _selProject != null
          ? '${_selProject!.objNo}/${_selProject!.name}'
          : _l10n.get('pleaseSelect'),
      onTap: () async {
        final result = await showSearchablePicker<ProjectCodeItem>(
          context,
          title: '${_l10n.get('select')}${_l10n.get('relatedProject')}',
          searchHint: _l10n.get('search'),
          loader: (keyword, page) => widget.api
              .getProjectCodes(keyword: keyword, page: page, size: 20)
              .then((list) => list.cast<ProjectCodeItem>()),
          labelBuilder: (p) => '${p.objNo}/${p.name}',
          onRefresh: () => widget.api.clearRefCache(),
        );
        if (result != null && mounted) {
          setState(() => _selProject = result);
        }
      },
      onClear: _selProject != null
          ? () => setState(() => _selProject = null)
          : null,
    );
  }

  // ── 5. 费用承担部门 ──
  Widget _buildCostDeptPicker(AppColorsExtension colors) {
    return _pickerCard(
      label: _l10n.get('costDept'),
      required: false,
      hasValue: _selDept != null,
      currentLabel: _selDept != null
          ? '${_selDept!.dep}/${_selDept!.name}'
          : _l10n.get('pleaseSelect'),
      onTap: () async {
        final result = await showSearchablePicker<DepartmentItem>(
          context,
          title: '${_l10n.get('select')}${_l10n.get('costDept')}',
          searchHint: _l10n.get('search'),
          loader: (keyword, page) => widget.api
              .getDepartments(keyword: keyword, page: page, size: 20)
              .then((list) => list.cast<DepartmentItem>()),
          labelBuilder: (d) => '${d.dep}/${d.name}',
          onRefresh: () => widget.api.clearRefCache(),
        );
        if (result != null && mounted) {
          setState(() => _selDept = result);
        }
      },
      onClear: _selDept != null ? () => setState(() => _selDept = null) : null,
    );
  }

  // ── 申请人 ──
  Widget _buildEmployeePicker(AppColorsExtension colors) {
    return _pickerCard(
      label: _l10n.get('applicant'),
      required: true,
      hasValue: _selEmployee != null,
      currentLabel: _selEmployee != null
          ? '${_selEmployee!.salNo}/${_selEmployee!.name}'
          : _l10n.get('pleaseSelect'),
      onTap: () async {
        final result = await showSearchablePicker<EmployeeItem>(
          context,
          title: '${_l10n.get('select')}${_l10n.get('applicant')}',
          searchHint: _l10n.get('search'),
          loader: (keyword, page) =>
              widget.api.getEmployees(keyword: keyword, page: page, size: 20),
          labelBuilder: (e) =>
              e.name.isEmpty ? e.salNo : '${e.salNo} ${e.name}',
          onRefresh: () => widget.api.clearRefCache(),
        );
        if (result != null && mounted) {
          setState(() => _selEmployee = result);
        }
      },
      onClear: _selEmployee != null
          ? () => setState(() => _selEmployee = null)
          : null,
    );
  }

  // ── 6. 预计开始日期 ──
  Widget _buildStartDatePicker(AppColorsExtension colors) {
    return _datePickerCard(
      label: _l10n.get('estimatedStartDate'),
      value: _startDate,
      hint: _l10n.get('pleaseSelect'),
      colors: colors,
      onPick: (d) => setState(() => _startDate = d),
      onClear: _startDate.isNotEmpty
          ? () => setState(() => _startDate = '')
          : null,
    );
  }

  // ── 7. 预计结束日期 ──
  Widget _buildEndDatePicker(AppColorsExtension colors) {
    return _datePickerCard(
      label: _l10n.get('estimatedEndDate'),
      value: _endDate,
      hint: _l10n.get('pleaseSelect'),
      colors: colors,
      onPick: (d) => setState(() => _endDate = d),
      onClear: _endDate.isNotEmpty ? () => setState(() => _endDate = '') : null,
    );
  }

  Widget _datePickerCard({
    required String label,
    required String value,
    required String hint,
    required AppColorsExtension colors,
    required ValueChanged<String> onPick,
    VoidCallback? onClear,
  }) {
    final tdTheme = TDTheme.of(context);
    final hasValue = value.isNotEmpty;
    return GestureDetector(
      onTap: () => _pickDate(onPick),
      child: Container(
        padding: const EdgeInsets.only(
          left: 16,
          right: 10,
          top: 12,
          bottom: 12,
        ),
        decoration: BoxDecoration(
          color: tdTheme.bgColorContainer,
          borderRadius: BorderRadius.circular(tdTheme.radiusDefault),
          border: Border.all(color: tdTheme.componentStrokeColor),
        ),
        child: Row(
          children: [
            TDText(
              label,
              maxLines: 1,
              overflow: TextOverflow.visible,
              font: tdTheme.fontBodyLarge,
              fontWeight: FontWeight.w400,
              style: const TextStyle(letterSpacing: 0),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.end,
                mainAxisSize: MainAxisSize.max,
                children: [
                  Flexible(
                    child: TDText(
                      value.isEmpty ? hint : value,
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                      font: tdTheme.fontBodyLarge,
                      fontWeight: FontWeight.w400,
                      textColor: value.isEmpty
                          ? tdTheme.textColorPlaceholder
                          : tdTheme.textColorPrimary,
                      textAlign: TextAlign.end,
                    ),
                  ),
                  const SizedBox(width: 4),
                  SizedBox(
                    width: 18,
                    height: 18,
                    child: hasValue
                        ? GestureDetector(
                            onTap: onClear,
                            child: Icon(
                              Icons.close,
                              size: 18,
                              color: tdTheme.textColorPlaceholder,
                            ),
                          )
                        : Icon(
                            Icons.chevron_right,
                            size: 18,
                            color: tdTheme.textColorPlaceholder,
                          ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _pickDate(ValueChanged<String> onPick) {
    final l10n = AppLocalizations.of(context);
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    final now = DateTime.now();
    FocusManager.instance.primaryFocus?.unfocus();
    TDPicker.showDatePicker(
      context,
      title: l10n.get('selectDate'),
      backgroundColor: colors.bgCard,
      useYear: true,
      useMonth: true,
      useDay: true,
      useHour: false,
      useMinute: false,
      useSecond: false,
      useWeekDay: false,
      dateStart: const [2020, 1, 1],
      dateEnd: [now.year + 1, 12, 31],
      initialDate: [now.year, now.month, now.day],
      onConfirm: (selected) {
        onPick(
          '${selected['year']}-${selected['month'].toString().padLeft(2, '0')}-${selected['day'].toString().padLeft(2, '0')}',
        );
        Navigator.of(context).pop();
      },
    );
  }

  // ── 8. 预估金额 ──
  Widget _buildAmountInput(AppColorsExtension colors) {
    return _inputCard(
      label: _l10n.get('estimatedAmount'),
      required: true,
      controller: _amountCtrl,
      hintText: '>0',
      keyboardType: const TextInputType.numberWithOptions(decimal: true),
      inputFormatters: [
        FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d{0,2}$')),
      ],
      focusNode: _amountFocus,
    );
  }

  // ── 9. 备注 ──
  Widget _buildRemarkInput(AppColorsExtension colors) {
    final tdTheme = TDTheme.of(context);
    return TDTextarea(
      controller: _remarkCtrl,
      focusNode: _remarkFocus,
      label: _l10n.get('remark'),
      hintText: _l10n.get('enterRemark'),
      maxLines: 3,
      minLines: 1,
      maxLength: 500,
      indicator: true,
      decoration: BoxDecoration(
        color: tdTheme.bgColorContainer,
        borderRadius: BorderRadius.circular(tdTheme.radiusDefault),
        border: Border.all(color: tdTheme.componentStrokeColor),
      ),
      onChanged: (_) => setState(() {}),
    );
  }

  // ── 操作按钮 ──
  Widget _buildActions() {
    return Row(
      children: [
        Expanded(
          child: TDButton(
            text: _l10n.get('cancel'),
            size: TDButtonSize.large,
            type: TDButtonType.outline,
            shape: TDButtonShape.rectangle,
            theme: TDButtonTheme.defaultTheme,
            onTap: () => Navigator.pop(context),
          ),
        ),
        const SizedBox(width: 12),
        Expanded(
          child: TDButton(
            text: _isEdit ? _l10n.get('confirmEdit') : _l10n.get('add'),
            size: TDButtonSize.large,
            type: TDButtonType.fill,
            shape: TDButtonShape.rectangle,
            theme: TDButtonTheme.primary,
            onTap: _confirm,
          ),
        ),
      ],
    );
  }

  Widget _inputCard({
    required String label,
    required bool required,
    required TextEditingController controller,
    required String hintText,
    TextInputType? keyboardType,
    List<TextInputFormatter>? inputFormatters,
    FocusNode? focusNode,
  }) {
    final tdTheme = TDTheme.of(context);
    final hasValue = controller.text.isNotEmpty;
    return Container(
      padding: const EdgeInsets.only(left: 16, right: 10, top: 12, bottom: 12),
      decoration: BoxDecoration(
        color: tdTheme.bgColorContainer,
        borderRadius: BorderRadius.circular(tdTheme.radiusDefault),
        border: Border.all(color: tdTheme.componentStrokeColor),
      ),
      child: Row(
        children: [
          TDText(
            label,
            maxLines: 1,
            overflow: TextOverflow.visible,
            font: tdTheme.fontBodyLarge,
            fontWeight: FontWeight.w400,
            style: const TextStyle(letterSpacing: 0),
          ),
          if (required)
            Padding(
              padding: const EdgeInsets.only(left: 4),
              child: TDText(
                '*',
                font: tdTheme.fontBodyLarge,
                fontWeight: FontWeight.w400,
                style: TextStyle(color: tdTheme.errorColor6),
              ),
            ),
          const SizedBox(width: 12),
          Expanded(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.end,
              mainAxisSize: MainAxisSize.max,
              children: [
                Flexible(
                  child: TextField(
                    controller: controller,
                    focusNode: focusNode,
                    textAlign: TextAlign.end,
                    keyboardType: keyboardType,
                    inputFormatters: inputFormatters,
                    style: TextStyle(
                      fontSize: 16,
                      color: tdTheme.textColorPrimary,
                    ),
                    decoration: InputDecoration(
                      hintText: hintText,
                      hintStyle: TextStyle(
                        fontSize: 16,
                        color: tdTheme.textColorPlaceholder,
                      ),
                      border: InputBorder.none,
                      isDense: true,
                      contentPadding: EdgeInsets.zero,
                    ),
                    onChanged: (_) => setState(() {}),
                  ),
                ),
                const SizedBox(width: 4),
                SizedBox(
                  width: 18,
                  height: 18,
                  child: hasValue
                      ? GestureDetector(
                          onTap: () {
                            controller.clear();
                            setState(() {});
                          },
                          child: Icon(
                            Icons.close,
                            size: 18,
                            color: tdTheme.textColorPlaceholder,
                          ),
                        )
                      : null,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
