/**
 * Board Pin Mapping Utility
 *
 * Maps wokwi-element pin names to simulator GPIO/pin numbers
 * for both Arduino Uno (AVR) and Nano RP2040 Connect (RP2040).
 *
 * The wokwi board elements expose pin names like 'D2', 'A0', 'TX', etc.
 * The simulators need numeric GPIO/pin numbers.
 */

/**
 * Nano RP2040 Connect element pin names → RP2040 GPIO numbers.
 * Derived from wokwi-nano-rp2040-connect-element.ts pinInfo descriptions.
 */
const NANO_RP2040_PIN_MAP: Record<string, number> = {
  'D2': 25,   // GPIO25 — LED_BUILTIN
  'D3': 15,   // GPIO15
  'D4': 16,   // GPIO16 — SPI0 MISO
  'D5': 17,   // GPIO17 — SPI0 CS
  'D6': 18,   // GPIO18 — SPI0 SCK
  'D7': 19,   // GPIO19 — SPI0 MOSI
  'D8': 20,   // GPIO20
  'D9': 21,   // GPIO21
  'D10': 5,   // GPIO05
  'D11': 7,   // GPIO07
  'D12': 4,   // GPIO04 — I2C0 SDA
  'D13': 6,   // GPIO06 — SPI0 SCK (alternate)
  'TX': 0,    // GPIO0 — UART0 TX
  'RX': 1,    // GPIO1 — UART0 RX
  'A0': 26,   // GPIO26 — ADC channel 0
  'A1': 27,   // GPIO27 — ADC channel 1
  'A2': 28,   // GPIO28 — ADC channel 2
  'A3': 29,   // GPIO29 — ADC channel 3
  'A4': 12,   // GPIO12
  'A5': 13,   // GPIO13
};

/**
 * Arduino Uno analog pin names → AVR pin numbers.
 * Digital pins D0-D13 are parsed numerically; only analog names need mapping.
 */
const ARDUINO_UNO_ANALOG_MAP: Record<string, number> = {
  'A0': 14,
  'A1': 15,
  'A2': 16,
  'A3': 17,
  'A4': 18,
  'A5': 19,
  'A6': 20,
  'A7': 21,
};

/**
 * Arduino Mega analog pin names → AVR pin numbers.
 * A0–A15 map to physical pins 54–69 on the ATmega2560.
 */
const ARDUINO_MEGA_ANALOG_MAP: Record<string, number> = {
  'A0': 54,  'A1': 55,  'A2': 56,  'A3': 57,
  'A4': 58,  'A5': 59,  'A6': 60,  'A7': 61,
  'A8': 62,  'A9': 63,  'A10': 64, 'A11': 65,
  'A12': 66, 'A13': 67, 'A14': 68, 'A15': 69,
};

/**
 * Raspberry Pi 3B physical pin number → BCM GPIO number.
 * Power / GND / special-function pins are mapped to -1 (not a GPIO).
 * Source: https://pinout.xyz
 */
export const PI3_PHYSICAL_TO_BCM: Record<number, number> = {
  1:  -1,  // 3.3V
  2:  -1,  // 5V
  3:  2,   // BCM2 (SDA1)
  4:  -1,  // 5V
  5:  3,   // BCM3 (SCL1)
  6:  -1,  // GND
  7:  4,   // BCM4 (GPCLK0)
  8:  14,  // BCM14 (TXD0 / ttyAMA0)
  9:  -1,  // GND
  10: 15,  // BCM15 (RXD0 / ttyAMA0)
  11: 17,  // BCM17
  12: 18,  // BCM18 (PWM0)
  13: 27,  // BCM27
  14: -1,  // GND
  15: 22,  // BCM22
  16: 23,  // BCM23
  17: -1,  // 3.3V
  18: 24,  // BCM24
  19: 10,  // BCM10 (MOSI)
  20: -1,  // GND
  21: 9,   // BCM9 (MISO)
  22: 25,  // BCM25
  23: 11,  // BCM11 (SCLK)
  24: 8,   // BCM8 (CE0)
  25: -1,  // GND
  26: 7,   // BCM7 (CE1)
  27: -1,  // ID_SD (reserved)
  28: -1,  // ID_SC (reserved)
  29: 5,   // BCM5
  30: -1,  // GND
  31: 6,   // BCM6
  32: 12,  // BCM12 (PWM0)
  33: 13,  // BCM13 (PWM1)
  34: -1,  // GND
  35: 19,  // BCM19 (MISO1)
  36: 16,  // BCM16 (CE2)
  37: 26,  // BCM26
  38: 20,  // BCM20 (MOSI1)
  39: -1,  // GND
  40: 21,  // BCM21 (SCLK1)
};

