import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:go_router/go_router.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart' as lucide;
import 'package:shadcn_ui/shadcn_ui.dart';

import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/core/constants/app_sections.dart';
import 'package:superport_v2/widgets/app_layout.dart';
import 'package:superport_v2/widgets/components/feedback.dart';
import 'package:superport_v2/widgets/components/filter_bar.dart';
import 'package:superport_v2/widgets/components/form_field.dart';
import 'package:superport_v2/widgets/components/empty_state.dart';
import 'package:superport_v2/widgets/components/responsive.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';
import 'package:superport_v2/widgets/components/superport_date_picker.dart';
import 'package:superport_v2/features/inventory/shared/widgets/partner_select_field.dart';
import 'package:superport_v2/features/inventory/shared/widgets/product_autocomplete_field.dart';
import 'package:superport_v2/features/inventory/shared/widgets/warehouse_select_field.dart';
import 'package:superport_v2/features/approvals/request/presentation/controllers/approval_request_controller.dart';
import 'package:superport_v2/features/approvals/request/presentation/utils/approval_form_initializer.dart';
import 'package:superport_v2/features/approvals/request/presentation/widgets/approval_step_configurator.dart';
import 'package:superport_v2/features/approvals/request/presentation/widgets/approval_template_picker.dart';
import 'package:superport_v2/features/approvals/domain/usecases/get_approval_draft_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/list_approval_drafts_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/save_approval_draft_use_case.dart';
import 'package:superport_v2/core/config/environment.dart';
import 'package:superport_v2/core/network/failure.dart';
import 'package:superport_v2/core/permissions/permission_manager.dart';
import 'package:superport_v2/core/permissions/permission_resources.dart';
import 'package:superport_v2/features/inventory/inbound/presentation/controllers/inbound_controller.dart';
import 'package:superport_v2/features/inventory/inbound/presentation/models/inbound_record.dart';
import 'package:superport_v2/features/inventory/inbound/presentation/specs/inbound_table_spec.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction_input.dart';
import 'package:superport_v2/features/inventory/transactions/domain/repositories/stock_transaction_repository.dart';
import 'package:superport_v2/features/inventory/transactions/presentation/services/transaction_detail_sync_service.dart';
import 'package:superport_v2/features/auth/application/auth_service.dart';
import 'package:superport_v2/features/inventory/transactions/presentation/widgets/transaction_detail_dialog.dart';
import 'package:superport_v2/features/inventory/shared/widgets/employee_autocomplete_field.dart'
    show InventoryEmployeeSuggestion;
import '../../../lookups/domain/entities/lookup_item.dart';
import '../../../lookups/domain/repositories/inventory_lookup_repository.dart';
import '../widgets/inbound_detail_view.dart';

const String _inboundTransactionTypeId = '입고';

class _InboundDetailSections {
  static const lines = 'lines';
}

/// 입고 목록과 상세/등록 모달을 관리하는 화면.
class InboundPage extends StatefulWidget {
  const InboundPage({super.key, required this.routeUri});

  final Uri routeUri;

  @override
  State<InboundPage> createState() => _InboundPageState();
}

/// 입고 화면의 상태와 사용자 상호작용을 제어한다.
class _InboundPageState extends State<InboundPage> {
  final TextEditingController _searchController = TextEditingController();
  final DateFormat _dateFormatter = DateFormat('yyyy-MM-dd');
  final TransactionDetailSyncService _detailSyncService =
      const TransactionDetailSyncService();
  final NumberFormat _currencyFormatter = NumberFormat.currency(
    locale: 'ko_KR',
    symbol: '₩',
    decimalDigits: 0,
  );

  String _query = '';
  String _pendingQuery = '';
  int? _appliedWarehouseId;
  int? _pendingWarehouseId;
  String? _appliedWarehouseLabel;
  String? _pendingWarehouseLabel;
  String? _appliedStatus;
  String? _pendingStatus;
  DateTimeRange? _appliedDateRange;
  DateTimeRange? _pendingDateRange;
  List<InboundRecord> _records = [];
  InboundRecord? _selectedRecord;

  PaginatedResult<StockTransaction>? _result;
  bool _isLoading = false;
  String? _errorMessage;
  Set<int> _processingTransactionIds = {};

  String? _routeSelectedNumber;
  String? _pendingDetailNumber;
  bool _suppressNextRouteSelection = false;

  late List<String> _statusOptions;
  final Map<String, LookupItem> _statusLookup = {};
  LookupItem? _transactionTypeLookup;
  Set<String> _appliedIncludes = {...InboundTableSpec.defaultIncludeOptions};
  Set<String> _pendingIncludes = {...InboundTableSpec.defaultIncludeOptions};
  late final ShadSelectController<String> _includeController;

  int _currentPage = 1;
  int _pageSize = InboundTableSpec.pageSizeOptions.first;
  _InboundSortField _sortField = _InboundSortField.processedAt;
  bool _sortAscending = false;
  InboundController? _controller;

  bool get _transitionsEnabled =>
      Environment.flag('FEATURE_STOCK_TRANSITIONS_ENABLED', defaultValue: true);

  @override
  void initState() {
    super.initState();
    _includeController = ShadSelectController<String>(
      initialValue: _pendingIncludes.toSet(),
    );
    _statusOptions = List<String>.from(InboundTableSpec.fallbackStatusOptions);
    _controller = _createController();
    _controller?.addListener(_handleControllerChanged);
    _applyRouteParameters(widget.routeUri, initialize: true);
    _initializeController();
  }

  InboundController? _createController() {
    final getIt = GetIt.I;
    if (!getIt.isRegistered<StockTransactionRepository>() ||
        !getIt.isRegistered<TransactionLineRepository>() ||
        !getIt.isRegistered<TransactionCustomerRepository>() ||
        !getIt.isRegistered<InventoryLookupRepository>()) {
      return null;
    }
    return InboundController(
      transactionRepository: getIt<StockTransactionRepository>(),
      lineRepository: getIt<TransactionLineRepository>(),
      customerRepository: getIt<TransactionCustomerRepository>(),
      lookupRepository: getIt<InventoryLookupRepository>(),
      fallbackStatusOptions: InboundTableSpec.fallbackStatusOptions,
      transactionTypeKeywords: InboundTableSpec.transactionTypeKeywords,
      saveDraftUseCase: getIt.isRegistered<SaveApprovalDraftUseCase>()
          ? getIt<SaveApprovalDraftUseCase>()
          : null,
      getDraftUseCase: getIt.isRegistered<GetApprovalDraftUseCase>()
          ? getIt<GetApprovalDraftUseCase>()
          : null,
      listDraftsUseCase: getIt.isRegistered<ListApprovalDraftsUseCase>()
          ? getIt<ListApprovalDraftsUseCase>()
          : null,
    );
  }

  void _initializeController() {
    final controller = _controller;
    if (controller == null) {
      return;
    }
    Future.microtask(() async {
      await controller.loadStatusOptions();
      await controller.loadApprovalStatuses();
      final requester = _resolveCurrentWriter();
      if (requester != null && _canRestoreApprovalDrafts) {
        await controller.loadApprovalDraftFromServer(requesterId: requester.id);
      }
      final hasType = await controller.resolveTransactionType();
      if (!mounted) {
        return;
      }
      if (hasType) {
        await _fetchTransactions(page: _currentPage);
      }
    });
  }

  bool get _canRestoreApprovalDrafts {
    final getIt = GetIt.I;
    if (!getIt.isRegistered<PermissionManager>()) {
      return false;
    }
    return getIt<PermissionManager>().can(
      PermissionResources.approvals,
      PermissionAction.view,
    );
  }

