"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentLocationCollector = void 0;
const child_process_1 = require("child_process");
const logger_1 = require("../logger");
const SUCCESS_CACHE_TTL_MS = 2 * 60 * 1000;
const FAILURE_RETRY_TTL_MS = 45 * 1000;
const LOCATION_ACQUISITION_TIMEOUT_MS = 30000;
const POWERSHELL_TIMEOUT_MS = 55000;
const buildWindowsGpsScript = () => `
$ErrorActionPreference = 'Stop'
function Emit-Json([hashtable]$payload) {
  [Console]::Out.WriteLine(($payload | ConvertTo-Json -Compress -Depth 6))
}
function To-NullableDouble($value) {
  try {
    if ($null -eq $value) { return $null }
    $number = [double]$value
    if ([double]::IsNaN($number) -or [double]::IsInfinity($number)) { return $null }
    return $number
  } catch {
    return $null
  }
}
function Ensure-LocationServices {
  foreach ($serviceName in @('lfsvc', 'SensorService', 'SensrSvc')) {
    try {
      Start-Service -Name $serviceName -ErrorAction SilentlyContinue
    } catch {}
  }
}
function Try-WinRtLocation {
  Add-Type -AssemblyName System.Runtime.WindowsRuntime | Out-Null
  $null = [Windows.Devices.Geolocation.Geolocator, Windows.Devices.Geolocation, ContentType = WindowsRuntime]
  $null = [Windows.Devices.Geolocation.PositionAccuracy, Windows.Devices.Geolocation, ContentType = WindowsRuntime]
  $geolocator = [Windows.Devices.Geolocation.Geolocator]::new()
  try { $geolocator.DesiredAccuracy = [Windows.Devices.Geolocation.PositionAccuracy]::High } catch {}
  try { $geolocator.DesiredAccuracyInMeters = 10 } catch {}
  try { $geolocator.MovementThreshold = 5 } catch {}
  try {
    $accessOp = [Windows.Devices.Geolocation.Geolocator]::RequestAccessAsync()
    $accessTask = [System.WindowsRuntimeSystemExtensions]::AsTask($accessOp)
    $accessTask.Wait(5000) | Out-Null
    if ($accessTask.IsCompleted -and -not $accessTask.IsFaulted -and -not $accessTask.IsCanceled) {
      $accessStatus = [string]$accessTask.Result
      if ($accessStatus -notin @('Allowed', 'Unspecified')) {
        throw "Location access status: $accessStatus"
      }
    }
  } catch {}
  try {
    $op = $geolocator.GetGeopositionAsync([TimeSpan]::FromMinutes(5), [TimeSpan]::FromMilliseconds(${LOCATION_ACQUISITION_TIMEOUT_MS}))
  } catch {
    $op = $geolocator.GetGeopositionAsync()
  }
  $task = [System.WindowsRuntimeSystemExtensions]::AsTask($op)
  if (-not $task.Wait(${POWERSHELL_TIMEOUT_MS})) { throw 'Location acquisition timed out' }
  if ($task.IsFaulted) { throw $task.Exception.GetBaseException().Message }
  if ($task.IsCanceled) { throw 'Location acquisition cancelled' }
  $position = $task.Result
  if ($null -eq $position) { throw 'No geoposition returned' }
  $coordinate = $position.Coordinate
  if ($null -eq $coordinate -or $null -eq $coordinate.Point) { throw 'No coordinate returned' }
  $point = $coordinate.Point.Position
  $source = 'windows-location'
  try {
    if ($null -ne $coordinate.SatelliteData) { $source = 'gps' }
  } catch {}
  return @{
    ok = $true
    latitude = [double]$point.Latitude
    longitude = [double]$point.Longitude
    accuracyMeters = To-NullableDouble $coordinate.Accuracy
    altitudeMeters = To-NullableDouble $point.Altitude
    altitudeAccuracyMeters = To-NullableDouble $coordinate.AltitudeAccuracy
    headingDegrees = To-NullableDouble $coordinate.Heading
    speedMetersPerSecond = To-NullableDouble $coordinate.Speed
    source = $source
    provider = 'winrt-geolocator'
    capturedAt = [DateTime]::UtcNow.ToString('o')
  }
}
function Try-GeoCoordinateWatcherLocation {
  Add-Type -AssemblyName System.Device | Out-Null
  $watcher = New-Object System.Device.Location.GeoCoordinateWatcher([System.Device.Location.GeoPositionAccuracy]::High)
  if (-not $watcher.TryStart($false, [TimeSpan]::FromMilliseconds(${LOCATION_ACQUISITION_TIMEOUT_MS}))) {
    throw 'GeoCoordinateWatcher timed out'
  }
  $coordinate = $watcher.Position.Location
  if ($null -eq $coordinate -or $coordinate.IsUnknown) {
    throw 'GeoCoordinateWatcher returned no coordinate'
  }
  return @{
    ok = $true
    latitude = To-NullableDouble $coordinate.Latitude
    longitude = To-NullableDouble $coordinate.Longitude
    accuracyMeters = To-NullableDouble $coordinate.HorizontalAccuracy
    altitudeMeters = To-NullableDouble $coordinate.Altitude
    altitudeAccuracyMeters = To-NullableDouble $coordinate.VerticalAccuracy
    headingDegrees = To-NullableDouble $coordinate.Course
    speedMetersPerSecond = To-NullableDouble $coordinate.Speed
    source = 'windows-location'
    provider = 'geo-coordinate-watcher'
    capturedAt = [DateTime]::UtcNow.ToString('o')
  }
}
try {
  Ensure-LocationServices
  $attemptErrors = New-Object System.Collections.Generic.List[string]
  foreach ($attempt in @(
    @{ Name = 'winrt-geolocator'; Script = { Try-WinRtLocation } },
    @{ Name = 'geo-coordinate-watcher'; Script = { Try-GeoCoordinateWatcherLocation } }
  )) {
    try {
      $payload = & $attempt.Script
      if ($null -ne $payload) {
        Emit-Json $payload
        return
      }
    } catch {
      $message = $_.Exception.Message
      if ([string]::IsNullOrWhiteSpace($message)) { $message = [string]$_ }
      $attemptErrors.Add(('{0}: {1}' -f $attempt.Name, $message))
    }
  }
  throw ($attemptErrors -join ' | ')
} catch {
  $message = $_.Exception.Message
  if ([string]::IsNullOrWhiteSpace($message)) { $message = [string]$_ }
  Emit-Json @{
    ok = $false
    error = $message
    capturedAt = [DateTime]::UtcNow.ToString('o')
  }
}
`.trim();
const toFiniteNumber = (value) => {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : undefined;
};
const normalizeText = (value) => {
    const text = String(value || '').trim();
    return text ? text : undefined;
};
const runEncodedPowerShell = async (script) => {
    const encoded = Buffer.from(script, 'utf16le').toString('base64');
    const candidates = ['powershell.exe', 'powershell'];
    let lastMissingBinary = null;
    for (const candidate of candidates) {
        try {
            const stdout = await new Promise((resolve, reject) => {
                (0, child_process_1.execFile)(candidate, ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', encoded], {
                    timeout: POWERSHELL_TIMEOUT_MS,
                    windowsHide: true,
                    maxBuffer: 1024 * 1024,
                }, (error, commandStdout, commandStderr) => {
                    if (error) {
                        const detail = String(commandStderr || commandStdout || error.message || '').trim();
                        const wrapped = new Error(detail ? `${error.message}: ${detail}` : error.message);
                        wrapped.code = error.code;
                        reject(wrapped);
                        return;
                    }
                    resolve(String(commandStdout || '').trim());
                });
            });
            return stdout;
        }
        catch (error) {
            if (error?.code === 'ENOENT') {
                lastMissingBinary = error instanceof Error ? error : new Error(String(error));
                continue;
            }
            throw error instanceof Error ? error : new Error(String(error));
        }
    }
    throw lastMissingBinary || new Error('PowerShell is not available');
};
class AgentLocationCollector {
    constructor() {
        this.currentStatus = {
            available: false,
            provider: 'windows-location',
        };
        this.lastAttemptAt = 0;
        this.lastSuccessAt = 0;
        this.refreshPromise = null;
        this.lastFailureMessage = '';
    }
    async collect() {
        if (process.platform !== 'win32')
            return undefined;
        const now = Date.now();
        if (this.cachedLocation && (now - this.lastSuccessAt) < SUCCESS_CACHE_TTL_MS) {
            return this.cachedLocation;
        }
        const retryWindowMs = this.cachedLocation ? SUCCESS_CACHE_TTL_MS : FAILURE_RETRY_TTL_MS;
        if (!this.refreshPromise && (now - this.lastAttemptAt) >= retryWindowMs) {
            this.lastAttemptAt = now;
            this.refreshPromise = this.refresh().finally(() => {
                this.refreshPromise = null;
            });
        }
        return this.cachedLocation;
    }
    getStatus() {
        return { ...this.currentStatus };
    }
    async refresh() {
        const attemptedAt = new Date().toISOString();
        this.currentStatus = {
            ...this.currentStatus,
            available: false,
            provider: this.currentStatus.provider || 'windows-location',
            lastAttemptAt: attemptedAt,
            lastSuccessAt: this.lastSuccessAt ? new Date(this.lastSuccessAt).toISOString() : this.currentStatus.lastSuccessAt,
        };
        try {
            const stdout = await runEncodedPowerShell(buildWindowsGpsScript());
            const jsonLine = stdout
                .split(/\r?\n/)
                .map((line) => line.trim())
                .filter(Boolean)
                .pop();
            if (!jsonLine) {
                this.logFailure('Windows geolocation returned no data');
                return;
            }
            const parsed = JSON.parse(jsonLine);
            if (!parsed?.ok) {
                this.logFailure(String(parsed?.error || 'Windows geolocation failed'));
                return;
            }
            const latitude = toFiniteNumber(parsed.latitude);
            const longitude = toFiniteNumber(parsed.longitude);
            if (latitude === undefined || longitude === undefined) {
                this.logFailure('Windows geolocation returned invalid coordinates');
                return;
            }
            if (Math.abs(latitude) < 0.000001 && Math.abs(longitude) < 0.000001) {
                this.logFailure('Windows geolocation returned 0,0 coordinates');
                return;
            }
            this.cachedLocation = {
                latitude,
                longitude,
                accuracyMeters: toFiniteNumber(parsed.accuracyMeters),
                altitudeMeters: toFiniteNumber(parsed.altitudeMeters),
                altitudeAccuracyMeters: toFiniteNumber(parsed.altitudeAccuracyMeters),
                headingDegrees: toFiniteNumber(parsed.headingDegrees),
                speedMetersPerSecond: toFiniteNumber(parsed.speedMetersPerSecond),
                source: normalizeText(parsed.source) || 'gps',
                provider: normalizeText(parsed.provider),
                capturedAt: normalizeText(parsed.capturedAt) || new Date().toISOString(),
            };
            this.lastSuccessAt = Date.now();
            this.lastFailureMessage = '';
            this.currentStatus = {
                available: true,
                provider: this.cachedLocation.provider || this.cachedLocation.source,
                lastAttemptAt: attemptedAt,
                lastSuccessAt: new Date(this.lastSuccessAt).toISOString(),
            };
            logger_1.logger.debug('Agent GPS location refreshed', {
                source: this.cachedLocation.source,
                accuracyMeters: this.cachedLocation.accuracyMeters,
            });
        }
        catch (error) {
            this.logFailure(error?.message || String(error));
        }
    }
    logFailure(message) {
        const normalized = String(message || '').trim();
        this.currentStatus = {
            available: false,
            provider: this.currentStatus.provider || 'windows-location',
            error: normalized || this.currentStatus.error,
            lastAttemptAt: this.currentStatus.lastAttemptAt || new Date().toISOString(),
            lastSuccessAt: this.lastSuccessAt ? new Date(this.lastSuccessAt).toISOString() : this.currentStatus.lastSuccessAt,
        };
        if (!normalized || normalized === this.lastFailureMessage)
            return;
        this.lastFailureMessage = normalized;
        logger_1.logger.debug('Agent GPS location unavailable', { error: normalized });
    }
}
exports.AgentLocationCollector = AgentLocationCollector;
//# sourceMappingURL=location.js.map