import "package:flutter/material.dart";

import "../api.dart";
import "../i18n.dart";
import "../main.dart";
import "../models.dart";
import "../format.dart";
import "../service_parts.dart";
import "../theme.dart";
import "../widgets/attachment_field.dart";
import "car_form_sheet.dart";
import "car_view_sheet.dart";
import "provider_tab.dart";
import "record_form_sheets.dart";

/// Serializes a car to the full update payload. The API's updateCar rewrites
/// every column from the body, so partial payloads would blank omitted fields —
/// always send them all, overriding only what changed.
Map<String, dynamic> _carPayload(Car car, {int? currentKm}) => {
      "name": car.name,
      "make": car.make,
      "model": car.model,
      "year": car.year,
      "registration": car.registration,
      "registrationCountry": car.registrationCountry,
      "vin": car.vin,
      "oilSpec": car.oilSpec,
      "transmissionOilSpec": car.transmissionOilSpec,
      "differentialOilSpec": car.differentialOilSpec,
      "brakeFluidSpec": car.brakeFluidSpec,
      "coolantSpec": car.coolantSpec,
      "fuelType": car.fuelType,
      "buildDate": car.buildDate,
      "firstRegistrationDate": car.firstRegistrationDate,
      "serviceIntervalDays": car.serviceIntervalDays,
      "serviceIntervalKm": car.serviceIntervalKm,
      "technicalCheckIntervalDays": car.technicalCheckIntervalDays,
      "currentKm": currentKm ?? car.currentKm,
    };

class CarDetailScreen extends StatefulWidget {
  final String carId;
  const CarDetailScreen({super.key, required this.carId});
  @override
  State<CarDetailScreen> createState() => _CarDetailScreenState();
}

class _CarDetailData {
  final Car car;
  final List<ServiceRecord> services;
  final List<TechnicalCheck> technicalChecks;
  final List<MaintenanceEntry> maintenance;
  final List<FuelEntry> fuel;
  final FuelStats fuelStats;
  final List<ChargingSession> charging;
  final ChargingStats chargingStats;
  final List<CarDocument> documents;
  final List<Part> parts;
  final List<Reminder> reminders;

  /// The manufacturer services this user has connected. Only read to decide
  /// whether the connected-service tab is on offer for a car that has no link
  /// yet — the tab loads its own data once it is open.
  final List<VehicleProvider> providers;

  _CarDetailData({
    required this.car,
    required this.services,
    required this.technicalChecks,
    required this.maintenance,
    required this.fuel,
    required this.fuelStats,
    required this.charging,
    required this.chargingStats,
    required this.documents,
    required this.parts,
    required this.reminders,
    required this.providers,
  });
}

class _CarDetailScreenState extends State<CarDetailScreen> {
  late Future<_CarDetailData> _future;

  @override
  void initState() {
    super.initState();
    _future = _load();
  }

  // One round trip per collection, all in flight together: the tabs are all
  // rendered by the same FutureBuilder, so waiting for them serially would show
  // the spinner for the sum of the requests rather than the slowest one.
  Future<_CarDetailData> _load() async {
    final results = await Future.wait([
      apiClient.getCar(widget.carId),
      apiClient.listCarServices(widget.carId),
      apiClient.listCarTechnicalChecks(widget.carId),
      apiClient.listCarMaintenance(widget.carId),
      apiClient.listCarFuelEntries(widget.carId),
      apiClient.getCarFuelStats(widget.carId),
      apiClient.listCarChargingSessions(widget.carId),
      apiClient.getCarChargingStats(widget.carId),
      apiClient.listCarDocuments(widget.carId),
      apiClient.listCarParts(widget.carId),
      apiClient.listCarReminders(widget.carId),
      // Only decides whether one tab is offered, so it must not be able to take
      // the whole page down with it — a provider plugin that is misconfigured
      // (or a server too old to know the endpoint) leaves the tab off instead.
      apiClient.listVehicleProviders().catchError((_) => <VehicleProvider>[]),
    ]);
    return _CarDetailData(
      car: results[0] as Car,
      services: results[1] as List<ServiceRecord>,
      technicalChecks: results[2] as List<TechnicalCheck>,
      maintenance: results[3] as List<MaintenanceEntry>,
      fuel: results[4] as List<FuelEntry>,
      fuelStats: results[5] as FuelStats,
      charging: results[6] as List<ChargingSession>,
      chargingStats: results[7] as ChargingStats,
      documents: results[8] as List<CarDocument>,
      parts: results[9] as List<Part>,
      reminders: results[10] as List<Reminder>,
      providers: results[11] as List<VehicleProvider>,
    );
  }

  void _reload() => setState(() => _future = _load());

  Future<void> _addService(Car car) async {
    final added = await showModalBottomSheet<bool>(
      context: context,
      isScrollControlled: true,
      builder: (_) => _ServiceSheet(carId: car.id, car: car),
    );
    if (added == true) _reload();
  }

  Future<void> _editService(Car car, ServiceRecord record) async {
    final saved = await showModalBottomSheet<bool>(
      context: context,
      isScrollControlled: true,
      builder: (_) => _ServiceSheet(carId: car.id, car: car, record: record),
    );
    if (saved == true) _reload();
  }

  Future<void> _deleteService(ServiceRecord record) async {
    final ok = await _confirm(t("car.services.confirmDelete"));
    if (!ok) return;
    try {
      await apiClient.deleteService(record.id);
      _reload();
    } catch (e) {
      _snack("Delete failed: $e");
    }
  }

  Future<void> _editCar(Car car) async {
    final updated = await showModalBottomSheet<Car?>(
      context: context,
      isScrollControlled: true,
      builder: (_) => CarFormSheet(car: car),
    );
    if (updated != null) _reload();
  }

  Future<void> _addPart(Car car) async {
    final saved = await showModalBottomSheet<bool>(
      context: context,
      isScrollControlled: true,
      builder: (_) => _PartSheet(carId: car.id),
    );
    if (saved == true) _reload();
  }

  Future<void> _editPart(Car car, Part part) async {
    final saved = await showModalBottomSheet<bool>(
      context: context,
      isScrollControlled: true,
      builder: (_) => _PartSheet(carId: car.id, part: part),
    );
    if (saved == true) _reload();
  }

  Future<void> _deletePart(Part part) async {
    final ok = await _confirm(t("car.parts.confirmDelete"));
    if (!ok) return;
    try {
      await apiClient.deletePart(part.id);
      _reload();
    } catch (e) {
      _snack("Delete failed: $e");
    }
  }

  /// Opens one of the record sheets and reloads if it saved. Every sheet pops
  /// `true` on success, so they all route through here.
  Future<void> _sheet(Widget sheet) async {
    final saved = await showModalBottomSheet<bool>(
      context: context,
      isScrollControlled: true,
      builder: (_) => sheet,
    );
    if (saved == true) _reload();
  }

  /// Confirms, deletes, then reloads. [confirmKey] is the collection's own
  /// confirmation string — "Delete this refill?" and the rest — rather than a
  /// noun slotted into one template, because the sentence does not survive that
  /// treatment in every language.
  Future<void> _deleteRecord(String confirmKey, Future<void> Function() delete) async {
    final ok = await _confirm(t(confirmKey));
    if (!ok) return;
    try {
      await delete();
      _reload();
    } catch (e) {
      _snack(t("errors.deleteFailed", params: {"error": e}));
    }
  }

  Future<void> _completeReminder(Reminder r) async {
    try {
      await apiClient.completeReminder(r.id);
      _reload();
    } catch (e) {
      _snack(t("errors.completeFailed", params: {"error": e}));
    }
  }

