import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:superport_v2/core/network/failure.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/common/utils/pagination_utils.dart';

import '../../../inventory/lookups/domain/entities/lookup_item.dart';
import '../../../inventory/lookups/domain/repositories/inventory_lookup_repository.dart';
import '../../domain/entities/approval.dart';
import '../../domain/entities/approval_draft.dart';
import '../../domain/entities/approval_proceed_status.dart';
import '../../domain/entities/approval_template.dart';
import '../../domain/errors/approval_access_denied_exception.dart';
import '../../domain/repositories/approval_repository.dart';
import '../../domain/repositories/approval_template_repository.dart';
import '../../domain/usecases/get_approval_draft_use_case.dart';
import '../../domain/usecases/list_approval_drafts_use_case.dart';
import '../../domain/usecases/save_approval_draft_use_case.dart';

enum ApprovalStatusFilter {
  all,
  draft,
  pending,
  inProgress,
  onHold,
  approved,
  rejected,
}

typedef DateRange = ({DateTime from, DateTime to});

const Map<ApprovalStepActionType, List<String>> _actionAliases = {
  ApprovalStepActionType.approve: ['approve', '승인'],
  ApprovalStepActionType.reject: ['reject', '반려'],
  ApprovalStepActionType.comment: ['comment', '코멘트', '의견'],
};

const Map<ApprovalStatusFilter, String> _defaultStatusCodes = {
  ApprovalStatusFilter.draft: 'draft',
  ApprovalStatusFilter.pending: 'pending',
  ApprovalStatusFilter.inProgress: 'in_progress',
  ApprovalStatusFilter.onHold: 'on_hold',
  ApprovalStatusFilter.approved: 'approved',
  ApprovalStatusFilter.rejected: 'rejected',
};

const List<String> _pendingFallbackStatusCodes = [
  'draft',
  'submitted',
  'in_progress',
];

/// 결재 목록 및 상세 화면 상태 컨트롤러
///
/// - 목록 조회/필터 상태와 선택된 결재 상세 데이터를 관리한다.
/// - 승인/반려 등의 후속 액션은 추후 구현 시 추가한다.
class ApprovalController extends ChangeNotifier {
  ApprovalController({
    required ApprovalRepository approvalRepository,
    required ApprovalTemplateRepository templateRepository,
    InventoryLookupRepository? lookupRepository,
    SaveApprovalDraftUseCase? saveDraftUseCase,
    GetApprovalDraftUseCase? getDraftUseCase,
    ListApprovalDraftsUseCase? listDraftsUseCase,
  }) : _repository = approvalRepository,
       _templateRepository = templateRepository,
       _lookupRepository = lookupRepository,
       _saveDraftUseCase = saveDraftUseCase,
       _getDraftUseCase = getDraftUseCase,
       _listDraftsUseCase = listDraftsUseCase;

  final ApprovalRepository _repository;
  final ApprovalTemplateRepository _templateRepository;
  final InventoryLookupRepository? _lookupRepository;
  final SaveApprovalDraftUseCase? _saveDraftUseCase;
  final GetApprovalDraftUseCase? _getDraftUseCase;
  final ListApprovalDraftsUseCase? _listDraftsUseCase;

  PaginatedResult<Approval>? _result;
  Approval? _selected;
  bool _isLoadingList = false;
  bool _isLoadingDetail = false;
  bool _isLoadingActions = false;
  bool _isSubmitting = false;
  bool _isPerformingAction = false;
  int? _processingStepId;
  bool _isLoadingTemplates = false;
  bool _isApplyingTemplate = false;
  int? _applyingTemplateId;
  ApprovalProceedStatus? _proceedStatus;
  ApprovalSubmissionInput? _submissionDraft;
  String? _errorMessage;
  bool _accessDenied = false;
  String? _accessDeniedMessage;
  ApprovalStatusFilter _statusFilter = ApprovalStatusFilter.all;
  int? _transactionIdFilter;
  int? _requestedById;
  String? _requestedByName;
  String? _requestedByEmployeeNo;
  List<ApprovalAction> _actions = const [];
  List<ApprovalTemplate> _templates = const [];
  final Map<String, LookupItem> _statusLookup = {};
  List<LookupItem> _statusOptions = const [];
  final Map<String, String> _statusCodeAliases = Map.fromEntries(
    _defaultStatusCodes.entries.map(
      (entry) => MapEntry(entry.value, entry.value),
    ),
  );