  void _handleControllerChanged() {
    if (!mounted) {
      return;
    }
    final controller = _controller;
    if (controller == null) {
      return;
    }
    final statusSource = controller.statusOptions.isEmpty
        ? controller.fallbackStatusOptions
        : controller.statusOptions;
    final resolvedStatusOptions = List<String>.from(statusSource);
    final resolvedLookup = Map<String, LookupItem>.from(
      controller.statusLookup,
    );

    var pendingStatus = _pendingStatus;
    if (pendingStatus != null &&
        !resolvedStatusOptions.contains(pendingStatus)) {
      pendingStatus = null;
    }
    var appliedStatus = _appliedStatus;
    if (appliedStatus != null &&
        !resolvedStatusOptions.contains(appliedStatus)) {
      appliedStatus = null;
    }

    setState(() {
      _statusOptions = resolvedStatusOptions.isEmpty
          ? List<String>.from(InboundTableSpec.fallbackStatusOptions)
          : resolvedStatusOptions;
      _statusLookup
        ..clear()
        ..addAll(resolvedLookup);
      _pendingStatus = pendingStatus;
      _appliedStatus = appliedStatus;
      _result = controller.result;
      _records = controller.records.toList();
      _isLoading = controller.isLoading;
      _errorMessage = controller.errorMessage;
      _processingTransactionIds = Set<int>.from(
        controller.processingTransactionIds,
      );
      _transactionTypeLookup =
          controller.transactionType ?? _transactionTypeLookup;
      _refreshSelection();
    });
    final detailNumber = _pendingDetailNumber;
    if (detailNumber != null) {
      _pendingDetailNumber = null;
      InboundRecord? matched;
      for (final record in _records) {
        if (record.transactionNumber == detailNumber) {
          matched = record;
          break;
        }
      }
      if (matched != null) {
        final target = matched;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          if (!mounted) {
            return;
          }
          _showDetailDialog(target);
        });
      }
    }
  }

  @override
  void didUpdateWidget(covariant InboundPage oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.routeUri != widget.routeUri) {
      _applyRouteParameters(widget.routeUri);
    }
  }

  @override
  void dispose() {
    _controller?.removeListener(_handleControllerChanged);
    _controller?.dispose();
    _searchController.dispose();
    _includeController.dispose();
    super.dispose();
  }

  Future<void> _fetchTransactions({int? page}) async {
    final controller = _controller;
    final lookup = controller?.transactionType ?? _transactionTypeLookup;
    if (controller == null || lookup == null) {
      return;
    }

    final targetPage = page ?? _currentPage;
    final includes = _appliedIncludes.isEmpty
        ? InboundTableSpec.defaultIncludeOptions.toList(growable: false)
        : _appliedIncludes.toList(growable: false);
    final statusItem = _appliedStatus == null
        ? null
        : _statusLookup[_appliedStatus!];

    setState(() {
      _currentPage = targetPage;
      _transactionTypeLookup = lookup;
    });

    final filter = StockTransactionListFilter(
      page: targetPage,
      pageSize: _pageSize,
      query: _query.isEmpty ? null : _query,
      transactionTypeId: lookup.id,
      transactionStatusId: statusItem != null && statusItem.id > 0
          ? statusItem.id
          : null,
      from: _appliedDateRange?.start,
      to: _appliedDateRange?.end,
      include: includes,
    );

    await controller.fetchTransactions(filter: filter);
  }

  @override
  Widget build(BuildContext context) {
    final theme = ShadTheme.of(context);
    final filtered = _filteredRecords;
    final responsive = ResponsiveBreakpoints.of(context);
    final totalItems = _result?.total ?? filtered.length;
    final totalPages = _calculateTotalPages(totalItems);
    final int currentPage = totalPages <= 1
        ? 1
        : (_currentPage < 1
              ? 1
              : (_currentPage > totalPages ? totalPages : _currentPage));
    final visibleRecords = filtered;

    return AppLayout(
      title: '입고 관리',
      subtitle: '입고 처리, 라인 품목, 상태를 한 화면에서 확인하고 관리합니다.',
      breadcrumbs: const [
        AppBreadcrumbItem(label: '대시보드', path: dashboardRoutePath),
        AppBreadcrumbItem(label: '입·출고', path: '/inventory/inbound'),
        AppBreadcrumbItem(label: '입고'),
      ],
      actions: [
        PermissionGate(
          resource: PermissionResources.stockTransactions,
          action: PermissionAction.create,
          child: ShadButton(
            leading: const Icon(lucide.LucideIcons.plus, size: 16),
            onPressed: _handleCreate,
            child: const Text('입고 등록'),
          ),
        ),
        PermissionGate(
          resource: PermissionResources.stockTransactions,
          action: PermissionAction.edit,
          child: ShadButton.outline(
            leading: const Icon(lucide.LucideIcons.pencil, size: 16),
            onPressed: _selectedRecord == null
                ? null
                : () => _handleEdit(_selectedRecord!),
            child: const Text('선택 항목 수정'),
          ),
        ),
      ],
      toolbar: FilterBar(
        actionConfig: FilterBarActionConfig(
          onApply: _applyFilters,
          onReset: _resetFilters,
          hasPendingChanges: _hasDirtyFilters,
          hasActiveFilters: _hasAppliedFilters,
        ),
        children: [
          SizedBox(
            width: 260,
            child: ShadInput(
              controller: _searchController,
              placeholder: const Text(InboundTableSpec.searchPlaceholder),
              leading: const Icon(lucide.LucideIcons.search, size: 16),
              onChanged: (_) {
                setState(() {
                  _pendingQuery = _searchController.text;
                });
              },
              onSubmitted: (_) => _applyFilters(),
            ),
          ),
          SizedBox(
            width: 220,
            child: SuperportDateRangePickerButton(
              value: _pendingDateRange,
              dateFormat: _dateFormatter,
              onChanged: (range) => setState(() => _pendingDateRange = range),
              firstDate: InboundTableSpec.dateRangeFirstDate,
              lastDate: InboundTableSpec.dateRangeLastDate,
            ),
          ),
          SizedBox(
            width: 200,
            child: InventoryWarehouseSelectField(
              key: ValueKey(_pendingWarehouseId ?? 'all'),
              initialWarehouseId: _pendingWarehouseId,
              includeAllOption: true,
              allLabel: InboundTableSpec.allWarehouseLabel,
              placeholder: const Text(InboundTableSpec.allWarehouseLabel),
              onChanged: (option) {
                setState(() {
                  _pendingWarehouseId = option?.id;
                  _pendingWarehouseLabel = option?.name;
                });
              },
            ),
          ),
          SizedBox(
            width: 200,
            child: ShadSelect<String?>(
              key: ValueKey(_pendingStatus ?? 'all'),
              initialValue: _pendingStatus,
              selectedOptionBuilder: (_, value) =>
                  Text(value ?? InboundTableSpec.allStatusLabel),
              onChanged: (value) {
                setState(() => _pendingStatus = value);
              },
              options: [
                ShadOption<String?>(
                  value: null,
                  child: Text(InboundTableSpec.allStatusLabel),
                ),
                for (final option in _statusOptions)
                  ShadOption<String?>(value: option, child: Text(option)),
              ],
            ),
          ),
          SizedBox(
            width: 180,
            child: ShadSelect<_InboundSortField>(
              key: ValueKey(_sortField),
              initialValue: _sortField,
              selectedOptionBuilder: (_, value) => Text(_sortLabel(value)),
              onChanged: (value) {
                if (value == null) return;
                setState(() {
                  _sortField = value;
                  _currentPage = 1;
                });
                _updateRoute(page: 1);
              },
              options: [
                for (final option in _InboundSortField.values)
                  ShadOption(value: option, child: Text(_sortLabel(option))),
              ],
            ),
          ),
          SizedBox(
            width: 220,
            child: ShadSelect<String>.multiple(
              key: ValueKey(_pendingIncludes.hashCode),
              controller: _includeController,
              initialValues: _pendingIncludes,
              placeholder: const Text(InboundTableSpec.includeEmptyLabel),
              selectedOptionsBuilder: (context, values) {
                if (values.isEmpty) {
                  return const Text(InboundTableSpec.includeEmptyLabel);
                }
                return Wrap(
                  spacing: 6,
                  runSpacing: 6,
                  children: [
                    for (final value in values)
                      ShadBadge(
                        child: Padding(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 8,
                            vertical: 4,
                          ),
                          child: Text(InboundTableSpec.includeLabel(value)),
                        ),
                      ),
                  ],
                );
              },
              onChanged: (values) {
                setState(() {
                  _pendingIncludes = values;
                });
              },
              options: [
                for (final option in InboundTableSpec.defaultIncludeOptions)
                  ShadOption(
                    value: option,
                    child: Text(InboundTableSpec.includeLabel(option)),
                  ),
              ],
            ),
          ),
          ShadButton.outline(
            onPressed: () {
              setState(() {
                _sortAscending = !_sortAscending;
                _currentPage = 1;
              });
              _updateRoute(page: 1);
            },
            leading: const Icon(lucide.LucideIcons.arrowUpDown, size: 16),
            child: Text(_sortAscending ? '오름차순' : '내림차순'),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          ShadCard(
            title: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('입고 내역', style: theme.textTheme.h3),
                Text('$totalItems건', style: theme.textTheme.muted),
              ],
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                SizedBox(
                  height: responsive.isMobile ? 520 : 420,
                  child: _isLoading && filtered.isEmpty
                      ? const Center(child: CircularProgressIndicator())
                      : filtered.isEmpty
                      ? SuperportEmptyState(
                          title: _errorMessage != null
                              ? '데이터를 불러오지 못했습니다.'
                              : '입고 데이터가 없습니다.',
                          description:
                              _errorMessage ?? '검색어와 기간을 조정해 다시 시도하세요.',
                        )
                      : ResponsiveLayoutBuilder(
                          mobile: (_) => _InboundMobileList(
                            records: visibleRecords,
                            selected: _selectedRecord,
                            onSelect: _handleRecordTap,
                            dateFormatter: _dateFormatter,
                            currencyFormatter: _currencyFormatter,
                            transitionsEnabled: _transitionsEnabled,
                            onSubmit: _submitRecord,
                            onComplete: _completeRecord,
                            onApprove: _approveRecord,
                            onReject: _rejectRecord,
                            onCancel: _cancelRecord,
                            canSubmit: _canSubmit,
                            canComplete: _canComplete,
                            canApprove: _canApprove,
                            canReject: _canReject,
                            canCancel: _canCancel,
                            isProcessing: (record) =>
                                _isProcessing(record.id) || _isLoading,
                          ),
                          tablet: (_) => _buildTableView(
                            visibleRecords,
                            DeviceBreakpoint.tablet,
                          ),
                          desktop: (_) => _buildTableView(
                            visibleRecords,
                            DeviceBreakpoint.desktop,
                          ),
                        ),
                ),
                if (filtered.isNotEmpty) ...[
                  const SizedBox(height: 12),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      SizedBox(
                        width: 170,
                        child: ShadSelect<int>(
                          key: ValueKey(_pageSize),
                          initialValue: _pageSize,
                          selectedOptionBuilder: (_, value) =>
                              Text('$value개 / 페이지'),
                          onChanged: (value) {
                            if (value == null) return;
                            setState(() {
                              _pageSize = value;
                              _currentPage = 1;
                            });
                            _updateRoute(page: 1, pageSize: value);
                            _fetchTransactions(page: 1);
                          },
                          options: [
                            for (final option
                                in InboundTableSpec.pageSizeOptions)
                              ShadOption(
                                value: option,
                                child: Text('$option개 / 페이지'),
                              ),
                          ],
                        ),
                      ),
                      Row(
                        children: [
                          Text(
                            '$totalItems건 · 페이지 $currentPage / $totalPages',
                            style: theme.textTheme.small,
                          ),
                          const SizedBox(width: 12),
                          ShadButton.ghost(
                            size: ShadButtonSize.sm,
                            onPressed: currentPage <= 1
                                ? null
                                : () => _goToPage(1),
                            child: const Icon(
                              lucide.LucideIcons.chevronsLeft,
                              size: 16,
                            ),
                          ),
                          const SizedBox(width: 8),
                          ShadButton.ghost(
                            size: ShadButtonSize.sm,
                            onPressed: currentPage <= 1
                                ? null
                                : () => _goToPage(currentPage - 1),
                            child: const Icon(
                              lucide.LucideIcons.chevronLeft,
                              size: 16,
                            ),
                          ),
                          const SizedBox(width: 8),
                          ShadButton.ghost(
                            size: ShadButtonSize.sm,
                            onPressed: currentPage >= totalPages
                                ? null
                                : () => _goToPage(currentPage + 1),
                            child: const Icon(
                              lucide.LucideIcons.chevronRight,
                              size: 16,
                            ),
                          ),
                          const SizedBox(width: 8),
                          ShadButton.ghost(
                            size: ShadButtonSize.sm,
                            onPressed: currentPage >= totalPages
                                ? null
                                : () => _goToPage(totalPages),
                            child: const Icon(
                              lucide.LucideIcons.chevronsRight,
                              size: 16,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ],
            ),
          ),
        ],
      ),
    );
  }

  List<InboundRecord> get _filteredRecords {
    final query = _query.trim().toLowerCase();
    final range = _appliedDateRange;
    final records = _records.where((record) {
      final matchesQuery =
          query.isEmpty ||
          record.number.toLowerCase().contains(query) ||
          record.transactionNumber.toLowerCase().contains(query) ||
          record.writer.toLowerCase().contains(query) ||
          record.items.any(
            (item) => item.product.toLowerCase().contains(query),
          );
      final matchesRange =
          range == null ||
          (!record.processedAt.isBefore(range.start) &&
              !record.processedAt.isAfter(range.end));
      final matchesWarehouse = () {
        if (_appliedWarehouseId != null) {
          return record.warehouseId == _appliedWarehouseId;
        }
        if (_appliedWarehouseLabel != null &&
            _appliedWarehouseLabel!.isNotEmpty) {
          return record.warehouse == _appliedWarehouseLabel;
        }
        return true;
      }();
      final matchesStatus =
          _appliedStatus == null || record.status == _appliedStatus;
      return matchesQuery && matchesRange && matchesWarehouse && matchesStatus;
    }).toList();

    records.sort((a, b) {
      final compare = switch (_sortField) {
        _InboundSortField.processedAt => a.processedAt.compareTo(b.processedAt),
        _InboundSortField.warehouse => a.warehouse.compareTo(b.warehouse),
        _InboundSortField.status => a.status.compareTo(b.status),
        _InboundSortField.writer => a.writer.compareTo(b.writer),
      };
      return _sortAscending ? compare : -compare;
    });

    return records;
  }

  List<String> _buildRecordRow(InboundRecord record, int displayIndex) {
    final primaryItem = record.items.isNotEmpty ? record.items.first : null;
    return [
      displayIndex.toString(),
      _dateFormatter.format(record.processedAt),
      record.warehouse,
      record.transactionNumber,
      primaryItem?.product ?? '-',
      primaryItem?.manufacturer ?? '-',
      primaryItem?.unit ?? '-',
      record.totalQuantity.toString(),
      _currencyFormatter.format(primaryItem?.price ?? 0),
      record.status,
      record.writer,
      record.itemCount.toString(),
      record.totalQuantity.toString(),
      record.remark.isEmpty ? '-' : record.remark,
    ];
  }

  Widget _buildTableView(
    List<InboundRecord> records,
    DeviceBreakpoint breakpoint,
  ) {
    final visibleColumns = _visibleColumnsFor(breakpoint);
    final baseOffset = _rowNumberOffset(records.length);
    return ShadTable.list(
      header: [
        for (final index in visibleColumns)
          ShadTableCell.header(child: Text(InboundTableSpec.headers[index])),
      ],
      children: [
        for (var i = 0; i < records.length; i++)
          _buildTableCells(records[i], visibleColumns, baseOffset + i + 1),
      ],
      columnSpanExtent: (index) =>
          const FixedTableSpanExtent(InboundTableSpec.columnSpanWidth),
      rowSpanExtent: (index) =>
          const FixedTableSpanExtent(InboundTableSpec.rowSpanHeight),
      onRowTap: (rowIndex) {
        if (rowIndex <= 0 || rowIndex > records.length) {
          return;
        }
        final record = records[rowIndex - 1];
        _selectRecord(record, openDetail: true);
      },
    );
  }

  void _handleRecordTap(InboundRecord record) {
    _selectRecord(record, openDetail: true);
  }

  List<int> _visibleColumnsFor(DeviceBreakpoint breakpoint) {
    return InboundTableSpec.visibleColumns(breakpoint);
  }

  void _selectRecord(InboundRecord? record, {bool openDetail = false}) {
    if (!mounted) return;
    setState(() {
      _selectedRecord = record;
    });
    final selectedNumber = record?.transactionNumber;
    _routeSelectedNumber = selectedNumber;
    _updateRoute(selected: selectedNumber);
    if (openDetail && record != null) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (!mounted) {
          return;
        }
        _showDetailDialog(record);
      });
    }
  }

  List<ShadTableCell> _buildTableCells(
    InboundRecord record,
    List<int> visibleColumns,
    int displayIndex,
  ) {
    final values = _buildRecordRow(record, displayIndex);
    return [
      for (final index in visibleColumns)
        ShadTableCell(
          child: Text(values[index], overflow: TextOverflow.ellipsis),
        ),
    ];
  }

  int _rowNumberOffset(int currentCount) {
    final page = _result?.page ?? _currentPage;
    final pageSize = _result?.pageSize ?? _pageSize;
    final safePage = page > 0 ? page : 1;
    final safePageSize = pageSize > 0
        ? pageSize
        : (currentCount > 0 ? currentCount : 1);
    return (safePage - 1) * safePageSize;
  }

  Future<void> _showDetailDialog(InboundRecord record) async {
    await showInventoryTransactionDetailDialog<void>(
      context: context,
      title: '입고 상세',
      description: '입고 트랜잭션과 라인 품목을 확인하세요.',
      summary: _buildInboundDetailSummary(record),
      summaryBadges: _buildInboundDetailBadges(record),
      metadata: _buildInboundDetailMetadata(record),
      sections: [
        SuperportDetailDialogSection(
          id: _InboundDetailSections.lines,
          label: '라인 품목',
          icon: lucide.LucideIcons.listChecks,
          builder: (_) => InboundDetailView(
            record: record,
            currencyFormatter: _currencyFormatter,
            transitionsEnabled: _transitionsEnabled,
          ),
        ),
      ],
      initialSectionId: _InboundDetailSections.lines,
      actions: _buildDetailActions(record),
    );
  }

  Widget _buildInboundDetailSummary(InboundRecord record) {
    final theme = ShadTheme.of(context);
    final processedAt = _dateFormatter.format(record.processedAt);
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(record.transactionNumber, style: theme.textTheme.h4),
        const SizedBox(height: 4),
        Text(
          '$processedAt · ${record.transactionType}',
          style: theme.textTheme.muted,
        ),
      ],
    );
  }

  List<Widget> _buildInboundDetailBadges(InboundRecord record) {
    final badges = <Widget>[ShadBadge(child: Text(record.status))];
    badges.add(
      record.isActive
          ? const ShadBadge.outline(child: Text('사용중'))
          : const ShadBadge.destructive(child: Text('미사용')),
    );
    if (record.partnerName != null && record.partnerName!.trim().isNotEmpty) {
      badges.add(ShadBadge.outline(child: Text(record.partnerName!.trim())));
    }
    return badges;
  }

  List<SuperportDetailMetadata> _buildInboundDetailMetadata(
    InboundRecord record,
  ) {
    final partnerSummary = _formatPartnerSummary(record);
    return [
      SuperportDetailMetadata.text(
        label: '트랜잭션번호',
        value: record.transactionNumber,
      ),
      SuperportDetailMetadata.text(
        label: '처리일자',
        value: _dateFormatter.format(record.processedAt),
      ),
      SuperportDetailMetadata.text(
        label: '트랜잭션 유형',
        value: record.transactionType,
      ),
      SuperportDetailMetadata.text(label: '상태', value: record.status),
      SuperportDetailMetadata.text(
        label: '사용 상태',
        value: record.isActive ? '사용중' : '미사용',
      ),
      SuperportDetailMetadata.text(label: '작성자', value: record.writer),
      SuperportDetailMetadata.text(label: '창고', value: record.warehouse),
      SuperportDetailMetadata.text(
        label: '창고 코드',
        value: _dashIfEmpty(record.warehouseCode),
      ),
      SuperportDetailMetadata.text(
        label: '창고 우편번호',
        value: _dashIfEmpty(record.warehouseZipcode),
      ),
      SuperportDetailMetadata.text(
        label: '창고 주소',
        value: _dashIfEmpty(record.warehouseAddress),
      ),
      SuperportDetailMetadata.text(label: '파트너사', value: partnerSummary),
      SuperportDetailMetadata.text(
        label: '파트너 코드',
        value: _dashIfEmpty(record.partnerCode),
      ),
      SuperportDetailMetadata.text(label: '품목 수', value: '${record.itemCount}'),
      SuperportDetailMetadata.text(
        label: '총 수량',
        value: '${record.totalQuantity}',
      ),
      SuperportDetailMetadata.text(
        label: '총 금액',
        value: _currencyFormatter.format(record.totalAmount),
      ),
      SuperportDetailMetadata.text(
        label: '예상 반납일',
        value: record.expectedReturnDate == null
            ? '-'
            : _dateFormatter.format(record.expectedReturnDate!),
      ),
      SuperportDetailMetadata.text(
        label: '비고',
        value: _dashIfEmpty(record.remark),
      ),
    ];
  }

  String _formatPartnerSummary(InboundRecord record) {
    final name = _dashIfEmpty(record.partnerName);
    final code = _dashIfEmpty(record.partnerCode);
    if (name == '-') {
      return '-';
    }
    return code == '-' ? name : '$name ($code)';
  }

  String _dashIfEmpty(String? value) {
    if (value == null) {
      return '-';
    }
    final trimmed = value.trim();
    return trimmed.isEmpty ? '-' : trimmed;
  }

  List<Widget> _buildDetailActions(InboundRecord record) {
    final isProcessing = _isProcessing(record.id);
    final actions = <Widget>[];

    if (_canSubmit(record)) {
      actions.add(
        ShadButton.outline(
          onPressed: isProcessing ? null : () => _submitRecord(record),
          child: const Text('상신'),
        ),
      );
    }
    if (_canApprove(record)) {
      actions.add(
        ShadButton.outline(
          onPressed: isProcessing ? null : () => _approveRecord(record),
          child: const Text('승인'),
        ),
      );
    }
    if (_canReject(record)) {
      actions.add(
        ShadButton.outline(
          onPressed: isProcessing ? null : () => _rejectRecord(record),
          child: const Text('반려'),
        ),
      );
    }
    if (_canCancel(record)) {
      actions.add(
        ShadButton.outline(
          onPressed: isProcessing ? null : () => _cancelRecord(record),
          child: const Text('취소'),
        ),
      );
    }
    if (_canComplete(record)) {
      actions.add(
        ShadButton.outline(
          onPressed: isProcessing ? null : () => _completeRecord(record),
          child: const Text('완료 처리'),
        ),
      );
    }
    actions.add(
      ShadButton.outline(
        leading: const Icon(lucide.LucideIcons.pencil, size: 16),
        onPressed: isProcessing ? null : () => _openEditFromDetail(record),
        child: const Text('수정'),
      ),
    );

    return actions;
  }

  void _openEditFromDetail(InboundRecord record) {
    final navigator = Navigator.of(context, rootNavigator: true);
    navigator.pop();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      if (!mounted) {
        return;
      }
      await _handleEdit(record, reopenOnCancel: true);
    });
  }

  Future<void> _handleCreate() async {
    final record = await _showInboundFormDialog();
    if (record != null) {
      _selectRecord(record, openDetail: true);
    }
  }

  Future<void> _handleEdit(
    InboundRecord record, {
    bool reopenOnCancel = false,
  }) async {
    final updated = await _showInboundFormDialog(initial: record);
    if (!mounted) {
      return;
    }
    if (updated != null) {
      _selectRecord(updated, openDetail: true);
    } else if (reopenOnCancel) {
      _selectRecord(record, openDetail: true);
    }
  }

  bool _isProcessing(int? id) {
    if (id == null) {
      return false;
    }
    return _processingTransactionIds.contains(id);
  }

  bool _canSubmit(InboundRecord record) {
    if (!_transitionsEnabled) {
      return false;
    }
    final normalized = record.status.trim();
    return normalized.contains('작성');
  }

  bool _canComplete(InboundRecord record) {
    if (!_transitionsEnabled) {
      return false;
    }
    final normalized = record.status.trim();
    return normalized.contains('대기') || normalized.contains('승인대기');
  }

  bool _canApprove(InboundRecord record) {
    if (!_transitionsEnabled) {
      return false;
    }
    final normalized = record.status.replaceAll(RegExp(r'\s+'), '');
    return normalized.contains('승인대기');
  }

  bool _canReject(InboundRecord record) {
    if (!_transitionsEnabled) {
      return false;
    }
    final normalized = record.status.replaceAll(RegExp(r'\s+'), '');
    return normalized.contains('승인대기');
  }

  bool _canCancel(InboundRecord record) {
    if (!_transitionsEnabled) {
      return false;
    }
    final normalized = record.status.replaceAll(RegExp(r'\s+'), '');
    return normalized.contains('작성중') || normalized.contains('승인대기');
  }

  /// 실패 객체에서 사용자에게 노출할 메시지를 추출한다.
  String _failureMessage(Object error, String fallbackMessage) {
    final failure = Failure.from(error);
    final description = failure.describe();
    if (description.isEmpty) {
      return fallbackMessage;
    }
    return description;
  }

  Future<void> _submitRecord(InboundRecord record) async {
    final controller = _controller;
    final id = record.id;
    if (controller == null || id == null) {
      if (mounted) {
        SuperportToast.error(context, '상신할 대상을 찾을 수 없습니다.');
      }
      return;
    }

    try {
      await controller.submitTransaction(id);
      if (!mounted) {
        return;
      }
      SuperportToast.success(context, '상신 요청이 완료되었습니다.');
    } catch (error) {
      if (!mounted) {
        return;
      }
      SuperportToast.error(
        context,
        _failureMessage(error, '상신 처리에 실패했습니다. 잠시 후 다시 시도하세요.'),
      );
    }
  }

  Future<void> _completeRecord(InboundRecord record) async {
    final controller = _controller;
    final id = record.id;
    if (controller == null || id == null) {
      if (mounted) {
        SuperportToast.error(context, '완료 처리할 대상을 찾을 수 없습니다.');
      }
      return;
    }

    try {
      await controller.completeTransaction(id);
      if (!mounted) {
        return;
      }
      SuperportToast.success(context, '완료 처리되었습니다.');
    } catch (error) {
      if (!mounted) {
        return;
      }
      SuperportToast.error(
        context,
        _failureMessage(error, '완료 처리에 실패했습니다. 다시 시도하세요.'),
      );
    }
  }

  Future<void> _approveRecord(InboundRecord record) async {
    final controller = _controller;
    final id = record.id;
    if (controller == null || id == null) {
      if (mounted) {
        SuperportToast.error(context, '승인할 대상을 찾을 수 없습니다.');
      }
      return;
    }

    try {
      await controller.approveTransaction(id);
      if (!mounted) {
        return;
      }
      SuperportToast.success(context, '승인 처리되었습니다.');
    } catch (error) {
      if (!mounted) {
        return;
      }
      SuperportToast.error(
        context,
        _failureMessage(error, '승인 처리에 실패했습니다. 잠시 후 다시 시도하세요.'),
      );
    }
  }

  Future<void> _rejectRecord(InboundRecord record) async {
    final controller = _controller;
    final id = record.id;
    if (controller == null || id == null) {
      if (mounted) {
        SuperportToast.error(context, '반려할 대상을 찾을 수 없습니다.');
      }
      return;
    }

    try {
      await controller.rejectTransaction(id);
      if (!mounted) {
        return;
      }
      SuperportToast.success(context, '반려 처리되었습니다.');
    } catch (error) {
      if (!mounted) {
        return;
      }
      SuperportToast.error(
        context,
        _failureMessage(error, '반려 처리에 실패했습니다. 잠시 후 다시 시도하세요.'),
      );
    }
  }

  Future<void> _cancelRecord(InboundRecord record) async {
    final controller = _controller;
    final id = record.id;
    if (controller == null || id == null) {
      if (mounted) {
        SuperportToast.error(context, '취소할 대상을 찾을 수 없습니다.');
      }
      return;
    }

    try {
      await controller.cancelTransaction(id);
      if (!mounted) {
        return;
      }
      SuperportToast.success(context, '취소 처리되었습니다.');
    } catch (error) {
      if (!mounted) {
        return;
      }
      SuperportToast.error(
        context,
        _failureMessage(error, '취소 처리에 실패했습니다. 잠시 후 다시 시도하세요.'),
      );
    }
  }

  void _applyFilters() {
    setState(() {
      _query = _pendingQuery.trim();
      _appliedDateRange = _pendingDateRange;
      _appliedWarehouseId = _pendingWarehouseId;
      _appliedWarehouseLabel = _pendingWarehouseLabel;
      _appliedStatus = _pendingStatus;
      _appliedIncludes = {..._pendingIncludes};
      _currentPage = 1;
      _refreshSelection();
    });
    _updateRoute(page: 1);
    _fetchTransactions(page: 1);
  }

  void _resetFilters() {
    setState(() {
      _searchController.clear();
      _pendingQuery = '';
      _query = '';
      _pendingDateRange = null;
      _appliedDateRange = null;
      _pendingWarehouseId = null;
      _appliedWarehouseId = null;
      _pendingWarehouseLabel = null;
      _appliedWarehouseLabel = null;
      _pendingStatus = null;
      _appliedStatus = null;
      _sortField = _InboundSortField.processedAt;
      _sortAscending = false;
      _pageSize = InboundTableSpec.pageSizeOptions.first;
      _currentPage = 1;
      _pendingIncludes = {...InboundTableSpec.defaultIncludeOptions};
      _appliedIncludes = {...InboundTableSpec.defaultIncludeOptions};
      _includeController
        ..value.clear()
        ..value.addAll(_pendingIncludes);
      _refreshSelection();
    });
    _updateRoute(page: 1, pageSize: InboundTableSpec.pageSizeOptions.first);
    _fetchTransactions(page: 1);
  }

  bool get _hasAppliedFilters =>
      _query.isNotEmpty ||
      _appliedDateRange != null ||
      _appliedWarehouseId != null ||
      (_appliedWarehouseLabel != null && _appliedWarehouseLabel!.isNotEmpty) ||
      _appliedStatus != null ||
      !_setEquals(
        _appliedIncludes,
        InboundTableSpec.defaultIncludeOptions.toSet(),
      );

  bool get _hasDirtyFilters =>
      _pendingQuery.trim() != _query ||
      !_isSameRange(_pendingDateRange, _appliedDateRange) ||
      _pendingWarehouseId != _appliedWarehouseId ||
      _pendingWarehouseLabel != _appliedWarehouseLabel ||
      _pendingStatus != _appliedStatus ||
      !_setEquals(_pendingIncludes, _appliedIncludes);

  bool _isSameRange(DateTimeRange? a, DateTimeRange? b) {
    if (identical(a, b)) {
      return true;
    }
    if (a == null || b == null) {
      return a == b;
    }
    return a.start == b.start && a.end == b.end;
  }

  void _applyRouteParameters(Uri uri, {bool initialize = false}) {
    final params = uri.queryParameters;
    final query = params['q'] ?? '';
    final warehouseIdParam = int.tryParse(params['warehouse_id'] ?? '');
    final warehouseParam = params['warehouse'];
    final statusParam = params['status'];
    final dateRange = _parseDateRange(params['start_date'], params['end_date']);
    final includesParam = params['include'];
    final resolvedSortField =
        _sortFieldFromParam(params['sort']) ?? _InboundSortField.processedAt;
    final resolvedSortAscending =
        (params['order'] ?? '').toLowerCase() == 'asc';
    final pageSizeParam = int.tryParse(params['page_size'] ?? '');
    final pageParam = int.tryParse(params['page'] ?? '');

    final warehouseId = warehouseIdParam;
    final warehouseLabel = warehouseParam != null && warehouseParam.isNotEmpty
        ? warehouseParam
        : null;
    final status = _statusOptions.contains(statusParam) ? statusParam : null;
    final includes = includesParam == null || includesParam.isEmpty
        ? {...InboundTableSpec.defaultIncludeOptions}
        : includesParam
              .split(',')
              .map((token) => token.trim())
              .where(
                (token) =>
                    InboundTableSpec.defaultIncludeOptions.contains(token),
              )
              .toSet();
    final pageSize =
        (pageSizeParam != null &&
            InboundTableSpec.pageSizeOptions.contains(pageSizeParam))
        ? pageSizeParam
        : InboundTableSpec.pageSizeOptions.first;
    final page = (pageParam != null && pageParam > 0) ? pageParam : 1;
    final selectedParam = params['selected']?.trim();
    final selectedNumber = selectedParam == null || selectedParam.isEmpty
        ? null
        : selectedParam;

    void assign() {
      _pendingQuery = query;
      _query = query;
      _searchController.text = query;
      _pendingWarehouseId = warehouseId;
      _appliedWarehouseId = warehouseId;
      _pendingWarehouseLabel = warehouseLabel;
      _appliedWarehouseLabel = warehouseLabel;
      _pendingStatus = status;
      _appliedStatus = status;
      _pendingDateRange = dateRange;
      _appliedDateRange = dateRange;
      _pendingIncludes = includes.isEmpty
          ? {...InboundTableSpec.defaultIncludeOptions}
          : includes;
      _appliedIncludes = {..._pendingIncludes};
      _includeController
        ..value.clear()
        ..value.addAll(_pendingIncludes);
      _sortField = resolvedSortField;
      _sortAscending = resolvedSortAscending;
      _pageSize = pageSize;
      _currentPage = page;
      _routeSelectedNumber = selectedNumber;
      if (_suppressNextRouteSelection) {
        _pendingDetailNumber = null;
      } else {
        _pendingDetailNumber = selectedNumber;
      }
      _suppressNextRouteSelection = false;
      _refreshSelection();
    }

    if (initialize) {
      assign();
      return;
    }

    setState(assign);
    _fetchTransactions(page: page);
  }

  void _goToPage(int page) {
    final totalItems = _result?.total ?? _filteredRecords.length;
    final totalPages = _calculateTotalPages(totalItems);
    final int target = page < 1 ? 1 : (page > totalPages ? totalPages : page);
    if (target == _currentPage) {
      return;
    }
    setState(() {
      _currentPage = target;
    });
    _updateRoute(page: target);
    _fetchTransactions(page: target);
  }

  void _refreshSelection() {
    final filtered = _filteredRecords;
    if (filtered.isEmpty) {
      _selectedRecord = null;
      return;
    }

    final selectedNumber = _routeSelectedNumber;
    if (selectedNumber != null) {
      for (final record in filtered) {
        if (record.transactionNumber == selectedNumber) {
          _selectedRecord = record;
          return;
        }
      }
    }

    final current = _selectedRecord;
    if (current != null) {
      InboundRecord? matched;
      if (current.id != null) {
        matched = filtered.firstWhere(
          (element) => element.id == current.id,
          orElse: () => filtered.first,
        );
      } else {
        matched = filtered.firstWhere(
          (element) => element.transactionNumber == current.transactionNumber,
          orElse: () => filtered.first,
        );
      }
      _selectedRecord = matched;
      return;
    }

    _selectedRecord = filtered.first;
  }

  void _updateRoute({int? page, int? pageSize, String? selected}) {
    if (!mounted) return;
    final targetPage = page ?? _currentPage;
    final targetPageSize = pageSize ?? _pageSize;

    final params = <String, String>{};
    if (_query.isNotEmpty) {
      params['q'] = _query;
    }
    if (_appliedWarehouseId != null) {
      params['warehouse_id'] = _appliedWarehouseId.toString();
      if (_appliedWarehouseLabel != null &&
          _appliedWarehouseLabel!.isNotEmpty) {
        params['warehouse'] = _appliedWarehouseLabel!;
      }
    } else if (_appliedWarehouseLabel != null &&
        _appliedWarehouseLabel!.isNotEmpty) {
      params['warehouse'] = _appliedWarehouseLabel!;
    }
    if (_appliedStatus != null && _appliedStatus!.isNotEmpty) {
      params['status'] = _appliedStatus!;
    }
    final dateRange = _appliedDateRange;
    if (dateRange != null) {
      params['start_date'] = _formatDateParam(dateRange.start);
      params['end_date'] = _formatDateParam(dateRange.end);
    }
    if (!_setEquals(
      _appliedIncludes,
      InboundTableSpec.defaultIncludeOptions.toSet(),
    )) {
      params['include'] = _appliedIncludes.join(',');
    }
    final sortParam = _encodeSortField(_sortField);
    if (sortParam != null) {
      params['sort'] = sortParam;
    }
    if (_sortAscending) {
      params['order'] = 'asc';
    }
    if (targetPage > 1) {
      params['page'] = targetPage.toString();
    }
    if (targetPageSize != InboundTableSpec.pageSizeOptions.first) {
      params['page_size'] = targetPageSize.toString();
    }
    final selectedNumber = selected ?? _selectedRecord?.transactionNumber;
    if (selectedNumber != null && selectedNumber.isNotEmpty) {
      params['selected'] = selectedNumber;
    }

    final uri = Uri(
      path: widget.routeUri.path,
      queryParameters: params.isEmpty ? null : params,
    );
    final newLocation = uri.toString();
    if (newLocation == widget.routeUri.toString()) {
      return;
    }
    final router = GoRouter.maybeOf(context);
    if (router == null) {
      return;
    }
    _suppressNextRouteSelection = true;
    router.go(newLocation);
  }

  DateTime? _parseDate(String? value) {
    if (value == null || value.isEmpty) {
      return null;
    }
    return DateTime.tryParse(value);
  }

  DateTimeRange? _parseDateRange(String? start, String? end) {
    final startDate = _parseDate(start);
    final endDate = _parseDate(end);
    if (startDate == null || endDate == null) {
      return null;
    }
    if (endDate.isBefore(startDate)) {
      return null;
    }
    return DateTimeRange(start: startDate, end: endDate);
  }

  String _formatDateParam(DateTime date) => _dateFormatter.format(date);

  bool _setEquals(Set<String> a, Set<String> b) {
    if (identical(a, b)) return true;
    if (a.length != b.length) return false;
    for (final value in a) {
      if (!b.contains(value)) return false;
    }
    return true;
  }

  _InboundSortField? _sortFieldFromParam(String? value) {
    switch (value) {
      case 'warehouse':
        return _InboundSortField.warehouse;
      case 'status':
        return _InboundSortField.status;
      case 'writer':
        return _InboundSortField.writer;
      case 'processed_at':
        return _InboundSortField.processedAt;
      default:
        return null;
    }
  }

  String? _encodeSortField(_InboundSortField field) {
    switch (field) {
      case _InboundSortField.processedAt:
        return null;
      case _InboundSortField.warehouse:
        return 'warehouse';
      case _InboundSortField.status:
        return 'status';
      case _InboundSortField.writer:
        return 'writer';
    }
  }

  String _sortLabel(_InboundSortField field) {
    return switch (field) {
      _InboundSortField.processedAt => '처리일자',
      _InboundSortField.warehouse => '창고',
      _InboundSortField.status => '상태',
      _InboundSortField.writer => '작성자',
    };
  }

  InventoryEmployeeSuggestion? _resolveCurrentWriter() {
    final getIt = GetIt.I;
    if (!getIt.isRegistered<AuthService>()) {
      return null;
    }
    final session = getIt<AuthService>().session;
    final user = session?.user;
    if (user == null) {
      return null;
    }
    final employeeNo = (user.employeeNo ?? '').trim().isEmpty
        ? user.id.toString()
        : user.employeeNo!.trim();
    return InventoryEmployeeSuggestion(
      id: user.id,
      employeeNo: employeeNo,
      name: user.name,
    );
  }

  int _calculateTotalPages(int totalItems) {
    if (totalItems <= 0) {
      return 1;
    }
    return (totalItems / _pageSize).ceil();
  }

  /// 입고 등록/수정 모달을 띄우고 사용자가 입력한 결과를 반환한다.
  ///
  /// [initial] 값이 있으면 수정 모드로 작동하며, 입력 검증 실패 시 토스트로 안내한다.
  Future<InboundRecord?> _showInboundFormDialog({
    InboundRecord? initial,
  }) async {
    final processedAt = ValueNotifier<DateTime>(
      initial?.processedAt ?? DateTime.now(),
    );
    final initialWarehouse = initial?.raw?.warehouse;
    final int? initialWarehouseId =
        initialWarehouse?.id ?? initial?.warehouseId;
    final String? initialWarehouseName =
        initialWarehouse?.name ?? initial?.warehouse;
    final String? initialWarehouseCode = initialWarehouse?.code;
    InventoryWarehouseOption? warehouseSelection;
    if (initialWarehouseId != null && initialWarehouseName != null) {
      warehouseSelection = InventoryWarehouseOption(
        id: initialWarehouseId,
        code: initialWarehouseCode ?? initialWarehouseName,
        name: initialWarehouseName,
      );
    }
    final statusValue = ValueNotifier<String>(
      initial?.status ?? _statusOptions.first,
    );
    final initialPartnerOption = () {
      final customers = initial?.raw?.customers;
      if (customers != null && customers.isNotEmpty) {
        final summary = customers.first.customer;
        return InventoryPartnerOption(
          id: summary.id,
          name: summary.name,
          code: summary.code,
        );
      }
      if (initial?.partnerId != null &&
          (initial?.partnerName?.trim().isNotEmpty ?? false)) {
        return InventoryPartnerOption(
          id: initial!.partnerId!,
          name: initial.partnerName!.trim(),
          code: (initial.partnerCode ?? '').trim(),
        );
      }
      return null;
    }();
    InventoryPartnerOption? partnerSelection = initialPartnerOption;
    InventoryEmployeeSuggestion? writerSelection;
    final initialWriter = initial?.raw?.createdBy;
    if (initialWriter != null) {
      writerSelection = InventoryEmployeeSuggestion(
        id: initialWriter.id,
        employeeNo: initialWriter.employeeNo,
        name: initialWriter.name,
      );
    } else if (initial?.writerId != null &&
        (initial?.writer ?? '').isNotEmpty) {
      writerSelection = InventoryEmployeeSuggestion(
        id: initial!.writerId!,
        employeeNo: initial.writer,
        name: initial.writer,
      );
    } else if (initial == null) {
      writerSelection = _resolveCurrentWriter();
    }
    String writerLabel(InventoryEmployeeSuggestion? suggestion) {
      if (suggestion == null) {
        return '';
      }
      return '${suggestion.name} (${suggestion.employeeNo})';
    }

    final approvalController = ApprovalRequestController();
    final defaultRequester = () {
      final writer = writerSelection;
      if (writer == null) {
        return null;
      }
      return ApprovalRequestParticipant(
        id: writer.id,
        name: writer.name,
        employeeNo: writer.employeeNo,
      );
    }();
    await ApprovalFormInitializer.populate(
      controller: approvalController,
      existingApproval: initial?.raw?.approval,
      draft: _controller?.approvalDraft,
      defaultRequester: defaultRequester,
    );
    if (!mounted) {
      return null;
    }

    final writerController = TextEditingController(
      text: writerLabel(writerSelection),
    );
    final remarkController = TextEditingController(text: initial?.remark ?? '');
    final assignedTransactionNo = initial?.transactionNumber;
    final assignedApprovalNo = initial?.raw?.approval?.approvalNo;
    final approvalNoteController = TextEditingController();
    final transactionTypeValue =
        initial?.transactionType ??
        _transactionTypeLookup?.name ??
        _inboundTransactionTypeId;
    final transactionTypeController = TextEditingController(
      text: transactionTypeValue,
    );

    final drafts =
        initial?.items
            .map((item) => _LineItemDraft.fromItem(item))
            .toList()
            .cast<_LineItemDraft>() ??
        [_LineItemDraft.empty()];

    final lineErrors = {
      for (final draft in drafts) draft: _LineItemFieldErrors.empty(),
    };

    String? writerError;
    String? warehouseError;
    String? partnerError;
    String? statusError;
    String? headerNotice;
    StateSetter? refreshForm;
    StateSetter? refreshCancelAction;
    StateSetter? refreshSaveAction;
    var isSaving = false;

    InboundRecord? result;

    final navigator = Navigator.of(context, rootNavigator: true);

    void updateSaving(bool next) {
      isSaving = next;
      refreshForm?.call(() {});
      refreshCancelAction?.call(() {});
      refreshSaveAction?.call(() {});
    }

    Future<void> handleSubmit() async {
      if (isSaving) {
        return;
      }

      final validationResult = _validateInboundForm(
        writerController: writerController,
        writerSelection: writerSelection,
        requireWriterSelection: initial == null,
        warehouseSelection: warehouseSelection,
        partnerSelection: partnerSelection,
        statusValue: statusValue.value,
        drafts: drafts,
        lineErrors: lineErrors,
      );
      writerError = validationResult.writerError;
      warehouseError = validationResult.warehouseError;
      partnerError = validationResult.partnerError;
      statusError = validationResult.statusError;
      headerNotice = validationResult.headerNotice;
      refreshForm?.call(() {});

      if (!validationResult.isValid) {
        SuperportToast.error(context, '입력 오류를 확인하고 다시 시도하세요.');
        return;
      }

      final controller = _controller;
      if (controller == null) {
        SuperportToast.error(context, '입고 컨트롤러를 찾을 수 없습니다.');
        return;
      }
      Future<InboundRecord?> resolveUpdatedRecord(int? id) async {
        if (id == null) {
          return null;
        }
        final detail = await controller.fetchTransactionDetail(id);
        if (detail != null) {
          return detail;
        }
        for (final record in controller.records) {
          if (record.id == id) {
            return record;
          }
        }
        return null;
      }

      final transactionTypeLookup =
          _transactionTypeLookup ?? controller.transactionType;
      if (transactionTypeLookup == null) {
        SuperportToast.error(context, '입고 트랜잭션 유형 정보를 불러오지 못했습니다.');
        return;
      }

      final statusItem = _statusLookup[statusValue.value];
      if (statusItem == null) {
        SuperportToast.error(context, '선택한 상태 정보를 확인할 수 없습니다.');
        return;
      }

      final warehouseId = warehouseSelection?.id ?? initialWarehouseId;
      if (warehouseId == null) {
        SuperportToast.error(context, '창고 정보를 확인할 수 없습니다.');
        return;
      }

      final createdById = writerSelection?.id ?? initial?.writerId;
      if (initial == null && createdById == null) {
        SuperportToast.error(context, '작성자 정보를 확인할 수 없습니다.');
        return;
      }

      final remarkText = remarkController.text.trim();
      final remarkValue = remarkText.isEmpty ? null : remarkText;
      final approvalNoteValue = approvalNoteController.text.trim();

      final transactionId = initial?.id;
      final initialRecord = initial;

      final lineDrafts = <TransactionLineDraft>[];
      for (var index = 0; index < drafts.length; index++) {
        final draft = drafts[index];
        final productId = draft.productId ?? draft.suggestion?.id;
        if (productId == null) {
          continue;
        }
        final quantity = int.tryParse(draft.quantity.text.trim()) ?? 0;
        final unitPrice = _parseCurrency(draft.price.text);
        final noteText = draft.remark.text.trim();
        lineDrafts.add(
          TransactionLineDraft(
            id: draft.lineId,
            lineNo: index + 1,
            productId: productId,
            quantity: quantity,
            unitPrice: unitPrice,
            note: noteText.isEmpty ? null : noteText,
          ),
        );
      }

      if (lineDrafts.isEmpty) {
        SuperportToast.error(context, '최소 1개 이상의 품목을 선택하세요.');
        return;
      }

      final createCustomers = () {
        final partner = partnerSelection;
        if (partner == null) {
          return const <TransactionCustomerCreateInput>[];
        }
        return [TransactionCustomerCreateInput(customerId: partner.id)];
      }();

      try {
        updateSaving(true);

        if (initialRecord != null) {
          if (transactionId == null) {
            updateSaving(false);
            SuperportToast.error(context, '수정 대상 ID를 확인할 수 없습니다.');
            return;
          }
          final updated = await controller.updateTransaction(
            transactionId,
            StockTransactionUpdateInput(
              transactionStatusId: statusItem.id,
              note: remarkValue,
            ),
            refreshAfter: false,
          );
          final currentLines =
              initialRecord.raw?.lines ?? const <StockTransactionLine>[];
          final currentCustomers = initialRecord.customers;
          final customerDrafts = () {
            final partner = partnerSelection;
            if (partner == null) {
              return const <TransactionCustomerDraft>[];
            }
            final existing = initialRecord.primaryCustomer;
            return [
              TransactionCustomerDraft(
                id: existing?.id ?? initialRecord.partnerLinkId,
                customerId: partner.id,
                note: existing?.note,
              ),
            ];
          }();
          final linePlan = _detailSyncService.buildLinePlan(
            drafts: lineDrafts,
            currentLines: currentLines,
          );
          final customerPlan = _detailSyncService.buildCustomerPlan(
            drafts: customerDrafts,
            currentCustomers: currentCustomers,
          );
          if (linePlan.hasChanges) {
            await controller.syncTransactionLines(transactionId, linePlan);
          }
          if (customerPlan.hasChanges) {
            await controller.syncTransactionCustomers(
              transactionId,
              customerPlan,
            );
          }
          await controller.refresh();
          final refreshed = await resolveUpdatedRecord(transactionId);
          result = refreshed ?? updated;
          updateSaving(false);
          if (!mounted) {
            return;
          }
          SuperportToast.success(
            context,
            '입고 정보가 수정되었습니다. (${updated.transactionNumber})',
          );
          navigator.pop();
          return;
        }

        if (createdById == null) {
          updateSaving(false);
          SuperportToast.error(context, '작성자 선택이 필요합니다.');
          return;
        }

        final approvalStatusId = () {
          final defaultStatus = controller.defaultApprovalStatus;
          if (defaultStatus != null) {
            return defaultStatus.id;
          }
          final statuses = controller.approvalStatuses;
          if (statuses.isNotEmpty) {
            return statuses.first.id;
          }
          return null;
        }();
        if (approvalStatusId == null) {
          updateSaving(false);
          SuperportToast.error(context, '결재 상태 정보를 불러오지 못했습니다. 잠시 후 다시 시도하세요.');
          return;
        }

        StockTransactionApprovalInput approvalInput;
        try {
          approvalInput = approvalController.buildTransactionApprovalInput(
            approvalStatusId: approvalStatusId,
            note: approvalNoteValue.isEmpty ? null : approvalNoteValue,
          );
        } on StateError catch (error) {
          updateSaving(false);
          SuperportToast.error(context, error.message);
          return;
        } catch (_) {
          updateSaving(false);
          SuperportToast.error(context, '결재 구성을 확인하고 다시 시도하세요.');
          return;
        }
        controller.updateApprovalDraft(approvalInput);

        final createLines = lineDrafts
            .map(
              (draft) => TransactionLineCreateInput(
                lineNo: draft.lineNo,
                productId: draft.productId,
                quantity: draft.quantity,
                unitPrice: draft.unitPrice,
                note: draft.note,
              ),
            )
            .toList(growable: false);
        final createInput = StockTransactionCreateInput(
          transactionTypeId: transactionTypeLookup.id,
          transactionStatusId: statusItem.id,
          warehouseId: warehouseId,
          transactionDate: processedAt.value,
          createdById: createdById,
          note: remarkValue,
          lines: createLines,
          customers: createCustomers,
          approval: approvalInput,
        );
        assert(() {
          debugPrint(
            '[InboundForm] POST /stock-transactions payload: '
            '${createInput.toPayload()}',
          );
          return true;
        }());
        final created = await controller.createTransaction(createInput);
        final refreshed = await resolveUpdatedRecord(created.id);
        result = refreshed ?? created;
        updateSaving(false);
        if (!mounted) {
          return;
        }
        SuperportToast.success(
          context,
          '입고가 등록되었습니다. (${created.transactionNumber})',
        );
        navigator.pop();
      } catch (error) {
        updateSaving(false);
        if (!mounted) {
          return;
        }
        assert(() {
          final failure = Failure.from(error);
          debugPrint(
            '[InboundForm] 저장 실패: status=${failure.statusCode} '
            'message="${failure.describe()}"',
          );
          debugPrint(
            '[InboundForm] 요청 페이로드: '
            '{transactionTypeId: ${transactionTypeLookup.id}, '
            'statusId: ${statusItem.id}, warehouseId: $warehouseId, '
            'createdById: $createdById, lineCount: ${lineDrafts.length}}',
          );
          return true;
        }());
        SuperportToast.error(
          context,
          _failureMessage(error, '저장 중 오류가 발생했습니다. 잠시 후 다시 시도하세요.'),
        );
      }
    }

    await showSuperportDialog<void>(
      context: context,
      title: initial == null ? '입고 등록' : '입고 수정',
      description: '입고 기본정보와 품목 라인을 입력하세요.',
      constraints: const BoxConstraints(maxWidth: 860, maxHeight: 720),
      onSubmit: handleSubmit,
      actions: [
        StatefulBuilder(
          builder: (context, setState) {
            refreshCancelAction = setState;
            return ShadButton.ghost(
              onPressed: isSaving ? null : () => navigator.pop(),
              child: const Text('취소'),
            );
          },
        ),
        StatefulBuilder(
          builder: (context, setState) {
            refreshSaveAction = setState;
            return ShadButton(
              onPressed: isSaving ? null : () => handleSubmit(),
              child: isSaving
                  ? const SizedBox(
                      width: 16,
                      height: 16,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : const Text('저장'),
            );
          },
        ),
      ],
      body: StatefulBuilder(
        builder: (context, setState) {
          refreshForm = setState;
          final theme = ShadTheme.of(context);
          final summary = _DraftSummary.fromDrafts(drafts);
          return SingleChildScrollView(
            padding: const EdgeInsets.only(right: 12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Wrap(
                  spacing: 16,
                  runSpacing: 16,
                  children: [
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '처리일자',
                        required: true,
                        child: SuperportDatePickerButton(
                          value: processedAt.value,
                          dateFormat: _dateFormatter,
                          firstDate: InboundTableSpec.dateRangeFirstDate,
                          lastDate: InboundTableSpec.dateRangeLastDate,
                          enabled: initial == null,
                          onChanged: (date) {
                            processedAt.value = date;
                            setState(() {});
                          },
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '창고',
                        required: true,
                        errorText: warehouseError,
                        child: InventoryWarehouseSelectField(
                          initialWarehouseId: initialWarehouseId,
                          enabled: initial == null,
                          onChanged: (option) {
                            warehouseSelection = option;
                            setState(() {
                              if (warehouseError != null) {
                                warehouseError = null;
                              }
                            });
                          },
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '파트너사',
                        required: true,
                        errorText: partnerError,
                        child: InventoryPartnerSelectField(
                          initialPartner: partnerSelection,
                          enabled: !isSaving,
                          onChanged: (option) {
                            partnerSelection = option;
                            setState(() {
                              if (partnerError != null) {
                                partnerError = null;
                              }
                            });
                          },
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '상태',
                        required: true,
                        errorText: statusError,
                        child: ShadSelect<String>(
                          initialValue: statusValue.value,
                          selectedOptionBuilder: (context, value) =>
                              Text(value),
                          onChanged: (value) {
                            if (value != null) {
                              statusValue.value = value;
                              setState(() {
                                if (statusError != null) {
                                  statusError = null;
                                }
                              });
                            }
                          },
                          enabled: initial?.status != '승인완료',
                          options: _statusOptions
                              .map(
                                (status) => ShadOption(
                                  value: status,
                                  child: Text(status),
                                ),
                              )
                              .toList(),
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '트랜잭션 유형',
                        required: true,
                        child: ShadInput(
                          controller: transactionTypeController,
                          readOnly: true,
                          enabled: false,
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '트랜잭션번호',
                        child: Text(
                          assignedTransactionNo ?? '저장 시 자동 생성',
                          style: assignedTransactionNo == null
                              ? theme.textTheme.muted
                              : theme.textTheme.p,
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '결재번호',
                        child: Text(
                          assignedApprovalNo ?? '저장 시 자동 생성',
                          style: assignedApprovalNo == null
                              ? theme.textTheme.muted
                              : theme.textTheme.p,
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 240,
                      child: SuperportFormField(
                        label: '작성자',
                        required: true,
                        errorText: writerError,
                        child: ShadInput(
                          controller: writerController,
                          readOnly: true,
                          enabled: false,
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 500,
                      child: SuperportFormField(
                        label: '결재 메모',
                        caption: initial != null
                            ? '등록된 결재 메모는 수정할 수 없습니다.'
                            : null,
                        child: ShadInput(
                          controller: approvalNoteController,
                          maxLines: 2,
                          readOnly: initial != null,
                          enabled: initial == null,
                        ),
                      ),
                    ),
                    SizedBox(
                      width: 500,
                      child: SuperportFormField(
                        label: '비고',
                        child: ShadInput(
                          controller: remarkController,
                          maxLines: 2,
                        ),
                      ),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                if (initial == null) ...[
                  ApprovalTemplatePicker(controller: approvalController),
                  const SizedBox(height: 16),
                ],
                ApprovalStepConfigurator(
                  controller: approvalController,
                  readOnly: initial != null,
                ),
                const SizedBox(height: 24),
                if (headerNotice != null)
                  Padding(
                    padding: const EdgeInsets.only(bottom: 12),
                    child: Text(
                      headerNotice!,
                      style: theme.textTheme.small.copyWith(
                        color: theme.colorScheme.destructive,
                      ),
                    ),
                  ),
                Wrap(
                  spacing: 12,
                  runSpacing: 12,
                  children: [
                    _SummaryBadge(
                      icon: lucide.LucideIcons.package,
                      label: '품목 수',
                      value: '${summary.itemCount}건',
                    ),
                    _SummaryBadge(
                      icon: lucide.LucideIcons.trendingUp,
                      label: '총 수량',
                      value: '${summary.totalQuantity} ea',
                    ),
                    _SummaryBadge(
                      icon: lucide.LucideIcons.coins,
                      label: '총 금액',
                      value: _currencyFormatter.format(summary.totalAmount),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text('라인 품목', style: theme.textTheme.h4),
                        const SizedBox(height: 4),
                        Text(
                          '제품, 제조사, 단위, 수량, 단가 정보를 입력하세요.',
                          style: theme.textTheme.muted,
                        ),
                      ],
                    ),
                    ShadButton.outline(
                      leading: const Icon(lucide.LucideIcons.plus, size: 16),
                      onPressed: () => setState(() {
                        final draft = _LineItemDraft.empty();
                        drafts.add(draft);
                        headerNotice = null;
                        lineErrors.putIfAbsent(
                          draft,
                          _LineItemFieldErrors.empty,
                        );
                      }),
                      child: const Text('품목 추가'),
                    ),
                  ],
                ),
                const SizedBox(height: 16),
                Column(
                  children: [
                    for (var index = 0; index < drafts.length; index++)
                      Padding(
                        padding: const EdgeInsets.only(bottom: 12),
                        child: Builder(
                          builder: (_) {
                            final draft = drafts[index];
                            return _LineItemRow(
                              draft: draft,
                              showLabels: index == 0,
                              onRemove: drafts.length == 1
                                  ? null
                                  : () => setState(() {
                                      draft.dispose();
                                      drafts.remove(draft);
                                      headerNotice = null;
                                      lineErrors.remove(draft);
                                    }),
                              errors:
                                  lineErrors[draft] ??
                                  _LineItemFieldErrors.empty(),
                              onFieldChanged: (field) {
                                setState(() {
                                  headerNotice = null;
                                  final error = lineErrors[draft];
                                  if (error == null) {
                                    lineErrors[draft] =
                                        _LineItemFieldErrors.empty();
                                  } else {
                                    error.clearField(field);
                                  }
                                });
                              },
                            );
                          },
                        ),
                      ),
                  ],
                ),
              ],
            ),
          );
        },
      ),
    );

    final disposeDrafts = List<_LineItemDraft>.from(drafts);
    final disposeStatus = statusValue;
    final disposeWriterController = writerController;
    final disposeRemarkController = remarkController;
    final disposeApprovalNoteController = approvalNoteController;
    final disposeTransactionTypeController = transactionTypeController;
    final disposeProcessedAt = processedAt;
    final disposeApprovalController = approvalController;

    WidgetsBinding.instance.addPostFrameCallback((_) {
      for (final draft in disposeDrafts) {
        draft.dispose();
      }
      disposeStatus.dispose();
      disposeWriterController.dispose();
      disposeRemarkController.dispose();
      disposeApprovalNoteController.dispose();
      disposeTransactionTypeController.dispose();
      disposeProcessedAt.dispose();
      disposeApprovalController.dispose();
    });

    return result;
  }
}

class _SummaryBadge extends StatelessWidget {
  const _SummaryBadge({
    required this.icon,
    required this.label,
    required this.value,
  });

  final IconData icon;
  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    final theme = ShadTheme.of(context);
    return ShadBadge.outline(
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, size: 14, color: theme.colorScheme.mutedForeground),
            const SizedBox(width: 8),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              mainAxisSize: MainAxisSize.min,
              children: [
                Text(label, style: theme.textTheme.small),
                const SizedBox(height: 2),
                Text(value, style: theme.textTheme.p),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

class _InboundMobileList extends StatelessWidget {
  const _InboundMobileList({
    required this.records,
    required this.selected,
    required this.onSelect,
    required this.dateFormatter,
    required this.currencyFormatter,
    this.transitionsEnabled = true,
    this.onSubmit,
    this.onComplete,
    this.onApprove,
    this.onReject,
    this.onCancel,
    this.canSubmit,
    this.canComplete,
    this.canApprove,
    this.canReject,
    this.canCancel,
    this.isProcessing,
  });

  final List<InboundRecord> records;
  final InboundRecord? selected;
  final ValueChanged<InboundRecord> onSelect;
  final DateFormat dateFormatter;
  final NumberFormat currencyFormatter;
  final bool transitionsEnabled;
  final Future<void> Function(InboundRecord record)? onSubmit;
  final Future<void> Function(InboundRecord record)? onComplete;
  final Future<void> Function(InboundRecord record)? onApprove;
  final Future<void> Function(InboundRecord record)? onReject;
  final Future<void> Function(InboundRecord record)? onCancel;
  final bool Function(InboundRecord record)? canSubmit;
  final bool Function(InboundRecord record)? canComplete;
  final bool Function(InboundRecord record)? canApprove;
  final bool Function(InboundRecord record)? canReject;
  final bool Function(InboundRecord record)? canCancel;
  final bool Function(InboundRecord record)? isProcessing;

  @override
  Widget build(BuildContext context) {
    final theme = ShadTheme.of(context);
    const radius = 12.0;
    return ListView.separated(
      itemCount: records.length,
      separatorBuilder: (_, __) => const SizedBox(height: 12),
      itemBuilder: (context, index) {
        final record = records[index];
        final isSelected = selected?.number == record.number;
        final primaryItem = record.items.isNotEmpty ? record.items.first : null;

        return AnimatedContainer(
          duration: const Duration(milliseconds: 180),
          curve: Curves.easeOut,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(radius),
            border: Border.all(
              color: isSelected
                  ? theme.colorScheme.primary
                  : theme.colorScheme.border,
            ),
            color: isSelected
                ? theme.colorScheme.accent.withValues(alpha: 0.08)
                : theme.colorScheme.card,
          ),
          child: Material(
            color: Colors.transparent,
            child: InkWell(
              borderRadius: BorderRadius.circular(radius),
              onTap: () => onSelect(record),
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Text(
                              record.transactionNumber,
                              style: theme.textTheme.h4,
                            ),
                            const SizedBox(height: 4),
                            Text(
                              '#${record.number.split('-').last}',
                              style: theme.textTheme.small,
                            ),
                          ],
                        ),
                        ShadBadge(child: Text(record.status)),
                      ],
                    ),
                    const SizedBox(height: 12),
                    Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: [
                        _MobileMetaChip(
                          icon: lucide.LucideIcons.calendar,
                          label: dateFormatter.format(record.processedAt),
                        ),
                        _MobileMetaChip(
                          icon: lucide.LucideIcons.building2,
                          label: record.warehouse,
                        ),
                        _MobileMetaChip(
                          icon: lucide.LucideIcons.user,
                          label: record.writer,
                        ),
                        _MobileMetaChip(
                          icon: lucide.LucideIcons.packageCheck,
                          label: '${record.itemCount}개 품목',
                        ),
                        _MobileMetaChip(
                          icon: lucide.LucideIcons.chartBar,
                          label: '총 ${record.totalQuantity}ea',
                        ),
                        if (primaryItem != null)
                          _MobileMetaChip(
                            icon: lucide.LucideIcons.coins,
                            label: currencyFormatter.format(primaryItem.price),
                          ),
                      ],
                    ),
                    if (record.remark.isNotEmpty) ...[
                      const SizedBox(height: 12),
                      Text(record.remark, style: theme.textTheme.muted),
                    ],
                    if (!transitionsEnabled) ...[
                      const SizedBox(height: 16),
                      Text(
                        '재고 상태 전이가 비활성화되어 있습니다.',
                        style: theme.textTheme.small,
                      ),
                    ] else if (_hasActions(record)) ...[
                      const SizedBox(height: 16),
                      Wrap(
                        spacing: 8,
                        children: [
                          if (onSubmit != null &&
                              (canSubmit?.call(record) ?? false))
                            ShadButton.outline(
                              onPressed: _isProcessing(record)
                                  ? null
                                  : () {
                                      onSubmit?.call(record);
                                    },
                              child: const Text('상신'),
                            ),
                          if (onApprove != null &&
                              (canApprove?.call(record) ?? false))
                            ShadButton.outline(
                              onPressed: _isProcessing(record)
                                  ? null
                                  : () {
                                      onApprove?.call(record);
                                    },
                              child: const Text('승인'),
                            ),
                          if (onReject != null &&
                              (canReject?.call(record) ?? false))
                            ShadButton.outline(
                              onPressed: _isProcessing(record)
                                  ? null
                                  : () {
                                      onReject?.call(record);
                                    },
                              child: const Text('반려'),
                            ),
                          if (onCancel != null &&
                              (canCancel?.call(record) ?? false))
                            ShadButton.outline(
                              onPressed: _isProcessing(record)
                                  ? null
                                  : () {
                                      onCancel?.call(record);
                                    },
                              child: const Text('취소'),
                            ),
                          if (onComplete != null &&
                              (canComplete?.call(record) ?? false))
                            ShadButton.outline(
                              onPressed: _isProcessing(record)
                                  ? null
                                  : () {
                                      onComplete?.call(record);
                                    },
                              child: const Text('완료 처리'),
                            ),
                        ],
                      ),
                    ],
                  ],
                ),
              ),
            ),
          ),
        );
      },
    );
  }

  bool _hasActions(InboundRecord record) {
    final submitAllowed =
        onSubmit != null && (canSubmit?.call(record) ?? false);
    final completeAllowed =
        onComplete != null && (canComplete?.call(record) ?? false);
    final approveAllowed =
        onApprove != null && (canApprove?.call(record) ?? false);
    final rejectAllowed =
        onReject != null && (canReject?.call(record) ?? false);
    final cancelAllowed =
        onCancel != null && (canCancel?.call(record) ?? false);
    return submitAllowed ||
        completeAllowed ||
        approveAllowed ||
        rejectAllowed ||
        cancelAllowed;
  }

  bool _isProcessing(InboundRecord record) {
    return isProcessing?.call(record) ?? false;
  }
}

class _MobileMetaChip extends StatelessWidget {
  const _MobileMetaChip({required this.icon, required this.label});

  final IconData icon;
  final String label;

  @override
  Widget build(BuildContext context) {
    final theme = ShadTheme.of(context);
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        color: theme.colorScheme.muted,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(icon, size: 14, color: theme.colorScheme.mutedForeground),
          const SizedBox(width: 6),
          Text(label, style: theme.textTheme.small),
        ],
      ),
    );
  }
}

