import "package:flutter/material.dart";

import "../api.dart";
import "../format.dart";
import "../i18n.dart";
import "../main.dart";
import "../models.dart";
import "../theme.dart";
import "../widgets/attachment_field.dart";

// The bottom-sheet forms for the record types behind the car's tabs: technical
// checks, refills, charges, workshop visits, documents and reminders. Each
// mirrors the web app's matching modal, and pops `true` when it saved so the car
// detail screen can reload.

/// Serializes a picked day as UTC midnight, matching what the web app sends for
/// the same field. These are date-only concepts, so the day the user tapped is
/// the whole of the value — converting a local midnight to UTC instead would
/// hand the server the previous day everywhere east of Greenwich.
String _isoDate(DateTime d) => DateTime.utc(d.year, d.month, d.day).toIso8601String();

String? _isoDateOrNull(DateTime? d) => d == null ? null : _isoDate(d);

/// Shared chrome for these sheets: title, error banner, scrolling body, and a
/// save button that reflects the in-flight state.
class _SheetScaffold extends StatelessWidget {
  final String title;
  final String? error;
  final bool saving;
  final String saveLabel;
  final VoidCallback? onSave;
  final List<Widget> children;

  const _SheetScaffold({
    required this.title,
    required this.saving,
    required this.saveLabel,
    required this.onSave,
    required this.children,
    this.error,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(
        left: 16,
        right: 16,
        top: 16,
        bottom: DriverVault.sheetBottomInset(context),
      ),
      child: SingleChildScrollView(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
            const SizedBox(height: 12),
            if (error != null)
              Padding(
                padding: const EdgeInsets.only(bottom: 8),
                child: Text(error!, style: const TextStyle(color: DriverVault.danger)),
              ),
            ...children,
            const SizedBox(height: 8),
            SizedBox(
              width: double.infinity,
              child: FilledButton(
                onPressed: saving ? null : onSave,
                child: Text(saving ? t("common.saving") : saveLabel),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

/// A plain text field with the sheets' shared decoration.
Widget _field(
  TextEditingController c,
  String label, {
  TextInputType? keyboard,
  String? hint,
  String? helper,
  TextCapitalization caps = TextCapitalization.none,
}) =>
    Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: TextField(
        controller: c,
        keyboardType: keyboard,
        textCapitalization: caps,
        decoration: InputDecoration(
          labelText: label,
          hintText: hint,
          helperText: helper,
          helperMaxLines: 4,
          border: const OutlineInputBorder(),
          isDense: true,
        ),
      ),
    );

/// A tappable read-only field that opens a date picker. [onChanged] receives
/// null when the value is cleared, which is how an optional date is unset.
Widget _dateField(
  BuildContext context,
  String label,
  DateTime? value,
  ValueChanged<DateTime?> onChanged, {
  bool clearable = true,
  String? helper,
}) =>
    Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: InkWell(
        onTap: () async {
          final picked = await showDatePicker(
            context: context,
            initialDate: value ?? DateTime.now(),
            firstDate: DateTime(2000),
            lastDate: DateTime.now().add(const Duration(days: 365 * 10)),
          );
          if (picked != null) onChanged(picked);
        },
        child: InputDecorator(
          decoration: InputDecoration(
            labelText: label,
            border: const OutlineInputBorder(),
            isDense: true,
            helperText: helper,
            helperMaxLines: 4,
            suffixIcon: value == null || !clearable
                ? const Icon(Icons.calendar_today, size: 18)
                : IconButton(
                    icon: const Icon(Icons.clear, size: 18),
                    onPressed: () => onChanged(null),
                  ),
          ),
          child: Text(value == null ? "—" : formatDate(value)),
        ),
      ),
    );

Widget _dropdown<T>(
  String label,
  T value,
  List<(T, String)> options,
  ValueChanged<T?> onChanged,
) =>
    Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: DropdownButtonFormField<T>(
        initialValue: value,
        decoration: InputDecoration(
            labelText: label, border: const OutlineInputBorder(), isDense: true),
        items: [
          for (final o in options) DropdownMenuItem(value: o.$1, child: Text(o.$2)),
        ],
        onChanged: onChanged,
      ),
    );

/// A bordered group with a caption and a hint beneath, standing in for the web's
/// <fieldset>.
Widget _group(BuildContext context, String legend, List<Widget> children, {String? hint}) => Padding(
      padding: const EdgeInsets.only(bottom: 10),
      child: InputDecorator(
        decoration: InputDecoration(
          labelText: legend,
          border: const OutlineInputBorder(),
          helperText: hint,
          helperMaxLines: 5,
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: children,
        ),
      ),
    );

double? _numOrNull(TextEditingController c) => double.tryParse(c.text.trim().replaceAll(",", "."));
double _num(TextEditingController c) => _numOrNull(c) ?? 0;
int _int(TextEditingController c) => int.tryParse(c.text.trim()) ?? 0;

// --- technical checks ---

/// Mirrors the web TechnicalCheckFormModal. Shaped like a service record minus
/// the odometer: an inspection falls due on a date whatever the mileage reads.
class TechnicalCheckSheet extends StatefulWidget {
  final String carId;
  final Car car;
  final TechnicalCheck? check; // null => create
  const TechnicalCheckSheet({super.key, required this.carId, required this.car, this.check});

  @override
  State<TechnicalCheckSheet> createState() => _TechnicalCheckSheetState();
}

class _TechnicalCheckSheetState extends State<TechnicalCheckSheet> {
  late DateTime _date;
  late String _result;
  DateTime? _validUntil;
  late final TextEditingController _cost, _station, _notes;
  final _pending = PendingAttachment();
  bool _saving = false;
  String? _error;

  bool get _isEdit => widget.check != null;

  @override
  void initState() {
    super.initState();
    final c = widget.check;
    _date = c?.date ?? DateTime.now();
    _result = c?.result ?? "passed";
    _validUntil = c?.validUntil;
    _cost = TextEditingController(text: (c != null && c.cost > 0) ? "${c.cost}" : "");
    _station = TextEditingController(text: c?.station ?? "");
    _notes = TextEditingController(text: c?.notes ?? "");
  }

  @override
  void dispose() {
    for (final c in [_cost, _station, _notes]) {
      c.dispose();
    }
    super.dispose();
  }

  /// What the server will derive if valid-until is left blank, shown so the
  /// effect of leaving it empty is visible before saving rather than after.
  String? get _derivedHint {
    if (_result == "failed") {
      return t("forms.technical.failedHint");
    }
    final days = widget.car.technicalCheckIntervalDays > 0
        ? widget.car.technicalCheckIntervalDays
        : 365;
    final next = _date.add(Duration(days: days));
    return t("forms.technical.derivedHint",
        params: {"days": days, "date": formatDate(next)});
  }

  Future<void> _save() async {
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "date": _isoDate(_date),
      "result": _result,
      // Blank means "derive it from the car's interval" — send null, not a date,
      // so clearing the field on an edit actually removes the override.
      "validUntil": _isoDateOrNull(_validUntil),
      "cost": _num(_cost),
      "station": _station.text.trim(),
      "notes": _notes.text.trim(),
    };
    try {
      final saved = _isEdit
          ? await apiClient.updateTechnicalCheck(widget.check!.id, payload)
          : await apiClient.createTechnicalCheck(payload);
      await _applyFile(saved.id);
      if (mounted) Navigator.pop(context, true);
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  // The metadata is already committed by the time this runs, so a file failure
  // is reported as an attachment error rather than a failed save.
  Future<void> _applyFile(String id) async {
    if (_pending.isEmpty) return;
    try {
      await applyAttachment("/technical-checks", id, _pending);
    } catch (e) {
      throw ApiException(0, t("errors.attachmentFailed", params: {"error": e}));
    }
  }

  @override
  Widget build(BuildContext context) {
    return _SheetScaffold(
      title: t(_isEdit ? "forms.technical.editTitle" : "forms.technical.addTitle"),
      error: _error,
      saving: _saving,
      saveLabel: t(_isEdit ? "common.saveChanges" : "forms.technical.submit"),
      onSave: _save,
      children: [
        Row(children: [
          Expanded(
            child: _dateField(context, t("forms.technical.date"), _date,
                (v) => setState(() => _date = v ?? _date),
                clearable: false),
          ),
          const SizedBox(width: 8),
          Expanded(
            child: _dropdown(
                t("forms.technical.result"),
                _result,
                [
                  ("passed", t("forms.technical.passed")),
                  ("failed", t("forms.technical.failed")),
                ],
                (v) => setState(() => _result = v ?? "passed")),
          ),
        ]),
        _dateField(context, t("forms.technical.validUntil"), _validUntil,
            (v) => setState(() => _validUntil = v),
            helper: _derivedHint),
        Row(children: [
          Expanded(
              child: _field(_cost, t("forms.technical.cost"),
                  keyboard: const TextInputType.numberWithOptions(decimal: true))),
          const SizedBox(width: 8),
          Expanded(
              child: _field(_station, t("forms.technical.station"),
                  hint: t("forms.technical.stationPlaceholder"))),
        ]),
        AttachmentField(
          path: "/technical-checks",
          record: widget.check,
          recordId: widget.check?.id,
          pending: _pending,
          onChanged: () => setState(() {}),
          legend: t("forms.technical.attachmentLegend"),
        ),
        const SizedBox(height: 10),
        _field(_notes, t("forms.technical.notes")),
      ],
    );
  }
}

// --- fuel ---

/// Mirrors the web FuelFormModal.
class FuelSheet extends StatefulWidget {
  final String carId;
  final FuelEntry? entry; // null => create
  const FuelSheet({super.key, required this.carId, this.entry});

  @override
  State<FuelSheet> createState() => _FuelSheetState();
}

class _FuelSheetState extends State<FuelSheet> {
  late DateTime _date;
  late final TextEditingController _km, _liters, _cost, _station, _notes;
  late bool _fullTank, _missedFill;
  final _pending = PendingAttachment();
  bool _saving = false;
  String? _error;

  bool get _isEdit => widget.entry != null;

  @override
  void initState() {
    super.initState();
    final e = widget.entry;
    _date = e?.date ?? DateTime.now();
    _km = TextEditingController(text: e == null ? "" : "${e.km}");
    _liters = TextEditingController(text: (e != null && e.liters > 0) ? "${e.liters}" : "");
    _cost = TextEditingController(text: (e != null && e.cost > 0) ? "${e.cost}" : "");
    // A full tank is the common case and the one that makes the entry count
    // towards efficiency, so it is the default.
    _fullTank = e?.fullTank ?? true;
    _missedFill = e?.missedFill ?? false;
    _station = TextEditingController(text: e?.station ?? "");
    _notes = TextEditingController(text: e?.notes ?? "");
  }

  @override
  void dispose() {
    for (final c in [_km, _liters, _cost, _station, _notes]) {
      c.dispose();
    }
    super.dispose();
  }

  String? get _pricePerLiter {
    final l = _numOrNull(_liters);
    final c = _numOrNull(_cost);
    if (l == null || c == null || l == 0 || c == 0) return null;
    return (c / l).toStringAsFixed(3);
  }

  Future<void> _save() async {
    final km = _int(_km);
    final liters = _num(_liters);
    if (_km.text.trim().isEmpty || km < 0) {
      setState(() => _error = t("forms.validation.odometer"));
      return;
    }
    if (liters <= 0) {
      setState(() => _error = t("forms.validation.liters"));
      return;
    }
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "date": _isoDate(_date),
      "km": km,
      "liters": liters,
      "cost": _num(_cost),
      "fullTank": _fullTank,
      "missedFill": _missedFill,
      "station": _station.text.trim(),
      "notes": _notes.text.trim(),
    };
    try {
      final saved = _isEdit
          ? await apiClient.updateFuelEntry(widget.entry!.id, payload)
          : await apiClient.createFuelEntry(payload);
      if (!_pending.isEmpty) {
        try {
          await applyAttachment("/fuel-entries", saved.id, _pending);
        } catch (e) {
          throw ApiException(0, t("errors.attachmentFailed", params: {"error": e}));
        }
      }
      if (mounted) Navigator.pop(context, true);
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final ppl = _pricePerLiter;
    return _SheetScaffold(
      title: t(_isEdit ? "forms.fuel.editTitle" : "forms.fuel.addTitle"),
      error: _error,
      saving: _saving,
      saveLabel: t(_isEdit ? "common.saveChanges" : "forms.fuel.submit"),
      onSave: _save,
      children: [
        Row(children: [
          Expanded(
            child: _dateField(context, t("forms.fuel.date"), _date,
                (v) => setState(() => _date = v ?? _date),
                clearable: false),
          ),
          const SizedBox(width: 8),
          Expanded(
              child: _field(_km, t("forms.fuel.odometer"),
                  keyboard: TextInputType.number, hint: "16138")),
        ]),
        Row(children: [
          Expanded(
              child: _field(_liters, t("forms.fuel.liters"),
                  keyboard: const TextInputType.numberWithOptions(decimal: true), hint: "42.5")),
          const SizedBox(width: 8),
          Expanded(
              child: _field(_cost, t("forms.fuel.cost"),
                  keyboard: const TextInputType.numberWithOptions(decimal: true), hint: "285.00")),
        ]),
        if (ppl != null)
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Text(t("forms.fuel.pricePerLiter", params: {"price": ppl}),
                style: Theme.of(context).textTheme.bodySmall),
          ),
        _group(
          context,
          t("forms.fuel.tank"),
          [
            CheckboxListTile(
              value: _fullTank,
              onChanged: (v) => setState(() => _fullTank = v ?? false),
              title: Text(t("forms.fuel.fullTank")),
              contentPadding: EdgeInsets.zero,
              controlAffinity: ListTileControlAffinity.leading,
              dense: true,
            ),
            CheckboxListTile(
              value: _missedFill,
              onChanged: (v) => setState(() => _missedFill = v ?? false),
              title: Text(t("forms.fuel.missedFill")),
              contentPadding: EdgeInsets.zero,
              controlAffinity: ListTileControlAffinity.leading,
              dense: true,
            ),
          ],
          hint: t("forms.fuel.tankHint"),
        ),
        Row(children: [
          Expanded(child: _field(_station, t("forms.fuel.station"),
                  hint: t("forms.fuel.stationPlaceholder"))),
          const SizedBox(width: 8),
          Expanded(child: _field(_notes, t("forms.fuel.notes"))),
        ]),
        AttachmentField(
          path: "/fuel-entries",
          record: widget.entry,
          recordId: widget.entry?.id,
          pending: _pending,
          onChanged: () => setState(() {}),
          legend: t("forms.fuel.attachmentLegend"),
        ),
      ],
    );
  }
}

// --- charging ---

/// Mirrors the web ChargingFormModal — one charge of an EV. The electric twin of
/// [FuelSheet], and derived the same way: consumption is measured between full
/// charges, so a top-up rolls into the next full one.
class ChargingSheet extends StatefulWidget {
  final String carId;
  final ChargingSession? entry; // null => create
  const ChargingSheet({super.key, required this.carId, this.entry});

  @override
  State<ChargingSheet> createState() => _ChargingSheetState();
}

class _ChargingSheetState extends State<ChargingSheet> {
  late DateTime _date;
  late final TextEditingController _km, _kwh, _cost, _location, _notes;
  late bool _fullCharge, _missedSession;
  final _pending = PendingAttachment();
  bool _saving = false;
  String? _error;

  bool get _isEdit => widget.entry != null;

  @override
  void initState() {
    super.initState();
    final e = widget.entry;
    _date = e?.date ?? DateTime.now();
    _km = TextEditingController(text: e == null ? "" : "${e.km}");
    _kwh = TextEditingController(text: (e != null && e.kwh > 0) ? "${e.kwh}" : "");
    _cost = TextEditingController(text: (e != null && e.cost > 0) ? "${e.cost}" : "");
    // A full charge is the common case and the one that makes the entry count
    // towards efficiency, so it is the default.
    _fullCharge = e?.fullCharge ?? true;
    _missedSession = e?.missedSession ?? false;
    _location = TextEditingController(text: e?.location ?? "");
    _notes = TextEditingController(text: e?.notes ?? "");
  }

  @override
  void dispose() {
    for (final c in [_km, _kwh, _cost, _location, _notes]) {
      c.dispose();
    }
    super.dispose();
  }

  String? get _pricePerKwh {
    final k = _numOrNull(_kwh);
    final c = _numOrNull(_cost);
    if (k == null || c == null || k == 0 || c == 0) return null;
    return (c / k).toStringAsFixed(3);
  }

  Future<void> _save() async {
    final km = _int(_km);
    final kwh = _num(_kwh);
    if (_km.text.trim().isEmpty || km < 0) {
      setState(() => _error = t("forms.validation.odometer"));
      return;
    }
    if (kwh <= 0) {
      setState(() => _error = t("forms.validation.kwh"));
      return;
    }
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "date": _isoDate(_date),
      "km": km,
      "kwh": kwh,
      "cost": _num(_cost),
      "fullCharge": _fullCharge,
      "missedSession": _missedSession,
      "location": _location.text.trim(),
      "notes": _notes.text.trim(),
    };
    try {
      final saved = _isEdit
          ? await apiClient.updateChargingSession(widget.entry!.id, payload)
          : await apiClient.createChargingSession(payload);
      if (!_pending.isEmpty) {
        try {
          await applyAttachment("/charging-sessions", saved.id, _pending);
        } catch (e) {
          throw ApiException(0, t("errors.attachmentFailed", params: {"error": e}));
        }
      }
      if (mounted) Navigator.pop(context, true);
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final ppk = _pricePerKwh;
    return _SheetScaffold(
      title: _isEdit ? t("forms.charging.editTitle") : t("forms.charging.addTitle"),
      error: _error,
      saving: _saving,
      saveLabel: t(_isEdit ? "common.saveChanges" : "forms.charging.submit"),
      onSave: _save,
      children: [
        Row(children: [
          Expanded(
            child: _dateField(context, "${t("forms.charging.date")} *", _date,
                (v) => setState(() => _date = v ?? _date),
                clearable: false),
          ),
          const SizedBox(width: 8),
          Expanded(
            child: _field(_km, "${t("forms.charging.odometer")} *",
                keyboard: TextInputType.number, hint: "16138"),
          ),
        ]),
        Row(children: [
          Expanded(
            child: _field(_kwh, "${t("forms.charging.kwh")} *",
                keyboard: const TextInputType.numberWithOptions(decimal: true), hint: "42.5"),
          ),
          const SizedBox(width: 8),
          Expanded(
            child: _field(_cost, t("forms.charging.cost"),
                keyboard: const TextInputType.numberWithOptions(decimal: true), hint: "85.00"),
          ),
        ]),
        if (ppk != null)
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: Text(t("forms.charging.pricePerKwh", params: {"price": ppk}),
                style: Theme.of(context).textTheme.bodySmall),
          ),
        _group(
          context,
          t("forms.charging.battery"),
          [
            CheckboxListTile(
              value: _fullCharge,
              onChanged: (v) => setState(() => _fullCharge = v ?? false),
              title: Text(t("forms.charging.fullCharge")),
              contentPadding: EdgeInsets.zero,
              controlAffinity: ListTileControlAffinity.leading,
              dense: true,
            ),
            CheckboxListTile(
              value: _missedSession,
              onChanged: (v) => setState(() => _missedSession = v ?? false),
              title: Text(t("forms.charging.missedSession")),
              contentPadding: EdgeInsets.zero,
              controlAffinity: ListTileControlAffinity.leading,
              dense: true,
            ),
          ],
          hint: t("forms.charging.batteryHint"),
        ),
        Row(children: [
          Expanded(
            child: _field(_location, t("forms.charging.location"),
                hint: t("forms.charging.locationPlaceholder")),
          ),
          const SizedBox(width: 8),
          Expanded(child: _field(_notes, t("forms.charging.notes"))),
        ]),
        AttachmentField(
          path: "/charging-sessions",
          record: widget.entry,
          recordId: widget.entry?.id,
          pending: _pending,
          onChanged: () => setState(() {}),
          legend: t("forms.charging.attachmentLegend"),
        ),
      ],
    );
  }
}

// --- maintenance ---

/// Mirrors the web MaintenanceFormModal — a workshop visit or repair, i.e. work
/// outside the routine service schedule.
class MaintenanceSheet extends StatefulWidget {
  final String carId;
  final MaintenanceEntry? entry; // null => create
  const MaintenanceSheet({super.key, required this.carId, this.entry});

  @override
  State<MaintenanceSheet> createState() => _MaintenanceSheetState();
}

// Keys only, in the order the API's models.go lists them; the labels are
// looked up per build so the picker follows the language picker. The same
// enums.* entries label these records on the car screen.
const _maintenanceTypeKeys = [
  "repair", "inspection", "bodywork", "tyres", "diagnostics", "recall", "warranty", "other",
];

const _maintenanceStatusKeys = ["scheduled", "in_progress", "completed"];

List<(String, String)> _options(String namespace, List<String> keys) =>
    [for (final key in keys) (key, t("enums.$namespace.$key"))];

class _MaintenanceSheetState extends State<MaintenanceSheet> {
  late DateTime _date;
  late String _type, _status;
  DateTime? _warrantyUntil;
  late final TextEditingController _km,
      _workshop,
      _location,
      _description,
      _partsUsed,
      _laborCost,
      _partsCost,
      _invoiceNumber,
      _notes;
  final _pending = PendingAttachment();
  bool _saving = false;
  String? _error;

  bool get _isEdit => widget.entry != null;

  @override
  void initState() {
    super.initState();
    final e = widget.entry;
    _date = e?.date ?? DateTime.now();
    _type = e?.type ?? "repair";
    _status = e?.status ?? "completed";
    _warrantyUntil = e?.warrantyUntil;
    // Maintenance is the one record whose odometer is optional, so 0 still
    // reads as "not recorded" here.
    _km = TextEditingController(text: (e != null && e.km > 0) ? "${e.km}" : "");
    _workshop = TextEditingController(text: e?.workshop ?? "");
    _location = TextEditingController(text: e?.location ?? "");
    _description = TextEditingController(text: e?.description ?? "");
    _partsUsed = TextEditingController(text: e?.partsUsed ?? "");
    _laborCost = TextEditingController(text: (e != null && e.laborCost > 0) ? "${e.laborCost}" : "");
    _partsCost = TextEditingController(text: (e != null && e.partsCost > 0) ? "${e.partsCost}" : "");
    _invoiceNumber = TextEditingController(text: e?.invoiceNumber ?? "");
    _notes = TextEditingController(text: e?.notes ?? "");
  }

  @override
  void dispose() {
    for (final c in [
      _km,
      _workshop,
      _location,
      _description,
      _partsUsed,
      _laborCost,
      _partsCost,
      _invoiceNumber,
      _notes
    ]) {
      c.dispose();
    }
    super.dispose();
  }

  Future<void> _save() async {
    final description = _description.text.trim();
    if (description.isEmpty) {
      setState(() => _error = t("forms.validation.description"));
      return;
    }
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "date": _isoDate(_date),
      "km": _int(_km),
      "type": _type,
      "status": _status,
      "workshop": _workshop.text.trim(),
      "location": _location.text.trim(),
      "description": description,
      "partsUsed": _partsUsed.text.trim(),
      "laborCost": _num(_laborCost),
      "partsCost": _num(_partsCost),
      "invoiceNumber": _invoiceNumber.text.trim(),
      "warrantyUntil": _isoDateOrNull(_warrantyUntil),
      "notes": _notes.text.trim(),
    };
    try {
      final saved = _isEdit
          ? await apiClient.updateMaintenance(widget.entry!.id, payload)
          : await apiClient.createMaintenance(payload);
      if (!_pending.isEmpty) {
        try {
          await applyAttachment("/maintenance", saved.id, _pending);
        } catch (e) {
          throw ApiException(0, t("errors.attachmentFailed", params: {"error": e}));
        }
      }
      if (mounted) Navigator.pop(context, true);
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final total = _num(_laborCost) + _num(_partsCost);
    return _SheetScaffold(
      title: t(_isEdit ? "forms.maintenance.editTitle" : "forms.maintenance.addTitle"),
      error: _error,
      saving: _saving,
      saveLabel: t(_isEdit ? "common.saveChanges" : "forms.maintenance.submit"),
      onSave: _save,
      children: [
        Row(children: [
          Expanded(
            child: _dateField(context, t("forms.maintenance.date"), _date,
                (v) => setState(() => _date = v ?? _date),
                clearable: false),
          ),
          const SizedBox(width: 8),
          Expanded(
              child: _field(_km, t("forms.maintenance.odometer"),
                  keyboard: TextInputType.number, hint: "16138")),
        ]),
        Row(children: [
          Expanded(
              child: _dropdown(t("forms.maintenance.type"), _type,
                  _options("maintenanceType", _maintenanceTypeKeys),
                  (v) => setState(() => _type = v ?? "repair"))),
          const SizedBox(width: 8),
          Expanded(
              child: _dropdown(t("forms.maintenance.status"), _status,
                  _options("maintenanceStatus", _maintenanceStatusKeys),
                  (v) => setState(() => _status = v ?? "completed"))),
        ]),
        _field(_description, t("forms.maintenance.description"),
            hint: t("forms.maintenance.descriptionPlaceholder")),
        Row(children: [
          Expanded(
              child: _field(_workshop, t("forms.maintenance.workshop"),
                  hint: t("forms.maintenance.workshopPlaceholder"))),
          const SizedBox(width: 8),
          Expanded(
              child: _field(_location, t("forms.maintenance.location"),
                  hint: t("forms.maintenance.locationPlaceholder"))),
        ]),
        _field(_partsUsed, t("forms.maintenance.partsUsed"),
            hint: t("forms.maintenance.partsUsedPlaceholder")),
        Row(children: [
          Expanded(
              child: _field(_laborCost, t("forms.maintenance.laborCost"),
                  keyboard: const TextInputType.numberWithOptions(decimal: true))),
          const SizedBox(width: 8),
          Expanded(
              child: _field(_partsCost, t("forms.maintenance.partsCost"),
                  keyboard: const TextInputType.numberWithOptions(decimal: true))),
        ]),
        if (total > 0)
          Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child:
                Text(t("forms.maintenance.total", params: {"total": formatMoney(total)}),
                    style: Theme.of(context).textTheme.bodySmall),
          ),
        Row(children: [
          Expanded(child: _field(_invoiceNumber, t("forms.maintenance.invoiceNumber"))),
          const SizedBox(width: 8),
          Expanded(
            child: _dateField(context, t("forms.maintenance.warrantyUntil"), _warrantyUntil,
                (v) => setState(() => _warrantyUntil = v)),
          ),
        ]),
        AttachmentField(
          path: "/maintenance",
          record: widget.entry,
          recordId: widget.entry?.id,
          pending: _pending,
          onChanged: () => setState(() {}),
          legend: t("forms.maintenance.attachmentLegend"),
        ),
        const SizedBox(height: 10),
        _field(_notes, t("forms.maintenance.notes")),
      ],
    );
  }
}

// --- documents ---

const _documentTypeKeys = [
  "insurance", "pollution", "registration", "inspection", "roadTax", "warranty", "other",
];

/// Mirrors the web DocumentFormModal.
class DocumentSheet extends StatefulWidget {
  final String carId;
  final CarDocument? doc; // null => create
  const DocumentSheet({super.key, required this.carId, this.doc});

