import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/intl.dart';
import 'package:uuid/uuid.dart';

import '../data/app_database.dart';
import '../models/work_order.dart';
import '../services/email_service.dart';
import '../services/dictation_text_accumulator.dart';
import '../services/image_storage_service.dart';
import '../services/pdf_service.dart';
import '../services/speech_dictation_service.dart';
import 'pdf_preview_screen.dart';

const _predefinedWorkOrderTitles = [
  'Instalación Básica',
  'Mantención Preventiva',
  'Mantención Correctiva',
  'Reubicación Equipo',
];

const _predefinedTaskTitles = [
  'Cambiar mecanismos del estanque del WC',
  'Cambiar sifones y desagües',
  'Cambiar válvulas, flexibles y conexiones',
  'Conectar cocinas, hornos o calefont',
  'Detectar fugas de gas',
  'Instalar lavamanos, lavaplatos o lavaderos',
  'Instalar o cambiar calefont',
  'Instalar puntos adicionales de agua',
  'Instalar termos eléctricos',
  'Instalar y cambiar llaves de paso',
  'Instalar y reparar inodoros',
  'Mantención de calefont',
  'Realizar mantenimiento preventivo',
  'Reparar tuberías y conexiones de gas',
  'Reparar WC que pierde agua o no descarga',
];

enum _TitleInputMode { manual, predefined }

final _clpFormat = NumberFormat.decimalPattern('es_CL');

String _formatClp(int value) => '\$${_clpFormat.format(value)}';

int _parsePrice(String value) {
  return int.tryParse(value.replaceAll(RegExp(r'[^0-9]'), '')) ?? 0;
}

class WorkOrderScreen extends StatefulWidget {
  const WorkOrderScreen({required this.details, this.order, super.key});

  final WorkOrderDetails details;
  final WorkOrder? order;

  @override
  State<WorkOrderScreen> createState() => _WorkOrderScreenState();
}

class _WorkOrderScreenState extends State<WorkOrderScreen> {
  final _database = AppDatabase.instance;
  final _imagePicker = ImagePicker();
  final _imageStorage = ImageStorageService();
  final _pdfService = PdfService();
  final _emailService = EmailService();
  final _dictationService = SpeechDictationService.instance;
  final _dictationText = DictationTextAccumulator();
  final _uuid = const Uuid();
  final _formKey = GlobalKey<FormState>();

  late final String _workOrderId;
  late final TextEditingController _titleController;
  late final TextEditingController _laborCostController;
  late _TitleInputMode _titleInputMode;
  late String _manualTitle;
  String? _selectedPredefinedTitle;
  late List<_TaskDraft> _tasks;
  late Set<String> _originalImagePaths;

  final Set<String> _newImagePaths = {};
  WorkOrder? _order;
  String? _dictatingTaskId;
  bool _dictationButtonHeld = false;
  bool _dictationStarting = false;
  bool _dictationRestarting = false;
  bool _suppressDictationRestart = false;
  bool _dirty = false;
  bool _busy = false;

  bool get _readOnly => _order?.isCompleted ?? false;

  @override
  void initState() {
    super.initState();
    _order = widget.order;
    _dirty =
        widget.order != null &&
        (widget.order!.details.client != widget.details.client ||
            widget.order!.details.phone != widget.details.phone ||
            widget.order!.details.address != widget.details.address);
    _workOrderId = widget.order?.id ?? _uuid.v4();
    final initialTitle = widget.order?.title ?? '';
    _selectedPredefinedTitle = _predefinedWorkOrderTitles.contains(initialTitle)
        ? initialTitle
        : null;
    _titleInputMode = initialTitle.isEmpty || _selectedPredefinedTitle != null
        ? _TitleInputMode.predefined
        : _TitleInputMode.manual;
    _manualTitle = _titleInputMode == _TitleInputMode.manual
        ? initialTitle
        : '';
    _titleController = TextEditingController(text: initialTitle);
    _titleController.addListener(_onTitleChanged);
    final initialLaborCost = widget.order?.laborCost ?? 0;
    _laborCostController = TextEditingController(
      text: initialLaborCost > 0 ? initialLaborCost.toString() : '',
    );
    _laborCostController.addListener(_markDirty);
    _tasks =
        widget.order?.tasks
            .map((task) => _TaskDraft.fromTask(task, _markDirty))
            .toList() ??
        [_TaskDraft.empty(_uuid.v4(), _markDirty)];
    _originalImagePaths =
        widget.order?.tasks
            .expand((task) => task.photos)
            .map((photo) => photo.path)
            .toSet() ??
        {};
  }

  @override
  void dispose() {
    unawaited(_dictationService.endSession());
    _titleController.removeListener(_onTitleChanged);
    _titleController.dispose();
    _laborCostController.removeListener(_markDirty);
    _laborCostController.dispose();
    for (final task in _tasks) {
      task.dispose();
    }
    super.dispose();
  }

  void _markDirty() {
    if (!_readOnly && mounted && !_dirty) {
      setState(() => _dirty = true);
    }
  }

  void _onTitleChanged() {
    if (_titleInputMode == _TitleInputMode.manual) {
      _manualTitle = _titleController.text;
    }
    _markDirty();
  }

  void _changeTitleInputMode(_TitleInputMode mode) {
    if (mode == _titleInputMode) return;

    final nextTitle = mode == _TitleInputMode.manual
        ? _manualTitle
        : _selectedPredefinedTitle ?? '';
    setState(() => _titleInputMode = mode);
    _titleController.text = nextTitle;
  }

  void _toggleTitleInputMode() {
    _changeTitleInputMode(
      _titleInputMode == _TitleInputMode.predefined
          ? _TitleInputMode.manual
          : _TitleInputMode.predefined,
    );
  }

  void _selectPredefinedTitle(String? title) {
    if (title == null) return;
    setState(() => _selectedPredefinedTitle = title);
    _titleController.text = title;
  }

  Future<void> _startDictation(_TaskDraft task) async {
    if (_dictationStarting) return;
    _dictationButtonHeld = true;
    _dictationStarting = true;
    if (mounted) {
      setState(() => _dictatingTaskId = task.id);
    }
    try {
      if (_dictationService.isListening) {
        _suppressDictationRestart = true;
        try {
          await _dictationService.stop();
        } finally {
          _suppressDictationRestart = false;
        }
      }

      final available = await _dictationService.initialize(
        onStatusChanged: _handleDictationStatus,
        onError: _handleDictationError,
      );
      if (!available) {
        _dictationButtonHeld = false;
        if (mounted) setState(() => _dictatingTaskId = null);
        _showMessage(
          'El dictado no está disponible. Revisa el permiso del micrófono.',
        );
        return;
      }
      if (!_dictationButtonHeld || !mounted) {
        if (mounted) setState(() => _dictatingTaskId = null);
        return;
      }

      _dictationText.begin(task.observationController.text);
      setState(() => _dictatingTaskId = task.id);
      await _dictationService.listen(onResult: _handleDictationResult);
    } catch (_) {
      if (!mounted) return;
      _dictationButtonHeld = false;
      setState(() => _dictatingTaskId = null);
      _showMessage('No fue posible iniciar el dictado.');
    } finally {
      _dictationStarting = false;
    }
  }

