import 'dart:collection';

import 'approval.dart';

/// 결재 초안 상태를 표현하는 열거형.
enum ApprovalDraftStatus { active, expired, archived }

/// 결재 초안 단계 정보를 나타낸다.
class ApprovalDraftStep {
  ApprovalDraftStep({
    required this.stepOrder,
    required this.approverId,
    this.approverRole,
    this.note,
    this.isOptional = false,
  });

  final int stepOrder;
  final int approverId;
  final String? approverRole;
  final String? note;
  final bool isOptional;

  ApprovalStepAssignmentItem toAssignment() {
    return ApprovalStepAssignmentItem(
      stepOrder: stepOrder,
      approverId: approverId,
      note: note,
    );
  }

  Map<String, dynamic> toJson() {
    final trimmedNote = note?.trim();
    return {
      'step_order': stepOrder,
      'approver_id': approverId,
      if (approverRole != null && approverRole!.trim().isNotEmpty)
        'approver_role': approverRole,
      if (trimmedNote != null && trimmedNote.isNotEmpty) 'note': trimmedNote,
      'is_optional': isOptional,
    };
  }
}

/// 결재 초안 본문을 나타낸다.
class ApprovalDraftPayload {
  ApprovalDraftPayload({
    this.title,
    this.summary,
    this.note,
    this.templateId,
    Map<String, dynamic>? metadata,
    List<ApprovalDraftStep>? steps,
  }) : metadata = metadata == null
           ? null
           : Map.unmodifiable(Map<String, dynamic>.from(metadata)),
       _steps = steps == null
           ? const []
           : List<ApprovalDraftStep>.unmodifiable(steps);

  final String? title;
  final String? summary;
  final String? note;
  final int? templateId;
  final Map<String, dynamic>? metadata;
  final List<ApprovalDraftStep> _steps;

  UnmodifiableListView<ApprovalDraftStep> get steps =>
      UnmodifiableListView(_steps);

  List<ApprovalStepAssignmentItem> toAssignments() {
    return _steps.map((step) => step.toAssignment()).toList(growable: false);
  }
}

/// 결재 초안 요약 정보를 담는다.
class ApprovalDraftSummary {
  ApprovalDraftSummary({
    required this.id,
    required this.requesterId,
    required this.status,
    required this.savedAt,
    this.requestId,
    this.transactionId,
    this.templateId,
    this.title,
    this.summary,
    this.expiresAt,
    this.sessionKey,
    this.stepCount = 0,
  });

  final int id;
  final int requesterId;
  final ApprovalDraftStatus status;
  final DateTime savedAt;
  final int? requestId;
  final int? transactionId;
  final int? templateId;
  final String? title;
  final String? summary;
  final DateTime? expiresAt;
  final String? sessionKey;
  final int stepCount;
}

/// 결재 초안 상세 정보를 나타낸다.
class ApprovalDraftDetail {
  ApprovalDraftDetail({
    required this.id,
    required this.requesterId,
    required this.payload,
    required this.savedAt,
    this.transactionId,
    this.templateId,
    this.expiresAt,
    this.sessionKey,
  });

  final int id;
  final int requesterId;
  final ApprovalDraftPayload payload;
  final DateTime savedAt;
  final int? transactionId;
  final int? templateId;
  final DateTime? expiresAt;
  final String? sessionKey;

  Map<String, dynamic>? get sanitizedMetadata =>
      _stripClientState(payload.metadata);

  ApprovalSubmissionInput toSubmissionInput({
    int? defaultStatusId,
    int? transactionIdOverride,
  }) {
    final statusId = _extractStatusId(payload.metadata) ?? defaultStatusId ?? 0;
    final assignments = payload.toAssignments();
    final cleanedMetadata = _stripClientState(payload.metadata);
    return ApprovalSubmissionInput(
      transactionId: transactionIdOverride ?? transactionId,
      templateId: payload.templateId ?? templateId,
      statusId: statusId,
      requesterId: requesterId,
      finalApproverId: assignments.isEmpty ? null : assignments.last.approverId,
      title: payload.title,
      summary: payload.summary,
      note: payload.note,
      metadata: cleanedMetadata,
      steps: assignments,
    );
  }
}