  @override
  State<DocumentSheet> createState() => _DocumentSheetState();
}

class _DocumentSheetState extends State<DocumentSheet> {
  late String _type;
  DateTime? _issueDate, _expiryDate;
  late final TextEditingController _title, _provider, _reference, _cost, _notes;
  final _pending = PendingAttachment();
  bool _saving = false;
  String? _error;

  bool get _isEdit => widget.doc != null;

  @override
  void initState() {
    super.initState();
    final d = widget.doc;
    _type = d?.type ?? "insurance";
    _issueDate = d?.issueDate;
    _expiryDate = d?.expiryDate;
    _title = TextEditingController(text: d?.title ?? "");
    _provider = TextEditingController(text: d?.provider ?? "");
    _reference = TextEditingController(text: d?.reference ?? "");
    _cost = TextEditingController(text: (d != null && d.cost > 0) ? "${d.cost}" : "");
    _notes = TextEditingController(text: d?.notes ?? "");
  }

  @override
  void dispose() {
    for (final c in [_title, _provider, _reference, _cost, _notes]) {
      c.dispose();
    }
    super.dispose();
  }

  Future<void> _save() async {
    final title = _title.text.trim();
    if (title.isEmpty) {
      setState(() => _error = t("forms.validation.title"));
      return;
    }
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "type": _type,
      "title": title,
      "provider": _provider.text.trim(),
      "reference": _reference.text.trim(),
      "issueDate": _isoDateOrNull(_issueDate),
      "expiryDate": _isoDateOrNull(_expiryDate),
      "cost": _num(_cost),
      "notes": _notes.text.trim(),
    };
    try {
      final saved = _isEdit
          ? await apiClient.updateDocument(widget.doc!.id, payload)
          : await apiClient.createDocument(payload);
      if (!_pending.isEmpty) {
        try {
          await applyAttachment("/car-documents", saved.id, _pending);
        } catch (e) {
          throw ApiException(0, t("errors.attachmentFailed", params: {"error": e}));
        }
      }
      if (mounted) Navigator.pop(context, true);
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return _SheetScaffold(
      title: t(_isEdit ? "forms.document.editTitle" : "forms.document.addTitle"),
      error: _error,
      saving: _saving,
      saveLabel: t(_isEdit ? "common.saveChanges" : "forms.document.submit"),
      onSave: _save,
      children: [
        _dropdown(t("forms.document.type"), _type,
            _options("documentType", _documentTypeKeys),
            (v) => setState(() => _type = v ?? "insurance")),
        _field(_title, t("forms.document.title"),
            hint: t("forms.document.titlePlaceholder")),
        Row(children: [
          Expanded(
              child: _field(_provider, t("forms.document.provider"),
                  hint: t("forms.document.providerPlaceholder"))),
          const SizedBox(width: 8),
          Expanded(child: _field(_reference, t("forms.document.reference"))),
        ]),
        Row(children: [
          Expanded(
              child: _dateField(context, t("forms.document.issued"), _issueDate,
                  (v) => setState(() => _issueDate = v))),
          const SizedBox(width: 8),
          Expanded(
              child: _dateField(context, t("forms.document.renewalDate"), _expiryDate,
                  (v) => setState(() => _expiryDate = v))),
        ]),
        Padding(
          padding: const EdgeInsets.only(bottom: 10),
          child: Text(t("forms.document.renewalHint"),
              style: Theme.of(context).textTheme.bodySmall),
        ),
        _field(_cost, t("forms.document.cost"),
            keyboard: const TextInputType.numberWithOptions(decimal: true)),
        AttachmentField(
          path: "/car-documents",
          record: widget.doc,
          recordId: widget.doc?.id,
          pending: _pending,
          onChanged: () => setState(() {}),
          legend: t("forms.document.attachmentLegend"),
        ),
        const SizedBox(height: 10),
        _field(_notes, t("forms.document.notes")),
      ],
    );
  }
}

// --- reminders ---

const _reminderTypeKeys = ["maintenance", "document", "service", "inspection", "other"];

/// Mirrors the web ReminderFormModal. Reminders carry no attachment.
class ReminderSheet extends StatefulWidget {
  final String carId;
  final Car car;
  final Reminder? reminder; // null => create
  const ReminderSheet({super.key, required this.carId, required this.car, this.reminder});

