/**
 * Remote Database Module
 *
 * Handles all API communication with the PostgreSQL backend server.
 * Provides GET and POST methods for fetching and pushing data.
 *
 * Usage:
 *   import { remoteGet, remotePost, getDistrictBoundary } from './remotedb.js';
 *
 *   const boundary = await getDistrictBoundary();
 */

// ============================================================================
// Configuration
// ============================================================================

const API_BASE = 'https://api.lupmis4luspa.org/api/spatial_planning';

const API_CREDENTIALS = {
  district_id: '1',
  api_token: '1c46538c712e9b5b'
};

// ============================================================================
// Server Reachability
// ============================================================================

/** Default timeout for API requests (ms) */
const REQUEST_TIMEOUT = 30_000;

/** Timeout for the fast reachability probe (ms) */
const PING_TIMEOUT = 5_000;

/** Cached result of the last reachability check */
let _serverReachable = null;

/**
 * Quick probe to determine if the API server is responding.
 * Sends a small POST to a lightweight endpoint with a short timeout.
 * The result is cached so subsequent calls within the same page load
 * return immediately.
 *
 * @param {boolean} [force=false] - Re-check even if a cached result exists
 * @returns {Promise<boolean>} true if the server responded in time
 */
export async function checkServerReachable(force = false) {
  if (_serverReachable !== null && !force) return _serverReachable;

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), PING_TIMEOUT);

  try {
    const response = await fetch(`${API_BASE}/get_layers.php`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
      body: JSON.stringify(API_CREDENTIALS),
      signal: controller.signal,
    });
    _serverReachable = response.ok;
  } catch {
    _serverReachable = false;
  } finally {
    clearTimeout(timer);
  }

  console.log('[RemoteDB] Server reachable:', _serverReachable);
  return _serverReachable;
}

/**
 * Returns the cached server-reachability flag (synchronous).
 * Returns null if checkServerReachable() has not been called yet.
 * @returns {boolean|null}
 */
export function isServerReachable() {
  return _serverReachable;
}

// ============================================================================
// Core Request Helpers
// ============================================================================

/**
 * Create an AbortController that auto-aborts after `ms` milliseconds.
 * If the caller already supplied a signal in `options`, it is combined
 * so that either the caller's abort or the timeout will cancel the request.
 */
function withTimeout(options, ms = REQUEST_TIMEOUT) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), ms);

  // If the caller provided their own signal, chain it
  if (options.signal) {
    options.signal.addEventListener('abort', () => controller.abort());
  }

  return {
    signal: controller.signal,
    clear: () => clearTimeout(timer),
  };
}

/**
 * Perform a GET request to the remote API.
 * Credentials are sent as URL query parameters.
 * Automatically times out after REQUEST_TIMEOUT ms.
 *
 * @param {string} endpoint - API endpoint filename (e.g. 'get_district_boundary.php')
 * @param {Object} [params={}] - Additional query parameters
 * @param {Object} [options={}] - Extra fetch options
 * @returns {Promise<Object>} Parsed JSON response
 */
export async function remoteGet(endpoint, params = {}, options = {}) {
  const url = new URL(`${API_BASE}/${endpoint}`);

  // Attach credentials and any extra params as query string
  const allParams = { ...API_CREDENTIALS, ...params };
  for (const [key, value] of Object.entries(allParams)) {
    url.searchParams.set(key, value);
  }

  console.log('[RemoteDB] GET', url.toString());

  const timeout = withTimeout(options);
  try {
    const response = await fetch(url.toString(), {
      method: 'GET',
      headers: {
        'Accept': 'application/json'
      },
      ...options,
      signal: timeout.signal,
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const data = await response.json();
    console.log('[RemoteDB] GET response:', endpoint, '→', typeof data === 'object' ? `${Array.isArray(data) ? data.length + ' items' : 'object'}` : data);
    return data;

  } catch (error) {
    if (error.name === 'AbortError') {
      console.error('[RemoteDB] GET timed out:', endpoint);
      throw new Error(`Request timed out: ${endpoint}`);
    }
    console.error('[RemoteDB] GET failed:', endpoint, error);
    throw error;
  } finally {
    timeout.clear();
  }
}

/**
 * Perform a POST request to the remote API.
 * Credentials are included in the JSON body.
 * Automatically times out after REQUEST_TIMEOUT ms.
 *
 * @param {string} endpoint - API endpoint filename (e.g. 'some_endpoint.php')
 * @param {Object} [body={}] - Request payload (credentials are merged in)
 * @param {Object} [options={}] - Extra fetch options
 * @returns {Promise<Object>} Parsed JSON response
 */
export async function remotePost(endpoint, body = {}, options = {}) {
  const url = `${API_BASE}/${endpoint}`;

  const payload = { ...API_CREDENTIALS, ...body };

  console.log('[RemoteDB] POST', url);

  const timeout = withTimeout(options);
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      body: JSON.stringify(payload),
      ...options,
      signal: timeout.signal,
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    }

    const data = await response.json();
    console.log('[RemoteDB] POST response:', endpoint, '→', typeof data === 'object' ? `${Array.isArray(data) ? data.length + ' items' : 'object'}` : data);
    return data;

  } catch (error) {
    if (error.name === 'AbortError') {
      console.error('[RemoteDB] POST timed out:', endpoint);
      throw new Error(`Request timed out: ${endpoint}`);
    }
    console.error('[RemoteDB] POST failed:', endpoint, error);
    throw error;
  } finally {
    timeout.clear();
  }
}

