'use strict';

const DEFAULT_TTL_MS = 2 * 60 * 60 * 1000;
const MAX_TTL_MS = 24 * 60 * 60 * 1000;
const MIN_TTL_MS = 60 * 1000;

const SOURCE_LABELS = {
  browser_geolocation: 'browser geolocation',
  mobile_geolocation: 'mobile geolocation',
  manual: 'manual override',
  calendar_inferred: 'calendar inference',
  ip_inferred: 'IP inference',
  chat_context: 'chat context',
};

const ALLOWED_SOURCES = new Set(Object.keys(SOURCE_LABELS));
const ALLOWED_PRECISION = new Set(['coarse', 'exact']);
const ALLOWED_PERMISSION_STATES = new Set(['granted', 'prompt', 'denied', 'unknown']);

function _finiteNumber(value) {
  const n = Number(value);
  return Number.isFinite(n) ? n : null;
}

function _parseDate(value, fallback = null) {
  if (!value) return fallback;
  const d = new Date(value);
  return Number.isNaN(d.getTime()) ? fallback : d;
}

function _iso(date) {
  return date.toISOString();
}

function _cleanLabel(value) {
  if (!value) return '';
  return String(value).replace(/\s+/g, ' ').trim().slice(0, 160);
}

function _roundCoord(value, precision) {
  const places = precision === 'exact' ? 6 : 2;
  const factor = Math.pow(10, places);
  return Math.round(value * factor) / factor;
}

function _ttlMs(input) {
  const ttl = _finiteNumber(input);
  if (ttl == null) return DEFAULT_TTL_MS;
  return Math.max(MIN_TTL_MS, Math.min(MAX_TTL_MS, Math.round(ttl)));
}

function _formatAge(ms) {
  const seconds = Math.max(0, Math.round(ms / 1000));
  if (seconds < 90) return `${seconds}s`;
  const minutes = Math.round(seconds / 60);
  if (minutes < 90) return `${minutes}m`;
  const hours = Math.round(minutes / 60);
  return `${hours}h`;
}

function normalizeLocationInput(input = {}, opts = {}) {
  const source = ALLOWED_SOURCES.has(input.source) ? input.source : (opts.source || 'browser_geolocation');
  const precision = ALLOWED_PRECISION.has(input.precision) ? input.precision : 'coarse';
  const permissionState = ALLOWED_PERMISSION_STATES.has(input.permission_state)
    ? input.permission_state
    : (input.permissionState && ALLOWED_PERMISSION_STATES.has(input.permissionState) ? input.permissionState : 'unknown');

  const capturedAt = _parseDate(input.captured_at || input.capturedAt, new Date());
  const ttl = _ttlMs(input.ttl_ms || input.ttlMs);
  const expiresAt = _parseDate(input.expires_at || input.expiresAt, new Date(capturedAt.getTime() + ttl));

  const latRaw = _finiteNumber(input.lat ?? input.latitude);
  const lonRaw = _finiteNumber(input.lon ?? input.longitude ?? input.lng);
  const location = {
    source,
    precision,
    permission_state: permissionState,
    captured_at: _iso(capturedAt),
    expires_at: _iso(expiresAt),
  };

  if (latRaw != null || lonRaw != null) {
    if (latRaw == null || lonRaw == null || latRaw < -90 || latRaw > 90 || lonRaw < -180 || lonRaw > 180) {
      throw new Error('Invalid latitude/longitude');
    }
    location.lat = _roundCoord(latRaw, precision);
    location.lon = _roundCoord(lonRaw, precision);
  }

  const accuracy = _finiteNumber(input.accuracy_m ?? input.accuracyM ?? input.accuracy);
  if (accuracy != null && accuracy >= 0) {
    location.accuracy_m = Math.round(Math.min(accuracy, 1000000));
  }

  const label = _cleanLabel(input.label || input.place || input.city);
  if (label) location.label = label;

  if (location.lat == null && location.lon == null && !location.label) {
    throw new Error('Location requires coordinates or a label');
  }

  return location;
}

function isLocationFresh(location, now = new Date()) {
  if (!location) return false;
  const expires = _parseDate(location.expires_at || location.expiresAt);
  if (!expires) return false;
  return expires.getTime() > now.getTime();
}

function summarizeLocation(location, now = new Date()) {
  if (!location) return null;
  const captured = _parseDate(location.captured_at || location.capturedAt);
  const expires = _parseDate(location.expires_at || location.expiresAt);
  const summary = {
    ...location,
    fresh: isLocationFresh(location, now),
  };
  if (captured) summary.age_ms = Math.max(0, now.getTime() - captured.getTime());
  if (expires) summary.expires_in_ms = expires.getTime() - now.getTime();
  return summary;
}

function formatLocationContextBlock(location, now = new Date()) {
  const summary = summarizeLocation(location, now);
  if (!summary || !summary.fresh) return '';

  const lines = ['## Current Location'];
  lines.push('Use this fresh current-location signal for weather, nearby, and local-context questions. Treat it as temporary sensor context, not a permanent memory.');
  const label = _cleanLabel(summary.label);
  if (label) lines.push(`Approximate place: ${label}`);
  const lat = _finiteNumber(summary.lat);
  const lon = _finiteNumber(summary.lon);
  if (lat != null && lon != null) {
    lines.push(`Coordinates (${summary.precision || 'coarse'}): ${lat}, ${lon}`);
  }
  const accuracy = _finiteNumber(summary.accuracy_m);
  if (accuracy != null && accuracy >= 0) lines.push(`Reported accuracy: about ${Math.round(accuracy)}m`);
  const age = summary.age_ms == null ? 'unknown' : _formatAge(summary.age_ms);
  const ttl = summary.expires_in_ms == null ? 'unknown' : _formatAge(summary.expires_in_ms);
  lines.push(`Captured: ${age} ago; expires in: ${ttl}; source: ${SOURCE_LABELS[summary.source] || summary.source || 'unknown'}`);
  lines.push('If this conflicts with an explicit user statement in the current conversation, ask a concise clarification.');
  return lines.join('\n');
}

function shouldUseLocationForMessage(message, topics = []) {
  const text = String(message || '');
  if (topics.includes('weather') || topics.includes('location')) return true;
  return /\b(weather|wheather|wether|temperature|rain|snow|forecast|outside|where\s+am\s+i|what\s+city\s+am\s+i\s+in|which\s+city\s+am\s+i\s+in|current\s+location|my\s+location|near\s+me|nearby|around\s+me|local\s+(weather|time|place|places|restaurants?|coffee)|physically\s+(am\s+i|located))\b|\bhow'?s\s+(?:the\s+)?whether\b|\bwhether\s+(?:today|tomorrow|outside)\b|天气|氣温|我在哪|附近|天気|날씨/i.test(text);
}

function isCurrentLocationQuestion(message, topics = []) {
  const text = String(message || '');
  return (topics.includes('location') || shouldUseLocationForMessage(text, topics))
    && /\b(where\s+am\s+i|what\s+city\s+am\s+i\s+in|which\s+city\s+am\s+i\s+in|current\s+location|my\s+location|where\s+are\s+we|what\s+city\s+are\s+we\s+in|which\s+city\s+are\s+we\s+in|physically\s+(?:am\s+i|located))\b|我在哪/i.test(text);
}

module.exports = {
  DEFAULT_TTL_MS,
  MAX_TTL_MS,
  normalizeLocationInput,
  isLocationFresh,
  summarizeLocation,
  formatLocationContextBlock,
  isCurrentLocationQuestion,
  shouldUseLocationForMessage,
};