  @override
  State<ReminderSheet> createState() => _ReminderSheetState();
}

class _ReminderSheetState extends State<ReminderSheet> {
  late String _type;
  DateTime? _dueDate;
  late final TextEditingController _title, _dueKm, _repeatDays, _repeatKm, _notes;
  bool _saving = false;
  String? _error;

  bool get _isEdit => widget.reminder != null;

  /// Mirrors the server's rule: a reminder with neither trigger would never fire.
  bool get _hasTrigger => _dueDate != null || _int(_dueKm) > 0;
  bool get _isRecurring => _int(_repeatDays) > 0 || _int(_repeatKm) > 0;

  @override
  void initState() {
    super.initState();
    final r = widget.reminder;
    _type = r?.type ?? "maintenance";
    _dueDate = r?.dueDate;
    _title = TextEditingController(text: r?.title ?? "");
    _dueKm = TextEditingController(text: (r != null && r.dueKm > 0) ? "${r.dueKm}" : "");
    _repeatDays =
        TextEditingController(text: (r != null && r.repeatDays > 0) ? "${r.repeatDays}" : "");
    _repeatKm = TextEditingController(text: (r != null && r.repeatKm > 0) ? "${r.repeatKm}" : "");
    _notes = TextEditingController(text: r?.notes ?? "");
  }

