import type {
  IBroadlinkCommand,
  IBroadlinkCommandResult,
  IBroadlinkConfig,
  IBroadlinkEvent,
  IBroadlinkPacket,
  IBroadlinkSnapshot,
} from './broadlink.types.js';
import { BroadlinkMapper } from './broadlink.mapper.js';
import {
  broadlinkIrPacketFromTimings,
  broadlinkPacketFromBase64,
  broadlinkPacketFromHex,
  broadlinkRfPacketFromTimings,
} from './broadlink.packet.js';

type TBroadlinkEventHandler = (eventArg: IBroadlinkEvent) => void;

export class BroadlinkClient {
  private snapshot?: IBroadlinkSnapshot;
  private readonly events: IBroadlinkEvent[] = [];
  private readonly eventHandlers = new Set<TBroadlinkEventHandler>();

  constructor(private readonly config: IBroadlinkConfig) {}

  public async getSnapshot(): Promise<IBroadlinkSnapshot> {
    this.snapshot = BroadlinkMapper.toSnapshot(this.config, undefined, this.events);
    return this.cloneSnapshot(this.snapshot);
  }

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

  public async refresh(): Promise<IBroadlinkSnapshot> {
    this.snapshot = BroadlinkMapper.toSnapshot(this.config, this.config.connected, this.events);
    this.emit({ type: 'snapshot_refreshed', data: this.cloneSnapshot(this.snapshot), timestamp: Date.now() });
    return this.cloneSnapshot(this.snapshot);
  }

  public async sendCommand(commandArg: IBroadlinkCommand): Promise<IBroadlinkCommandResult> {
    this.emit({ type: 'command_mapped', command: commandArg, deviceId: commandArg.deviceId, entityId: commandArg.entityId, timestamp: Date.now() });

    if (commandArg.type === 'delete_command') {
      const result = this.deleteCommand(commandArg);
      this.emit({ type: result.success ? 'command_deleted' : 'command_failed', command: commandArg, deviceId: commandArg.deviceId, entityId: commandArg.entityId, data: result, timestamp: Date.now() });
      return result;
    }

    if (commandArg.type === 'remote_turn_on' || commandArg.type === 'remote_turn_off') {
      const result: IBroadlinkCommandResult = { success: true, transmitted: false, data: { command: commandArg, reason: 'Remote entity enable state is local runtime state only.' } };
      this.emit({ type: 'command_executed', command: commandArg, deviceId: commandArg.deviceId, entityId: commandArg.entityId, data: result, timestamp: Date.now() });
      return result;
    }

    const executor = this.config.commandExecutor || this.config.transport?.execute.bind(this.config.transport);
    if (!executor) {
      const result: IBroadlinkCommandResult = {
        success: false,
        transmitted: false,
        error: this.unsupportedLiveControlMessage(),
        data: { command: commandArg },
      };
      this.emit({ type: 'command_failed', command: commandArg, deviceId: commandArg.deviceId, entityId: commandArg.entityId, data: result, timestamp: Date.now() });
      return result;
    }

    try {
      const result = this.commandResult(await executor(commandArg), commandArg);
      if (result.success) {
        this.patchSnapshot(commandArg);
      }
      this.emit({ type: result.success ? 'command_executed' : 'command_failed', command: commandArg, deviceId: commandArg.deviceId, entityId: commandArg.entityId, data: result, timestamp: Date.now() });
      return result;
    } catch (errorArg) {
      const result: IBroadlinkCommandResult = {
        success: false,
        transmitted: false,
        error: errorArg instanceof Error ? errorArg.message : String(errorArg),
        data: { command: commandArg },
      };
      this.emit({ type: 'command_failed', command: commandArg, deviceId: commandArg.deviceId, entityId: commandArg.entityId, data: result, timestamp: Date.now() });
      return result;
    }
  }

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

  public static packetFromBase64(valueArg: string): IBroadlinkPacket {
    return broadlinkPacketFromBase64(valueArg);
  }

  public static packetFromHex(valueArg: string): IBroadlinkPacket {
    return broadlinkPacketFromHex(valueArg);
  }

  public static irPacketFromTimings(timingsArg: number[]): IBroadlinkPacket {
    return broadlinkIrPacketFromTimings(timingsArg);
  }

  public static rfPacketFromTimings(optionsArg: { frequency: number; timings: number[]; repeatCount?: number }): IBroadlinkPacket {
    return broadlinkRfPacketFromTimings(optionsArg);
  }

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

  private commandResult(resultArg: unknown, commandArg: IBroadlinkCommand): IBroadlinkCommandResult {
    if (this.isCommandResult(resultArg)) {
      return { transmitted: true, ...resultArg };
    }
    return { success: true, transmitted: true, data: resultArg ?? { command: commandArg } };
  }

  private isCommandResult(valueArg: unknown): valueArg is IBroadlinkCommandResult {
    return typeof valueArg === 'object' && valueArg !== null && 'success' in valueArg;
  }

  private deleteCommand(commandArg: IBroadlinkCommand): IBroadlinkCommandResult {
    const remoteDevice = commandArg.remoteDevice;
    if (!remoteDevice || !commandArg.commandNames?.length) {
      return { success: false, transmitted: false, error: 'Broadlink delete_command requires data.device and data.command.' };
    }
    const devices = this.config.snapshot?.devices || this.config.devices || [];
    const targetDevice = devices.find((deviceArg) => BroadlinkMapper.deviceId(deviceArg) === commandArg.deviceId || deviceArg.id === commandArg.deviceId) || devices[0];
    const codes = targetDevice?.codes || this.config.codes;
    if (!codes?.[remoteDevice]) {
      return { success: false, transmitted: false, error: `Broadlink learned-code device not found: ${remoteDevice}.` };
    }
    for (const command of commandArg.commandNames) {
      delete codes[remoteDevice][command];
    }
    if (!Object.keys(codes[remoteDevice]).length) {
      delete codes[remoteDevice];
    }
    return { success: true, transmitted: false, data: { command: commandArg } };
  }

  private patchSnapshot(commandArg: IBroadlinkCommand): void {
    const snapshot = this.snapshot;
    if (!snapshot || !commandArg.deviceId || !commandArg.payload) {
      return;
    }
    const device = snapshot.devices.find((deviceArg) => BroadlinkMapper.deviceId(deviceArg) === commandArg.deviceId || deviceArg.id === commandArg.deviceId);
    if (!device) {
      return;
    }
    device.state = { ...(device.state || {}), ...commandArg.payload };
    device.available = true;
    device.updatedAt = new Date().toISOString();
  }

  private cloneSnapshot(snapshotArg: IBroadlinkSnapshot): IBroadlinkSnapshot {
    return JSON.parse(JSON.stringify(snapshotArg)) as IBroadlinkSnapshot;
  }

  private unsupportedLiveControlMessage(): string {
    return 'Broadlink live UDP/auth transport is not implemented in this dependency-free TypeScript port. Commands are mapped explicitly but not transmitted unless commandExecutor or transport.execute is provided.';
  }
}
