import type { IZhaClientCommand, IZhaCommandResult, IZhaConfig, IZhaEvent, IZhaSnapshot } from './zha.types.js';
import { ZhaMapper } from './zha.mapper.js';

type TZhaEventHandler = (eventArg: IZhaEvent) => void;

export class ZhaClient {
  private readonly events: IZhaEvent[] = [];
  private readonly eventHandlers = new Set<TZhaEventHandler>();

  constructor(private readonly config: IZhaConfig) {}

  public async getSnapshot(): Promise<IZhaSnapshot> {
    return ZhaMapper.toSnapshot(this.config, undefined, this.events);
  }

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

  public async sendCommand(commandArg: IZhaClientCommand): Promise<IZhaCommandResult> {
    this.emit({ type: 'command_mapped', command: commandArg, entityId: commandArg.entityId, uniqueId: commandArg.uniqueId, timestamp: Date.now() });
    if (this.config.commandExecutor) {
      const result = await this.config.commandExecutor(commandArg);
      return this.commandResult(result, commandArg);
    }
    return {
      success: false,
      error: 'ZHA live Zigbee radio writes require the zha/zigpy radio stack and serial protocol support, which is not implemented in this dependency-free TypeScript port. The mapped command was not sent.',
      data: { command: commandArg },
    };
  }

  public async connectLive(): Promise<void> {
    await new ZhaZigpyRadioConnection(this.config).connect();
  }

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

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

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

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

export class ZhaZigpyRadioConnection {
  constructor(private readonly config: IZhaConfig) {}

  public async connect(): Promise<void> {
    throw new Error(this.unsupportedMessage());
  }

  public async sendCommand(commandArg: IZhaClientCommand): Promise<void> {
    void commandArg;
    throw new Error(this.unsupportedMessage());
  }

  private unsupportedMessage(): string {
    const radioPath = this.config.radio?.path || this.config.device?.path || this.config.usbPath || this.config.usb_path || 'configured radio';
    return `ZHA live radio control for ${radioPath} requires zha/zigpy plus radio-specific serial framing. This TypeScript port intentionally does not guess those internals.`;
  }
}
