import type { IConfigFlow, IConfigFlowContext, IConfigFlowStep, IDiscoveryCandidate } from '../../core/types.js';
import { BluetoothLeTrackerMapper } from './bluetooth_le_tracker.mapper.js';
import type { IBluetoothLeAdvertisement, IBluetoothLeTrackedDevice, IBluetoothLeTrackerConfig, IBluetoothLeTrackerSnapshot } from './bluetooth_le_tracker.types.js';
import {
  bluetoothLeTrackerDefaultConsiderHomeSeconds,
  bluetoothLeTrackerDefaultScanIntervalSeconds,
  bluetoothLeTrackerDefaultTrackBatteryIntervalSeconds,
} from './bluetooth_le_tracker.types.js';

export class BluetoothLeTrackerConfigFlow implements IConfigFlow<IBluetoothLeTrackerConfig> {
  public async start(candidateArg: IDiscoveryCandidate, contextArg: IConfigFlowContext): Promise<IConfigFlowStep<IBluetoothLeTrackerConfig>> {
    void contextArg;
    const defaults = this.defaultsFromCandidate(candidateArg);
    return {
      kind: 'form',
      title: 'Configure Bluetooth LE Tracker',
      description: 'Configure known BLE devices or provide advertisement/snapshot data. Live Bluetooth scanning is not performed by this dependency-free TypeScript port.',
      fields: [
        { name: 'address', label: 'BLE address', type: 'text' },
        { name: 'name', label: 'Device name', type: 'text' },
        { name: 'trackNewDevices', label: 'Track new devices after repeated advertisements', type: 'boolean' },
        { name: 'trackBattery', label: 'Track battery from injected data', type: 'boolean' },
        { name: 'trackBatteryIntervalSeconds', label: 'Battery refresh interval seconds', type: 'number' },
        { name: 'scanIntervalSeconds', label: 'Scanner refresh interval seconds', type: 'number' },
        { name: 'considerHomeSeconds', label: 'Consider home seconds', type: 'number' },
        { name: 'knownDevicesJson', label: 'Known devices JSON', type: 'text' },
        { name: 'advertisementsJson', label: 'Advertisements JSON', type: 'text' },
        { name: 'snapshotJson', label: 'Snapshot JSON', type: 'text' },
      ],
      submit: async (valuesArg) => {
        const knownDevices = this.arrayValue<IBluetoothLeTrackedDevice>(valuesArg.knownDevicesJson, defaults.knownDevices);
        if (knownDevices === false) {
          return { kind: 'error', title: 'Invalid known devices', error: 'Known devices JSON must be an array.' };
        }
        const advertisements = this.arrayValue<IBluetoothLeAdvertisement>(valuesArg.advertisementsJson, defaults.advertisements);
        if (advertisements === false) {
          return { kind: 'error', title: 'Invalid advertisements', error: 'Advertisements JSON must be an array.' };
        }
        const snapshot = this.snapshotValue(valuesArg.snapshotJson, defaults.snapshot);
        if (snapshot === false) {
          return { kind: 'error', title: 'Invalid BLE tracker snapshot', error: 'Snapshot JSON must be a JSON object.' };
        }
        const address = BluetoothLeTrackerMapper.normalizeAddress(valuesArg.address) || defaults.address;
        const name = this.stringValue(valuesArg.name) || defaults.name;
        const trackedDevices = [...(knownDevices || [])];
        if (address && !trackedDevices.some((deviceArg) => BluetoothLeTrackerMapper.normalizeAddress(deviceArg.address || deviceArg.mac || deviceArg.macAddress || deviceArg.haMac) === address)) {
          trackedDevices.push({ address, name, track: true, trackBattery: this.booleanValue(valuesArg.trackBattery) ?? defaults.trackBattery });
        }
        return {
          kind: 'done',
          title: 'Bluetooth LE Tracker configured',
          config: {
            scanIntervalSeconds: this.numberValue(valuesArg.scanIntervalSeconds) ?? defaults.scanIntervalSeconds ?? bluetoothLeTrackerDefaultScanIntervalSeconds,
            trackNewDevices: this.booleanValue(valuesArg.trackNewDevices) ?? defaults.trackNewDevices ?? true,
            trackBattery: this.booleanValue(valuesArg.trackBattery) ?? defaults.trackBattery ?? false,
            trackBatteryIntervalSeconds: this.numberValue(valuesArg.trackBatteryIntervalSeconds) ?? defaults.trackBatteryIntervalSeconds ?? bluetoothLeTrackerDefaultTrackBatteryIntervalSeconds,
            considerHomeSeconds: this.numberValue(valuesArg.considerHomeSeconds) ?? defaults.considerHomeSeconds ?? bluetoothLeTrackerDefaultConsiderHomeSeconds,
            knownDevices: trackedDevices,
            advertisements: advertisements || undefined,
            snapshot: snapshot || undefined,
          },
        };
      },
    };
  }

