import { BluetoothLeTrackerMapper } from './bluetooth_le_tracker.mapper.js';
import type {
  IBluetoothLeAdvertisement,
  IBluetoothLeTrackedDevice,
  IBluetoothLeTrackerCommand,
  IBluetoothLeTrackerCommandResult,
  IBluetoothLeTrackerConfig,
  IBluetoothLeTrackerEvent,
  IBluetoothLeTrackerSnapshot,
} from './bluetooth_le_tracker.types.js';

type TBluetoothLeTrackerEventHandler = (eventArg: IBluetoothLeTrackerEvent) => void;

export class BluetoothLeTrackerClient {
  private injectedSnapshot?: IBluetoothLeTrackerSnapshot;
  private readonly injectedDevices: IBluetoothLeTrackedDevice[] = [];
  private readonly injectedAdvertisements: IBluetoothLeAdvertisement[] = [];
  private readonly events: IBluetoothLeTrackerEvent[] = [];
  private readonly eventHandlers = new Set<TBluetoothLeTrackerEventHandler>();

  constructor(private readonly config: IBluetoothLeTrackerConfig) {}

  public async getSnapshot(): Promise<IBluetoothLeTrackerSnapshot> {
    return BluetoothLeTrackerMapper.toSnapshot(this.runtimeConfig(), undefined, this.events);
  }

  public onEvent(handlerArg: TBluetoothLeTrackerEventHandler): () => void {
    this.eventHandlers.add(handlerArg);
    return () => this.eventHandlers.delete(handlerArg);
  }

  public async sendCommand(commandArg: IBluetoothLeTrackerCommand): Promise<IBluetoothLeTrackerCommandResult> {
    if (commandArg.type !== 'scan') {
      return { success: false, error: `Unsupported Bluetooth LE Tracker command: ${commandArg.type}` };
    }
    const payload = await this.injectedPayload(commandArg);
    if (!payload) {
      const error = 'Bluetooth LE live scanning is not implemented in this dependency-free TypeScript port. Provide injected advertisements, devices, a snapshot, or scanProvider data to refresh tracker state.';
      this.emit({ type: 'unsupported_scan', data: { service: commandArg.service }, timestamp: Date.now() });
      return { success: false, error };
    }
    if (payload.snapshot) {
      this.injectedSnapshot = payload.snapshot;
    }
    if (payload.devices?.length) {
      this.injectedDevices.push(...payload.devices);
    }
    if (payload.advertisements?.length) {
      this.injectedAdvertisements.push(...payload.advertisements);
    }
    const snapshot = await this.getSnapshot();
    this.emit({ type: payload.snapshot ? 'snapshot_updated' : 'scan_applied', data: { service: commandArg.service, snapshot }, timestamp: Date.now() });
    return { success: true, data: { snapshot } };
  }

  public async destroy(): Promise<void> {
    this.eventHandlers.clear();
  }

  private runtimeConfig(): IBluetoothLeTrackerConfig {
    return {
      ...this.config,
      snapshot: this.injectedSnapshot || this.config.snapshot,
      devices: [...(this.config.devices || []), ...this.injectedDevices],
      advertisements: [...(this.config.advertisements || []), ...this.injectedAdvertisements],
    };
  }

  private async injectedPayload(commandArg: IBluetoothLeTrackerCommand): Promise<{
    snapshot?: IBluetoothLeTrackerSnapshot;
    devices?: IBluetoothLeTrackedDevice[];
    advertisements?: IBluetoothLeAdvertisement[];
  } | undefined> {
    if (commandArg.snapshot || commandArg.devices?.length || commandArg.advertisements?.length) {
      return {
        snapshot: commandArg.snapshot,
        devices: commandArg.devices,
        advertisements: commandArg.advertisements,
      };
    }
    if (this.config.scanProvider) {
      const result = await this.config.scanProvider();
      return this.providerResult(result);
    }
    if (this.hasInjectedConfigData()) {
      return {
        snapshot: this.config.snapshot,
        devices: [...(this.config.devices || []), ...(this.config.knownDevices || []), ...(this.config.trackedDevices || [])],
        advertisements: this.config.advertisements,
      };
    }
    return undefined;
  }

  private providerResult(valueArg: unknown): {
    snapshot?: IBluetoothLeTrackerSnapshot;
    devices?: IBluetoothLeTrackedDevice[];
    advertisements?: IBluetoothLeAdvertisement[];
  } | undefined {
    if (Array.isArray(valueArg)) {
      return { advertisements: valueArg as IBluetoothLeAdvertisement[] };
    }
    if (!this.isRecord(valueArg)) {
      return undefined;
    }
    if (Array.isArray(valueArg.devices) || Array.isArray(valueArg.advertisements) || this.isRecord(valueArg.snapshot)) {
      return {
        snapshot: this.isRecord(valueArg.snapshot) ? valueArg.snapshot as unknown as IBluetoothLeTrackerSnapshot : undefined,
        devices: Array.isArray(valueArg.devices) ? valueArg.devices as IBluetoothLeTrackedDevice[] : undefined,
        advertisements: Array.isArray(valueArg.advertisements) ? valueArg.advertisements as IBluetoothLeAdvertisement[] : undefined,
      };
    }
    return { snapshot: valueArg as unknown as IBluetoothLeTrackerSnapshot };
  }

  private hasInjectedConfigData(): boolean {
    return Boolean(this.config.snapshot || this.config.devices?.length || this.config.knownDevices?.length || this.config.trackedDevices?.length || this.config.advertisements?.length || this.config.manualEntries?.length);
  }

  private emit(eventArg: IBluetoothLeTrackerEvent): void {
    this.events.push(eventArg);
    for (const handler of this.eventHandlers) {
      handler(eventArg);
    }
  }

  private isRecord(valueArg: unknown): valueArg is Record<string, unknown> {
    return typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
  }
}