  Future<void> _stopDictation() async {
    _dictationButtonHeld = false;
    if (_dictationService.isListening) {
      await _dictationService.stop();
    }
    if (mounted && _dictatingTaskId != null) {
      setState(() => _dictatingTaskId = null);
    }
  }

  void _handleDictationResult(String words, bool isFinalResult) {
    final taskId = _dictatingTaskId;
    final recognizedWords = words.trim();
    if (taskId == null || recognizedWords.isEmpty) return;

    _TaskDraft? activeTask;
    for (final task in _tasks) {
      if (task.id == taskId) {
        activeTask = task;
        break;
      }
    }
    if (activeTask == null) return;

    final updatedText = _dictationText.addResult(recognizedWords);
    activeTask.observationController.value = TextEditingValue(
      text: updatedText,
      selection: TextSelection.collapsed(offset: updatedText.length),
    );
  }

  void _handleDictationStatus(bool isListening) {
    if (isListening || !mounted) return;
    final taskId = _dictatingTaskId;
    if (taskId == null) return;

    if (_dictationButtonHeld && !_suppressDictationRestart) {
      unawaited(_restartDictationAfterPause(taskId));
    }
  }

  Future<void> _restartDictationAfterPause(String taskId) async {
    if (_dictationRestarting) return;
    _dictationRestarting = true;
    try {
      await Future<void>.delayed(const Duration(milliseconds: 800));
      if (!_dictationButtonHeld || !mounted || _dictatingTaskId != taskId) {
        return;
      }

      _TaskDraft? activeTask;
      for (final task in _tasks) {
        if (task.id == taskId) {
          activeTask = task;
          break;
        }
      }
      if (activeTask == null) return;

      _dictationText.begin(activeTask.observationController.text);
      await _dictationService.listen(onResult: _handleDictationResult);
    } catch (_) {
      if (!mounted) return;
      _dictationButtonHeld = false;
      setState(() => _dictatingTaskId = null);
      _showMessage(
        'El dictado se interrumpió. Mantén presionado para intentar nuevamente.',
      );
    } finally {
      _dictationRestarting = false;
    }
  }

  void _handleDictationError(String errorCode) {
    if (!mounted) return;
    final transientError =
        errorCode == 'error_no_match' || errorCode == 'error_speech_timeout';
    if (transientError && _dictationButtonHeld && _dictatingTaskId != null) {
      return;
    }

    _dictationButtonHeld = false;
    if (_dictatingTaskId != null) {
      setState(() => _dictatingTaskId = null);
    }

    final message = switch (errorCode) {
      'error_permission' || 'error_permission_denied' =>
        'Autoriza el micrófono para usar el dictado.',
      'error_no_match' ||
      'error_speech_timeout' => 'No se detectó una opción. Intenta nuevamente.',
      _ => 'El dictado se interrumpió. Intenta nuevamente.',
    };
    _showMessage(message);
  }

  void _addTask() {
    setState(() {
      _tasks.add(_TaskDraft.empty(_uuid.v4(), _markDirty));
      _dirty = true;
    });
  }