  Future<bool> _confirm(String message) async {
    final res = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        content: Text(message),
        actions: [
          TextButton(
              onPressed: () => Navigator.pop(ctx, false), child: Text(t("common.cancel"))),
          FilledButton(
            style: FilledButton.styleFrom(backgroundColor: DriverVault.danger),
            onPressed: () => Navigator.pop(ctx, true),
            child: Text(t("common.delete")),
          ),
        ],
      ),
    );
    return res == true;
  }

  void _snack(String msg) {
    if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
  }

  Future<void> _shareCar(Car car) async {
    await showModalBottomSheet<void>(
      context: context,
      isScrollControlled: true,
      builder: (_) => _ShareSheet(car: car),
    );
  }

  Future<void> _editOdometer(Car car) async {
    final controller = TextEditingController(text: "${car.currentKm}");
    final saved = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        title: Text(t("car.actions.odometer")),
        content: TextField(
          controller: controller,
          keyboardType: TextInputType.number,
          decoration: const InputDecoration(suffixText: "km", border: OutlineInputBorder()),
          autofocus: true,
        ),
        actions: [
          TextButton(
              onPressed: () => Navigator.pop(ctx, false), child: Text(t("common.cancel"))),
          FilledButton(
            onPressed: () async {
              await apiClient.updateCar(
                car.id,
                _carPayload(car, currentKm: int.tryParse(controller.text.trim()) ?? 0),
              );
              if (ctx.mounted) Navigator.pop(ctx, true);
            },
            child: Text(t("common.save")),
          ),
        ],
      ),
    );
    if (saved == true) _reload();
  }

  Future<void> _deleteCar(Car car, _CarDetailData data) async {
    final confirmed = await showDialog<bool>(
      context: context,
      builder: (_) => _DeleteCarDialog(car: car, data: data),
    );
    if (confirmed != true) return;
    try {
      await apiClient.deleteCar(car.id);
      if (mounted) Navigator.pop(context); // back to dashboard (it refreshes)
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text(t("errors.deleteFailed", params: {"error": e}))));
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<_CarDetailData>(
        future: _future,
        builder: (context, snap) {
          if (snap.connectionState == ConnectionState.waiting) {
            return const Scaffold(body: Center(child: CircularProgressIndicator()));
          }
          if (snap.hasError) {
            return Scaffold(
              appBar: AppBar(),
              body: Center(child: Text("${snap.error}")),
            );
          }
          final data = snap.data!;
          final car = data.car;
          final latest = data.services.isNotEmpty ? data.services.first : null;
          final status = serviceStatus(latest, car);

          final tabKeys = _tabKeys(car, data.providers);

          return DefaultTabController(
            // The tab set is a property of the car: the view picker switches
            // tabs off, and linking a provider adds one. Keying the controller
            // on it rebuilds the controller instead of leaving it addressing a
            // length that no longer exists.
            key: ValueKey(tabKeys.join("/")),
            length: tabKeys.length,
            child: Scaffold(
              appBar: AppBar(
                title: Text(car.name),
                actions: [
                  if (car.isOwner)
                    IconButton(
                      tooltip: t("car.actions.share"),
                      icon: const Icon(Icons.person_add_alt),
                      onPressed: () => _shareCar(car),
                    ),
                  if (car.canWrite)
                    IconButton(
                      tooltip: t("car.viewPicker.open"),
                      icon: const Icon(Icons.tune),
                      onPressed: () => _openViewPicker(car, data.providers),
                    ),
                  if (car.canWrite)
                    IconButton(
                      tooltip: t("car.actions.edit"),
                      icon: const Icon(Icons.edit_outlined),
                      onPressed: () => _editCar(car),
                    ),
                  if (car.canWrite)
                    IconButton(
                      tooltip: t("car.actions.odometer"),
                      icon: const Icon(Icons.speed),
                      onPressed: () => _editOdometer(car),
                    ),
                  if (car.isOwner)
                    IconButton(
                      tooltip: t("car.actions.delete"),
                      icon: const Icon(Icons.delete_outline),
                      onPressed: () => _deleteCar(car, data),
                    ),
                ],
                bottom: TabBar(
                  isScrollable: true,
                  tabs: [
                    for (final key in tabKeys)
                      Tab(text: _tabLabel(key, car, data.providers)),
                  ],
                ),
              ),
              body: Column(
                children: [
                  Padding(
                    padding: const EdgeInsets.fromLTRB(12, 12, 12, 0),
                    child: Column(
                      children: [
                        _StatusHeader(car: car, status: status),
                        if (car.isReadOnly) ...[
                          const SizedBox(height: 12),
                          const _ReadOnlyNotice(),
                        ],
                      ],
                    ),
                  ),
                  Expanded(
                    child: TabBarView(
                      children: [for (final key in tabKeys) _tabBody(key, data)],
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  // --- which tabs this car shows -------------------------------------------

  /// Whether the connected-service tab is on offer. It shows for a linked car,
  /// and also for an unlinked one as long as this user has some manufacturer
  /// account connected — that is where the tab offers to link it. A user with
  /// nothing connected never sees the tab at all.
  bool _showProviderTab(Car car, List<VehicleProvider> providers) =>
      car.provider.isNotEmpty || providers.any((p) => p.connected);

  /// The linked provider's display name ("MyToyota"), falling back to the raw
  /// plugin name and then to the generic tab label.
  String _providerLabel(Car car, List<VehicleProvider> providers) {
    for (final p in providers) {
      if (p.id == car.provider) return p.label;
    }
    return car.provider.isEmpty ? t("car.tabs.connected") : car.provider;
  }

  /// The tabs to show, in this car's arrangement: its stored order applied to
  /// the catalogue, minus the ones switched off, minus the connected service
  /// when it does not apply.
  List<String> _tabKeys(Car car, List<VehicleProvider> providers) =>
      arrangeKeys(kCarTabKeys, car.tabOrder)
          .where((key) => !car.hiddenTabs.contains(key))
          .where((key) => key != "provider" || _showProviderTab(car, providers))
          .toList();

  String _tabLabel(String key, Car car, List<VehicleProvider> providers) =>
      key == "provider" ? _providerLabel(car, providers) : t("car.tabs.$key");

  Future<void> _openViewPicker(Car car, List<VehicleProvider> providers) async {
    final updated = await showModalBottomSheet<Car>(
      context: context,
      isScrollControlled: true,
      builder: (_) => CarViewSheet(
        car: car,
        showProvider: _showProviderTab(car, providers),
        providerLabel: car.provider.isEmpty ? "" : _providerLabel(car, providers),
      ),
    );
    if (updated != null) _reload();
  }

  /// One tab's content, by key. The keys are the web app's, so the two apps
  /// cannot drift on what a stored arrangement means.
  Widget _tabBody(String key, _CarDetailData data) {
    final car = data.car;
    final latest = data.services.isNotEmpty ? data.services.first : null;

    switch (key) {
      case "provider":
        return ProviderTab(car: car, onCarUpdated: (_) => _reload());

      case "info":
        return _InfoTab(car: car, latest: latest);

      case "services":
        // The columns this car shows, in its arrangement — computed once for the
        // whole list rather than per tile, since every row shows the same ones.
        final columns = arrangeKeys(kServiceColumnKeys, car.serviceColumnOrder)
            .where((key) => !car.hiddenServiceColumns.contains(key))
            .toList();
        final parts = visibleParts(car);
        return _TabList(
          empty: data.services.isEmpty ? t("car.services.empty") : null,
          onAdd: car.canWrite ? () => _addService(car) : null,
          addLabel: t("car.services.add"),
          children: data.services
              .map((s) => _ServiceTile(
                    record: s,
                    columns: columns,
                    parts: parts,
                    onEdit: car.canWrite ? () => _editService(car, s) : null,
                    onDelete: car.canWrite ? () => _deleteService(s) : null,
                  ))
              .toList(),
        );

      case "technical":
        return _TabList(
          empty: data.technicalChecks.isEmpty ? t("car.technical.empty") : null,
          onAdd: car.canWrite ? () => _sheet(TechnicalCheckSheet(carId: car.id, car: car)) : null,
          addLabel: t("car.technical.add"),
          children: data.technicalChecks
              .map((c) => _TechnicalCheckTile(
                    check: c,
                    onEdit: car.canWrite
                        ? () => _sheet(TechnicalCheckSheet(carId: car.id, car: car, check: c))
                        : null,
                    onDelete: car.canWrite
                        ? () => _deleteRecord("car.technical.confirmDelete",
                            () => apiClient.deleteTechnicalCheck(c.id))
                        : null,
                  ))
              .toList(),
        );

      case "maintenance":
        return _TabList(
          empty: data.maintenance.isEmpty ? t("car.maintenance.empty") : null,
          onAdd: car.canWrite ? () => _sheet(MaintenanceSheet(carId: car.id)) : null,
          addLabel: t("car.maintenance.add"),
          children: data.maintenance
              .map((m) => _MaintenanceTile(
                    entry: m,
                    onEdit: car.canWrite
                        ? () => _sheet(MaintenanceSheet(carId: car.id, entry: m))
                        : null,
                    onDelete: car.canWrite
                        ? () => _deleteRecord("car.maintenance.confirmDelete",
                            () => apiClient.deleteMaintenance(m.id))
                        : null,
                  ))
              .toList(),
        );

      // The stats panel sits above the refill list.
      case "fuel":
        return _TabList(
          empty: null,
          onAdd: car.canWrite ? () => _sheet(FuelSheet(carId: car.id)) : null,
          addLabel: t("car.fuel.add"),
          children: [
            _FuelStatsPanel(stats: data.fuelStats),
            const SizedBox(height: 8),
            if (data.fuel.isEmpty)
              _Empty(t("car.fuel.empty"))
            else
              ...data.fuel.reversed.map((f) => _FuelTile(
                    entry: f,
                    onEdit: car.canWrite ? () => _sheet(FuelSheet(carId: car.id, entry: f)) : null,
                    onDelete: car.canWrite
                        ? () => _deleteRecord(
                            "car.fuel.confirmDelete", () => apiClient.deleteFuelEntry(f.id))
                        : null,
                  )),
          ],
        );

      // The electric twin of the fuel tab, laid out the same way.
      case "charging":
        return _TabList(
          empty: null,
          onAdd: car.canWrite ? () => _sheet(ChargingSheet(carId: car.id)) : null,
          addLabel: t("car.charging.add"),
          children: [
            _ChargingStatsPanel(stats: data.chargingStats),
            const SizedBox(height: 8),
            if (data.charging.isEmpty)
              _Empty(t("car.charging.empty"))
            else
              ...data.charging.reversed.map((c) => _ChargingTile(
                    entry: c,
                    onEdit: car.canWrite
                        ? () => _sheet(ChargingSheet(carId: car.id, entry: c))
                        : null,
                    onDelete: car.canWrite
                        ? () => _deleteRecord("car.charging.confirmDelete",
                            () => apiClient.deleteChargingSession(c.id))
                        : null,
                  )),
          ],
        );

      case "documents":
        return _TabList(
          empty: data.documents.isEmpty ? t("car.documents.empty") : null,
          onAdd: car.canWrite ? () => _sheet(DocumentSheet(carId: car.id)) : null,
          addLabel: t("car.documents.add"),
          children: data.documents
              .map((d) => _DocumentTile(
                    doc: d,
                    onEdit:
                        car.canWrite ? () => _sheet(DocumentSheet(carId: car.id, doc: d)) : null,
                    onDelete: car.canWrite
                        ? () => _deleteRecord(
                            "car.documents.confirmDelete", () => apiClient.deleteDocument(d.id))
                        : null,
                  ))
              .toList(),
        );

      case "parts":
        return _TabList(
          empty: data.parts.isEmpty ? t("car.parts.empty") : null,
          onAdd: car.canWrite ? () => _addPart(car) : null,
          addLabel: t("car.parts.add"),
          children: data.parts
              .map((p) => _PartTile(
                    part: p,
                    onEdit: car.canWrite ? () => _editPart(car, p) : null,
                    onDelete: car.canWrite ? () => _deletePart(p) : null,
                  ))
              .toList(),
        );

      case "reminders":
        return _TabList(
          empty: data.reminders.isEmpty ? t("car.reminders.empty") : null,
          onAdd: car.canWrite ? () => _sheet(ReminderSheet(carId: car.id, car: car)) : null,
          addLabel: t("car.reminders.add"),
          children: data.reminders
              .map((r) => _ReminderTile(
                    reminder: r,
                    // Auto reminders are derived from a document or service
                    // record, so they are read-only here — edit the record they
                    // came from instead.
                    onEdit: car.canWrite && !r.auto
                        ? () => _sheet(ReminderSheet(carId: car.id, car: car, reminder: r))
                        : null,
                    onDelete: car.canWrite && !r.auto
                        ? () => _deleteRecord(
                            "car.reminders.confirmDelete", () => apiClient.deleteReminder(r.id))
                        : null,
                    onComplete: car.canWrite && !r.auto && !r.done
                        ? () => _completeReminder(r)
                        : null,
                  ))
              .toList(),
        );
    }
    // Unreachable for the keys above; a key from a newer release reaching an
    // older build lands here rather than taking the page down.
    return const SizedBox.shrink();
  }
}

/// A scrollable tab body: an optional "+ Add" button, an empty-state message, or
/// the list of tiles.
class _TabList extends StatelessWidget {
  final String? empty;
  final VoidCallback? onAdd;
  final String addLabel;
  final List<Widget> children;
  const _TabList({
    required this.empty,
    required this.onAdd,
    required this.addLabel,
    required this.children,
  });

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.fromLTRB(12, 12, 12, 80),
      children: [
        if (onAdd != null)
          Align(
            alignment: Alignment.centerRight,
            child: FilledButton.icon(
              onPressed: onAdd,
              icon: const Icon(Icons.add, size: 18),
              label: Text(addLabel),
            ),
          ),
        const SizedBox(height: 8),
        if (empty != null) _Empty(empty!) else ...children,
      ],
    );
  }
}

/// Slim header shown above the tabs: car subtitle (make/model) plus the
/// service-status badge. The car's spec details now live in the Information tab.
class _StatusHeader extends StatelessWidget {
  final Car car;
  final Status status;
  const _StatusHeader({required this.car, required this.status});

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(12),
        side: BorderSide(color: DriverVault.isDark(context) ? DriverVault.darkBorder : DriverVault.ink100),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
        child: Row(
          children: [
            Expanded(
              child: Text(car.subtitle.isEmpty ? car.name : car.subtitle,
                  style: TextStyle(color: DriverVault.muted(context))),
            ),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
              decoration: BoxDecoration(
                  color: status.bg(DriverVault.isDark(context)), borderRadius: BorderRadius.circular(999)),
              child: Text(status.label,
                  style: TextStyle(color: status.fg(DriverVault.isDark(context)), fontWeight: FontWeight.w600)),
            ),
          ],
        ),
      ),
    );
  }
}

/// Information tab: the car's spec details (oil specs, odometer, interval, next
/// due, VIN) in a scrollable card.
///
/// Which rows show and in which order belongs to the car, not to this device —
/// everyone it is shared with sees the same page. Both are edited in the view
/// picker (see [CarViewSheet]).
class _InfoTab extends StatelessWidget {
  final Car car;
  final ServiceRecord? latest;
  const _InfoTab({required this.car, required this.latest});

  /// Every row this tab can show, by the key the car's arrangement names it by.
  /// Keys mirror hideableCarFields in the API's cars.go and car.info.* in the
  /// translation files.
  Map<String, String> _values() => {
        "oilSpec": _orDash(car.oilSpec),
        "transmissionOil": _orDash(car.transmissionOilSpec),
        "differentialOil": _orDash(car.differentialOilSpec),
        "brakeFluid": _orDash(car.brakeFluidSpec),
        "coolant": _orDash(car.coolantSpec),
        "odometer": formatKm(car.currentKm),
        "serviceInterval":
            "${_days(car.serviceIntervalDays)} · ${formatKm(car.serviceIntervalKm)}",
        "nextDue":
            "${formatDate(latest?.nextServiceDate)} · ${formatKm(latest?.nextServiceKm)}",
        "registrationPlate": _orDash(car.registration),
        "registrationCountry": _orDash(car.registrationCountry),
        "vin": _orDash(car.vin),
        "fuelType": _fuelLabel(car.fuelType),
        // Half-known dates print at their own precision: a build date is often
        // only a year, and _dateOrDash would show a dash for one.
        "buildDate": formatPartialDate(car.buildDate),
        "firstRegistration": formatPartialDate(car.firstRegistrationDate),
      };

  @override
  Widget build(BuildContext context) {
    final values = _values();
    final keys = arrangeKeys(kCarInfoFieldKeys, car.fieldOrder)
        .where((key) => !car.hiddenFields.contains(key))
        .toList();

    return ListView(
      padding: const EdgeInsets.fromLTRB(12, 12, 12, 80),
      children: [
        Card(
          elevation: 0,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(12),
            side: BorderSide(color: DriverVault.isDark(context) ? DriverVault.darkBorder : DriverVault.ink100),
          ),
          child: Padding(
            padding: const EdgeInsets.all(14),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (keys.isEmpty)
                  Text(t("car.info.allHidden"),
                      style: TextStyle(color: DriverVault.muted(context)))
                else
                  for (final key in keys) ...[
                    _kv(context, t("car.info.$key"), values[key] ?? "—"),
                    // The roadworthiness cycle is the car's other interval, and
                    // reads as a footnote to the service one rather than a row
                    // of its own — so it follows it, and goes when it goes.
                    if (key == "serviceInterval")
                      _kv(
                        context,
                        t("car.info.technicalCheckInterval"),
                        _days(car.technicalCheckIntervalDays > 0
                            ? car.technicalCheckIntervalDays
                            : 365),
                      ),
                  ],
              ],
            ),
          ),
        ),
      ],
    );
  }

  static String _orDash(String v) => v.isEmpty ? "—" : v;

  /// A day count as prose, so Polish gets its plural forms rather than an
  /// English-style n==1 split.
  static String _days(int n) => t("car.info.daysValue", n: n);

  static String _fuelLabel(String v) =>
      kFuelTypes.contains(v) ? t("enums.fuelType.$v") : "—";

  Widget _kv(BuildContext context, String k, String v) => Padding(
        padding: const EdgeInsets.symmetric(vertical: 3),
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            SizedBox(width: 150, child: Text(k, style: TextStyle(color: DriverVault.muted(context)))),
            Expanded(child: Text(v, style: DriverVault.mono(context, size: 13, weight: FontWeight.w500,
                color: Theme.of(context).colorScheme.onSurface))),
          ],
        ),
      );
}