  private defaultsFromCandidate(candidateArg: IDiscoveryCandidate): {
    address?: string;
    name?: string;
    trackNewDevices?: boolean;
    trackBattery?: boolean;
    trackBatteryIntervalSeconds?: number;
    scanIntervalSeconds?: number;
    considerHomeSeconds?: number;
    knownDevices?: IBluetoothLeTrackedDevice[];
    advertisements?: IBluetoothLeAdvertisement[];
    snapshot?: IBluetoothLeTrackerSnapshot;
  } {
    const metadata = candidateArg.metadata || {};
    return {
      address: BluetoothLeTrackerMapper.normalizeAddress(candidateArg.macAddress || metadata.address || metadata.haMac),
      name: candidateArg.name,
      trackNewDevices: this.booleanValue(metadata.trackNewDevices),
      trackBattery: this.booleanValue(metadata.trackBattery),
      trackBatteryIntervalSeconds: this.numberValue(metadata.trackBatteryIntervalSeconds),
      scanIntervalSeconds: this.numberValue(metadata.scanIntervalSeconds),
      considerHomeSeconds: this.numberValue(metadata.considerHomeSeconds),
      knownDevices: this.arrayCandidateValue<IBluetoothLeTrackedDevice>(metadata.knownDevices) || this.arrayCandidateValue<IBluetoothLeTrackedDevice>(metadata.trackedDevices) || this.arrayCandidateValue<IBluetoothLeTrackedDevice>(metadata.devices),
      advertisements: this.arrayCandidateValue<IBluetoothLeAdvertisement>(metadata.advertisements) || this.arrayCandidateValue<IBluetoothLeAdvertisement>(metadata.advertisement ? [metadata.advertisement] : undefined),
      snapshot: this.isSnapshot(metadata.snapshot) ? metadata.snapshot : undefined,
    };
  }

  private arrayValue<TValue>(valueArg: unknown, fallbackArg?: TValue[]): TValue[] | undefined | false {
    if (valueArg === undefined || valueArg === null || valueArg === '') {
      return fallbackArg;
    }
    if (Array.isArray(valueArg)) {
      return valueArg as TValue[];
    }
    if (typeof valueArg !== 'string') {
      return false;
    }
    try {
      const parsed = JSON.parse(valueArg) as unknown;
      return Array.isArray(parsed) ? parsed as TValue[] : false;
    } catch {
      return false;
    }
  }

  private snapshotValue(valueArg: unknown, fallbackArg?: IBluetoothLeTrackerSnapshot): IBluetoothLeTrackerSnapshot | undefined | false {
    if (valueArg === undefined || valueArg === null || valueArg === '') {
      return fallbackArg;
    }
    if (this.isSnapshot(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg !== 'string') {
      return false;
    }
    try {
      const parsed = JSON.parse(valueArg) as unknown;
      return this.isRecord(parsed) ? parsed as unknown as IBluetoothLeTrackerSnapshot : false;
    } catch {
      return false;
    }
  }

  private arrayCandidateValue<TValue>(valueArg: unknown): TValue[] | undefined {
    return Array.isArray(valueArg) ? valueArg as TValue[] : undefined;
  }

  private stringValue(valueArg: unknown): string | undefined {
    return typeof valueArg === 'string' && valueArg.trim() ? valueArg.trim() : undefined;
  }

  private booleanValue(valueArg: unknown): boolean | undefined {
    if (typeof valueArg === 'boolean') {
      return valueArg;
    }
    if (typeof valueArg === 'string') {
      if (['true', '1', 'yes', 'on'].includes(valueArg.toLowerCase())) return true;
      if (['false', '0', 'no', 'off'].includes(valueArg.toLowerCase())) return false;
    }
    return undefined;
  }

  private numberValue(valueArg: unknown): number | undefined {
    if (typeof valueArg === 'number' && Number.isFinite(valueArg)) {
      return valueArg;
    }
    if (typeof valueArg === 'string' && valueArg.trim()) {
      const parsed = Number(valueArg);
      return Number.isFinite(parsed) ? parsed : undefined;
    }
    return undefined;
  }

  private isSnapshot(valueArg: unknown): valueArg is IBluetoothLeTrackerSnapshot {
    return this.isRecord(valueArg);
  }

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