  Future<void> _removeTask(int index) async {
    final task = _tasks[index];
    final hasContent =
        task.titleController.text.trim().isNotEmpty ||
        task.observationController.text.trim().isNotEmpty ||
        task.photos.isNotEmpty ||
        task.parts.isNotEmpty;
    if (hasContent) {
      final confirmed = await showDialog<bool>(
        context: context,
        builder: (context) => AlertDialog(
          title: const Text('Quitar tarea'),
          content: const Text(
            'La tarea y sus fotografías se quitarán de esta orden.',
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context, false),
              child: const Text('Cancelar'),
            ),
            FilledButton(
              onPressed: () => Navigator.pop(context, true),
              child: const Text('Quitar'),
            ),
          ],
        ),
      );
      if (confirmed != true) return;
    }
    if (_dictatingTaskId == task.id) {
      await _stopDictation();
      if (!mounted) return;
    }
    setState(() {
      final removed = _tasks.removeAt(index);
      removed.dispose();
      _dirty = true;
    });
  }

  Future<void> _choosePhotos(_TaskDraft task, PhotoKind kind) async {
    final source = await showModalBottomSheet<ImageSource>(
      context: context,
      showDragHandle: true,
      builder: (context) => SafeArea(
        child: Padding(
          padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Agregar evidencia',
                style: TextStyle(fontSize: 19, fontWeight: FontWeight.w800),
              ),
              const SizedBox(height: 6),
              const Text(
                'Puedes tomar una foto o seleccionar varias desde la galería.',
                style: TextStyle(color: Color(0xFF627372)),
              ),
              const SizedBox(height: 16),
              ListTile(
                contentPadding: EdgeInsets.zero,
                leading: const _SourceIcon(icon: Icons.photo_camera_outlined),
                title: const Text('Tomar fotografía'),
                subtitle: const Text('Usar la cámara del dispositivo'),
                onTap: () => Navigator.pop(context, ImageSource.camera),
              ),
              ListTile(
                contentPadding: EdgeInsets.zero,
                leading: const _SourceIcon(icon: Icons.photo_library_outlined),
                title: const Text('Elegir de la galería'),
                subtitle: const Text('Seleccionar una o varias imágenes'),
                onTap: () => Navigator.pop(context, ImageSource.gallery),
              ),
            ],
          ),
        ),
      ),
    );
    if (source == null) return;

    try {
      setState(() => _busy = true);
      final selected = <XFile>[];
      if (source == ImageSource.camera) {
        final photo = await _imagePicker.pickImage(
          source: source,
          maxWidth: 1920,
          imageQuality: 82,
        );
        if (photo != null) selected.add(photo);
      } else {
        selected.addAll(
          await _imagePicker.pickMultiImage(maxWidth: 1920, imageQuality: 82),
        );
      }

      for (final sourceFile in selected) {
        final storedPath = await _imageStorage.storeImage(
          sourcePath: sourceFile.path,
          workOrderId: _workOrderId,
          taskId: task.id,
        );
        _newImagePaths.add(storedPath);
        final photo = TaskPhoto(id: _uuid.v4(), path: storedPath, kind: kind);
        if (kind == PhotoKind.before) {
          task.beforePhotos.add(photo);
        } else {
          task.afterPhotos.add(photo);
        }
      }
      if (!mounted) return;
      setState(() {
        if (selected.isNotEmpty) _dirty = true;
        _busy = false;
      });
    } catch (_) {
      if (!mounted) return;
      setState(() => _busy = false);
      _showMessage('No fue posible agregar las fotografías.');
    }
  }

  void _removePhoto(_TaskDraft task, TaskPhoto photo) {
    setState(() {
      task.beforePhotos.removeWhere((item) => item.id == photo.id);
      task.afterPhotos.removeWhere((item) => item.id == photo.id);
      _dirty = true;
    });
  }

  void _addPart(_TaskDraft task) {
    setState(() {
      task.parts.add(_PartDraft.empty(_uuid.v4(), _markDirty));
      _dirty = true;
    });
  }

  void _removePart(_TaskDraft task, _PartDraft part) {
    setState(() {
      task.parts.remove(part);
      part.dispose();
      _dirty = true;
    });
  }

  bool _validateDraft() {
    if (!_formKey.currentState!.validate()) return false;
    if (_tasks.isEmpty) {
      _showMessage('Agrega al menos una tarea.');
      return false;
    }
    for (var index = 0; index < _tasks.length; index++) {
      final task = _tasks[index];
      if (task.titleController.text.trim().isEmpty) {
        _showMessage('Escribe el nombre de la tarea ${index + 1}.');
        return false;
      }
      for (var partIndex = 0; partIndex < task.parts.length; partIndex++) {
        final part = task.parts[partIndex];
        if (part.nameController.text.trim().isEmpty) {
          _showMessage(
            'Escribe el nombre del repuesto ${partIndex + 1} '
            'de la tarea ${index + 1}.',
          );
          return false;
        }
        if (_parsePrice(part.priceController.text) <= 0) {
          _showMessage(
            'Ingresa un precio válido para el repuesto ${partIndex + 1} '
            'de la tarea ${index + 1}.',
          );
          return false;
        }
      }
    }
    return true;
  }

  bool _validateCompletion() {
    if (!_validateDraft()) return false;
    for (var index = 0; index < _tasks.length; index++) {
      final task = _tasks[index];
      if (task.observationController.text.trim().isEmpty) {
        _showMessage('Registra la observación de la tarea ${index + 1}.');
        return false;
      }
      if (task.beforePhotos.isEmpty) {
        _showMessage('Agrega una foto “antes” en la tarea ${index + 1}.');
        return false;
      }
      if (task.afterPhotos.isEmpty) {
        _showMessage('Agrega una foto “después” en la tarea ${index + 1}.');
        return false;
      }
    }
    return true;
  }

  WorkOrder _buildOrder({
    required WorkOrderStatus status,
    String? recipientEmail,
    String? pdfPath,
    DateTime? completedAt,
  }) {
    final now = DateTime.now();
    return WorkOrder(
      id: _workOrderId,
      title: _titleController.text.trim(),
      createdAt: _order?.createdAt ?? now,
      updatedAt: now,
      completedAt: completedAt ?? _order?.completedAt,
      status: status,
      details: widget.details,
      laborCost: _parsePrice(_laborCostController.text),
      recipientEmail: recipientEmail ?? _order?.recipientEmail,
      pdfPath: pdfPath ?? _order?.pdfPath,
      tasks: [
        for (var index = 0; index < _tasks.length; index++)
          WorkTask(
            id: _tasks[index].id,
            title: _tasks[index].titleController.text.trim(),
            observation: _tasks[index].observationController.text.trim(),
            position: index,
            photos: _tasks[index].photos,
            parts: [
              for (final part in _tasks[index].parts)
                TaskPart(
                  id: part.id,
                  name: part.nameController.text.trim(),
                  price: _parsePrice(part.priceController.text),
                ),
            ],
          ),
      ],
    );
  }

  Future<WorkOrder?> _saveDraft({bool showConfirmation = true}) async {
    if (!_validateDraft()) return null;
    final draft = _buildOrder(status: WorkOrderStatus.open);
    try {
      setState(() => _busy = true);
      await _database.saveWorkOrder(draft);
      await _cleanupUnusedImages();
      if (!mounted) return null;
      setState(() {
        _order = draft;
        _dirty = false;
        _busy = false;
      });
      if (showConfirmation) {
        _showMessage('Borrador guardado en el dispositivo.');
      }
      return draft;
    } catch (_) {
      if (!mounted) return null;
      setState(() => _busy = false);
      _showMessage('No fue posible guardar el borrador.');
      return null;
    }
  }

  Future<void> _finalize() async {
    if (!_validateCompletion()) return;
    final reportType = await _requestPdfReportType();
    if (reportType == null || !mounted) return;
    final recipient = await _requestEmail(_order?.recipientEmail);
    if (recipient == null || !mounted) return;

    setState(() => _busy = true);
    try {
      final completionTime = DateTime.now();
      final reportOrder = _buildOrder(
        status: WorkOrderStatus.completed,
        recipientEmail: recipient,
        completedAt: completionTime,
      );
      final pdfPath = await _pdfService.generate(reportOrder, type: reportType);
      final completedOrder = reportOrder.copyWith(
        updatedAt: DateTime.now(),
        pdfPath: pdfPath,
      );
      await _database.saveWorkOrder(completedOrder);
      await _cleanupUnusedImages();
      if (!mounted) return;
      setState(() {
        _order = completedOrder;
        _dirty = false;
      });

      try {
        await _emailService.sendReport(
          order: completedOrder,
          recipient: recipient,
          pdfPath: pdfPath,
          reportType: reportType,
        );
        if (!mounted) return;
        _showMessage('OT finalizada. El correo quedó listo para enviar.');
      } catch (_) {
        if (!mounted) return;
        _showMessage(
          'La OT y el PDF se guardaron, pero no se encontró una aplicación '
          'de correo configurada.',
        );
      }
    } catch (_) {
      if (!mounted) return;
      _showMessage('No fue posible finalizar la orden ni crear el PDF.');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<String?> _ensurePdf(PdfReportType reportType) async {
    final order = _order;
    if (order == null) return null;
    final existingPath = order.pdfPath;
    if (existingPath != null &&
        _pdfService.isCurrentReport(existingPath, type: reportType) &&
        await File(existingPath).exists()) {
      return existingPath;
    }

    final path = await _pdfService.generate(order, type: reportType);
    final updatedOrder = order.copyWith(
      updatedAt: DateTime.now(),
      pdfPath: path,
    );
    await _database.saveWorkOrder(updatedOrder);
    if (mounted) setState(() => _order = updatedOrder);
    return path;
  }

  Future<void> _openPdf() async {
    final reportType = await _requestPdfReportType();
    if (reportType == null || !mounted) return;
    try {
      setState(() => _busy = true);
      final path = await _ensurePdf(reportType);
      if (!mounted || path == null) return;
      setState(() => _busy = false);
      await Navigator.of(context).push<void>(
        MaterialPageRoute(
          builder: (_) => PdfPreviewScreen(
            pdfPath: path,
            title: '${_order!.title} - ${reportType.label}',
          ),
        ),
      );
    } catch (_) {
      if (!mounted) return;
      _showMessage('No fue posible abrir el informe PDF.');
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<void> _sendExistingReport() async {
    final currentOrder = _order;
    if (currentOrder == null) return;
    final reportType = await _requestPdfReportType();
    if (reportType == null || !mounted) return;
    final recipient = await _requestEmail(currentOrder.recipientEmail);
    if (recipient == null || !mounted) return;

    try {
      setState(() => _busy = true);
      final path = await _ensurePdf(reportType);
      if (path == null) return;
      final updatedOrder = _order!.copyWith(
        updatedAt: DateTime.now(),
        recipientEmail: recipient,
      );
      await _database.saveWorkOrder(updatedOrder);
      if (mounted) setState(() => _order = updatedOrder);
      await _emailService.sendReport(
        order: updatedOrder,
        recipient: recipient,
        pdfPath: path,
        reportType: reportType,
      );
      if (!mounted) return;
      _showMessage('El correo quedó listo para enviar.');
    } catch (_) {
      if (!mounted) return;
      _showMessage(
        'No se pudo abrir el correo. Verifica que exista una cuenta configurada.',
      );
    } finally {
      if (mounted) setState(() => _busy = false);
    }
  }

  Future<String?> _requestEmail(String? initialValue) async {
    return showDialog<String>(
      context: context,
      builder: (context) => EmailRecipientDialog(initialValue: initialValue),
    );
  }

  Future<PdfReportType?> _requestPdfReportType() {
    return showDialog<PdfReportType>(
      context: context,
      builder: (context) => const PdfReportTypeDialog(),
    );
  }

  Future<void> _cleanupUnusedImages() async {
    final currentPaths = _tasks
        .expand((task) => task.photos)
        .map((photo) => photo.path)
        .toSet();
    final unusedPaths = {
      ..._originalImagePaths.difference(currentPaths),
      ..._newImagePaths.difference(currentPaths),
    };
    await _imageStorage.deleteFiles(unusedPaths);
    _originalImagePaths = currentPaths;
    _newImagePaths.clear();
  }

  Future<void> _handleUnsavedExit() async {
    final action = await showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Cambios sin guardar'),
        content: const Text(
          'Puedes guardar la orden como borrador para continuar más tarde.',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context, 'cancel'),
            child: const Text('Seguir editando'),
          ),
          TextButton(
            onPressed: () => Navigator.pop(context, 'discard'),
            child: const Text('Descartar'),
          ),
          FilledButton(
            onPressed: () => Navigator.pop(context, 'save'),
            child: const Text('Guardar'),
          ),
        ],
      ),
    );
    if (!mounted) return;
    if (action == 'discard') {
      await _imageStorage.deleteFiles(_newImagePaths);
      if (mounted) Navigator.pop(context);
    } else if (action == 'save') {
      final saved = await _saveDraft(showConfirmation: false);
      if (saved != null && mounted) Navigator.pop(context);
    }
  }

  void _showMessage(String message) {
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: !_dirty || _readOnly,
      onPopInvokedWithResult: (didPop, result) {
        if (!didPop) _handleUnsavedExit();
      },
      child: Scaffold(
        appBar: AppBar(
          title: Text(
            _readOnly
                ? 'Detalle de la OT'
                : widget.order == null
                ? 'Nueva orden'
                : 'Editar orden',
          ),
          actions: [
            if (!_readOnly)
              IconButton(
                tooltip: 'Guardar borrador',
                onPressed: _busy ? null : _saveDraft,
                icon: const Icon(Icons.save_outlined),
              ),
            const SizedBox(width: 6),
          ],
          bottom: _busy
              ? const PreferredSize(
                  preferredSize: Size.fromHeight(3),
                  child: LinearProgressIndicator(minHeight: 3),
                )
              : null,
        ),
        body: AbsorbPointer(
          absorbing: _busy,
          child: Form(
            key: _formKey,
            child: ListView(
              padding: const EdgeInsets.fromLTRB(20, 12, 20, 32),
              children: [
                if (_readOnly) ...[
                  _CompletedBanner(order: _order!),
                  const SizedBox(height: 24),
                ],
                _OrderDetailsHeader(details: widget.details),
                const SizedBox(height: 24),
                if (_readOnly)
                  TextFormField(
                    controller: _titleController,
                    readOnly: true,
                    textCapitalization: TextCapitalization.sentences,
                    textInputAction: TextInputAction.next,
                    decoration: const InputDecoration(
                      hintText: 'Ej. Mantención preventiva bomba N.º 2',
                    ),
                    validator: (value) => (value?.trim().isEmpty ?? true)
                        ? 'Ingresa el título de la orden'
                        : null,
                  )
                else
                  Row(
                    children: [
                      Expanded(
                        child: _titleInputMode == _TitleInputMode.predefined
                            ? DropdownButtonFormField<String>(
                                initialValue: _selectedPredefinedTitle,
                                isExpanded: true,
                                decoration: const InputDecoration(
                                  hintText: 'Selecciona una opción',
                                ),
                                items: [
                                  for (final title
                                      in _predefinedWorkOrderTitles)
                                    DropdownMenuItem(
                                      value: title,
                                      child: Text(title),
                                    ),
                                ],
                                onChanged: _selectPredefinedTitle,
                                validator: (value) => value == null
                                    ? 'Selecciona una opción para el título'
                                    : null,
                              )
                            : TextFormField(
                                controller: _titleController,
                                autofocus: true,
                                textCapitalization:
                                    TextCapitalization.sentences,
                                textInputAction: TextInputAction.next,
                                decoration: const InputDecoration(
                                  hintText: 'Escribe el título de la orden',
                                ),
                                validator: (value) =>
                                    (value?.trim().isEmpty ?? true)
                                    ? 'Ingresa el título de la orden'
                                    : null,
                              ),
                      ),
                      const SizedBox(width: 6),
                      IconButton.filledTonal(
                        tooltip: _titleInputMode == _TitleInputMode.predefined
                            ? 'Escribir título de la orden'
                            : 'Usar una opción predefinida',
                        onPressed: _toggleTitleInputMode,
                        icon: Icon(
                          _titleInputMode == _TitleInputMode.predefined
                              ? Icons.edit_outlined
                              : Icons.format_quote_rounded,
                        ),
                      ),
                    ],
                  ),
                const SizedBox(height: 28),
                Row(
                  children: [
                    const Expanded(
                      child: _FieldLabel(
                        title: 'Tareas',
                        subtitle: 'Documenta cada actividad por separado.',
                      ),
                    ),
                    if (!_readOnly)
                      TextButton.icon(
                        onPressed: _addTask,
                        icon: const Icon(Icons.add_rounded),
                        label: const Text('Agregar'),
                      ),
                  ],
                ),
                const SizedBox(height: 12),
                for (var index = 0; index < _tasks.length; index++) ...[
                  _TaskCard(
                    key: ValueKey(_tasks[index].id),
                    number: index + 1,
                    task: _tasks[index],
                    readOnly: _readOnly,
                    isDictating: _dictatingTaskId == _tasks[index].id,
                    canDelete: !_readOnly,
                    onDelete: () => _removeTask(index),
                    onStartDictation: () => _startDictation(_tasks[index]),
                    onStopDictation: _stopDictation,
                    onAddPhotos: (kind) => _choosePhotos(_tasks[index], kind),
                    onRemovePhoto: (photo) =>
                        _removePhoto(_tasks[index], photo),
                    onAddPart: () => _addPart(_tasks[index]),
                    onRemovePart: (part) => _removePart(_tasks[index], part),
                  ),
                  const SizedBox(height: 16),
                ],
                if (!_readOnly)
                  OutlinedButton.icon(
                    onPressed: _addTask,
                    icon: const Icon(Icons.add_task_rounded),
                    label: const Text('Agregar otra tarea'),
                  ),
                const SizedBox(height: 24),
                const Divider(color: Color(0xFFE2EAE9)),
                const SizedBox(height: 16),
                TextFormField(
                  key: const Key('labor-cost-field'),
                  controller: _laborCostController,
                  readOnly: _readOnly,
                  keyboardType: TextInputType.number,
                  textInputAction: TextInputAction.done,
                  inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                  style: const TextStyle(
                    fontSize: 17,
                    fontWeight: FontWeight.w700,
                  ),
                  decoration: const InputDecoration(
                    labelText: 'Costo de mano de obra',
                    helperText: 'Monto total asociado a toda la OT.',
                    prefixText: r'$ ',
                    prefixIcon: Icon(Icons.payments_outlined),
                  ),
                  validator: (value) => _parsePrice(value ?? '') <= 0
                      ? 'Ingresa un costo de mano de obra válido'
                      : null,
                ),
              ],
            ),
          ),
        ),
        bottomNavigationBar: _BottomActions(
          readOnly: _readOnly,
          busy: _busy,
          onSave: _saveDraft,
          onFinalize: _finalize,
          onOpenPdf: _openPdf,
          onSendEmail: _sendExistingReport,
        ),
      ),
    );
  }
}

