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/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 'overtime_apply_api.dart';
import '../expense_apply/expense_apply_api.dart';
import 'widgets/overtime_apply_detail_dialog.dart';

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

  @override
  ConsumerState<OvertimeApplyCreatePage> createState() =>
      _OvertimeApplyCreatePageState();
}

class _OvertimeApplyCreatePageState
    extends ConsumerState<OvertimeApplyCreatePage> {
  static const _draftKey = 'overtime_apply';

  // ── 基本信息 ──
  final _reasonController = TextEditingController();
  final _reasonFocus = FocusNode();
  final _remarkController = TextEditingController();
  final _remarkFocus = FocusNode();
  final _scrollCtrl = ScrollController();

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

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

  // ── 参考数据（从 API 加载） ──
  bool _firstBuild = true;
  bool _refDataLoading = true;
  bool _addingDetail = false;

  // ── 申请部门 ──
  String _selectedDeptId = '';
  String _selectedDeptName = '';
  List<DepartmentItem> _departments = [];

  // ── 申请人 ──
  String _selectedApplicantId = '';
  String _selectedApplicantName = '';
  List<EmployeeItem> _employees = [];

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

  void _checkDataReady() {
    if (!_refDataLoading && 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(overtimeApplyApiProvider);
      final results = await Future.wait([
        api.getDepartments(),
        api.getEmployees(),
      ]);
      if (!mounted) return;
      setState(() {
        _departments = results[0] as List<DepartmentItem>;
        _employees = results[1] as List<EmployeeItem>;
        _refDataLoading = false;
        _autoSelectDept();
        _autoSelectApplicant();
      });
      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 _autoSelectApplicant() {
    if (_selectedApplicantId.isNotEmpty) return;
    final usr = HostAppChannel.usr;
    if (usr.isEmpty) return;
    final match = _employees.where((e) => e.salNo == usr);
    if (match.isNotEmpty) {
      _selectedApplicantId = match.first.salNo;
      _selectedApplicantName = match.first.name;
    }
  }

  Future<void> _showApplicantPicker() async {
    FocusScope.of(context).unfocus();
    FocusManager.instance.primaryFocus?.unfocus();
    final l10n = AppLocalizations.of(context);
    final api = ref.read(overtimeApplyApiProvider);
    final result = await showSearchablePicker<EmployeeItem>(
      context,
      title: '${l10n.get('select')}${l10n.get('applicant')}',
      searchHint: l10n.get('search'),
      loader: (keyword, page) =>
          api.getEmployees(keyword: keyword, page: page, size: 20),
      labelBuilder: (e) => e.name.isEmpty ? e.salNo : '${e.salNo} ${e.name}',
    );
    if (result != null && mounted) {
      setState(() {
        _selectedApplicantId = result.salNo;
        _selectedApplicantName = result.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() {
    _reasonController.dispose();
    _reasonFocus.dispose();
    _remarkController.dispose();
    _remarkFocus.dispose();
    _scrollCtrl.dispose();
    super.dispose();
  }

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

    if (_firstBuild) {
      return const SkeletonFormPage(sectionRows: [5, 3], bottomButtonCount: 2);
    }

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

    final pageContent = 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),
        ],
      ),
    );

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

        return pageContent;
      },
    );
  }

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

  Future<void> _restoreDraft() async {
    final data = await DraftStorage.load(_draftKey);
    if (data == null) return;
    setState(() {
      _reasonController.text = data['reason'] as String? ?? '';
      _remarkController.text = data['remark'] as String? ?? '';
      _selectedDeptId = data['deptId'] as String? ?? '';
      _selectedDeptName = data['deptName'] as String? ?? '';
      _selectedApplicantId = data['applicantId'] as String? ?? '';
      _selectedApplicantName = data['applicantName'] 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++,
              jbNo: m['jbNo'] as String?,
              itm: m['itm'] as int? ?? 0,
              salNo: m['salNo'] as String? ?? '',
              salName: m['salName'] as String? ?? '',
              dep: m['dep'] as String? ?? '',
              depName: m['depName'] as String? ?? '',
              jbType: m['jbType'] as String? ?? 'WORKING_DAY',
              jbDate: m['jbDate'] as String? ?? '',
              startTime: m['startTime'] as String? ?? '',
              endTime: m['endTime'] as String? ?? '',
              jbHours: (m['jbHours'] as num?)?.toDouble() ?? 0.0,
              jbDays: (m['jbDays'] as num?)?.toDouble() ?? 0.0,
              attPeriod: m['attPeriod'] as String? ?? '',
              reason: m['detailReason'] as String? ?? '',
              compensationType:
                  m['compensationType'] as String? ?? 'OVERTIME_PAY',
              compensationCount:
                  (m['compensationCount'] as num?)?.toDouble() ?? 0.0,
              adr: m['adr'] as String? ?? '',
              rem: m['rem'] 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,
            'jbNo': d.jbNo,
            'itm': d.itm,
            'salNo': d.salNo,
            'salName': d.salName,
            'dep': d.dep,
            'depName': d.depName,
            'jbType': d.jbType,
            'jbDate': d.jbDate,
            'startTime': d.startTime,
            'endTime': d.endTime,
            'jbHours': d.jbHours,
            'jbDays': d.jbDays,
            'attPeriod': d.attPeriod,
            'detailReason': d.reason,
            'compensationType': d.compensationType,
            'compensationCount': d.compensationCount,
            'adr': d.adr,
            'rem': d.rem,
          },
        )
        .toList();
    await DraftStorage.save(_draftKey, {
      'reason': _reasonController.text,
      'remark': _remarkController.text,
      'deptId': _selectedDeptId,
      'deptName': _selectedDeptName,
      'applicantId': _selectedApplicantId,
      'applicantName': _selectedApplicantName,
      'details': detailList,
    });
  }

  // ═══ 草稿弹窗 ═══
  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'),
        buttonStyle: TDDialogButtonStyle.text,
        leftBtn: TDDialogButtonOptions(
          title: l10n.get('discard'),
          titleColor: colors.danger,
          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>()!;
    final now = DateTime.now();
    final todayStr =
        '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';

    return FormSection(
      title: l10n.get('basicInfo'),
      leadingIcon: Icons.info_outline,
      children: [
        FormFieldRow(
          label: l10n.get('date'),
          value: todayStr,
          readOnly: true,
          showArrow: false,
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('dep'),
          value: _selectedDeptId.isNotEmpty
              ? '$_selectedDeptId/$_selectedDeptName'
              : '',
          hint: l10n.get('pleaseSelect'),
          onTap: _refDataLoading ? null : () => _showDeptPicker(),
        ),
        const SizedBox(height: 16),
        FormFieldRow(
          label: l10n.get('applicant'),
          required: true,
          value: _selectedApplicantId.isNotEmpty
              ? '$_selectedApplicantId/$_selectedApplicantName'
              : '',
          hint: l10n.get('pleaseSelect'),
          onTap: () => _showApplicantPicker(),
        ),
        const SizedBox(height: 16),
        _label(l10n.get('overtimeReason'), required: true),
        const SizedBox(height: 8),
        TDTextarea(
          controller: _reasonController,
          focusNode: _reasonFocus,
          hintText: l10n.get('enterOvertimeReason'),
          maxLines: 4,
          minLines: 1,
          maxLength: 1000,
          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,
        ),
      ],
    );
  }

  // ═══ 2. 加班明细 ═══
  Widget _buildDetailsSection(AppLocalizations l10n) {
    final colors = Theme.of(context).extension<AppColorsExtension>()!;
    return FormSection(
      title: l10n.get('overtimeDetails'),
      leadingIcon: Icons.access_time_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: 6),
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: colors.bgPage,
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Row(
                  children: [
                    Expanded(
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Row(
                            children: [
                              Expanded(
                                child: Text(
                                  '${d.salNo}${d.salName.isNotEmpty ? '/${d.salName}' : ''}',
                                  maxLines: 1,
                                  overflow: TextOverflow.ellipsis,
                                  style: TextStyle(
                                    fontSize: AppFontSizes.body,
                                    fontWeight: FontWeight.w500,
                                    color: colors.textPrimary,
                                  ),
                                ),
                              ),
                              Text(
                                '${d.jbHours.toStringAsFixed(1)}${l10n.get('hours')}',
                                style: TextStyle(
                                  fontSize: AppFontSizes.body,
                                  fontWeight: FontWeight.w600,
                                  color: colors.timePrimary,
                                ),
                              ),
                            ],
                          ),
                          if (d.jbType.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            _detailLabel(
                              '${l10n.get('jbType')}: ${_jbTypeLabel(d.jbType, l10n)}',
                              colors,
                            ),
                            _detailLabel(
                              '${l10n.get('applyDate')}: ${d.jbDate}',
                              colors,
                            ),
                          ],
                          if (d.startTime.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            Row(
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                Expanded(
                                  child: _detailLabel(
                                    '${l10n.get('startTime')}: ${d.startTime}',
                                    colors,
                                  ),
                                ),
                                if (_dayOfWeekLabel(d.startTime, l10n) != null)
                                  _dayOfWeekTag(
                                    _dayOfWeekLabel(d.startTime, l10n)!,
                                  ),
                              ],
                            ),
                          ],
                          if (d.endTime.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            Row(
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                Expanded(
                                  child: _detailLabel(
                                    '${l10n.get('endTime')}: ${d.endTime}',
                                    colors,
                                  ),
                                ),
                                if (_dayOfWeekLabel(d.endTime, l10n) != null)
                                  _dayOfWeekTag(
                                    _dayOfWeekLabel(d.endTime, l10n)!,
                                  ),
                              ],
                            ),
                          ],
                          if (d.jbDays > 0) ...[
                            const SizedBox(height: 2),
                            _detailLabel(
                              '${l10n.get('overtimeDays')}: ${d.jbDays.toStringAsFixed(1)}',
                              colors,
                            ),
                          ],
                          if (d.attPeriod.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            _detailLabel(
                              '${l10n.get('attPeriod')}: ${d.attPeriod}',
                              colors,
                            ),
                          ],
                          if (d.compensationType.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            _detailLabel(
                              '${l10n.get('compensationType')}: ${_compensationTypeLabel(d.compensationType, l10n)}',
                              colors,
                            ),
                            if (d.compensationType != 'NO_COMPENSATION' &&
                                d.compensationCount > 0)
                              _detailLabel(
                                '${l10n.get('compensationCount')}: ${d.compensationCount.toStringAsFixed(1)}',
                                colors,
                              ),
                          ],
                          if (d.adr.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            _detailLabel(
                              '${l10n.get('adr')}: ${d.adr}',
                              colors,
                            ),
                          ],
                          if (d.reason.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            _detailLabel(
                              '${l10n.get('overtimeDetailReason')}: ${d.reason}',
                              colors,
                            ),
                          ],
                          if (d.rem.isNotEmpty) ...[
                            const SizedBox(height: 2),
                            _detailLabel(
                              '${l10n.get('remark')}: ${d.rem}',
                              colors,
                            ),
                          ],
                        ],
                      ),
                    ),
                    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('totalOvertimeHours'),
                style: TextStyle(
                  fontSize: AppFontSizes.body,
                  fontWeight: FontWeight.w600,
                  color: colors.textPrimary,
                ),
              ),
              Text(
                '${_totalHours().toStringAsFixed(1)}${l10n.get('hours')}',
                style: TextStyle(
                  fontSize: AppFontSizes.subtitle,
                  fontWeight: FontWeight.w700,
                  color: colors.timePrimary,
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }

  double _totalHours() => _details.fold(0.0, (s, d) => s + d.jbHours);

  String _jbTypeLabel(String type, AppLocalizations l10n) {
    switch (type) {
      case 'WORKING_DAY':
        return l10n.get('workingDay');
      case 'REST_DAY':
        return l10n.get('restDay');
      case 'PUBLIC_HOLIDAY':
        return l10n.get('publicHoliday');
      case 'SPECIAL_HOLIDAY':
        return l10n.get('specialHoliday');
      case 'OTHER':
        return l10n.get('other');
      default:
        return type;
    }
  }

  String? _dayOfWeekLabel(String dateTimeStr, AppLocalizations l10n) {
    final dt = DateTime.tryParse(dateTimeStr);
    if (dt == null) return null;
    switch (dt.weekday) {
      case 1:
        return l10n.get('monday');
      case 2:
        return l10n.get('tuesday');
      case 3:
        return l10n.get('wednesday');
      case 4:
        return l10n.get('thursday');
      case 5:
        return l10n.get('friday');
      case 6:
        return l10n.get('saturday');
      case 7:
        return l10n.get('sunday');
      default:
        return null;
    }
  }

  String _compensationTypeLabel(String type, AppLocalizations l10n) {
    switch (type) {
      case 'OVERTIME_PAY':
        return l10n.get('overtimePay');
      case 'COMPENSATORY_LEAVE':
        return l10n.get('compensatoryLeave');
      case 'NO_COMPENSATION':
        return l10n.get('noCompensation');
      case 'OTHER':
        return l10n.get('other');
      default:
        return type;
    }
  }

  Widget _detailLabel(String text, AppColorsExtension colors) {
    return Padding(
      padding: const EdgeInsets.only(top: 2),
      child: Text(
        text,
        maxLines: 2,
        overflow: TextOverflow.ellipsis,
        style: TextStyle(
          fontSize: AppFontSizes.caption,
          color: colors.textSecondary,
        ),
      ),
    );
  }

  Widget _dayOfWeekTag(String label) {
    final tdTheme = TDTheme.of(context);
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 6),
      decoration: BoxDecoration(
        color: tdTheme.brandColor1,
        borderRadius: BorderRadius.circular(4),
      ),
      child: TDText(
        label,
        font: tdTheme.fontBodySmall,
        fontWeight: FontWeight.w500,
        textColor: tdTheme.brandColor7,
      ),
    );
  }

  Future<void> _showDetailDialog({int? editIndex}) async {
    if (_addingDetail) return;
    _addingDetail = true;
    try {
      final l10n = AppLocalizations.of(context);
      OvertimeDetailData? initialData;
      if (editIndex != null) {
        final d = _details[editIndex];
        initialData = OvertimeDetailData(
          jbNo: d.jbNo,
          itm: d.itm > 0 ? d.itm : null,
          salNo: d.salNo,
          salName: d.salName,
          dep: d.dep,
          depName: d.depName,
          jbType: d.jbType,
          jbDate: d.jbDate,
          startTime: d.startTime,
          endTime: d.endTime,
          jbHours: d.jbHours,
          jbDays: d.jbDays,
          attPeriod: d.attPeriod,
          reason: d.reason,
          compensationType: d.compensationType,
          compensationCount: d.compensationCount,
          adr: d.adr,
          rem: d.rem,
        );
      }
      FocusManager.instance.primaryFocus?.unfocus();
      final result = await OvertimeApplyDetailDialog.show(
        // ignore: use_build_context_synchronously
        context,
        api: ref.read(overtimeApplyApiProvider),
        l10n: l10n,
        initialData: initialData,
      );
      if (result != null && mounted) {
        setState(() {
          final item = _DetailItem(
            id: editIndex != null ? _details[editIndex].id : _detailIdCounter++,
            jbNo: result.jbNo,
            itm: result.itm ?? 0,
            salNo: result.salNo,
            salName: result.salName,
            dep: result.dep,
            depName: result.depName,
            jbType: result.jbType,
            jbDate: result.jbDate,
            startTime: result.startTime,
            endTime: result.endTime,
            jbHours: result.jbHours,
            jbDays: result.jbDays,
            attPeriod: result.attPeriod,
            reason: result.reason,
            compensationType: result.compensationType,
            compensationCount: result.compensationCount,
            adr: result.adr,
            rem: result.rem,
          );
          if (editIndex != null) {
            _details[editIndex] = item;
          } else {
            _details.add(item);
          }
        });
      }
    } finally {
      _addingDetail = false;
    }
  }

  // ═══ 3. 底部操作栏 ═══
  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(overtimeApplyApiProvider);
          final billNo = await api.submit(data);
          await DraftStorage.delete(_draftKey);
          if (mounted) {
            LoadingDialog.hide(context);
            TDToast.showSuccess(l10n.get('submitSuccess'), context: context);
            // BillSave 成功后异步提交审核流，不阻塞不弹窗
            if (billNo != null) {
              api.shSubmit(
                bilNo: billNo,
                bilDd: data['HeadData']['JB_DD']?.toString() ?? '',
              );
            }
            GoRouter.of(context).go('/overtime-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() {
    final now = DateTime.now();
    final jbDd =
        '${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';

    return {
      'HeadData': {
        'JB_DD': jbDd,
        'SAL_NO': _selectedApplicantId.isNotEmpty
            ? _selectedApplicantId
            : HostAppChannel.usr,
        'DEP': _selectedDeptId,
        'REASON': _reasonController.text.trim(),
        'REM': _remarkController.text,
        'USR': HostAppChannel.usr,
      },
      'BodyData1': _details.asMap().entries.map((e) {
        final d = e.value;
        return {
          'ITM': e.key + 1,
          'SAL_NO': d.salNo,
          'DEP': d.dep.isNotEmpty ? d.dep : _selectedDeptId,
          'JB_TYPE': d.jbType,
          'JB_DATE': d.jbDate,
          'START_TIME': d.startTime,
          'END_TIME': d.endTime,
          'JB_HOURS': d.jbHours,
          'JB_DAYS': d.jbDays,
          'ATT_PERIOD': d.attPeriod,
          'REASON': d.reason,
          'COMPENSATION_TYPE': d.compensationType,
          'COMPENSATION_COUNT': d.compensationCount,
          'ADR': d.adr,
          'REM': d.rem,
        };
      }).toList(),
    };
  }

  List<String> _validate(AppLocalizations l10n) {
    final e = <String>[];
    if (_reasonController.text.trim().isEmpty) {
      e.add(l10n.get('enterOvertimeReason'));
    }
    if (_details.isEmpty) e.add(l10n.get('addAtLeastOneDetail'));
    if (_selectedDeptId.isEmpty) e.add(l10n.get('selectDept'));
    if (_selectedApplicantId.isEmpty) e.add(l10n.get('selectApplicant'));
    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() =>
      _reasonController.text.isNotEmpty ||
      _details.isNotEmpty ||
      _remarkController.text.isNotEmpty;

  void _clearLocalState() {
    _reasonController.clear();
    _remarkController.clear();
    _details.clear();
    _detailIdCounter = 1;
    _selectedDeptId = '';
    _selectedDeptName = '';
    _selectedApplicantId = '';
    _selectedApplicantName = '';
  }

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

  void _showConfirmDialog(
    String title,
    String content,
    String leftText,
    String rightText,
    VoidCallback onConfirm,
  ) {
    _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();
          },
        ),
      ),
    );
  }

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

  Future<void> _showDeptPicker() async {
    FocusManager.instance.primaryFocus?.unfocus();
    final l10n = AppLocalizations.of(context);
    final api = ref.read(overtimeApplyApiProvider);
    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;
      });
    }
  }
}

class _DetailItem {
  final int id;
  final String? jbNo;
  final int itm;
  final String salNo;
  final String salName;
  final String dep;
  final String depName;
  final String jbType;
  final String jbDate;
  final String startTime;
  final String endTime;
  final double jbHours;
  final double jbDays;
  final String attPeriod;
  final String reason;
  final String compensationType;
  final double compensationCount;
  final String adr;
  final String rem;

  const _DetailItem({
    required this.id,
    this.jbNo,
    this.itm = 0,
    this.salNo = '',
    this.salName = '',
    this.dep = '',
    this.depName = '',
    this.jbType = 'WORKING_DAY',
    this.jbDate = '',
    this.startTime = '',
    this.endTime = '',
    this.jbHours = 0.0,
    this.jbDays = 0.0,
    this.attPeriod = '',
    this.reason = '',
    this.compensationType = '',
    this.compensationCount = 0.0,
    this.adr = '',
    this.rem = '',
  });
}
