import 'dart:io';

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart' as p;
import 'package:terepi_seged/enums/note_type.dart';
import 'package:terepi_seged/models/imported_layer_meta.dart';
import 'package:terepi_seged/models/measured_point.dart';
import 'package:terepi_seged/models/note_item.dart';
import 'package:terepi_seged/models/note_item_audio.dart';
import 'package:terepi_seged/models/note_item_photo.dart';
import 'package:terepi_seged/models/stakeout_point.dart';
import 'package:terepi_seged/models/track.dart';
import 'package:terepi_seged/services/device_identity_service.dart';
import 'package:uuid/uuid.dart';
import '../models/project.dart';

class AppDatabase {
  AppDatabase._();
  static final instance = AppDatabase._();
  static Database? _db;

  static const _uuid = Uuid();

  Future<Database> get database async {
    _db ??= await _open();
    return _db!;
  }

  Future<Database> _open() async {
    final oldDb = p.join(await getDatabasesPath(), 'terepi_seged.db');
    if (await File(oldDb).exists()) {
      await File(oldDb).delete();
    }

    final directory = await getExternalStorageDirectory();
    final dbDir = Directory(p.join(directory!.path, 'database'));

    if (!await dbDir.exists()) {
      await dbDir.create(recursive: true);
    }
    final path = p.join(dbDir.path, 'terepi_seged.db');

    return openDatabase(path,
        version: 5,
        onConfigure: (db) => db.execute('PRAGMA foreign_keys = ON'),
        onCreate: _onCreate,
        onUpgrade: _onUpgrade);
  }

  @visibleForTesting
  Future<void> createSchema(Database db) => _onCreate(db, 5);

  @visibleForTesting
  Future<void> upgradeSchema(Database db, int oldVersion, int newVersion) =>
      _onUpgrade(db, oldVersion, newVersion);