class _OrderDetailsHeader extends StatelessWidget {
  const _OrderDetailsHeader({required this.details});

  final WorkOrderDetails details;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: const Color(0xFFE7F1F0),
        borderRadius: BorderRadius.circular(16),
      ),
      child: Column(
        children: [
          _detailRow(
            icon: Icons.person_outline_rounded,
            label: 'Cliente',
            value: details.client,
          ),
        ],
      ),
    );
  }

  Widget _detailRow({
    required IconData icon,
    required String label,
    required String value,
  }) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Icon(icon, size: 21, color: const Color(0xFF0B5C5E)),
        const SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                label.toUpperCase(),
                style: const TextStyle(
                  color: Color(0xFF627372),
                  fontSize: 10,
                  fontWeight: FontWeight.w800,
                  letterSpacing: .7,
                ),
              ),
              const SizedBox(height: 3),
              Text(
                value.isEmpty ? 'No informado' : value,
                style: const TextStyle(
                  color: Color(0xFF163334),
                  fontSize: 14,
                  fontWeight: FontWeight.w700,
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}

class _TaskDraft {
  _TaskDraft({
    required this.id,
    required this.titleController,
    required this.observationController,
    required this.beforePhotos,
    required this.afterPhotos,
    required this.parts,
    required VoidCallback onChanged,
  }) : _onChanged = onChanged {
    titleController.addListener(_onChanged);
    observationController.addListener(_onChanged);
  }

  factory _TaskDraft.empty(String id, VoidCallback onChanged) {
    return _TaskDraft(
      id: id,
      titleController: TextEditingController(),
      observationController: TextEditingController(),
      beforePhotos: [],
      afterPhotos: [],
      parts: [],
      onChanged: onChanged,
    );
  }

  factory _TaskDraft.fromTask(WorkTask task, VoidCallback onChanged) {
    return _TaskDraft(
      id: task.id,
      titleController: TextEditingController(text: task.title),
      observationController: TextEditingController(text: task.observation),
      beforePhotos: List.of(task.beforePhotos),
      afterPhotos: List.of(task.afterPhotos),
      parts: task.parts
          .map((part) => _PartDraft.fromPart(part, onChanged))
          .toList(),
      onChanged: onChanged,
    );
  }

  final String id;
  final TextEditingController titleController;
  final TextEditingController observationController;
  final List<TaskPhoto> beforePhotos;
  final List<TaskPhoto> afterPhotos;
  final List<_PartDraft> parts;
  final VoidCallback _onChanged;

  List<TaskPhoto> get photos => [...beforePhotos, ...afterPhotos];

  void dispose() {
    titleController
      ..removeListener(_onChanged)
      ..dispose();
    observationController
      ..removeListener(_onChanged)
      ..dispose();
    for (final part in parts) {
      part.dispose();
    }
  }
}

class _PartDraft {
  _PartDraft({
    required this.id,
    required this.nameController,
    required this.priceController,
    required VoidCallback onChanged,
  }) : _onChanged = onChanged {
    nameController.addListener(_onChanged);
    priceController.addListener(_onChanged);
  }

  factory _PartDraft.empty(String id, VoidCallback onChanged) {
    return _PartDraft(
      id: id,
      nameController: TextEditingController(),
      priceController: TextEditingController(),
      onChanged: onChanged,
    );
  }

  factory _PartDraft.fromPart(TaskPart part, VoidCallback onChanged) {
    return _PartDraft(
      id: part.id,
      nameController: TextEditingController(text: part.name),
      priceController: TextEditingController(text: part.price.toString()),
      onChanged: onChanged,
    );
  }

  final String id;
  final TextEditingController nameController;
  final TextEditingController priceController;
  final VoidCallback _onChanged;

  int get price => _parsePrice(priceController.text);

  void dispose() {
    nameController
      ..removeListener(_onChanged)
      ..dispose();
    priceController
      ..removeListener(_onChanged)
      ..dispose();
  }
}

class _CompletedBanner extends StatelessWidget {
  const _CompletedBanner({required this.order});

  final WorkOrder order;

  @override
  Widget build(BuildContext context) {
    final date = order.completedAt ?? order.updatedAt;
    return _StatusBanner(
      icon: Icons.task_alt_rounded,
      color: const Color(0xFF24755E),
      background: const Color(0xFFE4F3ED),
      title: 'Orden finalizada',
      message:
          '${DateFormat('dd/MM/yyyy HH:mm').format(date.toLocal())} · '
          '${order.tasks.length} tarea(s) · ${order.totalPhotos} foto(s)',
    );
  }
}

class _StatusBanner extends StatelessWidget {
  const _StatusBanner({
    required this.icon,
    required this.color,
    required this.background,
    required this.title,
    required this.message,
  });

  final IconData icon;
  final Color color;
  final Color background;
  final String title;
  final String message;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: background,
        borderRadius: BorderRadius.circular(16),
      ),
      child: Row(
        children: [
          Icon(icon, color: color, size: 27),
          const SizedBox(width: 13),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  title,
                  style: TextStyle(color: color, fontWeight: FontWeight.w800),
                ),
                const SizedBox(height: 3),
                Text(
                  message,
                  style: const TextStyle(
                    color: Color(0xFF4E6260),
                    height: 1.35,
                    fontSize: 12,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _FieldLabel extends StatelessWidget {
  const _FieldLabel({required this.title, required this.subtitle});

  final String title;
  final String subtitle;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(
            color: Color(0xFF163334),
            fontSize: 17,
            fontWeight: FontWeight.w800,
          ),
        ),
        const SizedBox(height: 3),
        Text(
          subtitle,
          style: const TextStyle(color: Color(0xFF6A7A79), fontSize: 12),
        ),
      ],
    );
  }
}

