/**
 * Database Module
 *
 * Uses SQLocal directly with BroadcastChannel for cross-tab coordination.
 *
 * Why this approach instead of SharedWorker?
 * - SQLocal already uses its own internal worker for OPFS access
 * - Wrapping it in another SharedWorker adds complexity and causes issues
 * - BroadcastChannel provides simple cross-tab communication
 * - Each tab has its own SQLocal instance but they share the same OPFS database file
 *
 * Usage:
 *   import { sql, dbReady, addLocation, getLocations } from './database.js';
 *
 *   await dbReady;
 *   await addLocation('Point A', -1.5, 7.5);
 *   const locations = await getLocations();
 */

import { SQLocal } from 'sqlocal';

// Database configuration
const DATABASE_PATH = 'lupmis2.db';
const BROADCAST_CHANNEL = 'lupmis-db-sync';

// Create SQLocal instance
const db = new SQLocal(DATABASE_PATH);

// Get the sql tagged template function
const { sql } = db;

console.log('[Database] SQLocal instance created for:', DATABASE_PATH);

// Export sql for direct queries
export { sql };

// Create broadcast channel for cross-tab coordination
const channel = new BroadcastChannel(BROADCAST_CHANNEL);

// Track if database is ready
let isReady = false;
let readyResolve;
let readyReject;

export const dbReady = new Promise((resolve, reject) => {
  readyResolve = resolve;
  readyReject = reject;
});

// Database change listeners
const changeListeners = new Set();

/**
 * Subscribe to database changes (from any tab)
 * @param {Function} listener - Called with { table, action, id }
 * @returns {Function} Unsubscribe function
 */
export function onDatabaseChange(listener) {
  changeListeners.add(listener);
  return () => changeListeners.delete(listener);
}

// Handle messages from other tabs
channel.onmessage = (event) => {
  const { type, payload } = event.data;
  if (type === 'DB_CHANGE') {
    // Notify local listeners about changes from other tabs
    for (const listener of changeListeners) {
      try {
        listener(payload);
      } catch (e) {
        console.error('[Database] Change listener error:', e);
      }
    }
  }
};

/**
 * Broadcast a database change to other tabs
 */
function broadcastChange(table, action, id = null) {
  channel.postMessage({
    type: 'DB_CHANGE',
    payload: { table, action, id, timestamp: Date.now() }
  });

  // Also notify local listeners
  for (const listener of changeListeners) {
    try {
      listener({ table, action, id, timestamp: Date.now(), local: true });
    } catch (e) {
      console.error('[Database] Change listener error:', e);
    }
  }
}

// ============================================================================
// Database Initialization
// ============================================================================

/**
 * Initialize the database schema
 */