/// One service record, laid out from the columns the car shows and in the order
/// it arranges them — the same two lists the web app drives its table from, so a
/// column switched off there is off here too.
///
/// A card is not a table, so the columns render as pieces rather than cells:
/// consecutive short ones share a wrapping line (which flows in reading order,
/// keeping the arrangement intact), and the three that need room of their own —
/// the parts, the notes and the file — each break onto their own. The date is
/// the one piece that carries no heading: it is what the record *is*, which is
/// also why the server refuses to let it be switched off.
class _ServiceTile extends StatelessWidget {
  final ServiceRecord record;

  /// The visible column keys, already arranged. Never empty: date cannot be
  /// hidden.
  final List<String> columns;

  /// The parts this car still records. Passed in rather than read off the car
  /// for the same reason the columns are: every row shows the same ones.
  final List<ServicePart> parts;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  const _ServiceTile({
    required this.record,
    required this.columns,
    required this.parts,
    this.onEdit,
    this.onDelete,
  });

  @override
  Widget build(BuildContext context) {
    // One widget per run. A run of short columns is a wrapping line; the other
    // three are a run of one and render as themselves.
    final pieces = [
      for (final run in serviceColumnRuns(columns))
        if (kInlineServiceColumns.contains(run.first))
          Wrap(
            spacing: 14,
            runSpacing: 4,
            children: [for (final key in run) _value(context, key)],
          )
        else
          switch (run.first) {
            "parts" => _parts(context),
            "notes" => _notes(context),
            _ => _file(context),
          },
    ];

    return Card(
      elevation: 0,
      margin: const EdgeInsets.only(bottom: 8),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
        side: BorderSide(color: DriverVault.isDark(context) ? DriverVault.darkBorder : DriverVault.ink100),
      ),
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // The menu rides the first piece rather than a header line of its
            // own, so hiding columns shortens the card instead of leaving a gap
            // where the date used to be.
            Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(child: pieces.first),
                if (onEdit != null || onDelete != null)
                  _RowMenu(onEdit: onEdit, onDelete: onDelete),
              ],
            ),
            for (final piece in pieces.skip(1))
              Padding(padding: const EdgeInsets.only(top: 6), child: piece),
          ],
        ),
      ),
    );
  }

  /// One short column: its heading and its value side by side. The date goes
  /// bare and bold — a card list is read down its dates, and "Date" in front of
  /// one says nothing the date doesn't.
  Widget _value(BuildContext context, String key) {
    final text = switch (key) {
      "date" => formatDate(record.date),
      "km" => formatKm(record.km),
      "nextDate" => formatDate(record.nextServiceDate),
      _ => formatKm(record.nextServiceKm),
    };
    if (key == "date") {
      return Text(text, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14));
    }
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text("${serviceColumnLabel(key)} ",
            style: TextStyle(fontSize: 11, color: DriverVault.muted(context))),
        Text(text, style: const TextStyle(fontSize: 13)),
      ],
    );
  }

  /// The parts column: a chip per part this service changed. The web has to fit
  /// them in one table cell and so names the first and counts the rest behind a
  /// panel; a card has the width to simply show them all.
  Widget _parts(BuildContext context) {
    final changed = changedParts(record, parts);
    if (changed.isEmpty) return _empty(context, "parts");
    return Wrap(
      spacing: 6,
      runSpacing: 6,
      children: [for (final part in changed) _chip(context, t(part.chipLabel))],
    );
  }

  Widget _notes(BuildContext context) {
    if (record.notes.isEmpty) return _empty(context, "notes");
    return Text(record.notes,
        style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic));
  }

  Widget _file(BuildContext context) {
    if (!record.hasFile) return _empty(context, "file");
    return _AttachmentLine(path: "/service-records", id: record.id, record: record, top: 0);
  }

  /// A column that is on but has nothing to show. Named rather than blank: it
  /// was switched on deliberately, and a card that silently drops it reads as a
  /// record where a part or a receipt might simply not have loaded.
  Widget _empty(BuildContext context, String key) => Text(
        "${serviceColumnLabel(key)} ${t("common.empty")}",
        style: TextStyle(fontSize: 11, color: DriverVault.muted(context)),
      );

  Widget _chip(BuildContext context, String label) => Container(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
        decoration: BoxDecoration(
          color: DriverVault.isDark(context) ? DriverVault.successSoftDark : DriverVault.successSoft,
          borderRadius: BorderRadius.circular(6),
        ),
        child: Text(label, style: const TextStyle(color: DriverVault.success, fontSize: 11, fontWeight: FontWeight.w500)),
      );
}

