// ATmega328P Arduino addon
//
// Demonstrates how an addon can register an entirely new microcontroller.
// Adds the ATmega328P (Arduino Uno / Nano) as a selectable device with:
//   - Curated Pin I/O, PWM, ADC, Time, and Serial categories (existing blocks)
//   - Arduino-specific blocks: built-in LED, analog write, servo
//
// Pin constraints: digital 0-13, analog A0-A5 (14-19), PWM on 3/5/6/9/10/11
// Generates MicroPython (machine module) since the IDE's code pipeline is
// MicroPython-based. Users can flash MicroPython firmware onto compatible
// ATmega328P boards to use this.

// ─── Arduino-specific block definitions ───────────────────

api.Blockly.Blocks['arduino_builtin_led'] = {
  init() {
    this.appendDummyInput()
      .appendField('set built-in LED')
      .appendField(new api.Blockly.FieldDropdown([
        ['ON', '1'],
        ['OFF', '0'],
      ]), 'STATE');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(230);
    this.setTooltip('Turn the built-in LED (pin 13) on or off');
  },
};

api.Blockly.Blocks['arduino_analog_write'] = {
  init() {
    this.appendValueInput('VALUE')
      .setCheck('Number')
      .appendField('analog write pin')
      .appendField(new api.Blockly.FieldDropdown([
        ['3', '3'], ['5', '5'], ['6', '6'],
        ['9', '9'], ['10', '10'], ['11', '11'],
      ]), 'PIN')
      .appendField('value');
    this.appendDummyInput().appendField('(0-255)');
    this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(160);
    this.setTooltip('Write a PWM value (0-255) to a PWM-capable pin');
  },
};

api.Blockly.Blocks['arduino_analog_read'] = {
  init() {
    this.appendDummyInput()
      .appendField('analog read')
      .appendField(new api.Blockly.FieldDropdown([
        ['A0', '0'], ['A1', '1'], ['A2', '2'],
        ['A3', '3'], ['A4', '4'], ['A5', '5'],
      ]), 'CHANNEL');
    this.setOutput(true, 'Number');
    this.setColour(30);
    this.setTooltip('Read analog value from an ADC channel (0-1023)');
  },
};

api.Blockly.Blocks['arduino_servo_attach'] = {
  init() {
    this.appendDummyInput()
      .appendField('attach servo on pin')
      .appendField(new api.Blockly.FieldDropdown([
        ['9', '9'], ['10', '10'], ['11', '11'],
        ['3', '3'], ['5', '5'], ['6', '6'],
      ]), 'PIN');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(260);
    this.setTooltip('Attach a servo motor to a PWM pin');
  },
};

api.Blockly.Blocks['arduino_servo_write'] = {
  init() {
    this.appendValueInput('ANGLE')
      .setCheck('Number')
      .appendField('set servo on pin')
      .appendField(new api.Blockly.FieldDropdown([
        ['9', '9'], ['10', '10'], ['11', '11'],
        ['3', '3'], ['5', '5'], ['6', '6'],
      ]), 'PIN')
      .appendField('to');
    this.appendDummyInput().appendField('degrees');
    this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(260);
    this.setTooltip('Set servo angle (0-180 degrees)');
  },
};

api.Blockly.Blocks['arduino_tone'] = {
  init() {
    this.appendDummyInput()
      .appendField('play tone on pin')
      .appendField(new api.Blockly.FieldNumber(8, 0, 13, 1), 'PIN');
    this.appendValueInput('FREQ')
      .setCheck('Number')
      .appendField('freq');
    this.appendDummyInput().appendField('Hz');
    this.appendValueInput('DURATION')
      .setCheck('Number')
      .appendField('for');
    this.appendDummyInput().appendField('ms');
    this.setInputsInline(true);
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(300);
    this.setTooltip('Play a tone on a digital pin using PWM');
  },
};

api.Blockly.Blocks['arduino_no_tone'] = {
  init() {
    this.appendDummyInput()
      .appendField('stop tone on pin')
      .appendField(new api.Blockly.FieldNumber(8, 0, 13, 1), 'PIN');
    this.setPreviousStatement(true, null);
    this.setNextStatement(true, null);
    this.setColour(300);
    this.setTooltip('Stop any tone playing on a digital pin');
  },
};