  @override
  void dispose() {
    for (final c in [_title, _dueKm, _repeatDays, _repeatKm, _notes]) {
      c.dispose();
    }
    super.dispose();
  }

  Future<void> _save() async {
    if (_title.text.trim().isEmpty) {
      setState(() => _error = t("forms.validation.title"));
      return;
    }
    if (!_hasTrigger) {
      setState(() => _error = t("forms.reminder.noTrigger"));
      return;
    }
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "title": _title.text.trim(),
      "type": _type,
      "dueDate": _isoDateOrNull(_dueDate),
      "dueKm": _int(_dueKm),
      "repeatDays": _int(_repeatDays),
      "repeatKm": _int(_repeatKm),
      // Editing never silently closes a reminder; that is what Done does.
      "done": widget.reminder?.done ?? false,
      "notes": _notes.text.trim(),
    };
    try {
      if (_isEdit) {
        await apiClient.updateReminder(widget.reminder!.id, payload);
      } else {
        await apiClient.createReminder(payload);
      }
      if (mounted) Navigator.pop(context, true);
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _saving = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    final atKm = t("forms.reminder.currentKm", params: {"km": formatKm(widget.car.currentKm)});
    return _SheetScaffold(
      title: t(_isEdit ? "forms.reminder.editTitle" : "forms.reminder.addTitle"),
      error: _error,
      saving: _saving,
      saveLabel: t(_isEdit ? "common.saveChanges" : "forms.reminder.submit"),
      onSave: _hasTrigger ? _save : null,
      children: [
        _field(_title, t("forms.reminder.title"), hint: t("forms.reminder.titlePlaceholder")),
        _dropdown(t("forms.reminder.type"), _type,
            _options("reminderType", _reminderTypeKeys),
            (v) => setState(() => _type = v ?? "maintenance")),
        _group(
          context,
          t("forms.reminder.remindMe"),
          [
            _dateField(context, t("forms.reminder.onDate"), _dueDate,
                (v) => setState(() => _dueDate = v)),
            _field(_dueKm, t("forms.reminder.atOdometer"),
                keyboard: TextInputType.number, hint: "30000"),
          ],
          hint: "${t("forms.reminder.triggerHint")} $atKm",
        ),
        _group(
          context,
          t("forms.reminder.repeat"),
          [
            _field(_repeatDays, t("forms.reminder.everyDays"),
                keyboard: TextInputType.number, hint: "365"),
            _field(_repeatKm, t("forms.reminder.everyKm"),
                keyboard: TextInputType.number, hint: "15000"),
          ],
          hint: t(_isRecurring ? "forms.reminder.recurringHint" : "forms.reminder.oneOffHint"),
        ),
        _field(_notes, t("forms.reminder.notes")),
      ],
    );
  }
}