  PaginatedResult<Approval>? get result => _result;
  Approval? get selected => _selected;
  bool get isLoadingList => _isLoadingList;
  bool get isLoadingDetail => _isLoadingDetail;
  bool get isLoadingActions => _isLoadingActions;
  bool get isSubmitting => _isSubmitting;
  bool get isPerformingAction => _isPerformingAction;
  int? get processingStepId => _processingStepId;
  String? get errorMessage => _errorMessage;
  bool get isAccessDenied => _accessDenied;
  String? get accessDeniedMessage => _accessDeniedMessage;
  ApprovalStatusFilter get statusFilter => _statusFilter;
  int? get transactionIdFilter => _transactionIdFilter;
  int? get requestedById => _requestedById;
  String? get requestedByName => _requestedByName;
  String? get requestedByEmployeeNo => _requestedByEmployeeNo;
  List<ApprovalAction> get actionOptions => _actions;
  bool get hasActionOptions => _actions.isNotEmpty;
  List<ApprovalTemplate> get templates => _templates;
  bool get isLoadingTemplates => _isLoadingTemplates;
  bool get isApplyingTemplate => _isApplyingTemplate;
  int? get applyingTemplateId => _applyingTemplateId;
  ApprovalProceedStatus? get proceedStatus => _proceedStatus;
  bool get canProceedSelected => _proceedStatus?.canProceed ?? true;
  String? get cannotProceedReason {
    final reason = _proceedStatus?.reason?.trim();
    if (reason == null || reason.isEmpty) {
      return null;
    }
    return reason;
  }

  ApprovalSubmissionInput? get submissionDraft => _submissionDraft;
  bool get hasSubmissionDraft => _submissionDraft != null;

  List<LookupItem> get approvalStatusOptions => _statusOptions;

  int? get defaultApprovalStatusId {
    if (_statusOptions.isEmpty) {
      return null;
    }
    final defaultItem = _statusOptions.firstWhere(
      (item) => item.isDefault,
      orElse: () => _statusOptions.first,
    );
    return defaultItem.id;
  }

  LookupItem? approvalStatusById(int? id) {
    if (id == null) {
      return null;
    }
    final lookup = _statusLookup[id.toString()];
    if (lookup != null) {
      return lookup;
    }
    for (final item in _statusOptions) {
      if (item.id == id) {
        return item;
      }
    }
    return null;
  }

  Map<String, LookupItem> get statusLookup => _statusLookup;

  /// 결재 열람 제한 플래그를 초기화한다.
  void acknowledgeAccessDenied() {
    _accessDenied = false;
    _accessDeniedMessage = null;
  }

  /// 필터 조건과 페이지 정보를 기반으로 결재 목록을 조회한다.
  ///
  /// [page]가 1보다 작으면 1페이지로 보정한다. 조회 실패 시 [_errorMessage]에
  /// 예외 메시지를 기록하고, 선택된 상세가 목록에서 사라진 경우 자동으로 선택을 해제한다.
  Future<void> fetch({int page = 1}) async {
    _isLoadingList = true;
    _errorMessage = null;
    notifyListeners();
    try {
      final previous = _result;
      final int resolvedPage;
      if (page < 1) {
        resolvedPage = 1;
      } else if (previous != null && previous.pageSize > 0) {
        final calculated = (previous.total / previous.pageSize).ceil();
        final maxPage = calculated < 1 ? 1 : calculated;
        resolvedPage = page > maxPage ? maxPage : page;
      } else {
        resolvedPage = page;
      }
      final statusId = _statusIdFor(_statusFilter);
      final statusCodes = _statusCodesFor(_statusFilter);
      final response = await _repository.list(
        page: resolvedPage,
        pageSize: _result?.pageSize ?? 20,
        transactionId: _transactionIdFilter,
        approvalStatusId: statusId,
        requestedById: _requestedById,
        statusCodes: statusCodes.isEmpty ? null : statusCodes,
        includePending: _statusFilter == ApprovalStatusFilter.all,
        includeSteps: false,
        includeHistories: false,
      );
      _result = response;
      if (_selected != null) {
        final exists = response.items.any((item) => item.id == _selected?.id);
        if (!exists) {
          _selected = null;
          _proceedStatus = null;
        }
      }
    } catch (error) {
      if (error is ApprovalAccessDeniedException) {
        _accessDenied = true;
        _accessDeniedMessage = error.message;
        _result = null;
        _selected = null;
        _proceedStatus = null;
      } else {
        final failure = Failure.from(error);
        _errorMessage = failure.describe();
      }
    } finally {
      _isLoadingList = false;
      notifyListeners();
    }
  }