api.Blockly.Blocks['arduino_map'] = {
  init() {
    this.appendValueInput('VALUE').setCheck('Number').appendField('map');
    this.appendValueInput('FROM_LOW').setCheck('Number').appendField('from');
    this.appendValueInput('FROM_HIGH').setCheck('Number').appendField('-');
    this.appendValueInput('TO_LOW').setCheck('Number').appendField('to');
    this.appendValueInput('TO_HIGH').setCheck('Number').appendField('-');
    this.setInputsInline(true);
    this.setOutput(true, 'Number');
    this.setColour(200);
    this.setTooltip('Re-map a number from one range to another');
  },
};

// ─── Code generators ──────────────────────────────────────

var gen = api.pythonGenerator;

gen.forBlock['arduino_builtin_led'] = function (block) {
  var state = block.getFieldValue('STATE');
  gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC';
  gen.definitions_['led_pin13'] = 'led = Pin(13, Pin.OUT)';
  return 'led.value(' + state + ')\n';
};

gen.forBlock['arduino_analog_write'] = function (block) {
  var pin = block.getFieldValue('PIN');
  var value = gen.valueToCode(block, 'VALUE', gen.ORDER_NONE) || '0';
  gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC';
  gen.definitions_['pwm_aw_' + pin] = 'pwm_' + pin + ' = PWM(Pin(' + pin + '), freq=490)';
  // ATmega328P analogWrite is 0-255, MicroPython PWM duty is 0-1023
  return 'pwm_' + pin + '.duty(int(' + value + ' * 4))\n';
};

gen.forBlock['arduino_analog_read'] = function (block) {
  var ch = block.getFieldValue('CHANNEL');
  var pin = parseInt(ch, 10) + 14;
  gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC';
  gen.definitions_['adc_a' + ch] = 'adc_a' + ch + ' = ADC(Pin(' + pin + '))';
  return ['adc_a' + ch + '.read()', gen.ORDER_FUNCTION_CALL];
};

var SERVO_HELPER = [
  'def _servo_write(pwm, angle):',
  '    angle = max(0, min(180, int(angle)))',
  '    duty = int(26 + (angle / 180) * 102)',
  '    pwm.duty(duty)',
].join('\n');

gen.forBlock['arduino_servo_attach'] = function (block) {
  var pin = block.getFieldValue('PIN');
  gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC';
  gen.definitions_['servo_' + pin] = 'servo_' + pin + ' = PWM(Pin(' + pin + '), freq=50)';
  gen.definitions_['servo_helper'] = SERVO_HELPER;
  return '';
};

gen.forBlock['arduino_servo_write'] = function (block) {
  var pin = block.getFieldValue('PIN');
  var angle = gen.valueToCode(block, 'ANGLE', gen.ORDER_NONE) || '90';
  gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC';
  gen.definitions_['servo_' + pin] = 'servo_' + pin + ' = PWM(Pin(' + pin + '), freq=50)';
  gen.definitions_['servo_helper'] = SERVO_HELPER;
  return '_servo_write(servo_' + pin + ', ' + angle + ')\n';
};

gen.forBlock['arduino_tone'] = function (block) {
  var pin = block.getFieldValue('PIN');
  var freq = gen.valueToCode(block, 'FREQ', gen.ORDER_NONE) || '440';
  var duration = gen.valueToCode(block, 'DURATION', gen.ORDER_NONE) || '500';
  gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC';
  gen.definitions_['import_time'] = 'import time';
  var v = 'buzzer_' + pin;
  gen.definitions_['buzzer_' + pin] = v + ' = PWM(Pin(' + pin + '))';
  return v + '.freq(' + freq + ')\n' +
         v + '.duty(512)\n' +
         'time.sleep_ms(' + duration + ')\n' +
         v + '.duty(0)\n';
};

gen.forBlock['arduino_no_tone'] = function (block) {
  var pin = block.getFieldValue('PIN');
  gen.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC';
  var v = 'buzzer_' + pin;
  gen.definitions_['buzzer_' + pin] = v + ' = PWM(Pin(' + pin + '))';
  return v + '.duty(0)\n';
};