class _PartTile extends StatelessWidget {
  final Part part;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  const _PartTile({required this.part, this.onEdit, this.onDelete});
  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 0,
      margin: const EdgeInsets.only(bottom: 6),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(10),
        side: BorderSide(color: DriverVault.isDark(context) ? DriverVault.darkBorder : DriverVault.ink100),
      ),
      child: ListTile(
        dense: true,
        title: Text(part.name, style: const TextStyle(fontWeight: FontWeight.w600)),
        subtitle: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(part.partNumber.isEmpty ? "—" : part.partNumber,
                style: DriverVault.mono(context,
                    size: 12, weight: FontWeight.w400, color: DriverVault.muted(context))),
            if (part.notes.isNotEmpty)
              Text(part.notes,
                  style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic)),
            _AttachmentLine(path: "/parts", id: part.id, record: part),
          ],
        ),
        trailing: (onEdit != null || onDelete != null)
            ? _RowMenu(onEdit: onEdit, onDelete: onDelete)
            : null,
      ),
    );
  }
}

/// Overflow menu with Edit/Delete for a record row (write-gated). [onComplete]
/// adds the reminders' extra action.
class _RowMenu extends StatelessWidget {
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  final VoidCallback? onComplete;
  const _RowMenu({this.onEdit, this.onDelete, this.onComplete});
  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<String>(
      padding: EdgeInsets.zero,
      icon: const Icon(Icons.more_vert, size: 18),
      onSelected: (v) {
        if (v == "edit") onEdit?.call();
        if (v == "delete") onDelete?.call();
        if (v == "complete") onComplete?.call();
      },
      itemBuilder: (_) => [
        if (onComplete != null)
          PopupMenuItem(value: "complete", child: Text(t("car.reminders.markDone"))),
        if (onEdit != null) PopupMenuItem(value: "edit", child: Text(t("common.edit"))),
        if (onDelete != null)
          PopupMenuItem(
              value: "delete",
              child: Text(t("common.delete"),
                  style: const TextStyle(color: DriverVault.danger))),
      ],
    );
  }
}

/// The card every record tile sits in.
class _RecordCard extends StatelessWidget {
  final Widget child;
  const _RecordCard({required this.child});
  @override
  Widget build(BuildContext context) => Card(
        elevation: 0,
        margin: const EdgeInsets.only(bottom: 8),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(10),
          side: BorderSide(
              color: DriverVault.isDark(context) ? DriverVault.darkBorder : DriverVault.ink100),
        ),
        child: Padding(padding: const EdgeInsets.all(12), child: child),
      );
}

/// The status pill used by the record tiles, in the shared badge colours.
class _Badge extends StatelessWidget {
  final Status status;
  const _Badge(this.status);
  @override
  Widget build(BuildContext context) {
    final dark = DriverVault.isDark(context);
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
      decoration:
          BoxDecoration(color: status.bg(dark), borderRadius: BorderRadius.circular(999)),
      child: Text(status.label,
          style: TextStyle(
              color: status.fg(dark), fontSize: 11, fontWeight: FontWeight.w600)),
    );
  }
}

/// A tappable "Receipt.pdf" line that fetches the record's attachment and hands
/// it to the phone's viewer. Renders nothing when there is no file.
class _AttachmentLine extends StatelessWidget {
  final String path;
  final String id;
  final HasAttachment record;

  /// The gap above the line. Zero for a caller that already spaces the rows it
  /// stacks — the service card, whose file is one piece among several — and 6
  /// for the tiles that append this to a block of prose.
  final double top;
  const _AttachmentLine({
    required this.path,
    required this.id,
    required this.record,
    this.top = 6,
  });