/** BCM GPIO number → physical pin number (reverse map) */
export const PI3_BCM_TO_PHYSICAL: Record<number, number> = Object.fromEntries(
  Object.entries(PI3_PHYSICAL_TO_BCM)
    .filter(([, bcm]) => bcm >= 0)
    .map(([physical, bcm]) => [bcm, Number(physical)])
);

/**
 * ESP32 DevKit-C GPIO pin names → GPIO numbers.
 * Pin names are GPIO numbers directly (GPIO0–GPIO39).
 * Special aliases: TX=1, RX=3.
 */
const ESP32_PIN_MAP: Record<string, number> = {
  'TX': 1, 'RX': 3,
  'GPIO0': 0, 'GPIO1': 1, 'GPIO2': 2, 'GPIO3': 3,
  'GPIO4': 4, 'GPIO5': 5, 'GPIO6': 6, 'GPIO7': 7,
  'GPIO8': 8, 'GPIO9': 9, 'GPIO10': 10, 'GPIO11': 11,
  'GPIO12': 12, 'GPIO13': 13, 'GPIO14': 14, 'GPIO15': 15,
  'GPIO16': 16, 'GPIO17': 17, 'GPIO18': 18, 'GPIO19': 19,
  'GPIO20': 20, 'GPIO21': 21, 'GPIO22': 22, 'GPIO23': 23,
  'GPIO25': 25, 'GPIO26': 26, 'GPIO27': 27,
  'GPIO32': 32, 'GPIO33': 33, 'GPIO34': 34, 'GPIO35': 35,
  'GPIO36': 36, 'GPIO39': 39,
  // ADC aliases
  'VP': 36, 'VN': 39,
};

/** All known board component IDs in the simulator */
export const BOARD_COMPONENT_IDS = [
  'arduino-uno', 'arduino-nano', 'arduino-mega', 'nano-rp2040', 'raspberry-pi-3',
  'raspberry-pi-pico', 'pi-pico-w',
  'esp32', 'esp32-devkit-c-v4', 'esp32-cam', 'wemos-lolin32-lite',
  'esp32-s3', 'xiao-esp32-s3', 'arduino-nano-esp32',
  'esp32-c3', 'xiao-esp32-c3', 'aitewinrobot-esp32c3-supermini',
  'attiny85', 'riscv-generic',
];

/**
 * Check whether a componentId represents a board (not an external component).
 */
export function isBoardComponent(componentId: string): boolean {
  return BOARD_COMPONENT_IDS.some((id) => componentId === id || componentId.startsWith(id));
}

/**
 * Convert a board element pin name to a simulator-usable pin/GPIO number.
 *
 * For Arduino Uno: 'D0'-'D13' / '0'-'13' → 0-13, 'A0'-'A7' → 14-21
 * For Nano RP2040: 'D2'-'D13' / 'A0'-'A5' / 'TX' / 'RX' → GPIO number
 *
 * @returns Numeric pin/GPIO number, or null if unmapped
 */