class _TaskTitleField extends StatefulWidget {
  const _TaskTitleField({required this.task, required this.readOnly});

  final _TaskDraft task;
  final bool readOnly;

  @override
  State<_TaskTitleField> createState() => _TaskTitleFieldState();
}

class _TaskTitleFieldState extends State<_TaskTitleField> {
  late _TitleInputMode _inputMode;
  late String _manualTitle;
  String? _selectedPredefinedTitle;

  @override
  void initState() {
    super.initState();
    final initialTitle = widget.task.titleController.text;
    _selectedPredefinedTitle = _predefinedTaskTitles.contains(initialTitle)
        ? initialTitle
        : null;
    _inputMode = initialTitle.isEmpty || _selectedPredefinedTitle != null
        ? _TitleInputMode.predefined
        : _TitleInputMode.manual;
    _manualTitle = _inputMode == _TitleInputMode.manual ? initialTitle : '';
  }

  void _toggleInputMode() {
    final nextMode = _inputMode == _TitleInputMode.predefined
        ? _TitleInputMode.manual
        : _TitleInputMode.predefined;
    final nextTitle = nextMode == _TitleInputMode.manual
        ? _manualTitle
        : _selectedPredefinedTitle ?? '';
    setState(() => _inputMode = nextMode);
    widget.task.titleController.text = nextTitle;
  }