  @override
  Widget build(BuildContext context) {
    if (!record.hasFile) return const SizedBox.shrink();
    return Padding(
      padding: EdgeInsets.only(top: top),
      child: InkWell(
        onTap: () => openAttachment(context, path, id, record.fileName),
        child: Row(mainAxisSize: MainAxisSize.min, children: [
          Icon(Icons.attach_file, size: 14, color: DriverVault.brandOnTint(context)),
          const SizedBox(width: 4),
          Flexible(
            child: Text(
              record.fileName,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                  fontSize: 12,
                  color: DriverVault.brandOnTint(context),
                  fontWeight: FontWeight.w500),
            ),
          ),
        ]),
      ),
    );
  }
}

/// A muted secondary line, used for the tiles' detail rows.
Widget _sub(BuildContext context, String text) => Text(
      text,
      style: TextStyle(fontSize: 12, color: DriverVault.muted(context)),
    );

class _TechnicalCheckTile extends StatelessWidget {
  final TechnicalCheck check;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  const _TechnicalCheckTile({required this.check, this.onEdit, this.onDelete});

  @override
  Widget build(BuildContext context) {
    final dark = DriverVault.isDark(context);
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(formatDate(check.date), style: const TextStyle(fontWeight: FontWeight.w600)),
              Row(children: [
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
                  decoration: BoxDecoration(
                    color: check.passed
                        ? (dark ? DriverVault.successSoftDark : DriverVault.successSoft)
                        : (dark ? DriverVault.dangerSoftDark : DriverVault.dangerSoft),
                    borderRadius: BorderRadius.circular(6),
                  ),
                  child: Text(t(check.passed ? "car.technical.passed" : "car.technical.failed"),
                      style: TextStyle(
                          color: check.passed ? DriverVault.success : DriverVault.danger,
                          fontSize: 11,
                          fontWeight: FontWeight.w600)),
                ),
                if (onEdit != null || onDelete != null)
                  _RowMenu(onEdit: onEdit, onDelete: onDelete),
              ]),
            ],
          ),
          const SizedBox(height: 4),
          // A failed check derives no next date, so the badge is only meaningful
          // when there is one.
          if (check.nextCheckDate != null)
            Row(children: [
              Expanded(
                  child: _sub(
                      context,
                      t("car.technical.next",
                          params: {"date": formatDate(check.nextCheckDate)}))),
              _Badge(expiryStatus(check.expiry)),
            ])
          else
            _sub(context, t("forms.technical.failedHint")),
          if (check.cost > 0 || check.station.isNotEmpty) ...[
            const SizedBox(height: 4),
            _sub(
                context,
                [
                  if (check.cost > 0) formatMoney(check.cost),
                  if (check.station.isNotEmpty) check.station,
                ].join(" · ")),
          ],
          _AttachmentLine(path: "/technical-checks", id: check.id, record: check),
          if (check.notes.isNotEmpty) ...[
            const SizedBox(height: 6),
            Text(check.notes,
                style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic)),
          ],
        ],
      ),
    );
  }
}

class _MaintenanceTile extends StatelessWidget {
  final MaintenanceEntry entry;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  const _MaintenanceTile({required this.entry, this.onEdit, this.onDelete});

  /// An unknown value falls back to the raw key rather than a blank: the
  /// server is the authority on this enum, and a value added there should still
  /// be legible in an app that has not caught up.
  static String _label(String namespace, String value) {
    final label = t("enums.$namespace.$value");
    return label == "enums.$namespace.$value" ? value : label;
  }

  @override
  Widget build(BuildContext context) {
    final warranty = warrantyStatus(entry);
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Expanded(
                child: Text(entry.description.isEmpty ? "—" : entry.description,
                    style: const TextStyle(fontWeight: FontWeight.w600)),
              ),
              if (onEdit != null || onDelete != null)
                _RowMenu(onEdit: onEdit, onDelete: onDelete),
            ],
          ),
          const SizedBox(height: 4),
          _sub(
              context,
              [
                formatDate(entry.date),
                if (entry.km > 0) formatKm(entry.km), // optional on maintenance
                _label("maintenanceType", entry.type),
                _label("maintenanceStatus", entry.status),
              ].join(" · ")),
          if (entry.workshop.isNotEmpty || entry.location.isNotEmpty) ...[
            const SizedBox(height: 4),
            _sub(
                context,
                [
                  if (entry.workshop.isNotEmpty) entry.workshop,
                  if (entry.location.isNotEmpty) entry.location,
                ].join(" · ")),
          ],
          if (entry.partsUsed.isNotEmpty) ...[
            const SizedBox(height: 4),
            _sub(context, t("car.maintenance.partsUsed", params: {"parts": entry.partsUsed})),
          ],
          if (entry.totalCost > 0) ...[
            const SizedBox(height: 4),
            Text(t("forms.maintenance.total", params: {"total": formatMoney(entry.totalCost)}),
                style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)),
          ],
          if (warranty != null) ...[
            const SizedBox(height: 6),
            _Badge(warranty),
          ],
          _AttachmentLine(path: "/maintenance", id: entry.id, record: entry),
          if (entry.notes.isNotEmpty) ...[
            const SizedBox(height: 6),
            Text(entry.notes, style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic)),
          ],
        ],
      ),
    );
  }
}

/// The refill history's summary panel. Every figure here is the server's; the
/// nulls it sends for anything it could not derive read as "—".
class _FuelStatsPanel extends StatelessWidget {
  final FuelStats stats;
  const _FuelStatsPanel({required this.stats});

  @override
  Widget build(BuildContext context) {
    if (stats.entries == 0) return const SizedBox.shrink();
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(t("car.fuel.title"), style: const TextStyle(fontWeight: FontWeight.w600)),
          const SizedBox(height: 8),
          Wrap(
            spacing: 16,
            runSpacing: 8,
            children: [
              _stat(context, t("car.fuel.average"), formatConsumption(stats.avgConsumptionL100)),
              _stat(context, t("car.fuel.best"), formatConsumption(stats.bestConsumptionL100)),
              _stat(context, t("car.fuel.worst"), formatConsumption(stats.worstConsumptionL100)),
              _stat(context, t("car.fuel.average"), formatKmPerLiter(stats.avgKmPerLiter)),
              _stat(context, t("car.fuel.costPerKm"), formatMoney(stats.costPerKm)),
              _stat(context, t("car.fuel.colPerLiter"), formatMoney(stats.avgPricePerLiter)),
              _stat(context, t("car.fuel.totalLiters"), formatLiters(stats.totalLiters)),
              _stat(context, t("car.fuel.totalSpent"), formatMoney(stats.totalCost)),
              _stat(context, t("car.fuel.refills"), "${stats.entries}"),
              _stat(context, t("car.fuel.trackedDistance"), formatKm(stats.trackedDistanceKm)),
            ],
          ),
          const SizedBox(height: 8),
          // Without this the tracked distance reads as a mistake whenever the
          // history starts or ends on a partial fill.
          _sub(context, t("car.fuel.subtitle")),
        ],
      ),
    );
  }

  Widget _stat(BuildContext context, String label, String value) => SizedBox(
        width: 150,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(label, style: TextStyle(fontSize: 11, color: DriverVault.muted(context))),
            Text(value,
                style: DriverVault.mono(context,
                    size: 13,
                    weight: FontWeight.w600,
                    color: Theme.of(context).colorScheme.onSurface)),
          ],
        ),
      );
}

/// The charging summary, the electric twin of [_FuelStatsPanel]. Same caveat on
/// the tracked distance: it covers the full-charge windows only.
class _ChargingStatsPanel extends StatelessWidget {
  final ChargingStats stats;
  const _ChargingStatsPanel({required this.stats});

  @override
  Widget build(BuildContext context) {
    if (stats.entries == 0) return const SizedBox.shrink();
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(t("car.charging.title"), style: const TextStyle(fontWeight: FontWeight.w600)),
          const SizedBox(height: 8),
          Wrap(
            spacing: 16,
            runSpacing: 8,
            children: [
              _stat(context, t("car.charging.average"),
                  formatKwhConsumption(stats.avgConsumptionKwh100)),
              _stat(context, t("car.charging.best"),
                  formatKwhConsumption(stats.bestConsumptionKwh100)),
              _stat(context, t("car.charging.worst"),
                  formatKwhConsumption(stats.worstConsumptionKwh100)),
              _stat(context, t("car.charging.average"), formatKmPerKwh(stats.avgKmPerKwh)),
              _stat(context, t("car.charging.costPerKm"), formatMoney(stats.costPerKm)),
              _stat(context, t("car.charging.colPerKwh"), formatMoney(stats.avgPricePerKwh)),
              _stat(context, t("car.charging.totalKwh"), formatKwh(stats.totalKwh)),
              _stat(context, t("car.charging.totalSpent"), formatMoney(stats.totalCost)),
              _stat(context, t("car.charging.sessions"), "${stats.entries}"),
              _stat(context, t("car.charging.trackedDistance"), formatKm(stats.trackedDistanceKm)),
            ],
          ),
          const SizedBox(height: 8),
          // Without this the tracked distance reads as a mistake whenever the
          // history starts or ends on a partial charge.
          _sub(context, t("car.charging.subtitle")),
        ],
      ),
    );
  }

  Widget _stat(BuildContext context, String label, String value) => SizedBox(
        width: 150,
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(label, style: TextStyle(fontSize: 11, color: DriverVault.muted(context))),
            Text(value,
                style: DriverVault.mono(context,
                    size: 13,
                    weight: FontWeight.w600,
                    color: Theme.of(context).colorScheme.onSurface)),
          ],
        ),
      );
}

