import * as Blockly from 'blockly';

const NOTES = [
  { name: 'C4', freq: 262 },
  { name: 'D4', freq: 294 },
  { name: 'E4', freq: 330 },
  { name: 'F4', freq: 349 },
  { name: 'G4', freq: 392 },
  { name: 'A4', freq: 440 },
  { name: 'B4', freq: 494 },
  { name: 'C5', freq: 523 },
  { name: 'D5', freq: 587 },
  { name: 'E5', freq: 659 },
  { name: 'F5', freq: 698 },
  { name: 'G5', freq: 784 },
  { name: 'A5', freq: 880 },
];

const NOTE_STEP = 7;
const PADDING_Y = 16;
const NOTE_X = 82;
const PICKER_WIDTH = 134;
const PICKER_HEIGHT = PADDING_Y * 2 + 12 * NOTE_STEP;

const STAFF_LINE_POSITIONS = [2, 4, 6, 8, 10];
const NS = 'http://www.w3.org/2000/svg';

function noteY(index) {
  return PADDING_Y + (12 - index) * NOTE_STEP;
}

function svgEl(tag, attrs) {
  const el = document.createElementNS(NS, tag);
  for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v));
  return el;
}

export class FieldPitch extends Blockly.Field {
  SERIALIZABLE = true;
  CURSOR = 'pointer';

  constructor(value, validator) {
    super(String(value ?? '440'), validator);
  }

  static fromJson(options) {
    return new FieldPitch(options['pitch']);
  }

  getText_() {
    const note = NOTES.find(n => String(n.freq) === String(this.getValue()));
    return note ? note.name : String(this.getValue());
  }

  noteIndex_() {
    const v = String(this.getValue());
    const i = NOTES.findIndex(n => String(n.freq) === v);
    return i >= 0 ? i : 5;
  }

  doClassValidation_(newValue) {
    if (newValue == null) return null;
    return NOTES.find(n => String(n.freq) === String(newValue))
      ? String(newValue)
      : null;
  }

  showEditor_() {
    const picker = this.buildPicker_();
    Blockly.DropDownDiv.getContentDiv().appendChild(picker);
    Blockly.DropDownDiv.setColour(
      this.sourceBlock_.style.colourPrimary,
      this.sourceBlock_.style.colourTertiary,
    );
    Blockly.DropDownDiv.showPositionedByField(
      this,
      () => this.teardownPicker_(),
    );
    this.updatePicker_();
  }

  buildPicker_() {
    const svg = (this.pickerSvg_ = svgEl('svg', {
      width: PICKER_WIDTH,
      height: PICKER_HEIGHT,
    }));
    svg.style.cursor = 'pointer';
    svg.style.display = 'block';

    svg.appendChild(
      svgEl('rect', {
        x: 0,
        y: 0,
        width: PICKER_WIDTH,
        height: PICKER_HEIGHT,
        fill: '#fff',
        rx: 4,
        ry: 4,
      }),
    );

    for (const pos of STAFF_LINE_POSITIONS) {
      svg.appendChild(
        svgEl('line', {
          x1: 10,
          y1: noteY(pos),
          x2: PICKER_WIDTH - 10,
          y2: noteY(pos),
          stroke: '#aaa',
          'stroke-width': 1,
        }),
      );
    }

    const clef = svgEl('text', {
      x: 14,
      y: noteY(2) + 8,
      'font-size': 52,
      'font-family':
        '"Noto Music","Noto Sans Symbols2","Segoe UI Symbol",serif',
      fill: '#999',
      'pointer-events': 'none',
    });
    clef.textContent = '\u{1D11E}';
    svg.appendChild(clef);

    this.ledgerGrp_ = svgEl('g', {});
    svg.appendChild(this.ledgerGrp_);

    this.noteEl_ = svgEl('ellipse', { rx: 7, ry: 5, fill: '#000' });
    svg.appendChild(this.noteEl_);

    this.labelEl_ = svgEl('text', {
      'font-size': 11,
      'font-family': 'sans-serif',
      fill: '#333',
      'font-weight': 'bold',
      'pointer-events': 'none',
    });
    svg.appendChild(this.labelEl_);

    this.handleMove_ = (e) => {
      const rect = svg.getBoundingClientRect();
      const y = e.clientY - rect.top;
      const raw = (PADDING_Y + 12 * NOTE_STEP - y) / NOTE_STEP;
      const idx = Math.max(0, Math.min(12, Math.round(raw)));
      this.setValue(String(NOTES[idx].freq));
      this.updatePicker_();
    };
    this.handleClick_ = (e) => {
      this.handleMove_(e);
      Blockly.DropDownDiv.hideWithoutAnimation();
    };
    svg.addEventListener('mousemove', this.handleMove_);
    svg.addEventListener('click', this.handleClick_);

    return svg;
  }

  updatePicker_() {
    if (!this.noteEl_) return;
    const idx = this.noteIndex_();
    const y = noteY(idx);

    this.noteEl_.setAttribute('cx', NOTE_X);
    this.noteEl_.setAttribute('cy', y);
    this.noteEl_.setAttribute('transform', `rotate(-15 ${NOTE_X} ${y})`);

    while (this.ledgerGrp_.firstChild)
      this.ledgerGrp_.removeChild(this.ledgerGrp_.firstChild);
    if (idx <= 0) this.addLedger_(noteY(0));
    if (idx >= 12) this.addLedger_(noteY(12));

    this.labelEl_.textContent = NOTES[idx].name;
    this.labelEl_.setAttribute('x', NOTE_X + 14);
    this.labelEl_.setAttribute('y', y + 4);
  }

  addLedger_(y) {
    this.ledgerGrp_.appendChild(
      svgEl('line', {
        x1: NOTE_X - 14,
        y1: y,
        x2: NOTE_X + 14,
        y2: y,
        stroke: '#aaa',
        'stroke-width': 1,
      }),
    );
  }

  teardownPicker_() {
    if (this.pickerSvg_) {
      if (this.handleMove_)
        this.pickerSvg_.removeEventListener('mousemove', this.handleMove_);
      if (this.handleClick_)
        this.pickerSvg_.removeEventListener('click', this.handleClick_);
    }
    this.pickerSvg_ = null;
    this.noteEl_ = null;
    this.labelEl_ = null;
    this.ledgerGrp_ = null;
    this.handleMove_ = null;
    this.handleClick_ = null;
  }
}

Blockly.fieldRegistry.register('field_pitch', FieldPitch);
