#!/usr/bin/env python3
"""Generate patches.generated.js from src/patches.h.

Parses patch names and patch command strings from the C header and emits
a self-contained JS module that exposes them to the AMYboard web editor.
"""
import re
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
PATCHES_H = ROOT / "src" / "patches.h"
OUTPUT = ROOT / "build" / "patches.generated.js"

# Regex to match each entry in patch_commands[]:
#   /* 0: Juno A11 Brass Set 1 */ "v1w4a1,...Z",
# The table has reserved NULL gaps (259-383 before the drum kit bank at 384),
# so entries are placed by the index in their comment and gaps become ("", "").
ENTRY_RE = re.compile(
    r'/\*\s*(\d+):\s*(.+?)\s*\*/\s*"((?:[^"\\]|\\.)*)"'
)


def parse_patches(source: str):
    """Return list of (name, code) tuples from patches.h, indexed by patch number."""
    entries = {}
    for m in ENTRY_RE.finditer(source):
        entries[int(m.group(1))] = (m.group(2), m.group(3))
    return [entries.get(i, ("", "")) for i in range(max(entries) + 1)]


def generate_js(patches):
    """Build the complete JS source."""
    names_js = ",\n".join(f'    "{name}"' for name, _ in patches)
    codes_js = ",\n".join(f'    "{code}"' for _, code in patches)

    # Patch 257 is the amyboard default (patches[-1] stopped being it once
    # the drums and drum-kit patches were appended after it)
    amyboard_default = patches[257][1] if len(patches) > 257 else ""

    return f"""\
// AUTO-GENERATED by scripts/gen_patches_js.py — do not edit by hand.
// Source: src/patches.h
(function() {{
  const patches = [
{names_js},
  ];

  const patchCodes = [
{codes_js},
  ];

  window.amy_patches = patches;

  window.patch_code_for_patch_number = patchCodes;

  window.amyboard_patch_string = "{amyboard_default}";

  if (typeof window.onPatchChange !== "function") {{
    window.onPatchChange = function(_patchIndex) {{}};
  }}

  window.init_patches_select = function(selectId) {{
    const select = document.getElementById(selectId);
    if (!select) {{
      return;
    }}
    select.innerHTML = "";
    var groups = [
      {{ label: "AMYboard", start: 0, end: Math.min(patches.length, 128) }},
      {{ label: "DX7", start: 128, end: patches.length }},
    ];
    for (var g = 0; g < groups.length; g++) {{
      var grp = groups[g];
      if (grp.start >= patches.length) continue;
      var optgroup = document.createElement("optgroup");
      optgroup.label = grp.label;
      for (var index = grp.start; index < grp.end; index++) {{
        if (!patches[index]) continue;  // reserved gap
        var option = document.createElement("option");
        option.value = String(index);
        option.textContent = patches[index];
        optgroup.appendChild(option);
      }}
      select.appendChild(optgroup);
    }}
  }};
  window.init_patches_select_range = function(selectId, rangeStart, rangeEnd) {{
    const select = document.getElementById(selectId);
    if (!select) {{
      return;
    }}
    select.innerHTML = "";
    var start = Math.max(0, rangeStart || 0);
    var end = Math.min(patches.length, rangeEnd || patches.length);
    for (var index = start; index < end; index++) {{
      if (!patches[index]) continue;  // reserved gap
      var option = document.createElement("option");
      option.value = String(index);
      option.textContent = patches[index];
      select.appendChild(option);
    }}
  }};
}})();
"""


def main():
    source = PATCHES_H.read_text()
    patches = parse_patches(source)
    OUTPUT.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT.write_text(generate_js(patches))
    print(f"Generated {OUTPUT.relative_to(ROOT)} ({len(patches)} patches)")


if __name__ == "__main__":
    main()