class _ChargingTile extends StatelessWidget {
  final ChargingSession entry;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  const _ChargingTile({required this.entry, this.onEdit, this.onDelete});

  @override
  Widget build(BuildContext context) {
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(formatDate(entry.date), style: const TextStyle(fontWeight: FontWeight.w600)),
              Row(children: [
                Text(formatKm(entry.km), style: const TextStyle(fontSize: 13)),
                if (onEdit != null || onDelete != null)
                  _RowMenu(onEdit: onEdit, onDelete: onDelete),
              ]),
            ],
          ),
          const SizedBox(height: 4),
          _sub(
              context,
              [
                formatKwh(entry.kwh),
                if (entry.cost > 0) formatMoney(entry.cost),
                if (entry.pricePerKwh != null)
                  "${entry.pricePerKwh!.toStringAsFixed(3)}/kWh",
              ].join(" · ")),
          const SizedBox(height: 6),
          Wrap(spacing: 6, runSpacing: 6, children: [
            _tag(context,
                t(entry.fullCharge ? "car.charging.fullCharge" : "car.charging.partialCharge"),
                muted: !entry.fullCharge),
            if (entry.missedSession) _tag(context, t("car.charging.missedBefore"), warn: true),
            if (entry.location.isNotEmpty) _tag(context, entry.location, muted: true),
          ]),
          // Consumption exists only on a full charge that closes a computable
          // window; anything else has nothing to report.
          if (entry.consumptionKwh100 != null) ...[
            const SizedBox(height: 6),
            Text(
              t("car.charging.overDistance", params: {
                "consumption": formatKwhConsumption(entry.consumptionKwh100),
                "rate": formatKmPerKwh(entry.kmPerKwh),
                "distance": formatKm(entry.distanceKm),
              }),
              style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
            ),
          ],
          _AttachmentLine(path: "/charging-sessions", id: entry.id, record: entry),
          if (entry.notes.isNotEmpty) ...[
            const SizedBox(height: 6),
            Text(entry.notes, style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic)),
          ],
        ],
      ),
    );
  }

  Widget _tag(BuildContext context, String label, {bool muted = false, bool warn = false}) {
    final dark = DriverVault.isDark(context);
    final Color bg, fg;
    if (warn) {
      bg = dark ? DriverVault.warningSoftDark : DriverVault.warningSoft;
      fg = DriverVault.warning;
    } else if (muted) {
      bg = dark ? DriverVault.darkSunken : DriverVault.ink50;
      fg = DriverVault.muted(context);
    } else {
      bg = dark ? DriverVault.successSoftDark : DriverVault.successSoft;
      fg = DriverVault.success;
    }
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
      decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(6)),
      child: Text(label, style: TextStyle(fontSize: 11, color: fg, fontWeight: FontWeight.w600)),
    );
  }
}

class _FuelTile extends StatelessWidget {
  final FuelEntry entry;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  const _FuelTile({required this.entry, this.onEdit, this.onDelete});

  @override
  Widget build(BuildContext context) {
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(formatDate(entry.date), style: const TextStyle(fontWeight: FontWeight.w600)),
              Row(children: [
                Text(formatKm(entry.km), style: const TextStyle(fontSize: 13)),
                if (onEdit != null || onDelete != null)
                  _RowMenu(onEdit: onEdit, onDelete: onDelete),
              ]),
            ],
          ),
          const SizedBox(height: 4),
          _sub(
              context,
              [
                formatLiters(entry.liters),
                if (entry.cost > 0) formatMoney(entry.cost),
                if (entry.pricePerLiter != null)
                  "${entry.pricePerLiter!.toStringAsFixed(3)}/L",
              ].join(" · ")),
          const SizedBox(height: 6),
          Wrap(spacing: 6, runSpacing: 6, children: [
            _tag(context, t(entry.fullTank ? "car.fuel.fullTank" : "car.fuel.partialFill"),
                muted: !entry.fullTank),
            if (entry.missedFill) _tag(context, t("car.fuel.missedBefore"), warn: true),
            if (entry.station.isNotEmpty) _tag(context, entry.station, muted: true),
          ]),
          // Consumption exists only on a full tank that closes a computable
          // window; anything else has nothing to report.
          if (entry.consumptionL100 != null) ...[
            const SizedBox(height: 6),
            Text(
              t("car.fuel.overDistance", params: {
                "consumption": formatConsumption(entry.consumptionL100),
                "rate": formatKmPerLiter(entry.kmPerLiter),
                "distance": formatKm(entry.distanceKm),
              }),
              style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600),
            ),
          ],
          _AttachmentLine(path: "/fuel-entries", id: entry.id, record: entry),
          if (entry.notes.isNotEmpty) ...[
            const SizedBox(height: 6),
            Text(entry.notes, style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic)),
          ],
        ],
      ),
    );
  }

  Widget _tag(BuildContext context, String label, {bool muted = false, bool warn = false}) {
    final dark = DriverVault.isDark(context);
    final Color bg, fg;
    if (warn) {
      bg = dark ? DriverVault.warningSoftDark : DriverVault.warningSoft;
      fg = DriverVault.warning;
    } else if (muted) {
      bg = dark ? DriverVault.darkSunken : DriverVault.ink50;
      fg = DriverVault.muted(context);
    } else {
      bg = dark ? DriverVault.successSoftDark : DriverVault.successSoft;
      fg = DriverVault.success;
    }
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
      decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(6)),
      child: Text(label,
          style: TextStyle(color: fg, fontSize: 11, fontWeight: FontWeight.w500)),
    );
  }
}

class _DocumentTile extends StatelessWidget {
  final CarDocument doc;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  const _DocumentTile({required this.doc, this.onEdit, this.onDelete});

  @override
  Widget build(BuildContext context) {
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Expanded(
                child: Text(doc.title.isEmpty ? "—" : doc.title,
                    style: const TextStyle(fontWeight: FontWeight.w600)),
              ),
              Row(children: [
                _Badge(expiryStatus(doc.expiry)),
                if (onEdit != null || onDelete != null)
                  _RowMenu(onEdit: onEdit, onDelete: onDelete),
              ]),
            ],
          ),
          const SizedBox(height: 4),
          _sub(
              context,
              [
                _MaintenanceTile._label("documentType", doc.type),
                if (doc.provider.isNotEmpty) doc.provider,
                if (doc.reference.isNotEmpty) doc.reference,
              ].join(" · ")),
          const SizedBox(height: 4),
          _sub(
              context,
              t("car.documents.issuedRenews", params: {
                    "issued": formatDate(doc.issueDate),
                    "renews": formatDate(doc.expiryDate),
                  }) +
                  (doc.cost > 0 ? " · ${formatMoney(doc.cost)}" : "")),
          _AttachmentLine(path: "/car-documents", id: doc.id, record: doc),
          if (doc.notes.isNotEmpty) ...[
            const SizedBox(height: 6),
            Text(doc.notes, style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic)),
          ],
        ],
      ),
    );
  }
}

class _ReminderTile extends StatelessWidget {
  final Reminder reminder;
  final VoidCallback? onEdit;
  final VoidCallback? onDelete;
  final VoidCallback? onComplete;
  const _ReminderTile({
    required this.reminder,
    this.onEdit,
    this.onDelete,
    this.onComplete,
  });