enum _LineItemField { product, manufacturer, unit, quantity, price, remark }

class _LineItemFieldErrors {
  _LineItemFieldErrors();

  String? product;
  String? quantity;
  String? price;

  static _LineItemFieldErrors empty() => _LineItemFieldErrors();

  void clearField(_LineItemField field) {
    switch (field) {
      case _LineItemField.product:
        product = null;
        break;
      case _LineItemField.quantity:
        quantity = null;
        break;
      case _LineItemField.price:
        price = null;
        break;
      case _LineItemField.manufacturer:
      case _LineItemField.unit:
      case _LineItemField.remark:
        break;
    }
  }

  void clearAll() {
    product = null;
    quantity = null;
    price = null;
  }
}

/// 입고 등록 폼 내용을 검증하고 오류 메시지를 반환한다.
_InboundFormValidation _validateInboundForm({
  required TextEditingController writerController,
  required InventoryEmployeeSuggestion? writerSelection,
  required bool requireWriterSelection,
  required InventoryWarehouseOption? warehouseSelection,
  required InventoryPartnerOption? partnerSelection,
  required String statusValue,
  required List<_LineItemDraft> drafts,
  required Map<_LineItemDraft, _LineItemFieldErrors> lineErrors,
}) {
  var isValid = true;
  String? writerError;
  String? warehouseError;
  String? partnerError;
  String? statusError;
  String? headerNotice;

  final writerText = writerController.text.trim();
  if (writerText.isEmpty) {
    writerError = '작성자를 입력하세요.';
    isValid = false;
  }

  if (requireWriterSelection && writerSelection == null) {
    writerError = '작성자는 자동완성에서 선택하세요.';
    isValid = false;
  }

  if (warehouseSelection == null) {
    warehouseError = '창고를 선택하세요.';
    isValid = false;
  }

  if (partnerSelection == null) {
    partnerError = '파트너사를 선택하세요.';
    isValid = false;
  }

  if (statusValue.trim().isEmpty) {
    statusError = '상태를 선택하세요.';
    isValid = false;
  }

  var hasLineError = false;
  final seenProductKeys = <String>{};
  for (final draft in drafts) {
    final errors = lineErrors.putIfAbsent(draft, _LineItemFieldErrors.empty);
    errors.clearAll();

    final productText = draft.product.text.trim();
    if (productText.isEmpty) {
      errors.product = '제품을 입력하세요.';
      hasLineError = true;
      isValid = false;
    } else if (draft.suggestion == null) {
      errors.product = '제품은 목록에서 선택하세요.';
      hasLineError = true;
      isValid = false;
    }

    final productKey = (draft.suggestion?.code ?? productText.toLowerCase());
    if (productKey.isNotEmpty && !seenProductKeys.add(productKey)) {
      errors.product = '동일 제품이 중복되었습니다.';
      hasLineError = true;
      isValid = false;
    }
    final quantity = int.tryParse(
      draft.quantity.text.trim().isEmpty ? '0' : draft.quantity.text.trim(),
    );
    if (quantity == null || quantity < 1) {
      errors.quantity = '수량은 1 이상 정수여야 합니다.';
      hasLineError = true;
      isValid = false;
    }

    final price = _parseCurrency(draft.price.text);
    if (price < 0) {
      errors.price = '단가는 0 이상 숫자여야 합니다.';
      hasLineError = true;
      isValid = false;
    }
  }

  if (hasLineError) {
    headerNotice = '품목 행의 오류를 수정한 후 다시 저장하세요.';
  }

  return _InboundFormValidation(
    isValid: isValid,
    writerError: writerError,
    warehouseError: warehouseError,
    partnerError: partnerError,
    statusError: statusError,
    headerNotice: headerNotice,
  );
}