export function boardPinToNumber(boardId: string, pinName: string): number | null {
  if (boardId === 'arduino-uno' || boardId === 'arduino-nano') {
    // Try numeric (covers '0' through '13', also legacy examples using just numbers)
    const num = parseInt(pinName, 10);
    if (!isNaN(num) && num >= 0 && num <= 21) return num;
    // Try 'Dx' style
    if (pinName.startsWith('D')) {
      const d = parseInt(pinName.substring(1), 10);
      if (!isNaN(d)) return d;
    }
    // Analog naming
    return ARDUINO_UNO_ANALOG_MAP[pinName] ?? null;
  }

  if (boardId === 'arduino-mega') {
    // Digital pins D0–D53 parsed numerically
    const num = parseInt(pinName, 10);
    if (!isNaN(num) && num >= 0 && num <= 53) return num;
    if (pinName.startsWith('D')) {
      const d = parseInt(pinName.substring(1), 10);
      if (!isNaN(d) && d <= 53) return d;
    }
    return ARDUINO_MEGA_ANALOG_MAP[pinName] ?? null;
  }

  if (boardId === 'nano-rp2040') {
    return NANO_RP2040_PIN_MAP[pinName] ?? null;
  }

  // Raspberry Pi 3B — pinName is the physical pin number ("1" … "40")
  // We return the BCM GPIO number, or -1 for power/GND pins.
  if (boardId === 'raspberry-pi-3' || boardId.startsWith('raspberry-pi-3')) {
    const physical = parseInt(pinName, 10);
    if (!isNaN(physical)) return PI3_PHYSICAL_TO_BCM[physical] ?? null;
    return null;
  }

  // Pi Pico W — same GPIO mapping as Raspberry Pi Pico (GP0-GP28 → 0-28)
  if (boardId === 'pi-pico-w') {
    if (pinName.startsWith('GP')) {
      const n = parseInt(pinName.substring(2), 10);
      if (!isNaN(n)) return n;
    }
    const num = parseInt(pinName, 10);
    if (!isNaN(num)) return num;
    return null;
  }

  // ESP32 / ESP32-S3 / ESP32-C3 — GPIO numbers used directly
  if (boardId === 'esp32' || boardId.startsWith('esp32')) {
    // Try bare number first ("13" → 13)
    const num = parseInt(pinName, 10);
    if (!isNaN(num) && num >= 0 && num <= 39) return num;
    return ESP32_PIN_MAP[pinName] ?? null;
  }

  // ESP32 variants not starting with 'esp32'
  if (boardId === 'wemos-lolin32-lite') {
    // Pins named "GPIO34", "GPIO32" etc → strip prefix
    if (pinName.startsWith('GPIO')) return parseInt(pinName.substring(4), 10);
    const num = parseInt(pinName, 10);
    if (!isNaN(num)) return num;
    return ESP32_PIN_MAP[pinName] ?? null;
  }

  if (boardId === 'xiao-esp32-s3' || boardId === 'arduino-nano-esp32') {
    // D0-D13, A0-A7 → ESP32-S3 GPIO numbers
    const XIAO_S3_MAP: Record<string, number> = {
      'D0': 1, 'D1': 2, 'D2': 3, 'D3': 4, 'D4': 5, 'D5': 6,
      'D6': 43, 'D7': 44, 'D8': 7, 'D9': 8, 'D10': 9,
    };
    const NANO_ESP32_MAP: Record<string, number> = {
      'D0': 44, 'D1': 43, 'D2': 5, 'D3': 6, 'D4': 7, 'D5': 8,
      'D6': 9, 'D7': 10, 'D8': 17, 'D9': 18, 'D10': 21,
      'D11': 38, 'D12': 47, 'D13': 48,
      'A0': 1, 'A1': 2, 'A2': 3, 'A3': 4, 'A4': 11, 'A5': 12, 'A6': 13, 'A7': 14,
    };
    const map = boardId === 'arduino-nano-esp32' ? NANO_ESP32_MAP : XIAO_S3_MAP;
    if (pinName in map) return map[pinName];
    const num = parseInt(pinName, 10);
    if (!isNaN(num)) return num;
    return null;
  }

  if (boardId === 'xiao-esp32-c3') {
    const XIAO_C3_MAP: Record<string, number> = {
      'D0': 2, 'D1': 3, 'D2': 4, 'D3': 5, 'D4': 6, 'D5': 7,
      'D6': 21, 'D7': 20, 'D8': 8, 'D9': 9, 'D10': 10,
    };
    if (pinName in XIAO_C3_MAP) return XIAO_C3_MAP[pinName];
    const num = parseInt(pinName, 10);
    if (!isNaN(num)) return num;
    return null;
  }

  if (boardId === 'aitewinrobot-esp32c3-supermini') {
    const num = parseInt(pinName, 10);
    if (!isNaN(num)) return num;
    return ESP32_PIN_MAP[pinName] ?? null;
  }

  // ATtiny85 — PORTB only: PB0-PB5 → pins 0-5
  if (boardId === 'attiny85' || boardId.startsWith('attiny85')) {
    if (/^PB(\d+)$/.test(pinName)) return parseInt(pinName.substring(2), 10);
    // Numeric fallback
    const num = parseInt(pinName, 10);
    if (!isNaN(num) && num >= 0 && num <= 5) return num;
    return null;
  }

  // RISC-V generic (CH32V003 target) — PA0-PA7=0-7, PC0-PC7=8-15, PD0-PD7=16-23
  if (boardId === 'riscv-generic' || boardId.startsWith('riscv-generic')) {
    if (/^PA(\d+)$/.test(pinName)) return parseInt(pinName.substring(2), 10);
    if (/^PC(\d+)$/.test(pinName)) return 8 + parseInt(pinName.substring(2), 10);
    if (/^PD(\d+)$/.test(pinName)) return 16 + parseInt(pinName.substring(2), 10);
    // Numeric fallback
    const num = parseInt(pinName, 10);
    if (!isNaN(num) && num >= 0 && num <= 23) return num;
    return null;
  }

  return null;
}