  /// 결재 단계에서 사용할 수 있는 행위 목록을 로드한다.
  ///
  /// 이미 데이터가 존재하면 [force]가 `true`일 때만 재조회한다.
  Future<void> loadActionOptions({bool force = false}) async {
    if (_actions.isNotEmpty && !force) {
      return;
    }
    _isLoadingActions = true;
    _errorMessage = null;
    notifyListeners();
    try {
      final items = await _repository.listActions(activeOnly: true);
      _actions = items;
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
    } finally {
      _isLoadingActions = false;
      notifyListeners();
    }
  }

  Future<void> loadStatusLookups() async {
    final repository = _lookupRepository;
    if (repository == null) {
      return;
    }
    try {
      final items = await repository.fetchApprovalStatuses();
      _statusOptions = List.unmodifiable(items);
      _statusLookup
        ..clear()
        ..addEntries(
          items.expand((item) {
            final keys = <String>{};
            final code = item.code?.trim();
            if (code != null && code.isNotEmpty) {
              keys.add(code.toLowerCase());
            }
            final name = item.name.trim();
            if (name.isNotEmpty) {
              keys.add(name.toLowerCase());
            }
            keys.add(item.id.toString());
            return keys.map((key) => MapEntry(key, item));
          }),
        );
      for (final entry in _defaultStatusCodes.entries) {
        final defaultCode = entry.value;
        final normalized = defaultCode.toLowerCase();
        final lookup = _statusLookup[normalized];
        if (lookup != null) {
          final alias = lookup.code?.toLowerCase() ?? normalized;
          _statusCodeAliases[defaultCode] = alias;
        } else {
          _statusCodeAliases[defaultCode] = defaultCode;
        }
      }
      notifyListeners();
    } catch (_) {
      // 실패 시 기본 라벨 사용
    }
  }

  String statusLabel(ApprovalStatusFilter filter) {
    if (filter == ApprovalStatusFilter.all) {
      return '전체 상태 (임시저장·진행 포함)';
    }
    final code = _statusCodeFor(filter);
    if (code != null) {
      final normalized = code.toLowerCase();
      final lookup = _statusLookup[normalized];
      if (lookup != null && lookup.name.isNotEmpty) {
        return lookup.name;
      }
    }
    return switch (filter) {
      ApprovalStatusFilter.pending => '승인대기',
      ApprovalStatusFilter.inProgress => '진행중',
      ApprovalStatusFilter.onHold => '보류',
      ApprovalStatusFilter.approved => '승인완료',
      ApprovalStatusFilter.rejected => '반려',
      ApprovalStatusFilter.draft => '임시저장',
      ApprovalStatusFilter.all => '전체 상태',
    };
  }

  String? _statusCodeFor(ApprovalStatusFilter filter) {
    if (filter == ApprovalStatusFilter.all) {
      return null;
    }
    final defaultCode = _defaultStatusCodes[filter];
    if (defaultCode == null) {
      return null;
    }
    return _statusCodeAliases[defaultCode] ?? defaultCode;
  }

  int? _statusIdFor(ApprovalStatusFilter filter) {
    final code = _statusCodeFor(filter);
    if (code == null) {
      return null;
    }
    final lookup = _statusLookup[code.toLowerCase()];
    return lookup?.id;
  }

  List<String> _statusCodesFor(ApprovalStatusFilter filter) {
    if (filter == ApprovalStatusFilter.all) {
      return const <String>[];
    }
    final code = _statusCodeFor(filter);
    if (filter == ApprovalStatusFilter.pending) {
      if (code == null || code.toLowerCase() == 'pending') {
        return List<String>.unmodifiable(_pendingFallbackStatusCodes);
      }
    }
    if (code == null || code.isEmpty) {
      return const <String>[];
    }
    return List<String>.unmodifiable(<String>[code]);
  }