  Future<void> _onCreate(Database db, int _) async {
    // ── Projects ────────────────────────────────────────────────────
    // uuid: a projekt globális azonosítója. Közös projekthez csatlakozáskor
    // a Supabase-en lévő projekt uuid-jával jön létre a lokális sor, így a
    // lokális és a távoli projekt ugyanaz az entitás.

    await db.execute('''
      CREATE TABLE IF NOT EXISTS projects (
        id          INTEGER PRIMARY KEY AUTOINCREMENT,
        uuid        TEXT    NOT NULL UNIQUE,
        name        TEXT    NOT NULL,
        client      TEXT    NOT NULL DEFAULT '',
        description TEXT    NOT NULL DEFAULT '',
        crs         TEXT    NOT NULL DEFAULT 'eov',
        color       TEXT    NOT NULL DEFAULT '#185FA5',
        status      TEXT    NOT NULL DEFAULT 'active',
        is_default  INTEGER NOT NULL DEFAULT 0,
        is_local_only INTEGER    NOT NULL DEFAULT 1,
        last_synced_at TEXT,
        created_at  TEXT    NOT NULL,
        updated_at  TEXT    NOT NULL,
        deleted_at  TEXT,
        sync_status TEXT    NOT NULL DEFAULT 'pending',
        last_pulled_at TEXT
      )
    ''');

    // ── Bemért pontok ────────────────────────────────────────────────
    await db.execute('''
      CREATE TABLE IF NOT EXISTS measured_points (
        id          INTEGER PRIMARY KEY AUTOINCREMENT,
        uuid        TEXT    NOT NULL UNIQUE,
        project_id  INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
        name        TEXT    NOT NULL,
        eov_y       REAL,
        eov_x       REAL,
        eov_z       REAL,
        latitude    REAL,
        longitude   REAL,
        altitude    REAL,
        accuracy    REAL,
        fix_quality INTEGER,
        timestamp   TEXT    NOT NULL,
        note        TEXT    NOT NULL DEFAULT '',
        created_by  TEXT,
        device_id   TEXT,
        updated_at  TEXT,
        deleted_at  TEXT,
        sync_status TEXT    NOT NULL DEFAULT 'pending'
      )
    ''');
    await db
        .execute('CREATE INDEX idx_mp_project ON measured_points(project_id)');
    await db
        .execute('CREATE INDEX idx_mp_sync ON measured_points(sync_status)');

    // ── Track-ek ─────────────────────────────────────────────────────
    await db.execute('''
      CREATE TABLE IF NOT EXISTS tracks (
        id            INTEGER PRIMARY KEY AUTOINCREMENT,
        uuid          TEXT    NOT NULL UNIQUE,
        project_id    INTEGER REFERENCES projects(id) ON DELETE CASCADE,
        name          TEXT    NOT NULL,
        start_time    TEXT    NOT NULL,
        end_time      TEXT,
        status        TEXT    NOT NULL DEFAULT 'recording',
        source        TEXT    NOT NULL DEFAULT 'Telefon GPS',
        distance_m    REAL    NOT NULL DEFAULT 0,
        point_count   INTEGER NOT NULL DEFAULT 0,
        is_local_only INTEGER NOT NULL DEFAULT 0,
        sync_status   TEXT    NOT NULL DEFAULT 'pending',
        supabase_id   TEXT,
        created_by    TEXT,
        device_id     TEXT,
        updated_at    TEXT,
        deleted_at    TEXT
      )
    ''');
    await db.execute('CREATE INDEX idx_tracks_project ON tracks(project_id)');

    // ── Track pontok ─────────────────────────────────────────────────
    // uuid pontonként: az upsert-alapú feltöltés így idempotens —
    // ha egy batch kétszer megy fel (pl. hálózati hiba után), nem duplikál.

    await db.execute('''
      CREATE TABLE IF NOT EXISTS track_points (
        id        INTEGER PRIMARY KEY AUTOINCREMENT,
        uuid      TEXT    NOT NULL UNIQUE,
        track_id  INTEGER NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
        latitude  REAL    NOT NULL,
        longitude REAL    NOT NULL,
        altitude  REAL,
        accuracy  REAL,
        speed     REAL,
        heading   REAL,
        timestamp TEXT    NOT NULL,
        sync_status TEXT    NOT NULL DEFAULT 'pending'
      )
    ''');
    await db.execute(
        'CREATE INDEX idx_tp_track ON track_points(track_id, timestamp)');
    await db.execute('CREATE INDEX idx_tp_sync ON track_points(sync_status)');

    // ── Terepbejárás elemek ──────────────────────────────────────────
    await db.execute('''
      CREATE TABLE IF NOT EXISTS note_items (
        id           INTEGER PRIMARY KEY AUTOINCREMENT,
        uuid         TEXT    NOT NULL UNIQUE,
        project_id   INTEGER REFERENCES projects(id) ON DELETE CASCADE,
        type         TEXT    NOT NULL,
        points_json  TEXT    NOT NULL,
        color        TEXT    NOT NULL DEFAULT '#185FA5',
        opacity      REAL    NOT NULL DEFAULT 0.5,
        stroke_width REAL    NOT NULL DEFAULT 3.0,
        stroke_color TEXT    NOT NULL DEFAULT '#FFD700',
        label        TEXT    NOT NULL DEFAULT '',
        created_at   TEXT    NOT NULL,
        created_by   TEXT,
        updated_at   TEXT,
        deleted_at   TEXT,
        sync_status  TEXT    NOT NULL DEFAULT 'pending'
      )
    ''');
    await db
        .execute('CREATE INDEX idx_notes_project ON note_items(project_id)');

    await db.execute('''
  CREATE TABLE IF NOT EXISTS note_item_photos (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    note_item_id  INTEGER NOT NULL REFERENCES note_items(id) ON DELETE CASCADE,
    local_path    TEXT    NOT NULL,
    storage_path  TEXT,
    caption       TEXT    NOT NULL DEFAULT '',
    latitude      REAL,
    longitude     REAL,
    created_at    TEXT    NOT NULL
  )
''');
    await db.execute(
        'CREATE INDEX idx_photos_note ON note_item_photos(note_item_id)');

    await db.execute('''
  CREATE TABLE IF NOT EXISTS note_item_audios (
    id               INTEGER PRIMARY KEY AUTOINCREMENT,
    note_item_id     INTEGER NOT NULL REFERENCES note_items(id) ON DELETE CASCADE,
    local_path       TEXT    NOT NULL,
    caption          TEXT    NOT NULL DEFAULT '',
    duration_seconds INTEGER NOT NULL DEFAULT 0,
    latitude         REAL,
    longitude        REAL,
    created_at       TEXT    NOT NULL
  )
''');
    await db.execute(
        'CREATE INDEX idx_audios_note ON note_item_audios(note_item_id)');

    await db.execute('''
      CREATE TABLE IF NOT EXISTS pending_points (
        id               INTEGER PRIMARY KEY AUTOINCREMENT,
        point_number     INTEGER NOT NULL,
        gnss_number      TEXT,
        latitude         REAL    NOT NULL,
        longitude        REAL    NOT NULL,
        altitude         REAL,
        height_of_geoid  REAL,
        eov_x            REAL,
        eov_y            REAL,
        pole_height      REAL,
        horizontal_error REAL,
        vertical_error   REAL,
        description      TEXT,
        is_deleted       INTEGER NOT NULL DEFAULT 0,
        project_id       INTEGER NOT NULL DEFAULT 2,
        created_at       TEXT    NOT NULL,
        sync_status      TEXT    NOT NULL DEFAULT 'pending'
      )
    ''');
    await db.execute(
      'CREATE INDEX IF NOT EXISTS idx_pp_status '
      'ON pending_points(sync_status)',
    );

    await db.execute('''
  CREATE TABLE IF NOT EXISTS imported_layers (
    id           TEXT    PRIMARY KEY,
    name         TEXT    NOT NULL,
    source_type  TEXT    NOT NULL,
    local_path   TEXT    NOT NULL,
    storage_path TEXT,
    color_hex    TEXT,
    opacity      REAL,
    stroke_width REAL,
    is_visible   INTEGER NOT NULL DEFAULT 1,
    project_id   INTEGER,
    imported_at  TEXT    NOT NULL,
    synced_at    TEXT,
    version      INTEGER NOT NULL DEFAULT 0
  )
''');
    await db.execute(
        'CREATE INDEX idx_imp_layers_project ON imported_layers(project_id)');

    await _createStakeoutTable(db);
    await _createContactsOutbox(db);

    // Alap projekt létrehozása az első indításhoz
    final now = DateTime.now().toIso8601String();
    await db.insert('projects', {
      'uuid': _uuid.v4(),
      'name': 'Alapértelmezett projekt',
      'is_default': 1,
      'is_local_only': 1,
      'status': 'active',
      'created_at': now,
      'updated_at': now,
    });
  }

  Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
    if (oldVersion < 2) {
      await db.execute('''
      ALTER TABLE imported_layers ADD COLUMN color_hex    TEXT;
    ''');
      await db.execute('''
      ALTER TABLE imported_layers ADD COLUMN opacity      REAL;
    ''');
      await db.execute('''
      ALTER TABLE imported_layers ADD COLUMN stroke_width REAL;
    ''');
    }
    if (oldVersion < 3) {
      await _createStakeoutTable(db);
    }
    if (oldVersion < 4) {
      await _migrateToV4(db);
    }
    if (oldVersion < 5) {
      _createContactsOutbox(db);
    }
  }

  Future<void> _migrateToV4(Database db) async {
    // Új oszlopok. (Az SQLite ALTER TABLE nem tud UNIQUE megkötést
    // hozzáadni, ezért a uuid-egyediséget külön unique index adja
    // a backfill UTÁN.)
    await db.execute("ALTER TABLE projects ADD COLUMN deleted_at TEXT");
    await db.execute(
        "ALTER TABLE projects ADD COLUMN sync_status TEXT NOT NULL DEFAULT 'pending'");

    await db.execute("ALTER TABLE measured_points ADD COLUMN uuid TEXT");
    await db.execute("ALTER TABLE measured_points ADD COLUMN created_by TEXT");
    await db.execute("ALTER TABLE measured_points ADD COLUMN updated_at TEXT");
    await db.execute("ALTER TABLE measured_points ADD COLUMN deleted_at TEXT");
    await db.execute(
        "ALTER TABLE measured_points ADD COLUMN sync_status TEXT NOT NULL DEFAULT 'pending'");
    // A meglévő pontoknál a mérés időpontja jó kiindulás updated_at-nek.
    await db.execute(
        "UPDATE measured_points SET updated_at = timestamp WHERE updated_at IS NULL");

    await db.execute("ALTER TABLE tracks ADD COLUMN uuid TEXT");
    await db.execute("ALTER TABLE tracks ADD COLUMN created_by TEXT");
    await db.execute("ALTER TABLE tracks ADD COLUMN updated_at TEXT");
    await db.execute("ALTER TABLE tracks ADD COLUMN deleted_at TEXT");
    await db.execute(
        "UPDATE tracks SET updated_at = COALESCE(end_time, start_time) "
        "WHERE updated_at IS NULL");
    // Ha egy tracknek már van Supabase-azonosítója, azt örökli uuid-ként,
    // így a korábban feltöltött track ugyanaz az entitás marad.
    await db.execute(
        "UPDATE tracks SET uuid = supabase_id WHERE uuid IS NULL AND supabase_id IS NOT NULL");

    await db.execute("ALTER TABLE track_points ADD COLUMN uuid TEXT");
    await db.execute(
        "ALTER TABLE track_points ADD COLUMN sync_status TEXT NOT NULL DEFAULT 'pending'");

    await db.execute("ALTER TABLE note_items ADD COLUMN uuid TEXT");
    await db.execute("ALTER TABLE note_items ADD COLUMN created_by TEXT");
    await db.execute("ALTER TABLE note_items ADD COLUMN updated_at TEXT");
    await db.execute("ALTER TABLE note_items ADD COLUMN deleted_at TEXT");
    await db.execute(
        "ALTER TABLE note_items ADD COLUMN sync_status TEXT NOT NULL DEFAULT 'pending'");
    await db.execute(
        "UPDATE note_items SET updated_at = created_at WHERE updated_at IS NULL");

    await db.execute('ALTER TABLE projects ADD COLUMN last_pulled_at TEXT');
    await db.execute('ALTER TABLE measured_points ADD COLUMN device_id TEXT');
    await db.execute('ALTER TABLE tracks ADD COLUMN device_id TEXT');
    await db.execute(
        'ALTER TABLE imported_layers ADD COLUMN version INTEGER NOT NULL DEFAULT 0');

    // uuid-backfill a meglévő sorokra (Dartból, mert az SQLite nem tud
    // uuid-t generálni). Batch-elve, hogy sok sor esetén is gyors legyen.
    await _backfillUuids(db, 'measured_points');
    await _backfillUuids(db, 'tracks');
    await _backfillUuids(db, 'track_points');
    await _backfillUuids(db, 'note_items');

    // Egyediség kikényszerítése a backfill után.
    await db.execute(
        'CREATE UNIQUE INDEX IF NOT EXISTS idx_mp_uuid ON measured_points(uuid)');
    await db.execute(
        'CREATE UNIQUE INDEX IF NOT EXISTS idx_tracks_uuid ON tracks(uuid)');
    await db.execute(
        'CREATE UNIQUE INDEX IF NOT EXISTS idx_tp_uuid ON track_points(uuid)');
    await db.execute(
        'CREATE UNIQUE INDEX IF NOT EXISTS idx_notes_uuid ON note_items(uuid)');
    await db.execute(
        'CREATE INDEX IF NOT EXISTS idx_mp_sync ON measured_points(sync_status)');
    await db.execute(
        'CREATE INDEX IF NOT EXISTS idx_tp_sync ON track_points(sync_status)');

    // Egyszeri árva-takarítás: a foreign key-ek eddig nem érvényesültek,
    // így a korábbi törlések árva sorokat hagyhattak.
    await db.execute('''
      DELETE FROM tracks
      WHERE project_id IS NOT NULL
        AND project_id NOT IN (SELECT id FROM projects)
    ''');
    await db.execute('''
      DELETE FROM track_points
      WHERE track_id NOT IN (SELECT id FROM tracks)
    ''');
    await db.execute('''
      DELETE FROM measured_points
      WHERE project_id NOT IN (SELECT id FROM projects)
    ''');
    await db.execute('''
      DELETE FROM note_items
      WHERE project_id IS NOT NULL
        AND project_id NOT IN (SELECT id FROM projects)
    ''');
    await db.execute('''
      DELETE FROM note_item_photos
      WHERE note_item_id NOT IN (SELECT id FROM note_items)
    ''');
    await db.execute('''
      DELETE FROM note_item_audios
      WHERE note_item_id NOT IN (SELECT id FROM note_items)
    ''');
  }

  /// uuid-t ad minden olyan sornak, ahol még NULL — 500-as batch-ekben.
  Future<void> _backfillUuids(Database db, String table) async {
    while (true) {
      final rows = await db.query(table,
          columns: ['id'], where: 'uuid IS NULL', limit: 500);
      if (rows.isEmpty) break;

      final batch = db.batch();
      for (final row in rows) {
        batch.update(table, {'uuid': _uuid.v4()},
            where: 'id = ?', whereArgs: [row['id']]);
      }
      await batch.commit(noResult: true);
    }
  }

  // ═══════════════════════════════════════════════════════════════
  // Segédfüggvények a szinkron-oszlopokhoz
  // ═══════════════════════════════════════════════════════════════

  /// Beszúrandó map kiegészítése a szinkron-oszlopokkal, ha hiányoznak.
  /// Így a modellek egyelőre változatlanok maradhatnak — a DB-réteg
  /// garantálja, hogy minden új sor szinkron-képes legyen.
  Map<String, dynamic> _withSyncColumns(Map<String, dynamic> map) {
    final m = Map<String, dynamic>.from(map);
    m['uuid'] ??= _uuid.v4();
    m['updated_at'] ??= DateTime.now().toIso8601String();
    m['sync_status'] ??= 'pending';
    return m;
  }

  String get _now => DateTime.now().toIso8601String();

  // ── Projects CRUD ─────────────────────────────────────────────────

  Future<int> insertProject(Project p) async =>
      (await database).insert('projects', p.toMap());

  Future<void> updateProject(Project p) async => (await database).update(
      'projects', {...p.toMap(), 'sync_status': 'pending'},
      where: 'id = ?', whereArgs: [p.id]);

  Future<void> archiveProject(int id) async => (await database).update(
      'projects',
      {
        'status': 'archived',
        'updated_at': DateTime.now().toIso8601String(),
        'sync_status': 'pending'
      },
      where: 'id = ?',
      whereArgs: [id]);

  Future<List<Project>> listProjects({bool includeArchived = false}) async {
    final rows = await (await database).query(
      'projects',
      where: includeArchived
          ? 'deleted_at IS NULL'
          : "status = 'active' AND deleted_at IS NULL",
      orderBy: 'updated_at DESC',
    );
    return rows.map(Project.fromMap).toList();
  }

  Future<Project?> getProject(int id) async {
    final rows = await (await database)
        .query('projects', where: 'id = ?', whereArgs: [id], limit: 1);
    return rows.isEmpty ? null : Project.fromMap(rows.first);
  }

  /// Projekt keresése a globális uuid alapján — közös projekthez
  /// csatlakozáskor ezzel derül ki, megvan-e már lokálisan.
  Future<Project?> getProjectByUuid(String uuid) async {
    final rows = await (await database)
        .query('projects', where: 'uuid = ?', whereArgs: [uuid], limit: 1);
    return rows.isEmpty ? null : Project.fromMap(rows.first);
  }

  // Projekt statisztikák — a listázáshoz
  Future<Map<String, int>> getProjectStats(int projectId) async {
    final db = await database;
    final points = Sqflite.firstIntValue(await db.rawQuery(
            'SELECT COUNT(*) FROM measured_points WHERE project_id = ?',
            [projectId])) ??
        0;
    final tracks = Sqflite.firstIntValue(await db.rawQuery(
            'SELECT COUNT(*) FROM tracks WHERE project_id = ?', [projectId])) ??
        0;
    final notes = Sqflite.firstIntValue(await db.rawQuery(
            'SELECT COUNT(*) FROM note_items WHERE project_id = ?',
            [projectId])) ??
        0;
    return {'points': points, 'tracks': tracks, 'notes': notes};
  }

  Future<int> insertTrack(Track track) async {
    final db = await database;
    final map = _withSyncColumns(track.toMap());
    map['device_id'] ??= Get.isRegistered<DeviceIdentityService>() &&
            DeviceIdentityService.to.isReady
        ? DeviceIdentityService.to.appInstanceId
        : null;
    return db.insert('tracks', map);
  }

  Future<void> updateTrack(Track track) async {
    final db = await database;
    await db.update(
      'tracks',
      {...track.toMap(), 'updated_at': _now, 'sync_status': 'pending'},
      where: 'id = ?',
      whereArgs: [track.id],
    );
  }

  /// Soft delete — a törlés ténye is szinkronizálódik a többi eszközre.
  /// (A track pontjai a szerveroldalon CASCADE-del törlődnek majd.)
  Future<void> deleteTrack(int id) async {
    final db = await database;
    await db.update(
      'tracks',
      {'deleted_at': _now, 'updated_at': _now, 'sync_status': 'pending'},
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<List<Track>> listTracks() async {
    final db = await database;
    final rows = await db.query('tracks',
        where: 'deleted_at IS NULL', orderBy: 'start_time DESC');
    return rows.map(Track.fromMap).toList();
  }

  Future<Track?> getTrack(int id) async {
    final db = await database;
    final rows = await db.query(
      'tracks',
      where: 'id = ?',
      whereArgs: [id],
      limit: 1,
    );
    return rows.isEmpty ? null : Track.fromMap(rows.first);
  }

  // ═══════════════════════════════════════════════════════════════
  // TRACK POINTS
  // ═══════════════════════════════════════════════════════════════

  Future<void> addPoint(TrackPoint point, double newDistance) async {
    final db = await database;
    try {
      await db.transaction((txn) async {
        await txn.insert('track_points', _withTrackPointColumns(point.toMap()));
        await txn.rawUpdate('''
        UPDATE tracks
        SET distance_m = ?,
            point_count     = point_count + 1
        WHERE id = ?
      ''', [newDistance, point.trackId]);
      });
    } catch (e) {
      print(
          'addPoint hiba: $e  -  trackId=${point.trackId}   dist=$newDistance');
    }
  }

  /// track_points: nincs updated_at (append-only), csak uuid + sync_status.
  Map<String, dynamic> _withTrackPointColumns(Map<String, dynamic> map) {
    final m = Map<String, dynamic>.from(map);
    m['uuid'] ??= _uuid.v4();
    m['sync_status'] ??= 'pending';
    return m;
  }

  Future<List<TrackPoint>> getPoints(int trackId) async {
    final db = await database;
    final rows = await db.query(
      'track_points',
      where: 'track_id = ?',
      whereArgs: [trackId],
      orderBy: 'timestamp ASC',
    );
    return rows.map(TrackPoint.fromMap).toList();
  }

  Future<List<({double lat, double lon})>> getLatLons(int trackId) async {
    final db = await database;
    final rows = await db.query(
      'track_points',
      columns: ['latitude', 'longitude'],
      where: 'track_id = ?',
      whereArgs: [trackId],
      orderBy: 'timestamp ASC',
    );
    return rows
        .map((r) => (
              lat: r['latitude'] as double,
              lon: r['longitude'] as double,
            ))
        .toList();
  }

  // ═══════════════════════════════════════════════════════════════
  // PENDING POINTS (szinkron queue)
  // ═══════════════════════════════════════════════════════════════

  Future<int> insertPendingPoint(Map<String, dynamic> point) async {
    final db = await database;
    return db.insert('pending_points', {
      'point_number': point['pointNumber'],
      'gnss_number': point['gnssNumber'],
      'latitude': point['latitude'],
      'longitude': point['longitude'],
      'altitude': point['altitude'],
      'height_of_geoid': point['heightOfGeoid'],
      'eov_x': point['eovX'],
      'eov_y': point['eovY'],
      'pole_height': point['poleHeight'],
      'horizontal_error': point['horizontalError'],
      'vertical_error': point['verticalError'],
      'description': point['description'],
      'is_deleted': (point['isDeleted'] == true) ? 1 : 0,
      'project_id': point['projectId'] ?? 2,
      'created_at': DateTime.now().toIso8601String(),
      'sync_status': 'pending',
    });
  }

  Future<List<Map<String, dynamic>>> getPendingPoints() async {
    final db = await database;
    // JOIN a projects táblával — lokális projektek kiszűrve
    return db.rawQuery('''
    SELECT pp.*
    FROM   pending_points pp
    LEFT JOIN projects pr ON pr.id = pp.project_id
    WHERE  pp.sync_status = 'pending'
      AND  (pr.is_local_only = 0 OR pr.is_local_only IS NULL)
    ORDER BY pp.id ASC
  ''');
  }

  Future<int> getPendingCount() async {
    final db = await database;
    final result = await db.rawQuery(
      "SELECT COUNT(*) AS cnt FROM pending_points "
      "WHERE sync_status = 'pending'",
    );
    return (result.first['cnt'] as int?) ?? 0;
  }

  Future<void> markPointSynced(int id) async {
    final db = await database;
    await db.update(
      'pending_points',
      {'sync_status': 'synced'},
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<void> markPointError(int id) async {
    final db = await database;
    await db.update(
      'pending_points',
      {'sync_status': 'error'},
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  /// Sikeresen szinkronizált pontok törlése (takarítás)
  Future<void> purgeSyncedPoints() async {
    final db = await database;
    await db.delete(
      'pending_points',
      where: 'sync_status = ?',
      whereArgs: ['synced'],
    );
  }

  // -------------------  Terepbejárás pontok, vonalak, területek

  /// Elem mentése - visszaadja a kapott AQLite id-t
  Future<int> insertNoteItem(NoteItem item) async {
    final db = await database;
    return db.insert('note_items', _withSyncColumns(item.toMap()));
  }

  /// Elem frissítése (szín, label, koordináták módosítása után).
  Future<void> updateNoteItem(NoteItem item) async {
    final db = await database;
    await db.update(
      'note_items',
      {...item.toMap(), 'updated_at': _now, 'sync_status': 'pending'},
      where: 'id = ?',
      whereArgs: [item.id],
    );
  }

  /// Egy elem törlése.
  Future<void> deleteNoteItem(int id) async {
    final db = await database;
    await db.update(
      'note_items',
      {'deleted_at': _now, 'updated_at': _now, 'sync_status': 'pending'},
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  /// Projekt összes eleme — opcionálisan típus szerint szűrve.
  Future<List<NoteItem>> listNoteItems(int? projectId, {NoteType? type}) async {
    final db = await database;

    String? where;
    List<Object?> whereArgs = [];

    if (projectId != null) {
      where = type != null ? 'project_id = ? AND type = ?' : 'project_id = ?';
      whereArgs = type != null ? [projectId, type.name] : [projectId];
    } else {
      // Projekt nélküli elemek
      where = type != null ? 'type = ?' : null;
      whereArgs = type != null ? [type.name] : [];
    }

    final rows = await db.query(
      'note_items',
      where: where,
      whereArgs: whereArgs,
      orderBy: 'created_at ASC',
    );
    return rows.map(NoteItem.fromMap).toList();
  }

  /// Egyetlen elem lekérése id alapján.
  Future<NoteItem?> getNoteItem(int id) async {
    final db = await database;
    final rows = await db.query(
      'note_items',
      where: 'id = ?',
      whereArgs: [id],
      limit: 1,
    );
    return rows.isEmpty ? null : NoteItem.fromMap(rows.first);
  }

  /// Projekt összes elemének törlése — soft delete.
  Future<void> deleteAllNoteItems(int projectId) async {
    final db = await database;
    await db.update(
      'note_items',
      {'deleted_at': _now, 'updated_at': _now, 'sync_status': 'pending'},
      where: 'project_id = ? AND deleted_at IS NULL',
      whereArgs: [projectId],
    );
  }

  // -------- NoteItemPhoto

  Future<int> insertNotePhoto(NoteItemPhoto photo) async {
    final db = await database;
    return db.insert('note_item_photos', photo.toMap());
  }

  Future<void> updateNotePhoto(NoteItemPhoto photo) async {
    final db = await database;
    await db.update(
      'note_item_photos',
      photo.toMap(),
      where: 'id = ?',
      whereArgs: [photo.id],
    );
  }

  Future<void> deleteNotePhoto(int id) async {
    final db = await database;
    await db.delete('note_item_photos', where: 'id = ?', whereArgs: [id]);
  }

  Future<List<NoteItemPhoto>> listNotePhotos(int noteItemId) async {
    final db = await database;
    final rows = await db.query(
      'note_item_photos',
      where: 'note_item_id = ?',
      whereArgs: [noteItemId],
      orderBy: 'created_at ASC',
    );
    return rows.map(NoteItemPhoto.fromMap).toList();
  }

  Future<void> deleteAllNotePhotos(int noteItemId) async {
    final db = await database;
    await db.delete('note_item_photos',
        where: 'note_item_id = ?', whereArgs: [noteItemId]);
  }

  Future<int> countNotePhotos(int noteItemId) async {
    final db = await database;
    final res = await db.rawQuery(
      'SELECT COUNT(*) FROM note_item_photos WHERE note_item_id = ?',
      [noteItemId],
    );
    return Sqflite.firstIntValue(res) ?? 0;
  }
  // --------------   NoteItemAudio

  Future<int> insertNoteAudio(NoteItemAudio audio) async {
    final db = await database;
    return db.insert('note_item_audios', audio.toMap());
  }

  Future<void> updateNoteAudio(NoteItemAudio audio) async {
    final db = await database;
    await db.update('note_item_audios', audio.toMap(),
        where: 'id = ?', whereArgs: [audio.id]);
  }

  Future<void> deleteNoteAudio(int id) async {
    final db = await database;
    await db.delete('note_item_audios', where: 'id = ?', whereArgs: [id]);
  }

  Future<List<NoteItemAudio>> listNoteAudios(int noteItemId) async {
    final db = await database;
    final rows = await db.query(
      'note_item_audios',
      where: 'note_item_id = ?',
      whereArgs: [noteItemId],
      orderBy: 'created_at ASC',
    );
    return rows.map(NoteItemAudio.fromMap).toList();
  }

  Future<int> countNoteAudios(int noteItemId) async {
    final db = await database;
    final res = await db.rawQuery(
      'SELECT COUNT(*) FROM note_item_audios WHERE note_item_id = ?',
      [noteItemId],
    );
    return Sqflite.firstIntValue(res) ?? 0;
  }

  // -----------  Layer meta adatok
  Future<void> insertImportedLayer(ImportedLayerMeta meta) async {
    final db = await database;
    await db.insert('imported_layers', meta.toMap(),
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  Future<void> updateImportedLayer(ImportedLayerMeta meta) async {
    final db = await database;
    await db.update('imported_layers', meta.toMap(),
        where: 'id = ?', whereArgs: [meta.id]);
  }

  Future<void> deleteImportedLayer(String id) async {
    final db = await database;
    await db.delete('imported_layers', where: 'id = ?', whereArgs: [id]);
  }

  Future<List<ImportedLayerMeta>> listImportedLayers({int? projectId}) async {
    final db = await database;
    final rows = await db.query(
      'imported_layers',
      where: projectId != null ? 'project_id = ?' : null,
      whereArgs: projectId != null ? [projectId] : null,
      orderBy: 'imported_at DESC',
    );
    return rows.map(ImportedLayerMeta.fromMap).toList();
  }

  // ------------   Bemért pontok

  Future<int> insertMeasuredPoint(MeasuredPoint point) async {
    final db = await database;
    final map = _withSyncColumns(point.toMap());
    map['device_id'] ??= Get.isRegistered<DeviceIdentityService>() &&
            DeviceIdentityService.to.isReady
        ? DeviceIdentityService.to.appInstanceId
        : null;
    return db.insert('measured_points', map);
  }

  /// Soft delete — a törlés is szinkronizálódik.
  Future<void> deleteMeasuredPoint(int id) async {
    final db = await database;
    await db.update(
      'measured_points',
      {'deleted_at': _now, 'updated_at': _now, 'sync_status': 'pending'},
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  Future<void> deleteAllMeasuredPoints(int projectId) async {
    final db = await database;
    await db.update(
      'measured_points',
      {'deleted_at': _now, 'updated_at': _now, 'sync_status': 'pending'},
      where: 'project_id = ? AND deleted_at IS NULL',
      whereArgs: [projectId],
    );
  }

  Future<List<MeasuredPoint>> listMeasuredPoints(int projectId) async {
    final db = await database;
    final rows = await db.query(
      'measured_points',
      where: 'project_id = ?',
      whereArgs: [projectId],
      orderBy: 'timestamp ASC',
    );
    return rows.map(MeasuredPoint.fromMap).toList();
  }

  // ═══════════════════════════════════════════════════════════════
  // KITŰZÉS (stakeout_points) — szeizmikus line/station modell
  // ═══════════════════════════════════════════════════════════════

  Future<void> _createStakeoutTable(Database db) async {
    await db.execute('''
      CREATE TABLE IF NOT EXISTS stakeout_points (
        id             INTEGER PRIMARY KEY AUTOINCREMENT,
        uuid           TEXT    NOT NULL UNIQUE,
        project_id     INTEGER NOT NULL,
        line_id        TEXT    NOT NULL DEFAULT '',
        station        INTEGER NOT NULL,
        name           TEXT    NOT NULL,
        point_type     TEXT    NOT NULL DEFAULT 'geofon',
        source         TEXT    NOT NULL DEFAULT 'csv',
        plan_eov_y     REAL    NOT NULL,
        plan_eov_x     REAL    NOT NULL,
        plan_eov_z     REAL,
        plan_lat       REAL    NOT NULL,
        plan_lon       REAL    NOT NULL,
        status         TEXT    NOT NULL DEFAULT 'pending',
        measured_eov_y REAL,
        measured_eov_x REAL,
        measured_eov_z REAL,
        measured_lat   REAL,
        measured_lon   REAL,
        dev_inline     REAL,
        dev_crossline  REAL,
        dev_dz         REAL,
        fix_quality    INTEGER,
        accuracy       REAL,
        tilt_deg       REAL,
        staked_at      TEXT,
        is_offset      INTEGER NOT NULL DEFAULT 0,
        parent_uuid    TEXT,
        offset_dist    REAL,
        offset_bearing REAL,
        created_by     TEXT,
        device_id      TEXT,
        created_at     TEXT    NOT NULL,
        updated_at     TEXT    NOT NULL,
        deleted_at     TEXT,
        sync_status    TEXT    NOT NULL DEFAULT 'pending'
      )
    ''');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_sp_proj_line '
        'ON stakeout_points(project_id, line_id, station)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_sp_status '
        'ON stakeout_points(status)');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_sp_sync '
        'ON stakeout_points(sync_status)');
  }

  Future<int> insertStakeoutPoint(StakeoutPoint p) async {
    final db = await database;
    final map = p.toMap();
    map['device_id'] ??= Get.isRegistered<DeviceIdentityService>() &&
            DeviceIdentityService.to.isReady
        ? DeviceIdentityService.to.appInstanceId
        : null;
    ;
    return db.insert('stakeout_points', map);
  }

  /// Tömeges beszúrás importhoz — tranzakcióban; az azonos
  /// (projekt, vonal, station) sorokat kihagyja. Visszaadja a
  /// ténylegesen beszúrt darabszámot.
  Future<int> insertStakeoutPoints(List<StakeoutPoint> points) async {
    final db = await database;
    var inserted = 0;
    await db.transaction((txn) async {
      for (final p in points) {
        final dup = await txn.query('stakeout_points',
            columns: ['id'],
            where: 'project_id = ? AND line_id = ? AND station = ? '
                'AND deleted_at IS NULL',
            whereArgs: [p.projectId, p.lineId, p.station],
            limit: 1);
        if (dup.isNotEmpty) continue;
        final map = p.toMap();
        map['device_id'] ??= Get.isRegistered<DeviceIdentityService>() &&
                DeviceIdentityService.to.isReady
            ? DeviceIdentityService.to.appInstanceId
            : null;
        await txn.insert('stakeout_points', map);
        inserted++;
      }
    });
    return inserted;
  }

  Future<void> updateStakeoutPoint(StakeoutPoint p) async {
    final db = await database;
    final map = p.toMap()
      ..['updated_at'] = DateTime.now().toIso8601String()
      ..['sync_status'] = 'pending';
    await db.update('stakeout_points', map, where: 'id = ?', whereArgs: [p.id]);
  }

  /// Soft delete — a törlés is szinkronizálható lesz (4. ütem).
  Future<void> softDeleteStakeoutPoint(int id) async {
    final db = await database;
    final now = DateTime.now().toIso8601String();
    await db.update('stakeout_points',
        {'deleted_at': now, 'updated_at': now, 'sync_status': 'pending'},
        where: 'id = ?', whereArgs: [id]);
  }

  Future<List<StakeoutPoint>> listStakeoutPoints(int projectId) async {
    final db = await database;
    final rows = await db.query('stakeout_points',
        where: 'project_id = ? AND deleted_at IS NULL',
        whereArgs: [projectId],
        orderBy: 'line_id ASC, station ASC');
    return rows.map(StakeoutPoint.fromMap).toList();
  }

  /// Feltöltésre váró kitűzési pontok — a projekt uuid-jával együtt.
  /// A csak lokális projektek SQL-szinten kizárva.
  Future<List<Map<String, dynamic>>> pendingStakeoutPoints() async {
    final db = await database;
    return db.rawQuery('''
      SELECT sp.*, pr.uuid AS project_uuid
      FROM   stakeout_points sp
      JOIN   projects pr ON pr.id = sp.project_id
      WHERE  sp.sync_status = 'pending' AND pr.is_local_only = 0
      ORDER BY sp.id ASC
    ''');
  }

  // ═══════════════════════════════════════════════════════════════
  // SZINKRON-SEGÉDEK — a TsSyncService használja őket
  // ═══════════════════════════════════════════════════════════════
  //
  // FONTOS: minden lekérdezés kihagyja az is_local_only = 1 projekteket
  // és azok adatait — a "csak lokális" projekt SOHA nem hagyja el az
  // eszközt, semmilyen szinkron-útvonalon.

  /// Feltöltésre váró (nem lokális) projektek.
  Future<List<Map<String, dynamic>>> pendingProjects() async {
    final db = await database;
    return db.query('projects',
        where: "is_local_only = 0 AND sync_status = 'pending'");
  }

  /// Feltöltésre váró bemért pontok — a projekt uuid-jával együtt.
  Future<List<Map<String, dynamic>>> pendingMeasuredPoints() async {
    final db = await database;
    return db.rawQuery('''
      SELECT mp.*, pr.uuid AS project_uuid
      FROM   measured_points mp
      JOIN   projects pr ON pr.id = mp.project_id
      WHERE  mp.sync_status = 'pending' AND pr.is_local_only = 0
      ORDER BY mp.id ASC
    ''');
  }

  /// Feltöltésre váró trackek — a projekt uuid-jával együtt.
  Future<List<Map<String, dynamic>>> pendingTracks() async {
    final db = await database;
    return db.rawQuery('''
      SELECT t.*, pr.uuid AS project_uuid
      FROM   tracks t
      JOIN   projects pr ON pr.id = t.project_id
      WHERE  t.sync_status = 'pending' AND pr.is_local_only = 0
      ORDER BY t.id ASC
    ''');
  }

  /// Feltöltésre váró track-pontok, batch-ben — track- és projekt-uuid-val.
  /// Csak lezárt vagy futó, NEM törölt, NEM lokális trackek pontjai.
  Future<List<Map<String, dynamic>>> pendingTrackPoints(
      {int limit = 500}) async {
    final db = await database;
    return db.rawQuery('''
      SELECT tp.*, t.uuid AS track_uuid, pr.uuid AS project_uuid
      FROM   track_points tp
      JOIN   tracks t   ON t.id = tp.track_id
      JOIN   projects pr ON pr.id = t.project_id
      WHERE  tp.sync_status = 'pending'
        AND  t.deleted_at IS NULL
        AND  t.sync_status != 'pending'
        AND  pr.is_local_only = 0
      ORDER BY tp.id ASC
      LIMIT ?
    ''', [limit]);
  }

  /// Feltöltésre váró terepbejárás-elemek (csak projekthez rendeltek).
  Future<List<Map<String, dynamic>>> pendingNoteItems() async {
    final db = await database;
    return db.rawQuery('''
      SELECT n.*, pr.uuid AS project_uuid
      FROM   note_items n
      JOIN   projects pr ON pr.id = n.project_id
      WHERE  n.sync_status = 'pending' AND pr.is_local_only = 0
      ORDER BY n.id ASC
    ''');
  }

  /// Összes feltöltésre váró sor száma (UI-jelvényhez).
  Future<int> pendingSyncCount() async {
    final db = await database;
    final res = await db.rawQuery('''
      SELECT
        (SELECT COUNT(*) FROM projects p
          WHERE p.is_local_only = 0 AND p.sync_status = 'pending') +
        (SELECT COUNT(*) FROM measured_points mp
          JOIN projects pr ON pr.id = mp.project_id
          WHERE mp.sync_status = 'pending' AND pr.is_local_only = 0) +
        (SELECT COUNT(*) FROM tracks t
          JOIN projects pr ON pr.id = t.project_id
          WHERE t.sync_status = 'pending' AND pr.is_local_only = 0) +
        (SELECT COUNT(*) FROM track_points tp
          JOIN tracks t ON t.id = tp.track_id
          JOIN projects pr ON pr.id = t.project_id
          WHERE tp.sync_status = 'pending' AND pr.is_local_only = 0)
        AS cnt
    ''');
    return (res.first['cnt'] as int?) ?? 0;
  }

  /// Sorok megjelölése szinkronizáltként, uuid alapján (500-as darabokban,
  /// az SQLite paraméter-limit miatt).
  Future<void> markSyncedByUuid(String table, List<String> uuids) async {
    if (uuids.isEmpty) return;
    final db = await database;
    for (var i = 0; i < uuids.length; i += 500) {
      final chunk =
          uuids.sublist(i, i + 500 > uuids.length ? uuids.length : i + 500);
      final placeholders = List.filled(chunk.length, '?').join(',');
      await db.rawUpdate(
        "UPDATE $table SET sync_status = 'synced' WHERE uuid IN ($placeholders)",
        chunk,
      );
    }
  }

  // ── Szinkron-kurzor (projektenként) ──────────────────────────────

  Future<String?> getProjectCursor(int localProjectId) async {
    final db = await database;
    final rows = await db.query('projects',
        columns: ['last_pulled_at'],
        where: 'id = ?',
        whereArgs: [localProjectId],
        limit: 1);
    return rows.isEmpty ? null : rows.first['last_pulled_at'] as String?;
  }

  Future<void> setProjectCursor(int localProjectId, String cursor) async {
    final db = await database;
    await db.update('projects', {'last_pulled_at': cursor},
        where: 'id = ?', whereArgs: [localProjectId]);
  }

  // ── Távoli sorok lokális alkalmazása ─────────────────────────────

  /// Távoli projekt-sor lokális upsertje uuid alapján.
  /// Visszaadja a lokális id-t. Csatlakozáskor és letöltéskor is ez fut.
  Future<int> upsertProjectFromRemote(Map<String, dynamic> remote) async {
    final db = await database;
    final existing = await getProjectByUuid(remote['id'] as String);

    final map = <String, dynamic>{
      'uuid': remote['id'],
      'name': remote['name'] ?? '',
      'client': remote['client'] ?? '',
      'description': remote['description'] ?? '',
      'crs': remote['crs'] ?? 'eov',
      'color': remote['color'] ?? '#185FA5',
      'status': remote['status'] ?? 'active',
      'is_local_only': 0,
      'updated_at': remote['updated_at'] ?? DateTime.now().toIso8601String(),
      'deleted_at': remote['deleted_at'],
      'sync_status': 'synced',
      'last_synced_at': DateTime.now().toIso8601String(),
    };

    if (existing == null) {
      map['created_at'] =
          remote['created_at'] ?? DateTime.now().toIso8601String();
      return db.insert('projects', map);
    }

    // Lokális, még fel nem töltött módosítás nyer (push fut előbb).
    final localPending = await db.query('projects',
        columns: ['sync_status'],
        where: 'id = ?',
        whereArgs: [existing.id],
        limit: 1);
    if (localPending.first['sync_status'] == 'pending') return existing.id!;

    await db.update('projects', map, where: 'id = ?', whereArgs: [existing.id]);
    return existing.id!;
  }

  /// Általános távoli-sor alkalmazás uuid-kulcsú táblára
  /// (measured_points / tracks / note_items).
  ///
  /// LWW-szabály: ha a lokális sor 'pending' (van el nem küldött helyi
  /// módosítás), a távolit kihagyjuk — a push úgyis előbb fut, mint a pull.
  Future<void> applyRemoteRow(
      String table, Map<String, dynamic> localMap) async {
    final db = await database;
    final uuid = localMap['uuid'] as String;
    final rows = await db.query(table,
        columns: ['id', 'sync_status'],
        where: 'uuid = ?',
        whereArgs: [uuid],
        limit: 1);

    if (rows.isEmpty) {
      // Ismeretlen sor tombstone-ja: nincs mit törölni, kihagyjuk.
      if (localMap['deleted_at'] != null) return;
      await db.insert(table, localMap);
      return;
    }
    if (rows.first['sync_status'] == 'pending') return;

    await db.update(table, localMap,
        where: 'id = ?', whereArgs: [rows.first['id']]);
  }

  /// Track lokális id-ja uuid alapján (a track-pontok letöltéséhez).
  Future<int?> getTrackIdByUuid(String uuid) async {
    final db = await database;
    final rows = await db.query('tracks',
        columns: ['id'], where: 'uuid = ?', whereArgs: [uuid], limit: 1);
    return rows.isEmpty ? null : rows.first['id'] as int;
  }

  /// Távoli track-pontok beszúrása — a uuid unique index miatt az
  /// ismétlődések csendben kimaradnak (idempotens).
  Future<void> insertRemoteTrackPoints(List<Map<String, dynamic>> maps) async {
    if (maps.isEmpty) return;
    final db = await database;
    final batch = db.batch();
    for (final m in maps) {
      batch.insert('track_points', m,
          conflictAlgorithm: ConflictAlgorithm.ignore);
    }
    await batch.commit(noResult: true);
    // A point_count/distance a saját eszközön mérésnél frissül; letöltött
    // pontoknál a track sora már a helyes összesítőkkel érkezik a szerverről.
  }

  // ── Réteg-szinkron segédek ───────────────────────────────────────

  Future<int> getImportedLayerVersion(String id) async {
    final db = await database;
    final rows = await db.query('imported_layers',
        columns: ['version'], where: 'id = ?', whereArgs: [id], limit: 1);
    return rows.isEmpty ? -1 : (rows.first['version'] as int? ?? 0);
  }

  Future<void> upsertImportedLayerRaw(Map<String, dynamic> map) async {
    final db = await database;
    await db.insert('imported_layers', map,
        conflictAlgorithm: ConflictAlgorithm.replace);
  }

  /// Egyszeri javítás: a korábban ANDROID_ID-vel (vagy üresen) beírt
  /// device_id-k cseréje az appInstanceId-re. A sorok ezen az eszközön
  /// keletkeztek, így a csere helyes; a sync_status visszaáll pending-re,
  /// hogy a helyes érték felmenjen.
  Future<void> repairDeviceIds(String correctId) async {
    final db = await database;
    for (final t in ['measured_points', 'tracks', 'stakeout_points']) {
      await db.rawUpdate(
          "UPDATE $t SET device_id = ?, sync_status = 'pending' "
          "WHERE device_id IS NOT NULL AND length(device_id) <> 36",
          [correctId]);
    }
  }

  Future<void> _createContactsOutbox(Database db) async {
    await db.execute('''
      CREATE TABLE IF NOT EXISTS contacts_outbox (
        local_uuid  TEXT PRIMARY KEY,
        project_id TEXT NOT NULL,
        name        TEXT NOT NULL,
        address     TEXT NOT NULL DEFAULT '',
        phone       TEXT NOT NULL DEFAULT '',
        email       TEXT NOT NULL DEFAULT '',
        note        TEXT NOT NULL DEFAULT '',
        created_at  TEXT NOT NULL
      )
    ''');
    await db.execute('CREATE INDEX IF NOT EXISTS idx_contacts_outbox_project '
        'ON contacts_outbox(project_id)');
  }

  Future<void> insertPendingContact(Map<String, dynamic> row) async {
    final db = await database;
    await db.insert('contacts_outbox', row);
  }

  /// [projectId] NÉLKÜL (a flush-hoz) MINDEN várólistás sort ad vissza —
  /// a feltöltés projekttől függetlenül fusson. [projectId]-vel (a UI
  /// listához) csak az adott projekt pending sorait.
  Future<List<Map<String, dynamic>>> listPendingContacts(
      {String? projectId}) async {
    final db = await database;
    if (projectId != null) {
      return db.query('contacts_outbox',
          where: 'project_id = ?',
          whereArgs: [projectId],
          orderBy: 'created_at ASC');
    }
    return db.query('contacts_outbox', orderBy: 'created_at ASC');
  }

  Future<void> updatePendingContact(
      String localUuid, Map<String, dynamic> fields) async {
    final db = await database;
    await db.update('contacts_outbox', fields,
        where: 'local_uuid = ?', whereArgs: [localUuid]);
  }

  Future<void> deletePendingContact(String localUuid) async {
    final db = await database;
    await db.delete('contacts_outbox',
        where: 'local_uuid = ?', whereArgs: [localUuid]);
  }

  Future<int> countPendingContacts({String? projectId}) async {
    final db = await database;
    if (projectId != null) {
      return Sqflite.firstIntValue(await db.rawQuery(
              'SELECT COUNT(*) FROM contacts_outbox WHERE project_id = ?',
              [projectId])) ??
          0;
    }
    return Sqflite.firstIntValue(
            await db.rawQuery('SELECT COUNT(*) FROM contacts_outbox')) ??
        0;
  }

  // Future<void> testOnly() async {
  //   final db = await database;
  //   await db.execute(
  //       "ALTER TABLE contacts_outbox ADD COLUMN project_id TEXT NOT NULL DEFAULT ''");
  //   await db.execute('CREATE INDEX IF NOT EXISTS idx_contacts_outbox_project '
  //       'ON contacts_outbox(project_id)');
  // }
}
