import type { AVLSearchParams, GTFSRTSearchParams, SIRIVMData, GTFSRTData, BoundingBox } from '../types/index.js';
import { HttpClient } from './http-client.js';

/**
 * Client for interacting with the BODS Automatic Vehicle Location (AVL) API
 * 
 * The AVL API provides real-time bus location data in two formats:
 * - SIRI-VM (XML format)
 * - GTFS-RT (Protocol Buffers format)
 * 
 * Data is updated every 10 seconds.
 */
export class AVLClient {
  constructor(private httpClient: HttpClient) {}

  /**
   * Get real-time vehicle locations in SIRI-VM format (XML)
   * 
   * @param params - Search parameters to filter vehicle data
   * @returns Promise resolving to SIRI-VM XML data
   * 
   * @example
   * ```typescript
   * // Get all vehicles from specific operators
   * const vehicles = await client.avl.getSIRIVM({
   *   operatorRef: ['SCGH', 'SCLI']
   * });
   * 
   * // Get vehicles in a specific area (Liverpool)
   * const areaVehicles = await client.avl.getSIRIVM({
   *   boundingBox: [-2.930, 53.374, -3.085, 53.453]
   * });
   * 
   * // Get vehicles on a specific line
   * const lineVehicles = await client.avl.getSIRIVM({
   *   lineRef: '85A'
   * });
   * ```
   */
  async getSIRIVM(params: AVLSearchParams = {}): Promise<SIRIVMData> {
    const response = await this.httpClient.get<string>('/api/v1/datafeed', params);
    return { xmlData: response.data };
  }

  /**
   * Get a specific SIRI-VM datafeed by ID
   * 
   * @param datafeedId - The unique datafeed identifier
   * @returns Promise resolving to SIRI-VM XML data
   * 
   * @example
   * ```typescript
   * const datafeed = await client.avl.getSIRIVMById(123);
   * ```
   */
  async getSIRIVMById(datafeedId: number): Promise<SIRIVMData> {
    const response = await this.httpClient.get<string>(`/api/v1/datafeed/${datafeedId}/`);
    return { xmlData: response.data };
  }

  /**
   * Get real-time vehicle locations in GTFS-RT format (Protocol Buffers)
   * 
   * @param params - Search parameters to filter vehicle data
   * @returns Promise resolving to GTFS-RT protobuf data
   * 
   * @example
   * ```typescript
   * // Get all vehicles in GTFS-RT format
   * const vehicles = await client.avl.getGTFSRT({
   *   boundingBox: [-2.930, 53.374, -3.085, 53.453]
   * });
   * 
   * // Get vehicles for a specific route
   * const routeVehicles = await client.avl.getGTFSRT({
   *   routeId: 'route_123'
   * });
   * ```
   */
  async getGTFSRT(params: GTFSRTSearchParams = {}): Promise<GTFSRTData> {
    const response = await this.httpClient.get<ArrayBuffer>('/api/v1/gtfsrtdatafeed/', params);
    return { protobufData: response.data };
  }

  /**
   * Get vehicles by operator in SIRI-VM format
   * 
   * @param operatorRef - Operator reference(s) (often National Operator Code)
   * @param additionalParams - Additional search parameters
   * @returns Promise resolving to SIRI-VM XML data
   * 
   * @example
   * ```typescript
   * const stagecoachVehicles = await client.avl.getByOperator(['SCMN', 'SCGH']);
   * ```
   */
  async getByOperator(
    operatorRef: string | string[],
    additionalParams: Omit<AVLSearchParams, 'operatorRef'> = {}
  ): Promise<SIRIVMData> {
    return this.getSIRIVM({
      ...additionalParams,
      operatorRef: Array.isArray(operatorRef) ? operatorRef : [operatorRef]
    });
  }

  /**
   * Get vehicles by line reference in SIRI-VM format
   * 
   * @param lineRef - Line reference (route number/identifier)
   * @param additionalParams - Additional search parameters
   * @returns Promise resolving to SIRI-VM XML data
   * 
   * @example
   * ```typescript
   * const line85AVehicles = await client.avl.getByLine('85A');
   * ```
   */
  async getByLine(
    lineRef: string,
    additionalParams: Omit<AVLSearchParams, 'lineRef'> = {}
  ): Promise<SIRIVMData> {
    return this.getSIRIVM({
      ...additionalParams,
      lineRef
    });
  }

  /**
   * Get vehicles by vehicle reference in SIRI-VM format
   * 
   * @param vehicleRef - Vehicle reference/identifier
   * @param additionalParams - Additional search parameters
   * @returns Promise resolving to SIRI-VM XML data
   * 
   * @example
   * ```typescript
   * const specificVehicle = await client.avl.getByVehicle('BUSC-001');
   * ```
   */
  async getByVehicle(
    vehicleRef: string,
    additionalParams: Omit<AVLSearchParams, 'vehicleRef'> = {}
  ): Promise<SIRIVMData> {
    return this.getSIRIVM({
      ...additionalParams,
      vehicleRef
    });
  }

  /**
   * Get vehicles in a specific geographic area
   * 
   * @param boundingBox - Geographic bounding box [minLng, minLat, maxLng, maxLat]
   * @param format - Data format ('siri-vm' or 'gtfs-rt')
   * @param additionalParams - Additional search parameters
   * @returns Promise resolving to location data in specified format
   * 
   * @example
   * ```typescript
   * // Liverpool area in SIRI-VM format
   * const liverpoolVehicles = await client.avl.getByArea(
   *   [-2.930, 53.374, -3.085, 53.453],
   *   'siri-vm'
   * );
   * 
   * // Manchester area in GTFS-RT format
   * const manchesterVehicles = await client.avl.getByArea(
   *   [-2.3, 53.4, -2.2, 53.5],
   *   'gtfs-rt'
   * );
   * ```
   */
  async getByArea(
    boundingBox: BoundingBox,
    format: 'siri-vm' | 'gtfs-rt' = 'siri-vm',
    additionalParams: Record<string, any> = {}
  ): Promise<SIRIVMData | GTFSRTData> {
    if (format === 'gtfs-rt') {
      return this.getGTFSRT({
        ...additionalParams,
        boundingBox
      });
    }
    
    return this.getSIRIVM({
      ...additionalParams,
      boundingBox
    });
  }

  /**
   * Helper method to create bounding box from center point and radius
   * 
   * @param centerLat - Center latitude
   * @param centerLng - Center longitude  
   * @param radiusKm - Radius in kilometers
   * @returns Bounding box coordinates
   * 
   * @example
   * ```typescript
   * const bbox = client.avl.createBoundingBox(53.4808, -2.2426, 5); // Manchester, 5km radius
   * const nearbyVehicles = await client.avl.getByArea(bbox);
   * ```
   */
  createBoundingBox(centerLat: number, centerLng: number, radiusKm: number): BoundingBox {
    // Approximate degrees per kilometer
    const latDegPerKm = 1 / 111;
    const lngDegPerKm = 1 / (111 * Math.cos(centerLat * Math.PI / 180));
    
    const latOffset = radiusKm * latDegPerKm;
    const lngOffset = radiusKm * lngDegPerKm;
    
    return [
      centerLng - lngOffset, // minLng
      centerLat - latOffset, // minLat
      centerLng + lngOffset, // maxLng
      centerLat + latOffset  // maxLat
    ];
  }
}