  @override
  Widget build(BuildContext context) {
    final r = reminder;
    return _RecordCard(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Expanded(
                child: Text(
                  r.title.isEmpty ? "—" : r.title,
                  style: TextStyle(
                    fontWeight: FontWeight.w600,
                    decoration: r.done ? TextDecoration.lineThrough : null,
                    color: r.done ? DriverVault.muted(context) : null,
                  ),
                ),
              ),
              Row(children: [
                _Badge(reminderStatus(r)),
                if (onEdit != null || onDelete != null || onComplete != null)
                  _RowMenu(onEdit: onEdit, onDelete: onDelete, onComplete: onComplete),
              ]),
            ],
          ),
          const SizedBox(height: 4),
          _sub(
              context,
              [
                _MaintenanceTile._label("reminderType", r.type),
                if (r.dueDate != null)
                  t("car.reminders.on", params: {"date": formatDate(r.dueDate)}),
                if (r.dueKm > 0) t("car.reminders.at", params: {"km": formatKm(r.dueKm)}),
              ].join(" · ")),
          if (r.repeats) ...[
            const SizedBox(height: 4),
            _sub(
                context,
                t("car.reminders.repeatsEvery", params: {
                  "every": [
                    if (r.repeatDays > 0) "${r.repeatDays}d",
                    if (r.repeatKm > 0) formatKm(r.repeatKm),
                  ].join(" · ")
                })),
          ],
          if (r.auto) ...[
            const SizedBox(height: 6),
            _sub(context, t("car.reminders.autoHint")),
          ],
          if (r.notes.isNotEmpty) ...[
            const SizedBox(height: 6),
            Text(r.notes, style: const TextStyle(fontSize: 12, fontStyle: FontStyle.italic)),
          ],
        ],
      ),
    );
  }
}

class _Empty extends StatelessWidget {
  final String text;
  const _Empty(this.text);
  @override
  Widget build(BuildContext context) => Padding(
        padding: const EdgeInsets.symmetric(vertical: 16),
        child: Center(child: Text(text, style: const TextStyle(color: Colors.grey))),
      );
}

/// Shown on a car that was shared with the current user read-only.
class _ReadOnlyNotice extends StatelessWidget {
  const _ReadOnlyNotice();
  @override
  Widget build(BuildContext context) {
    final onTint = DriverVault.brandOnTint(context);
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
      decoration: BoxDecoration(
        color: DriverVault.brandTint(context),
        borderRadius: BorderRadius.circular(10),
      ),
      child: Row(
        children: [
          Icon(Icons.visibility_outlined, size: 18, color: onTint),
          const SizedBox(width: 8),
          Expanded(
            child: Text(
              t("car.readOnlyNotice"),
              style: TextStyle(color: onTint, fontSize: 13),
            ),
          ),
        ],
      ),
    );
  }
}

/// Owner-only bottom sheet to manage who a car is shared with: list current
/// grants, add one by email at a chosen permission, or remove one.
class _ShareSheet extends StatefulWidget {
  final Car car;
  const _ShareSheet({required this.car});
  @override
  State<_ShareSheet> createState() => _ShareSheetState();
}

class _ShareSheetState extends State<_ShareSheet> {
  late Future<List<CarShare>> _future;
  final _email = TextEditingController();
  String _permission = "read";
  bool _submitting = false;
  String? _error;

  @override
  void initState() {
    super.initState();
    _future = apiClient.listCarShares(widget.car.id);
  }

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

  void _reload() => setState(() => _future = apiClient.listCarShares(widget.car.id));

  /// The two permission levels, built per call because t() reads the live
  /// locale — a const list would freeze the language the sheet opened in.
  List<DropdownMenuItem<String>> _permissionItems() => [
        DropdownMenuItem(value: "read", child: Text(t("forms.share.read"))),
        DropdownMenuItem(value: "write", child: Text(t("forms.share.write"))),
      ];

  Future<void> _add() async {
    final email = _email.text.trim();
    if (email.isEmpty) return;
    setState(() {
      _submitting = true;
      _error = null;
    });
    try {
      await apiClient.addCarShare(widget.car.id, email, _permission);
      _email.clear();
      _permission = "read";
      _reload();
    } catch (e) {
      setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _submitting = false);
    }
  }

  Future<void> _setPermission(CarShare s, String value) async {
    try {
      await apiClient.addCarShare(widget.car.id, s.email, value);
      _reload();
    } catch (e) {
      setState(() => _error = e.toString());
    }
  }

  Future<void> _remove(CarShare s) async {
    try {
      await apiClient.removeCarShare(widget.car.id, s.userId);
      _reload();
    } catch (e) {
      setState(() => _error = e.toString());
    }
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(
        left: 16,
        right: 16,
        top: 16,
        bottom: DriverVault.sheetBottomInset(context),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(t("forms.share.title", params: {"name": widget.car.name}),
              style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          const SizedBox(height: 4),
          Text(
            t("forms.share.body"),
            style: const TextStyle(color: Colors.grey, fontSize: 12),
          ),
          const SizedBox(height: 12),
          if (_error != null)
            Padding(
              padding: const EdgeInsets.only(bottom: 8),
              child: Text(_error!, style: const TextStyle(color: DriverVault.danger)),
            ),
          Row(
            children: [
              Expanded(
                child: TextField(
                  controller: _email,
                  keyboardType: TextInputType.emailAddress,
                  decoration: InputDecoration(
                    labelText: t("forms.share.userEmail"),
                    border: const OutlineInputBorder(),
                    isDense: true,
                  ),
                ),
              ),
              const SizedBox(width: 8),
              DropdownButton<String>(
                value: _permission,
                onChanged: (v) => setState(() => _permission = v ?? "read"),
                items: _permissionItems(),
              ),
            ],
          ),
          const SizedBox(height: 8),
          SizedBox(
            width: double.infinity,
            child: FilledButton(
              onPressed: _submitting ? null : _add,
              child: Text(t(_submitting ? "forms.share.submitting" : "forms.share.submit")),
            ),
          ),
          const SizedBox(height: 16),
          Text(t("forms.share.peopleWithAccess"),
              style: const TextStyle(fontWeight: FontWeight.w600)),
          const SizedBox(height: 4),
          FutureBuilder<List<CarShare>>(
            future: _future,
            builder: (context, snap) {
              if (snap.connectionState == ConnectionState.waiting) {
                return const Padding(
                  padding: EdgeInsets.all(12),
                  child: Center(child: CircularProgressIndicator()),
                );
              }
              final shares = snap.data ?? [];
              if (shares.isEmpty) {
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 12),
                  child: Text(t("forms.share.notShared"),
                      style: const TextStyle(color: Colors.grey)),
                );
              }
              return Column(
                children: shares
                    .map((s) => ListTile(
                          contentPadding: EdgeInsets.zero,
                          dense: true,
                          title: Text(s.label),
                          subtitle: s.name.isNotEmpty ? Text(s.email) : null,
                          trailing: Row(
                            mainAxisSize: MainAxisSize.min,
                            children: [
                              DropdownButton<String>(
                                value: s.permission,
                                underline: const SizedBox.shrink(),
                                onChanged: (v) => v == null ? null : _setPermission(s, v),
                                items: _permissionItems(),
                              ),
                              IconButton(
                                tooltip: t("common.remove"),
                                icon: const Icon(Icons.close, size: 18),
                                onPressed: () => _remove(s),
                              ),
                            ],
                          ),
                        ))
                    .toList(),
              );
            },
          ),
        ],
      ),
    );
  }
}

/// Type-to-confirm dialog for deleting a car. The delete button is enabled only
/// once the user types the car's exact name — guarding this cascade delete
/// (which also removes all of the car's service records and parts).
class _DeleteCarDialog extends StatefulWidget {
  final Car car;
  final _CarDetailData data;
  const _DeleteCarDialog({required this.car, required this.data});
  @override
  State<_DeleteCarDialog> createState() => _DeleteCarDialogState();
}

class _DeleteCarDialogState extends State<_DeleteCarDialog> {
  final _confirm = TextEditingController();
  bool _deleting = false;

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

  bool get _canDelete => _confirm.text.trim() == widget.car.name;

  /// One collection's count as prose. The plural form is the translation
  /// file's job — Polish needs three of them, which a trailing "s" cannot do.
  String _count(String key, int n) => t("car.delete.$key", n: n);

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text(t("car.delete.title"),
          style: const TextStyle(color: DriverVault.danger, fontWeight: FontWeight.w700)),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(t("car.delete.body", params: {
            "name": widget.car.name,
            "services": _count("services", widget.data.services.length),
            "maintenance": _count("maintenance", widget.data.maintenance.length),
            "fuel": _count("fuel", widget.data.fuel.length),
            "charging": _count("charging", widget.data.charging.length),
            "documents": _count("documents", widget.data.documents.length),
            "parts": _count("parts", widget.data.parts.length),
          })),
          const SizedBox(height: 16),
          Text(t("car.delete.typeToConfirm", params: {"name": "“${widget.car.name}”"}),
              style: const TextStyle(fontSize: 13, color: Colors.grey)),
          const SizedBox(height: 6),
          TextField(
            controller: _confirm,
            autofocus: true,
            decoration: InputDecoration(hintText: widget.car.name, border: const OutlineInputBorder()),
            onChanged: (_) => setState(() {}),
          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: _deleting ? null : () => Navigator.pop(context, false),
          child: Text(t("common.cancel")),
        ),
        FilledButton(
          style: FilledButton.styleFrom(backgroundColor: const Color(0xFFDC2626)),
          onPressed: (!_canDelete || _deleting)
              ? null
              : () {
                  setState(() => _deleting = true);
                  Navigator.pop(context, true);
                },
          child: Text(t(_deleting ? "car.delete.deleting" : "car.delete.confirm")),
        ),
      ],
    );
  }
}