  /// 활성화된 결재 템플릿 목록을 조회해 캐싱한다.
  ///
  /// 템플릿이 비어 있거나 [force]가 `true`이면 API를 다시 호출한다.
  Future<void> loadTemplates({bool force = false}) async {
    if (_templates.isNotEmpty && !force) {
      return;
    }
    _isLoadingTemplates = true;
    _errorMessage = null;
    notifyListeners();
    try {
      final templates = await fetchAllPaginatedItems<ApprovalTemplate>(
        pageSize: 200,
        request: (page, pageSize) => _templateRepository.list(
          page: page,
          pageSize: pageSize,
          isActive: true,
        ),
      );
      _templates = templates;
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
    } finally {
      _isLoadingTemplates = false;
      notifyListeners();
    }
  }

  /// 특정 결재를 선택하고 상세 정보를 로드한다.
  ///
  /// 상세와 이력, 단계 정보를 모두 포함해 최신 상태를 유지하며, 실패 시
  /// [_errorMessage]로 사용자에게 전달할 메시지를 구성한다.
  Future<void> selectApproval(int id) async {
    _isLoadingDetail = true;
    _errorMessage = null;
    _proceedStatus = null;
    notifyListeners();
    try {
      final detail = await _repository.fetchDetail(
        id,
        includeSteps: true,
        includeHistories: true,
      );
      _selected = detail;
      if (detail.id != null) {
        await _loadProceedStatus(detail.id!);
      }
    } catch (error) {
      if (error is ApprovalAccessDeniedException) {
        _accessDenied = true;
        _accessDeniedMessage = error.message;
        _selected = null;
        _proceedStatus = null;
      } else {
        final failure = Failure.from(error);
        debugPrint(
          '[ApprovalController] 결재 상세 조회 실패: ${failure.describe()}',
        ); // 에러 발생 시 콘솔에 남겨 즉시 파악할 수 있도록 한다.
        _errorMessage = failure.describe();
      }
    } finally {
      _isLoadingDetail = false;
      notifyListeners();
    }
  }

  /// 선택된 결재 상세를 비우고 화면을 초기화한다.
  void clearSelection() {
    _selected = null;
    _proceedStatus = null;
    notifyListeners();
  }

  /// 결재 상신 초안을 보관한다.
  void cacheSubmissionDraft(ApprovalSubmissionInput draft) {
    _submissionDraft = draft;
    notifyListeners();
    _persistSubmissionDraft(draft);
  }

  /// 저장된 결재 상신 초안을 반환하고 초기화한다.
  ApprovalSubmissionInput? consumeSubmissionDraft() {
    final draft = _submissionDraft;
    if (draft == null) {
      return null;
    }
    _submissionDraft = null;
    notifyListeners();
    return draft;
  }

  /// 결재 상신 초안을 초기화한다.
  void clearSubmissionDraft() {
    if (_submissionDraft == null) {
      return;
    }
    _submissionDraft = null;
    notifyListeners();
  }

  Future<ApprovalSubmissionInput?> restoreSubmissionDraft({
    required int requesterId,
    int? transactionId,
  }) async {
    final listUseCase = _listDraftsUseCase;
    final getUseCase = _getDraftUseCase;
    if (listUseCase == null || getUseCase == null) {
      return null;
    }
    try {
      final filter = ApprovalDraftListFilter(
        requesterId: requesterId,
        transactionId: transactionId,
        pageSize: 10,
      );
      final result = await listUseCase.call(filter);
      if (result.items.isEmpty) {
        return null;
      }
      final sessionKey = _submissionSessionKey(requesterId);
      final summary = result.items.firstWhere(
        (item) => item.sessionKey == sessionKey,
        orElse: () => result.items.first,
      );
      final detail = await getUseCase.call(
        id: summary.id,
        requesterId: requesterId,
      );
      if (detail == null) {
        return null;
      }
      final submission = detail.toSubmissionInput(
        defaultStatusId: _defaultSubmissionStatusId(),
        transactionIdOverride: transactionId ?? detail.transactionId,
      );
      _submissionDraft = submission;
      notifyListeners();
      return submission;
    } catch (error, stackTrace) {
      debugPrint('[ApprovalController] 초안 복구 실패: $error\n$stackTrace');
      return null;
    }
  }

  void _persistSubmissionDraft(ApprovalSubmissionInput draft) {
    final useCase = _saveDraftUseCase;
    if (useCase == null) {
      return;
    }
    if (draft.steps.isEmpty) {
      return;
    }
    final input = _buildSubmissionDraftInput(draft);
    if (!input.hasSteps) {
      return;
    }
    unawaited(
      Future<void>(() async {
        try {
          await useCase.call(input);
        } catch (error, stackTrace) {
          debugPrint('[ApprovalController] 초안 저장 실패: $error\n$stackTrace');
        }
      }),
    );
  }

