import { broadlinkIrTickUs } from './broadlink.constants.js';
import type { IBroadlinkPacket, TBroadlinkPacketKind } from './broadlink.types.js';

const rf433Range = [433_050_000, 434_790_000] as const;
const rf315Range = [314_950_000, 315_250_000] as const;

export const broadlinkPacketFromBase64 = (valueArg: string, sourceArg: IBroadlinkPacket['source'] = 'base64'): IBroadlinkPacket => {
  const cleanValue = valueArg.startsWith('b64:') ? valueArg.slice(4) : valueArg;
  const padded = cleanValue + '='.repeat((4 - cleanValue.length % 4) % 4);
  return broadlinkPacketFromBytes([...Buffer.from(padded, 'base64')], sourceArg);
};

export const broadlinkPacketFromHex = (valueArg: string): IBroadlinkPacket => {
  const cleanValue = valueArg.replace(/[^0-9a-f]/gi, '');
  return broadlinkPacketFromBytes([...Buffer.from(cleanValue, 'hex')], 'hex');
};

export const broadlinkIrPacketFromTimings = (timingsArg: number[]): IBroadlinkPacket => {
  const bytes = [0x26, 0x00, 0x00, 0x00];
  appendBroadlinkPulses(bytes, timingsArg, Math.floor);
  setPayloadLength(bytes);
  return broadlinkPacketFromBytes(bytes, 'ir_timings');
};

export const broadlinkRfPacketFromTimings = (optionsArg: { frequency: number; timings: number[]; repeatCount?: number }): IBroadlinkPacket => {
  const typeByte = broadlinkRfTypeByte(optionsArg.frequency);
  const bytes = [typeByte, optionsArg.repeatCount || 0, 0x00, 0x00];
  appendBroadlinkPulses(bytes, optionsArg.timings, Math.round);
  setPayloadLength(bytes);
  return { ...broadlinkPacketFromBytes(bytes, 'rf_timings'), frequency: optionsArg.frequency, repeatCount: optionsArg.repeatCount || 0 };
};

export const broadlinkRfTypeByte = (frequencyArg: number): number => {
  if (frequencyArg >= rf433Range[0] && frequencyArg <= rf433Range[1]) {
    return 0xb2;
  }
  if (frequencyArg >= rf315Range[0] && frequencyArg <= rf315Range[1]) {
    return 0xb4;
  }
  throw new Error(`Broadlink RF frequency is not supported: ${frequencyArg}.`);
};

export const broadlinkPacketFromBytes = (bytesArg: number[], sourceArg?: IBroadlinkPacket['source']): IBroadlinkPacket => {
  const normalized = bytesArg.map((byteArg) => byteArg & 0xff);
  const buffer = Buffer.from(normalized);
  const firstByte = normalized[0];
  return {
    base64: buffer.toString('base64'),
    hex: buffer.toString('hex'),
    bytes: normalized,
    byteLength: normalized.length,
    kind: packetKind(firstByte),
    firstByte,
    source: sourceArg,
  };
};

const appendBroadlinkPulses = (bytesArg: number[], timingsArg: number[], roundArg: (valueArg: number) => number): void => {
  for (const timing of timingsArg) {
    const ticks = roundArg(Math.abs(timing) / broadlinkIrTickUs);
    const div = Math.floor(ticks / 256);
    const mod = ticks % 256;
    if (div) {
      bytesArg.push(0x00, div & 0xff);
    }
    bytesArg.push(mod & 0xff);
  }
};

const setPayloadLength = (bytesArg: number[]): void => {
  const payloadLength = bytesArg.length - 4;
  bytesArg[2] = payloadLength & 0xff;
  bytesArg[3] = payloadLength >> 8;
};

const packetKind = (firstByteArg: number | undefined): TBroadlinkPacketKind => {
  if (firstByteArg === 0x26) {
    return 'ir';
  }
  if (firstByteArg === 0xb2 || firstByteArg === 0xd7) {
    return 'rf433';
  }
  if (firstByteArg === 0xb4) {
    return 'rf315';
  }
  if (firstByteArg === 0xb1 || firstByteArg === 0xb3) {
    return 'rf';
  }
  return 'unknown';
};