  void _selectPredefinedTitle(String? title) {
    if (title == null) return;
    setState(() => _selectedPredefinedTitle = title);
    widget.task.titleController.text = title;
  }

  @override
  Widget build(BuildContext context) {
    if (widget.readOnly) {
      return TextFormField(
        controller: widget.task.titleController,
        readOnly: true,
        decoration: const InputDecoration(labelText: 'Tarea realizada'),
      );
    }

    return Row(
      children: [
        Expanded(
          child: _inputMode == _TitleInputMode.predefined
              ? DropdownButtonFormField<String>(
                  initialValue: _selectedPredefinedTitle,
                  isExpanded: true,
                  decoration: const InputDecoration(
                    hintText: 'Selecciona una opción',
                  ),
                  items: [
                    for (final title in _predefinedTaskTitles)
                      DropdownMenuItem(value: title, child: Text(title)),
                  ],
                  onChanged: _selectPredefinedTitle,
                )
              : TextFormField(
                  controller: widget.task.titleController,
                  autofocus: true,
                  textCapitalization: TextCapitalization.sentences,
                  onChanged: (value) => _manualTitle = value,
                  decoration: const InputDecoration(
                    hintText: 'Escribe el título de la tarea',
                  ),
                ),
        ),
        const SizedBox(width: 6),
        IconButton.filledTonal(
          tooltip: _inputMode == _TitleInputMode.predefined
              ? 'Escribir título de la tarea'
              : 'Usar una opción predefinida',
          onPressed: _toggleInputMode,
          icon: Icon(
            _inputMode == _TitleInputMode.predefined
                ? Icons.edit_outlined
                : Icons.format_quote_rounded,
          ),
        ),
      ],
    );
  }
}

class _HoldToDictateButton extends StatelessWidget {
  const _HoldToDictateButton({
    required this.isDictating,
    required this.onStart,
    required this.onStop,
  });

  final bool isDictating;
  final VoidCallback onStart;
  final VoidCallback onStop;

  @override
  Widget build(BuildContext context) {
    return Semantics(
      button: true,
      label: 'Mantén presionado para dictar la observación',
      child: Listener(
        behavior: HitTestBehavior.opaque,
        onPointerDown: (_) => onStart(),
        onPointerUp: (_) => onStop(),
        onPointerCancel: (_) => onStop(),
        child: SizedBox(
          width: 48,
          height: 48,
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 150),
            decoration: BoxDecoration(
              color: isDictating
                  ? const Color(0xFFFFDAD6)
                  : const Color(0xFFD9EEEE),
              shape: BoxShape.circle,
            ),
            alignment: Alignment.center,
            child: Icon(
              Icons.mic_rounded,
              color: isDictating
                  ? const Color(0xFFB3261E)
                  : const Color(0xFF0B5C5E),
            ),
          ),
        ),
      ),
    );
  }
}

class _TaskCard extends StatelessWidget {
  const _TaskCard({
    required this.number,
    required this.task,
    required this.readOnly,
    required this.isDictating,
    required this.canDelete,
    required this.onDelete,
    required this.onStartDictation,
    required this.onStopDictation,
    required this.onAddPhotos,
    required this.onRemovePhoto,
    required this.onAddPart,
    required this.onRemovePart,
    super.key,
  });

  final int number;
  final _TaskDraft task;
  final bool readOnly;
  final bool isDictating;
  final bool canDelete;
  final VoidCallback onDelete;
  final VoidCallback onStartDictation;
  final VoidCallback onStopDictation;
  final ValueChanged<PhotoKind> onAddPhotos;
  final ValueChanged<TaskPhoto> onRemovePhoto;
  final VoidCallback onAddPart;
  final ValueChanged<_PartDraft> onRemovePart;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Container(
                  width: 32,
                  height: 32,
                  alignment: Alignment.center,
                  decoration: const BoxDecoration(
                    color: Color(0xFF0B5C5E),
                    shape: BoxShape.circle,
                  ),
                  child: Text(
                    '$number',
                    style: const TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.w800,
                    ),
                  ),
                ),
                const SizedBox(width: 10),
                const Expanded(
                  child: Text(
                    'Tarea',
                    style: TextStyle(
                      color: Color(0xFF163334),
                      fontSize: 16,
                      fontWeight: FontWeight.w800,
                    ),
                  ),
                ),
                if (canDelete)
                  IconButton(
                    tooltip: 'Quitar tarea',
                    onPressed: onDelete,
                    color: const Color(0xFF8C4A43),
                    icon: const Icon(Icons.delete_outline_rounded),
                  ),
              ],
            ),
            const SizedBox(height: 14),
            _TaskTitleField(task: task, readOnly: readOnly),
            const SizedBox(height: 13),
            Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(
                  child: TextFormField(
                    controller: task.observationController,
                    readOnly: readOnly,
                    textCapitalization: TextCapitalization.sentences,
                    keyboardType: TextInputType.multiline,
                    autocorrect: true,
                    enableSuggestions: true,
                    spellCheckConfiguration: const SpellCheckConfiguration(),
                    minLines: 3,
                    maxLines: 5,
                    decoration: const InputDecoration(
                      labelText: 'Observación',
                      hintText: 'Describe el hallazgo y el trabajo ejecutado',
                    ),
                  ),
                ),
                if (!readOnly) ...[
                  const SizedBox(width: 8),
                  Padding(
                    padding: const EdgeInsets.only(top: 8),
                    child: _HoldToDictateButton(
                      isDictating: isDictating,
                      onStart: onStartDictation,
                      onStop: onStopDictation,
                    ),
                  ),
                ],
              ],
            ),
            const SizedBox(height: 20),
            const Divider(color: Color(0xFFE2EAE9)),
            const SizedBox(height: 12),
            _PartsSection(
              parts: task.parts,
              readOnly: readOnly,
              onAdd: onAddPart,
              onRemove: onRemovePart,
            ),
            const SizedBox(height: 20),
            const Divider(color: Color(0xFFE2EAE9)),
            const SizedBox(height: 12),
            _PhotoSection(
              title: 'Antes de la mantención',
              hint: 'Estado inicial',
              icon: Icons.history_rounded,
              photos: task.beforePhotos,
              readOnly: readOnly,
              onAdd: () => onAddPhotos(PhotoKind.before),
              onRemove: onRemovePhoto,
            ),
            const SizedBox(height: 20),
            _PhotoSection(
              title: 'Después de la mantención',
              hint: 'Resultado final',
              icon: Icons.auto_awesome_rounded,
              photos: task.afterPhotos,
              readOnly: readOnly,
              onAdd: () => onAddPhotos(PhotoKind.after),
              onRemove: onRemovePhoto,
            ),
          ],
        ),
      ),
    );
  }
}