  ApprovalDraftSaveInput _buildSubmissionDraftInput(
    ApprovalSubmissionInput draft,
  ) {
    final steps = draft.steps
        .map(
          (step) => ApprovalDraftStep(
            stepOrder: step.stepOrder,
            approverId: step.approverId,
            note: step.note,
          ),
        )
        .toList(growable: false);
    return ApprovalDraftSaveInput(
      requesterId: draft.requesterId,
      transactionId: draft.transactionId,
      templateId: draft.templateId,
      title: draft.title,
      summary: draft.summary,
      note: draft.note,
      metadata: draft.metadata,
      sessionKey: _submissionSessionKey(draft.requesterId),
      statusId: draft.statusId,
      steps: steps,
    );
  }

  int? _defaultSubmissionStatusId() {
    final pendingId = _statusIdFor(ApprovalStatusFilter.pending);
    if (pendingId != null && pendingId > 0) {
      return pendingId;
    }
    final draftId = _statusIdFor(ApprovalStatusFilter.draft);
    if (draftId != null && draftId > 0) {
      return draftId;
    }
    return null;
  }

  String _submissionSessionKey(int requesterId) =>
      'approval_submission_$requesterId';

  /// 결재를 생성하고 목록/상세 상태를 최신화한다.
  Future<Approval?> createApproval(ApprovalCreateInput input) async {
    _setSubmitting(true);
    _errorMessage = null;
    try {
      final created = await _repository.create(input);
      await fetch(page: 1);
      _selected = created;
      if (created.id != null) {
        await _loadProceedStatus(created.id!);
      }
      notifyListeners();
      return created;
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
      notifyListeners();
      return null;
    } finally {
      _setSubmitting(false);
    }
  }

  /// 결재 기본 정보를 수정하고 현재 페이지를 유지한다.
  Future<Approval?> updateApproval(ApprovalUpdateInput input) async {
    _setSubmitting(true);
    _errorMessage = null;
    try {
      final updated = await _repository.update(input);
      final currentPage = _result?.page ?? 1;
      await fetch(page: currentPage);
      _selected = updated;
      if (updated.id != null) {
        await _loadProceedStatus(updated.id!);
      }
      notifyListeners();
      return updated;
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
      notifyListeners();
      return null;
    } finally {
      _setSubmitting(false);
    }
  }

  /// 결재 단계에 대해 승인/반려/코멘트 등 지정된 행위를 수행한다.
  ///
  /// - 유효한 단계 ID와 액션이 존재해야 하며, 실행 중에는 중복 호출을 방지한다.
  /// - API 호출이 성공하면 목록과 상세 상태를 동기화하고, 실패 시 오류 메시지를 기록한다.
  Future<bool> performStepAction({
    required ApprovalStep step,
    required ApprovalStepActionType type,
    String? note,
  }) async {
    final approvalId = _selected?.id;
    if (approvalId == null) {
      _errorMessage = '선택한 결재 정보가 없어 단계를 처리할 수 없습니다.';
      notifyListeners();
      return false;
    }
    if (step.id == null) {
      _errorMessage = '단계 식별자가 없어 행위를 수행할 수 없습니다.';
      notifyListeners();
      return false;
    }
    final action = _findActionByType(type);
    if (action == null) {
      _errorMessage = '사용 가능한 결재 행위를 찾을 수 없습니다.';
      notifyListeners();
      return false;
    }

    _isPerformingAction = true;
    _processingStepId = step.id;
    _errorMessage = null;
    notifyListeners();
    try {
      final proceedStatus = await _repository.canProceed(approvalId);
      _proceedStatus = proceedStatus;
      if (!proceedStatus.canProceed) {
        _errorMessage = proceedStatus.reason ?? '결재 단계가 현재 상태에서 진행될 수 없습니다.';
        return false;
      }

      final sanitizedNote = note?.trim();
      final updated = await _repository.performStepAction(
        ApprovalStepActionInput(
          stepId: step.id!,
          actionId: action.id,
          note: sanitizedNote?.isEmpty ?? true ? null : sanitizedNote,
        ),
      );
      _selected = updated;
      if (_result != null && updated.id != null) {
        final items = _result!.items
            .map((item) => item.id == updated.id ? updated : item)
            .toList();
        _result = _result!.copyWith(items: items);
      }
      if (updated.id != null) {
        await _loadProceedStatus(updated.id!);
      } else {
        await _loadProceedStatus(approvalId);
      }
      return true;
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
      return false;
    } finally {
      _isPerformingAction = false;
      _processingStepId = null;
      notifyListeners();
    }
  }