gen.forBlock['arduino_map'] = function (block) {
  var value = gen.valueToCode(block, 'VALUE', gen.ORDER_NONE) || '0';
  var fromLow = gen.valueToCode(block, 'FROM_LOW', gen.ORDER_NONE) || '0';
  var fromHigh = gen.valueToCode(block, 'FROM_HIGH', gen.ORDER_NONE) || '1023';
  var toLow = gen.valueToCode(block, 'TO_LOW', gen.ORDER_NONE) || '0';
  var toHigh = gen.valueToCode(block, 'TO_HIGH', gen.ORDER_NONE) || '255';
  gen.definitions_['_arduino_map'] =
    'def _map(x, in_min, in_max, out_min, out_max):\n' +
    '    return int((x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min)';
  return [
    '_map(' + value + ', ' + fromLow + ', ' + fromHigh + ', ' + toLow + ', ' + toHigh + ')',
    gen.ORDER_FUNCTION_CALL,
  ];
};

// ─── Register device ──────────────────────────────────────

api.registerDevice({
  id: 'atmega328p',
  label: 'ATmega328P (Arduino)',
  firmware: {
    label: 'MicroPython (ATmega328P)',
    url: 'https://micropython.org/download/',
    canFlashInBrowser: false,
    instructions: 'Flash MicroPython firmware via your preferred tool, then connect here.',
  },
  categories: [
    {
      kind: 'category',
      name: 'Pin I/O',
      colour: '230',
      contents: [
        { kind: 'block', type: 'pin_set_mode' },
        { kind: 'block', type: 'pin_digital_write' },
        { kind: 'block', type: 'pin_digital_read' },
        { kind: 'block', type: 'arduino_builtin_led' },
      ],
    },
    {
      kind: 'category',
      name: 'Analog',
      colour: '30',
      contents: [
        { kind: 'block', type: 'arduino_analog_read' },
        {
          kind: 'block',
          type: 'arduino_analog_write',
          inputs: {
            VALUE: { shadow: { type: 'math_number', fields: { NUM: 128 } } },
          },
        },
        {
          kind: 'block',
          type: 'arduino_map',
          inputs: {
            VALUE: { shadow: { type: 'math_number', fields: { NUM: 512 } } },
            FROM_LOW: { shadow: { type: 'math_number', fields: { NUM: 0 } } },
            FROM_HIGH: { shadow: { type: 'math_number', fields: { NUM: 1023 } } },
            TO_LOW: { shadow: { type: 'math_number', fields: { NUM: 0 } } },
            TO_HIGH: { shadow: { type: 'math_number', fields: { NUM: 255 } } },
          },
        },
      ],
    },
    {
      kind: 'category',
      name: 'Servo',
      colour: '260',
      contents: [
        { kind: 'block', type: 'arduino_servo_attach' },
        {
          kind: 'block',
          type: 'arduino_servo_write',
          inputs: {
            ANGLE: { shadow: { type: 'math_number', fields: { NUM: 90 } } },
          },
        },
      ],
    },
    {
      kind: 'category',
      name: 'Sound',
      colour: '300',
      contents: [
        {
          kind: 'block',
          type: 'arduino_tone',
          inputs: {
            FREQ: { shadow: { type: 'math_number', fields: { NUM: 440 } } },
            DURATION: { shadow: { type: 'math_number', fields: { NUM: 500 } } },
          },
        },
        { kind: 'block', type: 'arduino_no_tone' },
      ],
    },
    {
      kind: 'category',
      name: 'Time',
      colour: '120',
      contents: [
        {
          kind: 'block',
          type: 'sleep_seconds',
          inputs: {
            SECONDS: { shadow: { type: 'math_number', fields: { NUM: 1 } } },
          },
        },
        {
          kind: 'block',
          type: 'sleep_ms',
          inputs: {
            MS: { shadow: { type: 'math_number', fields: { NUM: 100 } } },
          },
        },
        { kind: 'block', type: 'ticks_ms' },
      ],
    },
    {
      kind: 'category',
      name: 'Serial / Print',
      colour: '60',
      contents: [
        {
          kind: 'block',
          type: 'print_text',
          inputs: {
            TEXT: { shadow: { type: 'text', fields: { TEXT: 'Hello!' } } },
          },
        },
      ],
    },
  ],
});