double _parseCurrency(String input) {
  final normalized = input.replaceAll(RegExp(r'[^0-9.-]'), '');
  return double.tryParse(normalized.isEmpty ? '0' : normalized) ?? 0;
}

class _InboundFormValidation {
  const _InboundFormValidation({
    required this.isValid,
    this.writerError,
    this.warehouseError,
    this.partnerError,
    this.statusError,
    this.headerNotice,
  });

  final bool isValid;
  final String? writerError;
  final String? warehouseError;
  final String? partnerError;
  final String? statusError;
  final String? headerNotice;
}

class _DraftSummary {
  const _DraftSummary({
    required this.itemCount,
    required this.totalQuantity,
    required this.totalAmount,
  });

  final int itemCount;
  final int totalQuantity;
  final double totalAmount;

  factory _DraftSummary.fromDrafts(List<_LineItemDraft> drafts) {
    var quantity = 0;
    var amount = 0.0;
    for (final draft in drafts) {
      final qty = int.tryParse(draft.quantity.text.trim()) ?? 0;
      final price = _parseCurrency(draft.price.text);
      quantity += qty;
      amount += price * qty;
    }
    return _DraftSummary(
      itemCount: drafts.length,
      totalQuantity: quantity,
      totalAmount: amount,
    );
  }
}