// ============================================================================
// Spatial Planning Endpoints
// ============================================================================

/**
 * Fetch district boundary geometry from the server.
 *
 * @returns {Promise<Object>} District boundary GeoJSON or API response
 */
export async function getDistrictBoundary() {
  return remotePost('get_district_boundary.php');
}

/**
 * Fetch the list of available map layer categories from the server.
 *
 * Response format:
 * { success: true, data: [{ id, name, description, createdt, editdt }, ...] }
 *
 * @returns {Promise<Object>} Layer categories list
 */
export async function getLayers() {
  return remotePost('get_layers.php');
}

/**
 * Fetch all collector zones for the current district.
 *
 * Expected response:
 * { success: true, data: [{ id, zone_name, boundary: "MULTIPOLYGON(...)", ... }, ...] }
 *
 * @returns {Promise<Object>} Collector zones list
 */
export async function getCollectorZones() {
  return remotePost('get_all_collector_zone_per_district.php');
}

/**
 * Fetch all parcels for the current district.
 *
 * Expected response:
 * { success: true, data: [{ id, ..., polygon: "POLYGON(...)" | "MULTIPOLYGON(...)", ... }, ...] }
 *
 * @returns {Promise<Object>} Parcels list
 */
export async function getDistrictParcels() {
  return remotePost('get_parcels_per_district.php');
}

/**
 * Fetch all building footprints for the current district.
 *
 * Expected response:
 * { success: true, data: [{ id, ..., polygon: "POLYGON(...)" | "MULTIPOLYGON(...)", ... }, ...] }
 *
 * @returns {Promise<Object>} Building footprints list
 */
export async function getBuildingFootprints() {
  return remotePost('get_all_footprint_per_district.php');
}

/**
 * Fetch the Contours hillshade elevation layer from the server.
 *
 * Source: table `contours_hillshade` in the local PostgreSQL `public` schema
 * (imported from OpenTopography's viz.hh_hillshade).
 *
 * Expected response:
 * { success: true, data: [{ id, elevation, geom: "LINESTRING(...)" | "MULTILINESTRING(...)" | "POLYGON(...)", ... }, ...] }
 *
 * @returns {Promise<Object>} Contours hillshade list
 */
export async function getContoursHillshade() {
  return remotePost('get_contours_hillshade.php');
}

/**
 * Fetch the OSM roads layer from the server.
 *
 * Source: table `pi_osm_roads` in the local PostgreSQL `public` schema
 * (imported from OpenStreetMap road network for the district).
 *
 * Expected response:
 * { success: true, data: [{ id, ..., geom: "LINESTRING(...)" | "MULTILINESTRING(...)", ... }, ...] }
 *
 * @returns {Promise<Object>} OSM roads list
 */
export async function getOSMRoads() {
  return remotePost('get_osm_roads.php');
}

// ============================================================================
// Exports
// ============================================================================

export default {
  checkServerReachable,
  isServerReachable,
  remoteGet,
  remotePost,
  getDistrictBoundary,
  getLayers,
  getDistrictParcels,
  getCollectorZones,
  getBuildingFootprints,
  getContoursHillshade,
  getOSMRoads,
};
