import type {
  IVelbusChannel,
  IVelbusCommand,
  IVelbusCommandResult,
  IVelbusConfig,
  IVelbusEvent,
  IVelbusSnapshot,
} from './velbus.types.js';

const liveBusUnsupported = 'Velbus live serial/TCP bus control is not implemented; provide config.snapshot for manual snapshot runtime.';

export class VelbusClient {
  private snapshot?: IVelbusSnapshot;
  private readonly eventHandlers = new Set<(eventArg: IVelbusEvent) => void>();

  constructor(private readonly config: IVelbusConfig) {
    this.snapshot = config.snapshot ? cloneSnapshot({
      ...config.snapshot,
      gateway: config.snapshot.gateway || config.gateway,
    }) : undefined;
  }

  public async getSnapshot(): Promise<IVelbusSnapshot> {
    if (this.snapshot) {
      return cloneSnapshot(this.snapshot);
    }

    return {
      gateway: this.config.gateway,
      modules: [],
      connected: false,
      updatedAt: new Date().toISOString(),
      limitations: [liveBusUnsupported],
    };
  }

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

  public async sendCommand(commandArg: IVelbusCommand): Promise<IVelbusCommandResult> {
    if (!this.snapshot || this.config.mutationMode === 'readonly') {
      const event = this.createEvent('command_failed', commandArg, liveBusUnsupported);
      this.emit(event);
      return { success: false, error: liveBusUnsupported };
    }

    const channel = this.findChannel(commandArg);
    if (!channel) {
      const error = `Velbus channel not found for command ${commandArg.type}.`;
      this.emit(this.createEvent('command_failed', commandArg, error));
      return { success: false, error };
    }

    this.applyManualCommand(channel, commandArg);
    this.snapshot.updatedAt = new Date().toISOString();
    const event = this.createEvent('command_applied', commandArg, { channel });
    this.snapshot.events = [...(this.snapshot.events || []), event];
    this.emit(event);
    return { success: true, data: { snapshot: await this.getSnapshot() } };
  }

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

  private findChannel(commandArg: IVelbusCommand): IVelbusChannel | undefined {
    for (const module of this.snapshot?.modules || []) {
      if (commandArg.moduleAddress !== undefined && module.address !== commandArg.moduleAddress) {
        continue;
      }
      const channel = module.channels.find((channelArg) => {
        if (commandArg.channelId !== undefined && channelArg.id === commandArg.channelId) {
          return true;
        }
        return commandArg.channelNumber !== undefined && channelArg.channelNumber === commandArg.channelNumber;
      });
      if (channel) {
        return channel;
      }
    }
    return undefined;
  }

  private applyManualCommand(channelArg: IVelbusChannel, commandArg: IVelbusCommand): void {
    if (commandArg.type === 'turn_on') {
      channelArg.state = true;
      if (channelArg.kind === 'dimmer') {
        channelArg.brightness = typeof commandArg.value === 'number' ? clamp(commandArg.value, 0, 100) : (channelArg.brightness && channelArg.brightness > 0 ? channelArg.brightness : 100);
      }
      return;
    }

    if (commandArg.type === 'turn_off') {
      channelArg.state = false;
      if (channelArg.kind === 'dimmer') {
        channelArg.brightness = 0;
      }
      return;
    }

    if (commandArg.type === 'open') {
      channelArg.state = 'open';
      channelArg.position = 100;
      return;
    }

    if (commandArg.type === 'close') {
      channelArg.state = 'closed';
      channelArg.position = 0;
      return;
    }

    if (commandArg.type === 'stop') {
      channelArg.state = channelArg.position === 0 ? 'closed' : 'open';
      return;
    }

    if (commandArg.type === 'set_value') {
      if (channelArg.kind === 'blind') {
        channelArg.position = typeof commandArg.value === 'number' ? clamp(commandArg.value, 0, 100) : channelArg.position;
        channelArg.state = channelArg.position === 0 ? 'closed' : 'open';
      } else if (channelArg.kind === 'dimmer' || channelArg.kind === 'led') {
        channelArg.brightness = typeof commandArg.value === 'number' ? clamp(commandArg.value, 0, 100) : channelArg.brightness;
        channelArg.state = Boolean(channelArg.brightness && channelArg.brightness > 0);
      } else if (channelArg.kind === 'climate') {
        channelArg.targetTemperature = typeof commandArg.value === 'number' ? commandArg.value : channelArg.targetTemperature;
      } else {
        channelArg.state = commandArg.value ?? channelArg.state ?? null;
      }
    }
  }

  private createEvent(typeArg: IVelbusEvent['type'], commandArg: IVelbusCommand, dataArg: unknown): IVelbusEvent {
    return {
      type: typeArg,
      moduleAddress: commandArg.moduleAddress,
      channelId: commandArg.channelId,
      entityId: commandArg.entityId,
      deviceId: commandArg.deviceId,
      command: commandArg,
      data: dataArg,
      timestamp: Date.now(),
    };
  }

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

const cloneSnapshot = (snapshotArg: IVelbusSnapshot): IVelbusSnapshot => JSON.parse(JSON.stringify(snapshotArg)) as IVelbusSnapshot;

const clamp = (valueArg: number, minArg: number, maxArg: number): number => Math.min(maxArg, Math.max(minArg, valueArg));