/// 결재 초안 목록 필터.
class ApprovalDraftListFilter {
  const ApprovalDraftListFilter({
    required this.requesterId,
    this.page = 1,
    this.pageSize = 20,
    this.transactionId,
    this.includeExpired = false,
  }) : assert(page > 0, 'page는 1 이상이어야 합니다.');

  final int requesterId;
  final int page;
  final int pageSize;
  final int? transactionId;
  final bool includeExpired;

  Map<String, dynamic> toQuery() {
    return {
      'page': page,
      'page_size': pageSize,
      'requester_id': requesterId,
      if (transactionId != null) 'transaction_id': transactionId,
      if (includeExpired) 'include_expired': includeExpired,
    };
  }
}

/// 결재 초안 저장 입력 모델.
class ApprovalDraftSaveInput {
  ApprovalDraftSaveInput({
    required this.requesterId,
    required List<ApprovalDraftStep> steps,
    this.requestId,
    this.transactionId,
    this.templateId,
    this.title,
    this.summary,
    this.note,
    Map<String, dynamic>? metadata,
    this.sessionKey,
    this.statusId,
  }) : metadata = metadata == null
           ? null
           : Map.unmodifiable(Map<String, dynamic>.from(metadata)),
       steps = List<ApprovalDraftStep>.unmodifiable(steps);

  final int requesterId;
  final List<ApprovalDraftStep> steps;
  final int? requestId;
  final int? transactionId;
  final int? templateId;
  final String? title;
  final String? summary;
  final String? note;
  final Map<String, dynamic>? metadata;
  final String? sessionKey;
  final int? statusId;

  bool get hasSteps => steps.isNotEmpty;

  Map<String, dynamic> toJson() {
    final payload = <String, dynamic>{
      'requester_id': requesterId,
      if (requestId != null) 'request_id': requestId,
      if (transactionId != null) 'transaction_id': transactionId,
      if (templateId != null) 'template_id': templateId,
      if (title != null && title!.trim().isNotEmpty) 'title': title,
      if (summary != null && summary!.trim().isNotEmpty) 'summary': summary,
      if (note != null && note!.trim().isNotEmpty) 'note': note,
      if (sessionKey != null && sessionKey!.trim().isNotEmpty)
        'session_key': sessionKey,
    };
    final mergedMetadata = _mergeStatus(source: metadata, statusId: statusId);
    if (mergedMetadata != null && mergedMetadata.isNotEmpty) {
      payload['metadata'] = mergedMetadata;
    }
    payload['steps'] = steps
        .map((step) => step.toJson())
        .toList(growable: false);
    return payload;
  }
}

const _clientStateKey = '_client_state';
const _statusKey = 'status_id';

Map<String, dynamic>? _mergeStatus({
  Map<String, dynamic>? source,
  int? statusId,
}) {
  if (statusId == null) {
    return source;
  }
  final merged = source == null
      ? <String, dynamic>{}
      : Map<String, dynamic>.from(source);
  final client = merged[_clientStateKey];
  final state = client is Map<String, dynamic>
      ? Map<String, dynamic>.from(client)
      : <String, dynamic>{};
  state[_statusKey] = statusId;
  merged[_clientStateKey] = state;
  return merged;
}

int? _extractStatusId(Map<String, dynamic>? metadata) {
  if (metadata == null || metadata.isEmpty) {
    return null;
  }
  final client = metadata[_clientStateKey];
  if (client is Map<String, dynamic>) {
    final value = client[_statusKey];
    if (value is int) {
      return value;
    }
    if (value is String) {
      return int.tryParse(value);
    }
  }
  return null;
}

Map<String, dynamic>? _stripClientState(Map<String, dynamic>? metadata) {
  if (metadata == null || metadata.isEmpty) {
    return metadata;
  }
  if (!metadata.containsKey(_clientStateKey)) {
    return metadata;
  }
  final cloned = Map<String, dynamic>.from(metadata);
  cloned.remove(_clientStateKey);
  return cloned.isEmpty ? null : cloned;
}