/// Bottom sheet for adding or editing a service record.
class _ServiceSheet extends StatefulWidget {
  final String carId;
  final Car car;
  final ServiceRecord? record; // null => create
  const _ServiceSheet({required this.carId, required this.car, this.record});
  @override
  State<_ServiceSheet> createState() => _ServiceSheetState();
}

class _ServiceSheetState extends State<_ServiceSheet> {
  late DateTime _date;
  late final TextEditingController _km;
  late final TextEditingController _notes;

  /// Which parts this record says were changed, keyed the way [kServiceParts]
  /// keys them, so a part added to that list turns up in this sheet without a
  /// second edit here.
  ///
  /// Every part, not only the shown ones: an edit has to send back what a part
  /// this car has switched off already said, because the API rewrites all of the
  /// booleans from the body and an omitted one would come back false.
  late final Map<String, bool> _changed;

  /// The parts this car still records, and so the only ones with a checkbox.
  late final List<ServicePart> _parts;
  final _pending = PendingAttachment();
  bool _saving = false;
  String? _error;

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

  @override
  void initState() {
    super.initState();
    final r = widget.record;
    _date = r?.date ?? DateTime.now();
    _km = TextEditingController(text: r == null ? "" : "${r.km}");
    _notes = TextEditingController(text: r?.notes ?? "");
    _parts = visibleParts(widget.car);
    // An existing record is read through the part's own accessor; a new one
    // starts from the part's default, which is why an oil change comes ticked —
    // unless this car has switched that part off, since ticking a box nobody was
    // shown is not a default, it's a guess.
    _changed = {
      for (final part in kServiceParts)
        part.key: r == null ? part.initial && _parts.contains(part) : part.changed(r),
    };
  }

  @override
  void dispose() {
    _km.dispose();
    _notes.dispose();
    super.dispose();
  }

  Future<void> _save() async {
    final km = int.tryParse(_km.text.trim()) ?? 0;
    if (_km.text.trim().isEmpty || km < 0) {
      setState(() => _error = t("forms.validation.odometer"));
      return;
    }
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "date": _date.toUtc().toIso8601String(),
      "km": km,
      for (final part in kServiceParts) part.field: _changed[part.key],
      "notes": _notes.text.trim(),
    };
    try {
      final saved = _isEdit
          ? await apiClient.updateService(widget.record!.id, payload)
          : await apiClient.createService(payload);
      if (!_pending.isEmpty) {
        try {
          await applyAttachment("/service-records", 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 Padding(
      padding: EdgeInsets.only(
        left: 16,
        right: 16,
        top: 16,
        bottom: DriverVault.sheetBottomInset(context),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(t(_isEdit ? "forms.service.editTitle" : "forms.service.addTitle"),
              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)),
            ),
          Row(
            children: [
              Expanded(
                child: OutlinedButton.icon(
                  icon: const Icon(Icons.calendar_today, size: 16),
                  label: Text(formatDate(_date)),
                  onPressed: () async {
                    final picked = await showDatePicker(
                      context: context,
                      initialDate: _date,
                      firstDate: DateTime(2000),
                      lastDate: DateTime.now().add(const Duration(days: 365)),
                    );
                    if (picked != null) setState(() => _date = picked);
                  },
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: TextField(
                  controller: _km,
                  keyboardType: TextInputType.number,
                  decoration: InputDecoration(
                      labelText: t("forms.service.odometer"),
                      border: const OutlineInputBorder()),
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          for (final part in _parts)
            CheckboxListTile(
              value: _changed[part.key] ?? false,
              onChanged: (v) => setState(() => _changed[part.key] = v ?? false),
              title: Text(t(part.label)),
              contentPadding: EdgeInsets.zero,
              controlAffinity: ListTileControlAffinity.leading,
            ),
          TextField(
            controller: _notes,
            decoration: InputDecoration(
                labelText: t("forms.service.notes"), border: const OutlineInputBorder()),
          ),
          const SizedBox(height: 8),
          AttachmentField(
            path: "/service-records",
            record: widget.record,
            recordId: widget.record?.id,
            pending: _pending,
            onChanged: () => setState(() {}),
            legend: t("forms.service.attachmentLegend"),
          ),
          const SizedBox(height: 8),
          Text(
            t("forms.service.autoHint", params: {
              "days": widget.car.serviceIntervalDays,
              "km": formatKm(widget.car.serviceIntervalKm),
            }),
            style: const TextStyle(color: Colors.grey, fontSize: 12),
          ),
          const SizedBox(height: 12),
          SizedBox(
            width: double.infinity,
            child: FilledButton(
              onPressed: _saving ? null : _save,
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 12),
                child: Text(_saving
                    ? t("common.saving")
                    : t(_isEdit ? "common.saveChanges" : "forms.service.submit")),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

/// Bottom sheet for adding or editing a part.
class _PartSheet extends StatefulWidget {
  final String carId;
  final Part? part; // null => create
  const _PartSheet({required this.carId, this.part});
  @override
  State<_PartSheet> createState() => _PartSheetState();
}

class _PartSheetState extends State<_PartSheet> {
  late final TextEditingController _name;
  late final TextEditingController _partNumber;
  late final TextEditingController _notes;
  final _pending = PendingAttachment();
  bool _saving = false;
  String? _error;

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

  @override
  void initState() {
    super.initState();
    _name = TextEditingController(text: widget.part?.name ?? "");
    _partNumber = TextEditingController(text: widget.part?.partNumber ?? "");
    _notes = TextEditingController(text: widget.part?.notes ?? "");
  }

  @override
  void dispose() {
    _name.dispose();
    _partNumber.dispose();
    _notes.dispose();
    super.dispose();
  }

  Future<void> _save() async {
    final name = _name.text.trim();
    if (name.isEmpty) {
      setState(() => _error = t("forms.validation.partName"));
      return;
    }
    setState(() {
      _saving = true;
      _error = null;
    });
    final payload = {
      "car": widget.carId,
      "name": name,
      "partNumber": _partNumber.text.trim(),
      "notes": _notes.text.trim(),
    };
    try {
      final saved = _isEdit
          ? await apiClient.updatePart(widget.part!.id, payload)
          : await apiClient.createPart(payload);
      if (!_pending.isEmpty) {
        try {
          await applyAttachment("/parts", 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 Padding(
      padding: EdgeInsets.only(
        left: 16,
        right: 16,
        top: 16,
        bottom: DriverVault.sheetBottomInset(context),
      ),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(t(_isEdit ? "forms.part.editTitle" : "forms.part.addTitle"),
              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)),
            ),
          TextField(
            controller: _name,
            textCapitalization: TextCapitalization.words,
            decoration: InputDecoration(
                labelText: t("forms.part.name"),
                hintText: t("forms.part.namePlaceholder"),
                border: const OutlineInputBorder()),
          ),
          const SizedBox(height: 8),
          TextField(
            controller: _partNumber,
            decoration: InputDecoration(
                labelText: t("forms.part.partNumber"),
                hintText: "04152-YZZA7",
                border: const OutlineInputBorder()),
          ),
          const SizedBox(height: 8),
          TextField(
            controller: _notes,
            decoration: InputDecoration(
                labelText: t("forms.part.notes"),
                hintText: t("forms.part.notesPlaceholder"),
                border: const OutlineInputBorder()),
          ),
          const SizedBox(height: 8),
          AttachmentField(
            path: "/parts",
            record: widget.part,
            recordId: widget.part?.id,
            pending: _pending,
            onChanged: () => setState(() {}),
            legend: t("forms.part.attachmentLegend"),
          ),
          const SizedBox(height: 12),
          SizedBox(
            width: double.infinity,
            child: FilledButton(
              onPressed: _saving ? null : _save,
              child: Padding(
                padding: const EdgeInsets.symmetric(vertical: 12),
                child: Text(_saving
                    ? t("common.saving")
                    : t(_isEdit ? "common.saveChanges" : "forms.part.submit")),
              ),
            ),
          ),
        ],
      ),
    );
  }
}