export async function initSchema() {
  try {
    console.log('[Database] Initializing schema...');

    // Test connection
    const testResult = await sql`SELECT sqlite_version() as version`;
    console.log('[Database] SQLite version:', testResult[0]?.version);

    // Create locations table
    console.log('[Database] Creating locations table...');
    await sql`
      CREATE TABLE IF NOT EXISTS locations (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        longitude REAL NOT NULL,
        latitude REAL NOT NULL,
        description TEXT,
        category TEXT DEFAULT 'default',
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
        synced INTEGER DEFAULT 0
      )
    `;

    // Verify table exists
    const tablesAfterLocations = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;
    console.log('[Database] Locations table exists:', tablesAfterLocations.length > 0);

    // Create sync_log table
    console.log('[Database] Creating sync_log table...');
    await sql`
      CREATE TABLE IF NOT EXISTS sync_log (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        table_name TEXT NOT NULL,
        record_id INTEGER NOT NULL,
        action TEXT NOT NULL,
        timestamp TEXT DEFAULT CURRENT_TIMESTAMP,
        synced INTEGER DEFAULT 0
      )
    `;

    // Create remote_data cache table
    console.log('[Database] Creating remote_data table...');
    await sql`
      CREATE TABLE IF NOT EXISTS remote_data (
        key TEXT PRIMARY KEY,
        data TEXT NOT NULL,
        fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
      )
    `;

    // Create collector_zones table for caching zone data
    console.log('[Database] Creating collector_zones table...');
    await sql`
      CREATE TABLE IF NOT EXISTS collector_zones (
        id INTEGER PRIMARY KEY,
        zone_name TEXT,
        geometry_wkt TEXT,
        properties TEXT,
        fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
      )
    `;

    // Create parcels table for caching parcel data
    // status: 'verified' = from API, 'new' = drawn locally, needs verification
    console.log('[Database] Creating parcels table...');
    await sql`
      CREATE TABLE IF NOT EXISTS parcels (
        id INTEGER PRIMARY KEY,
        geometry_wkt TEXT,
        properties TEXT,
        status TEXT DEFAULT 'verified',
        fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
      )
    `;

    // Migrate: add status column if it doesn't exist (for existing databases)
    try {
      await sql`SELECT status FROM parcels LIMIT 1`;
    } catch {
      console.log('[Database] Adding status column to parcels table...');
      await sql`ALTER TABLE parcels ADD COLUMN status TEXT DEFAULT 'verified'`;
    }

    // Create building_footprints table for caching footprint data
    console.log('[Database] Creating building_footprints table...');
    await sql`
      CREATE TABLE IF NOT EXISTS building_footprints (
        id INTEGER PRIMARY KEY,
        geometry_wkt TEXT,
        properties TEXT,
        fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
      )
    `;

    // Create osm_roads table for caching the OSM road network
    console.log('[Database] Creating osm_roads table...');
    await sql`
      CREATE TABLE IF NOT EXISTS osm_roads (
        osm_id INTEGER PRIMARY KEY,
        geometry_wkt TEXT,
        properties TEXT,
        fetched_at TEXT DEFAULT CURRENT_TIMESTAMP
      )
    `;

    // Create indexes
    await sql`CREATE INDEX IF NOT EXISTS idx_locations_category ON locations(category)`;
    await sql`CREATE INDEX IF NOT EXISTS idx_locations_synced ON locations(synced)`;

    // Final verification
    const allTables = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`;
    console.log('[Database] All tables:', allTables.map(t => t.name));

    isReady = true;
    readyResolve(true);
    console.log('[Database] ✓ Schema initialized');

  } catch (error) {
    console.error('[Database] ✗ Schema init failed:', error);
    readyReject(error);
    throw error;
  }
}

// ============================================================================
// Location Operations
// ============================================================================

/**
 * Add a new location
 */
export async function addLocation(name, longitude, latitude, options = {}) {
  const { description = null, category = 'default' } = options;

  console.log('[Database] Adding location:', name, longitude, latitude, category);

  try {
    // Check table exists first
    const tableCheck = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;
    console.log('[Database] Table check before insert:', tableCheck);

    if (tableCheck.length === 0) {
      console.error('[Database] ✗ locations table does not exist!');
      throw new Error('locations table does not exist');
    }

    // Insert - using explicit values
    console.log('[Database] Executing INSERT...');
    await sql`
    INSERT INTO locations (name, longitude, latitude, description, category)
    VALUES (${name}, ${longitude}, ${latitude}, ${description}, ${category})
  `;
    console.log('[Database] INSERT completed');

    // Get the ID
    const idResult = await sql`SELECT last_insert_rowid() as id`;
    const newId = idResult[0]?.id;
    console.log('[Database] New ID:', newId);

    // Verify it was actually inserted
    const verifyResult = await sql`SELECT * FROM locations WHERE id = ${newId}`;
    console.log('[Database] Verify insert:', verifyResult);

    if (verifyResult.length === 0) {
      console.error('[Database] ✗ Insert verification failed - row not found!');
      throw new Error('Insert verification failed');
    }

  // Log for sync
    await sql`
      INSERT INTO sync_log (table_name, record_id, action)
      VALUES ('locations', ${newId}, 'INSERT')
    `;

  // Broadcast to other tabs
  broadcastChange('locations', 'INSERT', newId);

    console.log('[Database] ✓ Location added:', newId);
  return { id: newId };

  } catch (error) {
    console.error('[Database] ✗ Failed to add location:', error);
    throw error;
}
}

/**
 * Get all locations
 */
export async function getLocations(options = {}) {
  const { category = null, limit = 1000 } = options;

  try {
    // First check if table exists
    const tableCheck = await sql`SELECT name FROM sqlite_master WHERE type='table' AND name='locations'`;
    console.log('[Database] getLocations - table exists:', tableCheck.length > 0);

    if (tableCheck.length === 0) {
      console.warn('[Database] locations table does not exist yet');
      return [];
    }

    let results;
  if (category) {
      results = await sql`
      SELECT * FROM locations
      WHERE category = ${category}
      ORDER BY created_at DESC
      LIMIT ${limit}
    `;
    } else {
      results = await sql`
    SELECT * FROM locations
    ORDER BY created_at DESC
    LIMIT ${limit}
  `;
}

    console.log('[Database] getLocations returned', results.length, 'rows');
    return results;

  } catch (error) {
    console.error('[Database] getLocations error:', error);
    return [];
  }
}

export async function getLocation(id) {
  try {
  const results = await sql`SELECT * FROM locations WHERE id = ${id}`;
  return results[0] || null;
  } catch (error) {
    console.error('[Database] getLocation error:', error);
    return null;
}
}

/**
 * Update a location
 */
export async function updateLocation(id, updates) {
  const { name, longitude, latitude, description, category } = updates;

  try {
    const location = await getLocation(id);
    if (!location) {
      throw new Error(`Location ${id} not found`);
    }

  await sql`
    UPDATE locations
    SET
        name = ${name ?? location.name},
        longitude = ${longitude ?? location.longitude},
        latitude = ${latitude ?? location.latitude},
        description = ${description ?? location.description},
        category = ${category ?? location.category},
      updated_at = CURRENT_TIMESTAMP,
      synced = 0
    WHERE id = ${id}
  `;

  // Log for sync
  await sql`
    INSERT INTO sync_log (table_name, record_id, action)
    VALUES ('locations', ${id}, 'UPDATE')
  `;

  // Broadcast to other tabs
  broadcastChange('locations', 'UPDATE', id);
    console.log('[Database] ✓ Location updated:', id);

  } catch (error) {
    console.error('[Database] ✗ updateLocation error:', error);
    throw error;
}
}

/**
 * Delete a location
 */
export async function deleteLocation(id) {
  try {
  await sql`
    INSERT INTO sync_log (table_name, record_id, action)
    VALUES ('locations', ${id}, 'DELETE')
  `;

  await sql`DELETE FROM locations WHERE id = ${id}`;

  // Broadcast to other tabs
  broadcastChange('locations', 'DELETE', id);
    console.log('[Database] ✓ Location deleted:', id);

  } catch (error) {
    console.error('[Database] ✗ deleteLocation error:', error);
    throw error;
}
}

/**
 * Get location count
 */
export async function getLocationCount() {
  try {
  const result = await sql`SELECT COUNT(*) as count FROM locations`;
  return result[0]?.count ?? 0;
  } catch (error) {
    console.error('[Database] getLocationCount error:', error);
    return 0;
}
}

// ============================================================================
// Sync Operations
// ============================================================================

/**
 * Get unsynced changes
 */
export async function getUnsyncedChanges() {
  return sql`SELECT * FROM sync_log WHERE synced = 0 ORDER BY timestamp ASC`;
}

/**
 * Mark changes as synced
 */
export async function markSynced(syncLogIds) {
  if (!syncLogIds.length) return;
  for (const id of syncLogIds) {
    await sql`UPDATE sync_log SET synced = 1 WHERE id = ${id}`;
}
}

/**
 * Get locations that need syncing
 */
export async function getUnsyncedLocations() {
  return sql`SELECT * FROM locations WHERE synced = 0`;
}

/**
 * Mark locations as synced
 */
export async function markLocationsSynced(ids) {
  if (!ids.length) return;
  for (const id of ids) {
    await sql`UPDATE locations SET synced = 1 WHERE id = ${id}`;
}
}

// ============================================================================
// Remote Data Cache
// ============================================================================

/**
 * Save remote API data to the local cache.
 * Uses INSERT OR REPLACE so the same key is always overwritten.
 *
 * @param {string} key - Unique identifier (e.g. 'district_boundary')
 * @param {Object|Array} data - JSON-serialisable data to cache
 */
export async function saveRemoteData(key, data) {
  try {
    const json = JSON.stringify(data);
    await sql`
      INSERT OR REPLACE INTO remote_data (key, data, fetched_at)
      VALUES (${key}, ${json}, CURRENT_TIMESTAMP)
    `;
    console.log('[Database] ✓ Remote data cached:', key);
  } catch (error) {
    console.error('[Database] ✗ Failed to cache remote data:', key, error);
    throw error;
  }
}

/**
 * Retrieve cached remote data by key.
 *
 * @param {string} key - Unique identifier (e.g. 'district_boundary')
 * @returns {Promise<Object|null>} Parsed data, or null if not cached
 */
export async function getRemoteData(key) {
  try {
    const rows = await sql`SELECT data, fetched_at FROM remote_data WHERE key = ${key}`;
    if (rows.length === 0) return null;
    const parsed = JSON.parse(rows[0].data);
    console.log('[Database] ✓ Remote data loaded from cache:', key, '(fetched', rows[0].fetched_at + ')');
    return parsed;
  } catch (error) {
    console.error('[Database] ✗ Failed to read cached remote data:', key, error);
    return null;
  }
}

// ============================================================================
// Collector Zones
// ============================================================================

/**
 * Save collector zones to the local table.
 * Replaces all existing rows.
 *
 * @param {Array} zones - Array of zone objects from the API
 */
export async function saveCollectorZones(zones) {
  try {
    await sql`DELETE FROM collector_zones`;
    for (const z of zones) {
      const props = JSON.stringify(z);
      await sql`
        INSERT INTO collector_zones (id, zone_name, geometry_wkt, properties, fetched_at)
        VALUES (${z.colzonenr || z.id}, ${z.colzonename || z.zone_name || ''}, ${z.polygon || z.boundary || ''}, ${props}, CURRENT_TIMESTAMP)
      `;
    }
    console.log('[Database] ✓ Saved', zones.length, 'collector zones');
  } catch (error) {
    console.error('[Database] ✗ Failed to save collector zones:', error);
    throw error;
  }
}

/**
 * Load all cached collector zones from the local table.
 * @returns {Promise<Array|null>} Array of zone objects, or null if empty
 */
export async function getLocalCollectorZones() {
  try {
    const rows = await sql`SELECT properties FROM collector_zones ORDER BY id`;
    if (rows.length === 0) return null;
    return rows.map(r => JSON.parse(r.properties));
  } catch (error) {
    console.error('[Database] ✗ Failed to read local collector zones:', error);
    return null;
  }
}

// ============================================================================
// Parcels
// ============================================================================

/**
 * Save parcels to the local table.
 * Replaces all existing rows.
 *
 * @param {Array} parcels - Array of parcel objects from the API
 */
export async function saveParcels(parcels) {
  try {
    await sql`DELETE FROM parcels`;
    let saved = 0;
    for (const p of parcels) {
      const id = p.id || p.parcelid || p.parcel_id || null;
      if (id == null) continue; // skip rows without a usable ID
      const props = JSON.stringify(p);
      // API field names: 'boundary' (WKT), 'polygon', 'geom', 'wkt'
      const wkt = p.boundary || p.polygon || p.geom || p.wkt || '';
      await sql`
        INSERT OR REPLACE INTO parcels (id, geometry_wkt, properties, fetched_at)
        VALUES (${id}, ${wkt}, ${props}, CURRENT_TIMESTAMP)
      `;
      saved++;
    }
    console.log('[Database] ✓ Saved', saved, 'parcels (from', parcels.length, 'rows,', parcels.length - saved, 'duplicates replaced)');
  } catch (error) {
    console.error('[Database] ✗ Failed to save parcels:', error);
    throw error;
  }
}

/**
 * Load all cached parcels from the local table.
 * @returns {Promise<Array|null>} Array of parcel objects, or null if empty
 */
export async function getLocalParcels() {
  try {
    const rows = await sql`SELECT properties FROM parcels ORDER BY id`;
    if (rows.length === 0) return null;
    return rows.map(r => JSON.parse(r.properties));
  } catch (error) {
    console.error('[Database] ✗ Failed to read local parcels:', error);
    return null;
  }
}

/**
 * Update a single parcel's properties in the local table.
 * Only the properties JSON blob is updated; geometry stays unchanged.
 *
 * @param {number|string} parcelId - The parcel id
 * @param {Object} updatedProps - Full updated properties object
 */
export async function updateParcel(parcelId, updatedProps) {
  try {
    const props = JSON.stringify(updatedProps);
    await sql`UPDATE parcels SET properties = ${props} WHERE id = ${parcelId}`;
    console.log('[Database] ✓ Parcel updated:', parcelId);
    broadcastChange('parcels', 'UPDATE', parcelId);
  } catch (error) {
    console.error('[Database] ✗ Failed to update parcel:', parcelId, error);
    throw error;
  }
}

/**
 * Insert a newly drawn parcel into the local table.
 * The parcel is tagged with status='new' to indicate it needs verification.
 *
 * @param {string} geometryWkt - WKT geometry string (EPSG:4326)
 * @param {Object} properties - Attribute properties from the form
 * @returns {Promise<{id: number}>} The new row id
 */
export async function insertNewParcel(geometryWkt, properties) {
  try {
    const props = JSON.stringify(properties);
    await sql`
      INSERT INTO parcels (id, geometry_wkt, properties, status, fetched_at)
      VALUES (NULL, ${geometryWkt}, ${props}, 'new', CURRENT_TIMESTAMP)
    `;
    const idResult = await sql`SELECT last_insert_rowid() as id`;
    const newId = idResult[0]?.id;
    console.log('[Database] ✓ New parcel inserted:', newId, '(status: new)');
    broadcastChange('parcels', 'INSERT', newId);
    return { id: newId };
  } catch (error) {
    console.error('[Database] ✗ Failed to insert new parcel:', error);
    throw error;
  }
}

// ============================================================================
// Building Footprints
// ============================================================================

/**
 * Save building footprints to the local table.
 * Replaces all existing rows.
 *
 * @param {Array} footprints - Array of footprint objects from the API
 */
export async function saveBuildingFootprints(footprints) {
  try {
    // Log first entry's keys and value types to help debug field names
    if (footprints.length > 0) {
      const first = footprints[0];
      const types = {};
      for (const [k, v] of Object.entries(first)) {
        types[k] = v === null ? 'null' : typeof v;
      }
      console.log('[Database] First footprint field types:', types);
    }

    await sql`DELETE FROM building_footprints`;
    for (const f of footprints) {
      const props = JSON.stringify(f);

      // Geometry may arrive as a string (WKT) or an object (GeoJSON) —
      // coerce to a string so SQLocal can bind it.
      let rawWkt = f.polygon || f.boundary || f.geom || f.wkt || f.footprint || '';
      const wkt = typeof rawWkt === 'object' ? JSON.stringify(rawWkt) : String(rawWkt);

      // ID must be a primitive (number or null)
      let rawId = f.id || f.footprint_id || f.building_id || null;
      const id = (rawId !== null && typeof rawId === 'object') ? null : rawId;

      await sql`
        INSERT INTO building_footprints (id, geometry_wkt, properties, fetched_at)
        VALUES (${id}, ${wkt}, ${props}, CURRENT_TIMESTAMP)
      `;
    }
    console.log('[Database] ✓ Saved', footprints.length, 'building footprints');
  } catch (error) {
    console.error('[Database] ✗ Failed to save building footprints:', error);
    throw error;
  }
}

/**
 * Load all cached building footprints from the local table.
 * @returns {Promise<Array|null>} Array of footprint objects, or null if empty
 */
export async function getLocalBuildingFootprints() {
  try {
    const rows = await sql`SELECT properties FROM building_footprints ORDER BY id`;
    if (rows.length === 0) return null;
    return rows.map(r => JSON.parse(r.properties));
  } catch (error) {
    console.error('[Database] ✗ Failed to read local building footprints:', error);
    return null;
  }
}

/**
 * Save OSM roads to the local SQLite table.
 * Replaces all existing rows.
 *
 * @param {Array} roads - Array of road objects from the API
 */
export async function saveOSMRoads(roads) {
  try {
    if (roads.length > 0) {
      const first = roads[0];
      const types = {};
      for (const [k, v] of Object.entries(first)) {
        types[k] = v === null ? 'null' : typeof v;
      }
      console.log('[Database] First road field types:', types);
    }

    await sql`DELETE FROM osm_roads`;
    for (const r of roads) {
      const props = JSON.stringify(r);

      // Geometry — may arrive as WKT string or GeoJSON object
      let rawWkt = r.geom || r.geometry || r.wkt || r.road || r.line || '';
      const wkt = typeof rawWkt === 'object' ? JSON.stringify(rawWkt) : String(rawWkt);

      // osm_id must be a primitive — fall back to null if missing or malformed
      let rawId = r.osm_id ?? r.osmid ?? r.id ?? null;
      const osmId = (rawId !== null && typeof rawId === 'object') ? null : rawId;

      await sql`
        INSERT OR REPLACE INTO osm_roads (osm_id, geometry_wkt, properties, fetched_at)
        VALUES (${osmId}, ${wkt}, ${props}, CURRENT_TIMESTAMP)
      `;
    }
    console.log('[Database] ✓ Saved', roads.length, 'OSM roads');
  } catch (error) {
    console.error('[Database] ✗ Failed to save OSM roads:', error);
    throw error;
  }
}

/**
 * Load all cached OSM roads from the local table.
 * @returns {Promise<Array|null>} Array of road objects, or null if empty
 */
export async function getLocalOSMRoads() {
  try {
    const rows = await sql`SELECT properties FROM osm_roads ORDER BY osm_id`;
    if (rows.length === 0) return null;
    return rows.map(r => JSON.parse(r.properties));
  } catch (error) {
    console.error('[Database] ✗ Failed to read local OSM roads:', error);
    return null;
  }
}

// ============================================================================
// Export / Import
// ============================================================================

/**
 * Export database for backup
 */
export async function exportDatabase() {
  return db.getDatabaseFile();
}

/**
 * Import database from backup
 */
export async function importDatabase(data) {
  await db.overwriteDatabaseFile(data);
  broadcastChange('*', 'IMPORT', null);
}

/**
 * Download database as file
 */
export async function downloadDatabase(filename = 'lupmis-backup.sqlite3') {
  const data = await exportDatabase();
  const blob = new Blob([data], { type: 'application/x-sqlite3' });
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();

  URL.revokeObjectURL(url);
}

// Export to GeoJSON
export async function exportToGeoJSON() {
  const locations = await getLocations();

  return {
    type: 'FeatureCollection',
    features: locations.map((loc) => ({
      type: 'Feature',
      properties: {
        id: loc.id,
        name: loc.name,
        category: loc.category,
        notes: loc.notes,
        created_at: loc.created_at,
      },
      geometry: {
        type: 'Point',
        coordinates: [loc.lon, loc.lat],
      },
    })),
  };
}

// ============================================================================
// Utility & Debug
// ============================================================================

/**
 * Get database status
 */
export async function getDatabaseStatus() {
  try {
  const tables = await sql`
    SELECT name FROM sqlite_master
    WHERE type='table' AND name NOT LIKE 'sqlite_%'
    ORDER BY name
  `;

  const locationCount = await getLocationCount();

  return {
    ready: isReady,
    databasePath: DATABASE_PATH,
    tables: tables.map(t => t.name),
    locationCount
  };
  } catch (error) {
    return {
      ready: false,
      error: error.message
    };
}
}

// ============================================================================
// Cached-Layer Management
// ============================================================================

/**
 * Tables that hold data fetched from the server.
 *
 * Clearing these is safe — the corresponding loaders (loadParcels,
 * loadBuildingFootprints, loadOSMRoads, loadCollectorZones, …) will re-fetch
 * the data from the API on the next app start.
 *
 * NOT included: user-created tables (`locations`, `pending_changes`) — those
 * hold local work that must not be auto-deleted.
 */
export const CACHED_LAYER_TABLES = Object.freeze([
  'parcels',
  'building_footprints',
  'osm_roads',
  'collector_zones',
  'remote_data',
]);

/**
 * Check whether a table name is in the cleared-layer allow-list.
 * @param {string} tableName
 * @returns {boolean}
 */
export function isCachedLayerTable(tableName) {
  return CACHED_LAYER_TABLES.includes(tableName);
}

/**
 * Delete all rows from a single cached-layer table.
 * Rejects unknown table names so this can't be abused to drop user data.
 *
 * @param {string} tableName - One of CACHED_LAYER_TABLES
 * @returns {Promise<number>} Number of rows that were in the table before deletion
 */
export async function clearTable(tableName) {
  if (!isCachedLayerTable(tableName)) {
    throw new Error(`Refusing to clear "${tableName}" — not a known cached-layer table`);
  }

  const before = await sql(`SELECT COUNT(*) AS n FROM "${tableName}"`);
  const count = before[0]?.n ?? 0;

  await sql(`DELETE FROM "${tableName}"`);
  console.log(`[Database] ✓ Cleared "${tableName}" (${count} rows)`);
  broadcastChange(tableName, 'CLEAR', null);
  return count;
}

/**
 * Clear every cached-layer table (whatever exists in this database).
 * Tables that don't exist yet are skipped silently.
 *
 * @returns {Promise<{ table: string, count: number }[]>} per-table report
 */
export async function clearAllCachedLayers() {
  const existing = await sql`
    SELECT name FROM sqlite_master
    WHERE type='table' AND name IN (
      'parcels', 'building_footprints', 'osm_roads', 'collector_zones', 'remote_data'
    )
  `;
  const existingNames = new Set(existing.map((r) => r.name));

  const results = [];
  for (const tableName of CACHED_LAYER_TABLES) {
    if (!existingNames.has(tableName)) continue;
    try {
      const count = await clearTable(tableName);
      results.push({ table: tableName, count });
    } catch (err) {
      console.error(`[Database] Failed to clear ${tableName}:`, err);
      results.push({ table: tableName, count: 0, error: err.message });
    }
  }

  const total = results.reduce((s, r) => s + r.count, 0);
  console.log(`[Database] ✓ Cleared all cached layers: ${total} rows across ${results.length} tables`);
  return results;
}

/**
 * Get a list of all tables with their row counts.
 * @returns {Promise<Array<{name: string, count: number}>>}
 */
export async function getTableStats() {
  const tables = await sql`
    SELECT name FROM sqlite_master
    WHERE type='table' AND name NOT LIKE 'sqlite_%'
    ORDER BY name
  `;

  if (tables.length === 0) return [];

  // Build a UNION ALL query — table names come from sqlite_master so safe to embed.
  // sql() accepts a plain string as first argument (not only tagged template).
  const query = tables
    .map(t => `SELECT '${t.name}' AS name, COUNT(*) AS count FROM "${t.name}"`)
    .join(' UNION ALL ');

  return sql(query);
}

/**
 * Get all rows from a given table (max 200 rows for safety).
 * Table name is validated against sqlite_master before querying.
 * @param {string} tableName - Name of the table to read
 * @param {number} [limit=200] - Max rows to return
 * @returns {Promise<{columns: string[], rows: Object[]}>}
 */
export async function getTableContent(tableName, limit = 200) {
  // Validate that the table actually exists (prevent injection)
  const valid = await sql`
    SELECT name FROM sqlite_master
    WHERE type='table' AND name = ${tableName}
  `;
  if (valid.length === 0) {
    throw new Error(`Table "${tableName}" does not exist`);
  }

  const rows = await sql(`SELECT * FROM "${tableName}" LIMIT ${limit}`);

  // Extract column names from the first row (or return empty)
  const columns = rows.length > 0 ? Object.keys(rows[0]) : [];

  return { columns, rows };
}

// Debug function - call from console to test
export async function testDatabase() {
  console.log('=== DATABASE TEST ===');

  try {
    // 1. Check connection
    const version = await sql`SELECT sqlite_version() as v`;
    console.log('1. SQLite version:', version[0].v);

    // 2. Check tables
    const tables = await sql`SELECT name FROM sqlite_master WHERE type='table'`;
    console.log('2. Tables:', tables.map(t => t.name));

    // 3. Try to insert a test row
    console.log('3. Inserting test row...');
    await sql`INSERT INTO locations (name, longitude, latitude, category) VALUES ('TEST', -1.0, 7.0, 'test')`;

    // 4. Read it back
    const rows = await sql`SELECT * FROM locations WHERE name = 'TEST'`;
    console.log('4. Test row:', rows);

    // 5. Count all rows
    const count = await sql`SELECT COUNT(*) as c FROM locations`;
    console.log('5. Total rows:', count[0].c);

    // 6. Delete test row
    await sql`DELETE FROM locations WHERE name = 'TEST'`;
    console.log('6. Test row deleted');

    console.log('=== TEST PASSED ===');
    return true;
  } catch (error) {
    console.error('=== TEST FAILED ===', error);
    return false;
}
}

// Expose to window for debugging
if (typeof window !== 'undefined') {
  window.testDatabase = testDatabase;
  window.dbStatus = getDatabaseStatus;
}

export async function closeDatabase() {
  channel.close();
  if (db.destroy) {
    await db.destroy();
}
}

export default {
  sql,
  dbReady,
  initSchema,
  addLocation,
  getLocations,
  getLocation,
  updateLocation,
  deleteLocation,
  getLocationCount,
  getUnsyncedChanges,
  getUnsyncedLocations,
  markSynced,
  markLocationsSynced,
  saveRemoteData,
  getRemoteData,
  saveCollectorZones,
  getLocalCollectorZones,
  saveParcels,
  getLocalParcels,
  updateParcel,
  insertNewParcel,
  saveBuildingFootprints,
  getLocalBuildingFootprints,
  saveOSMRoads,
  getLocalOSMRoads,
  CACHED_LAYER_TABLES,
  isCachedLayerTable,
  clearTable,
  clearAllCachedLayers,
  exportDatabase,
  exportToGeoJSON,
  importDatabase,
  downloadDatabase,
  getDatabaseStatus,
  getTableStats,
  getTableContent,
  testDatabase,
  onDatabaseChange,
  closeDatabase
};