  /// 템플릿 단계 정보를 현재 결재에 덮어씌운다.
  ///
  /// 선택된 결재가 없거나 템플릿에 등록된 단계가 없으면 즉시 실패로 처리한다.
  Future<bool> applyTemplate(int templateId) async {
    final approvalId = _selected?.id;
    if (approvalId == null) {
      _errorMessage = '선택된 결재가 없어 템플릿을 적용할 수 없습니다.';
      notifyListeners();
      return false;
    }

    _isApplyingTemplate = true;
    _applyingTemplateId = templateId;
    _errorMessage = null;
    notifyListeners();
    try {
      final template = await _templateRepository.fetchDetail(
        templateId,
        includeSteps: true,
      );
      if (template.steps.isEmpty) {
        _errorMessage = '선택한 템플릿에 등록된 단계가 없습니다.';
        return false;
      }

      final sortedSteps = List.of(template.steps)
        ..sort((a, b) => a.stepOrder.compareTo(b.stepOrder));
      final input = ApprovalStepAssignmentInput(
        approvalId: approvalId,
        steps: sortedSteps
            .map(
              (step) => ApprovalStepAssignmentItem(
                stepOrder: step.stepOrder,
                approverId: step.approver.id,
                note: step.note,
              ),
            )
            .toList(),
      );
      final updated = await _repository.assignSteps(input);
      _selected = updated;
      if (_result != null && updated.id != null) {
        final items = _result!.items
            .map((item) => item.id == updated.id ? updated : item)
            .toList();
        _result = _result!.copyWith(items: items);
      }
      return true;
    } catch (error) {
      final failure = Failure.from(error);
      _errorMessage = failure.describe();
      return false;
    } finally {
      _isApplyingTemplate = false;
      _applyingTemplateId = null;
      notifyListeners();
    }
  }

  /// 상태 필터 값을 변경한다.
  void updateStatusFilter(ApprovalStatusFilter filter) {
    _statusFilter = filter;
    notifyListeners();
  }

  /// 트랜잭션 ID 필터를 갱신한다. null이면 조건을 제거한다.
  void updateTransactionFilter(int? transactionId) {
    _transactionIdFilter = transactionId;
    notifyListeners();
  }

  /// 상신자(요청자) 필터를 갱신한다. null 값을 전달하면 조건을 제거한다.
  void updateRequestedByFilter({int? id, String? name, String? employeeNo}) {
    _requestedById = id;
    _requestedByName = name;
    _requestedByEmployeeNo = employeeNo;
    notifyListeners();
  }

  /// 상태/트랜잭션/상신자 필터를 초기값으로 되돌린다.
  void clearFilters() {
    _statusFilter = ApprovalStatusFilter.all;
    _transactionIdFilter = null;
    _requestedById = null;
    _requestedByName = null;
    _requestedByEmployeeNo = null;
    notifyListeners();
  }

  /// 사용자에게 노출 중인 오류 메시지를 초기화한다.
  void clearError() {
    _errorMessage = null;
    notifyListeners();
  }

  void _setSubmitting(bool value) {
    if (_isSubmitting == value) {
      return;
    }
    _isSubmitting = value;
    notifyListeners();
  }

  /// 액션 타입과 동일한 코드(또는 별칭)를 가진 결재 행위를 찾는다.
  ApprovalAction? _findActionByType(ApprovalStepActionType type) {
    final aliases = _actionAliases[type] ?? [type.code];
    for (final action in _actions) {
      final normalized = action.name.toLowerCase();
      for (final alias in aliases) {
        if (normalized == alias.toLowerCase()) {
          return action;
        }
      }
    }
    return null;
  }

  Future<void> _loadProceedStatus(int approvalId) async {
    try {
      final status = await _repository.canProceed(approvalId);
      _proceedStatus = status;
    } catch (error) {
      _proceedStatus = null;
      final failure = Failure.from(error);
      _errorMessage ??= failure.describe();
    }
  }
}