class _PartsSection extends StatelessWidget {
  const _PartsSection({
    required this.parts,
    required this.readOnly,
    required this.onAdd,
    required this.onRemove,
  });

  final List<_PartDraft> parts;
  final bool readOnly;
  final VoidCallback onAdd;
  final ValueChanged<_PartDraft> onRemove;

  int get _subtotal => parts.fold(0, (total, part) => total + part.price);

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            const Icon(
              Icons.inventory_2_outlined,
              color: Color(0xFF0B5C5E),
              size: 20,
            ),
            const SizedBox(width: 8),
            const Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Repuestos utilizados',
                    style: TextStyle(
                      color: Color(0xFF163334),
                      fontSize: 18,
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                  Text(
                    'Registra cada repuesto y su precio en pesos.',
                    style: TextStyle(color: Color(0xFF71807F), fontSize: 11),
                  ),
                ],
              ),
            ),
            if (!readOnly)
              IconButton.filledTonal(
                key: const Key('add-part-button'),
                tooltip: 'Agregar repuesto',
                onPressed: onAdd,
                icon: const Icon(Icons.add_rounded, size: 21),
              ),
          ],
        ),
        if (parts.isEmpty)
          Container(
            width: double.infinity,
            margin: const EdgeInsets.only(top: 10),
            padding: const EdgeInsets.symmetric(vertical: 18),
            decoration: BoxDecoration(
              color: const Color(0xFFF5F8F7),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFFDCE7E5)),
            ),
            child: const Text(
              'Sin repuestos registrados',
              textAlign: TextAlign.center,
              style: TextStyle(color: Color(0xFF7A8988), fontSize: 12),
            ),
          )
        else ...[
          const SizedBox(height: 12),
          for (var index = 0; index < parts.length; index++) ...[
            _PartInputRow(
              key: ValueKey(parts[index].id),
              number: index + 1,
              part: parts[index],
              readOnly: readOnly,
              onRemove: () => onRemove(parts[index]),
            ),
            if (index < parts.length - 1) const SizedBox(height: 10),
          ],
          const SizedBox(height: 12),
          Container(
            width: double.infinity,
            padding: const EdgeInsets.symmetric(horizontal: 13, vertical: 10),
            decoration: BoxDecoration(
              color: const Color(0xFFE7F1F0),
              borderRadius: BorderRadius.circular(11),
            ),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text(
                  'Subtotal de repuestos',
                  style: TextStyle(
                    color: Color(0xFF526665),
                    fontWeight: FontWeight.w700,
                  ),
                ),
                AnimatedBuilder(
                  animation: Listenable.merge(
                    parts.map((part) => part.priceController),
                  ),
                  builder: (context, _) => Text(
                    _formatClp(_subtotal),
                    style: const TextStyle(
                      color: Color(0xFF0B5C5E),
                      fontSize: 16,
                      fontWeight: FontWeight.w900,
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ],
    );
  }
}

class _PartInputRow extends StatelessWidget {
  const _PartInputRow({
    required this.number,
    required this.part,
    required this.readOnly,
    required this.onRemove,
    super.key,
  });

  final int number;
  final _PartDraft part;
  final bool readOnly;
  final VoidCallback onRemove;

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Expanded(
              child: TextFormField(
                key: Key('part-name-${part.id}'),
                controller: part.nameController,
                readOnly: readOnly,
                textCapitalization: TextCapitalization.sentences,
                textInputAction: TextInputAction.next,
                style: const TextStyle(
                  fontSize: 17,
                  fontWeight: FontWeight.w400,
                ),
                decoration: InputDecoration(
                  labelText: 'Repuesto $number',
                  hintText: 'Nombre del repuesto',
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Ingresa el nombre';
                  }
                  return null;
                },
              ),
            ),
            if (!readOnly) ...[
              const SizedBox(width: 4),
              IconButton(
                tooltip: 'Quitar repuesto',
                onPressed: onRemove,
                color: const Color(0xFF8C4A43),
                icon: const Icon(Icons.remove_circle_outline_rounded),
              ),
            ],
          ],
        ),
        const SizedBox(height: 8),
        TextFormField(
          key: Key('part-price-${part.id}'),
          controller: part.priceController,
          readOnly: readOnly,
          keyboardType: TextInputType.number,
          textInputAction: TextInputAction.done,
          inputFormatters: [FilteringTextInputFormatter.digitsOnly],
          style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700),
          decoration: const InputDecoration(
            labelText: 'Precio',
            prefixText: r'$ ',
          ),
          validator: (value) {
            if (_parsePrice(value ?? '') <= 0) {
              return 'Precio inválido';
            }
            return null;
          },
        ),
      ],
    );
  }
}

class _PhotoSection extends StatelessWidget {
  const _PhotoSection({
    required this.title,
    required this.hint,
    required this.icon,
    required this.photos,
    required this.readOnly,
    required this.onAdd,
    required this.onRemove,
  });

  final String title;
  final String hint;
  final IconData icon;
  final List<TaskPhoto> photos;
  final bool readOnly;
  final VoidCallback onAdd;
  final ValueChanged<TaskPhoto> onRemove;