enum _InboundSortField { processedAt, warehouse, status, writer }

class _LineItemRow extends StatelessWidget {
  const _LineItemRow({
    required this.draft,
    required this.showLabels,
    required this.onRemove,
    required this.errors,
    required this.onFieldChanged,
  });

  final _LineItemDraft draft;
  final bool showLabels;
  final VoidCallback? onRemove;
  final _LineItemFieldErrors errors;
  final void Function(_LineItemField field) onFieldChanged;

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Expanded(
          child: SuperportFormField(
            label: showLabels ? '제품' : '',
            required: showLabels,
            errorText: errors.product,
            child: InventoryProductAutocompleteField(
              productController: draft.product,
              productFocusNode: draft.productFocus,
              manufacturerController: draft.manufacturer,
              unitController: draft.unit,
              initialSuggestion: draft.suggestion,
              onSuggestionSelected: (suggestion) {
                draft.suggestion = suggestion;
                draft.productId = suggestion?.id;
                onFieldChanged(_LineItemField.product);
              },
              onChanged: () => onFieldChanged(_LineItemField.product),
            ),
          ),
        ),
        const SizedBox(width: 12),
        Expanded(
          child: SuperportFormField(
            label: showLabels ? '제조사' : '',
            caption: showLabels ? '제품 선택 시 자동 입력됩니다.' : null,
            child: ShadInput(
              controller: draft.manufacturer,
              placeholder: const Text('자동 입력'),
              readOnly: true,
              enabled: true,
            ),
          ),
        ),
        const SizedBox(width: 12),
        SizedBox(
          width: 80,
          child: SuperportFormField(
            label: showLabels ? '단위' : '',
            caption: showLabels ? '제품에 연결된 단위입니다.' : null,
            child: ShadInput(
              controller: draft.unit,
              placeholder: const Text('자동'),
              readOnly: true,
              enabled: true,
            ),
          ),
        ),
        const SizedBox(width: 12),
        SizedBox(
          width: 100,
          child: SuperportFormField(
            label: showLabels ? '수량' : '',
            required: showLabels,
            errorText: errors.quantity,
            child: ShadInput(
              controller: draft.quantity,
              placeholder: const Text('수량'),
              keyboardType: TextInputType.number,
              onChanged: (_) => onFieldChanged(_LineItemField.quantity),
            ),
          ),
        ),
        const SizedBox(width: 12),
        SizedBox(
          width: 120,
          child: SuperportFormField(
            label: showLabels ? '단가' : '',
            required: showLabels,
            errorText: errors.price,
            child: ShadInput(
              controller: draft.price,
              placeholder: const Text('단가'),
              keyboardType: TextInputType.number,
              onChanged: (_) => onFieldChanged(_LineItemField.price),
            ),
          ),
        ),
        const SizedBox(width: 12),
        Expanded(
          child: SuperportFormField(
            label: showLabels ? '비고' : '',
            child: ShadInput(
              controller: draft.remark,
              placeholder: const Text('비고'),
              onChanged: (_) => onFieldChanged(_LineItemField.remark),
            ),
          ),
        ),
        const SizedBox(width: 12),
        ShadButton.ghost(
          size: ShadButtonSize.sm,
          onPressed: onRemove,
          child: const Icon(lucide.LucideIcons.trash2, size: 16),
        ),
      ],
    );
  }
}