  void _preview(BuildContext context, TaskPhoto photo) {
    showDialog<void>(
      context: context,
      builder: (context) => Dialog(
        backgroundColor: Colors.black,
        insetPadding: const EdgeInsets.all(12),
        child: Stack(
          children: [
            InteractiveViewer(
              minScale: .8,
              maxScale: 4,
              child: Image.file(
                File(photo.path),
                width: double.infinity,
                height: MediaQuery.sizeOf(context).height * .72,
                fit: BoxFit.contain,
                errorBuilder: (_, _, _) => const SizedBox(
                  height: 260,
                  child: Center(
                    child: Text(
                      'Imagen no disponible',
                      style: TextStyle(color: Colors.white),
                    ),
                  ),
                ),
              ),
            ),
            Positioned(
              right: 8,
              top: 8,
              child: IconButton.filled(
                onPressed: () => Navigator.pop(context),
                icon: const Icon(Icons.close_rounded),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Icon(icon, color: const Color(0xFF0B5C5E), size: 20),
            const SizedBox(width: 8),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    title,
                    style: const TextStyle(
                      color: Color(0xFF163334),
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                  Text(
                    '$hint · ${photos.length} foto(s)',
                    style: const TextStyle(
                      color: Color(0xFF71807F),
                      fontSize: 11,
                    ),
                  ),
                ],
              ),
            ),
            if (!readOnly)
              IconButton.filledTonal(
                tooltip: 'Agregar fotografías',
                onPressed: onAdd,
                icon: const Icon(Icons.add_a_photo_outlined, size: 20),
              ),
          ],
        ),
        if (photos.isEmpty)
          Container(
            width: double.infinity,
            margin: const EdgeInsets.only(top: 10),
            padding: const EdgeInsets.symmetric(vertical: 18),
            decoration: BoxDecoration(
              color: const Color(0xFFF5F8F7),
              borderRadius: BorderRadius.circular(12),
              border: Border.all(color: const Color(0xFFDCE7E5)),
            ),
            child: const Text(
              'Sin fotografías',
              textAlign: TextAlign.center,
              style: TextStyle(color: Color(0xFF7A8988), fontSize: 12),
            ),
          )
        else
          GridView.builder(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            padding: const EdgeInsets.only(top: 10),
            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
            ),
            itemCount: photos.length,
            itemBuilder: (context, index) {
              final photo = photos[index];
              return InkWell(
                onTap: () => _preview(context, photo),
                borderRadius: BorderRadius.circular(11),
                child: ClipRRect(
                  borderRadius: BorderRadius.circular(11),
                  child: Stack(
                    fit: StackFit.expand,
                    children: [
                      Image.file(
                        File(photo.path),
                        fit: BoxFit.cover,
                        errorBuilder: (_, _, _) => Container(
                          color: const Color(0xFFE7EEED),
                          child: const Icon(Icons.broken_image_outlined),
                        ),
                      ),
                      if (!readOnly)
                        Positioned(
                          right: 4,
                          top: 4,
                          child: InkWell(
                            onTap: () => onRemove(photo),
                            borderRadius: BorderRadius.circular(20),
                            child: Container(
                              padding: const EdgeInsets.all(4),
                              decoration: const BoxDecoration(
                                color: Color(0xCC172221),
                                shape: BoxShape.circle,
                              ),
                              child: const Icon(
                                Icons.close_rounded,
                                color: Colors.white,
                                size: 16,
                              ),
                            ),
                          ),
                        ),
                    ],
                  ),
                ),
              );
            },
          ),
      ],
    );
  }
}

class _SourceIcon extends StatelessWidget {
  const _SourceIcon({required this.icon});

  final IconData icon;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 44,
      height: 44,
      decoration: BoxDecoration(
        color: const Color(0xFFE7F1F0),
        borderRadius: BorderRadius.circular(12),
      ),
      child: Icon(icon, color: const Color(0xFF0B5C5E)),
    );
  }
}

@visibleForTesting
class PdfReportTypeDialog extends StatelessWidget {
  const PdfReportTypeDialog({super.key});

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Tipo de informe'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('Selecciona quién recibirá este informe.'),
          const SizedBox(height: 14),
          for (final type in PdfReportType.values) ...[
            Card(
              margin: EdgeInsets.zero,
              clipBehavior: Clip.antiAlias,
              child: ListTile(
                key: Key('report-type-${type.fileSegment}'),
                leading: Icon(
                  type == PdfReportType.client
                      ? Icons.person_outline_rounded
                      : Icons.verified_user_outlined,
                ),
                title: Text(type.label),
                subtitle: Text(type.description),
                trailing: const Icon(Icons.chevron_right_rounded),
                onTap: () => Navigator.pop(context, type),
              ),
            ),
            if (type != PdfReportType.values.last) const SizedBox(height: 10),
          ],
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('Cancelar'),
        ),
      ],
    );
  }
}

@visibleForTesting
class EmailRecipientDialog extends StatefulWidget {
  const EmailRecipientDialog({this.initialValue, super.key});

  final String? initialValue;

  @override
  State<EmailRecipientDialog> createState() => _EmailRecipientDialogState();
}

class _EmailRecipientDialogState extends State<EmailRecipientDialog> {
  final _formKey = GlobalKey<FormState>();
  late final TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController(text: widget.initialValue ?? '');
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _submit() {
    if (_formKey.currentState!.validate()) {
      Navigator.pop(context, _controller.text.trim());
    }
  }

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Enviar informe'),
      content: Form(
        key: _formKey,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              'El PDF se adjuntará al correo con el resumen del trabajo.',
            ),
            const SizedBox(height: 16),
            TextFormField(
              controller: _controller,
              autofocus: true,
              keyboardType: TextInputType.emailAddress,
              textInputAction: TextInputAction.done,
              decoration: const InputDecoration(
                labelText: 'Correo destinatario',
                hintText: 'nombre@empresa.cl',
                prefixIcon: Icon(Icons.alternate_email_rounded),
              ),
              validator: (value) {
                final email = value?.trim() ?? '';
                final valid = RegExp(
                  r'^[^@\s]+@[^@\s]+\.[^@\s]+$',
                ).hasMatch(email);
                return valid ? null : 'Ingresa un correo válido';
              },
              onFieldSubmitted: (_) => _submit(),
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('Cancelar'),
        ),
        FilledButton.icon(
          onPressed: _submit,
          icon: const Icon(Icons.mail_outline_rounded),
          label: const Text('Continuar'),
        ),
      ],
    );
  }
}

class _BottomActions extends StatelessWidget {
  const _BottomActions({
    required this.readOnly,
    required this.busy,
    required this.onSave,
    required this.onFinalize,
    required this.onOpenPdf,
    required this.onSendEmail,
  });

  final bool readOnly;
  final bool busy;
  final VoidCallback onSave;
  final VoidCallback onFinalize;
  final VoidCallback onOpenPdf;
  final VoidCallback onSendEmail;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.fromLTRB(
        20,
        12,
        20,
        12 + MediaQuery.paddingOf(context).bottom,
      ),
      decoration: const BoxDecoration(
        color: Colors.white,
        border: Border(top: BorderSide(color: Color(0xFFDCE7E5))),
      ),
      child: Row(
        children: [
          Expanded(
            child: OutlinedButton.icon(
              onPressed: busy ? null : (readOnly ? onOpenPdf : onSave),
              icon: Icon(
                readOnly ? Icons.picture_as_pdf_outlined : Icons.save_outlined,
              ),
              label: Text(readOnly ? 'Ver PDF' : 'Guardar'),
            ),
          ),
          const SizedBox(width: 12),
          Expanded(
            child: FilledButton.icon(
              onPressed: busy ? null : (readOnly ? onSendEmail : onFinalize),
              icon: Icon(
                readOnly ? Icons.mail_outline_rounded : Icons.task_alt_rounded,
              ),
              label: Text(readOnly ? 'Enviar' : 'Finalizar OT'),
            ),
          ),
        ],
      ),
    );
  }
}