class _LineItemDraft {
  _LineItemDraft._({
    required this.product,
    required this.productFocus,
    required this.manufacturer,
    required this.unit,
    required this.quantity,
    required this.price,
    required this.remark,
    this.lineId,
    this.productId,
  });

  final TextEditingController product;
  final FocusNode productFocus;
  final TextEditingController manufacturer;
  final TextEditingController unit;
  final TextEditingController quantity;
  final TextEditingController price;
  final TextEditingController remark;
  final int? lineId;
  int? productId;
  InventoryProductSuggestion? suggestion;

  factory _LineItemDraft.empty() {
    return _LineItemDraft._(
      product: TextEditingController(),
      productFocus: FocusNode(),
      manufacturer: TextEditingController(),
      unit: TextEditingController(text: 'EA'),
      quantity: TextEditingController(text: '1'),
      price: TextEditingController(text: '0'),
      remark: TextEditingController(),
      lineId: null,
      productId: null,
    );
  }

  factory _LineItemDraft.fromItem(InboundLineItem item) {
    final draft = _LineItemDraft._(
      product: TextEditingController(text: item.product),
      productFocus: FocusNode(),
      manufacturer: TextEditingController(text: item.manufacturer),
      unit: TextEditingController(text: item.unit),
      quantity: TextEditingController(text: '${item.quantity}'),
      price: TextEditingController(text: item.price.toStringAsFixed(0)),
      remark: TextEditingController(text: item.remark),
      lineId: item.id,
      productId: item.productId,
    );
    draft.suggestion = InventoryProductSuggestion(
      id: item.productId,
      code: item.productCode,
      name: item.product,
      vendorName: item.manufacturer,
      unitName: item.unit,
    );
    return draft;
  }

  void dispose() {
    product.dispose();
    productFocus.dispose();
    manufacturer.dispose();
    unit.dispose();
    quantity.dispose();
    price.dispose();
    remark.dispose();
  }
}
