// Chord Engine - Industry-standard chord transposition and parsing

const NOTES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
const FLAT_NOTES = [
  "C",
  "Db",
  "D",
  "Eb",
  "E",
  "F",
  "Gb",
  "G",
  "Ab",
  "A",
  "Bb",
  "B",
];

// Keywords to ignore when parsing chords (section markers)
const SECTION_KEYWORDS = [
  "verse",
  "chorus",
  "pre-chorus",
  "prechorus",
  "bridge",
  "intro",
  "outro",
  "tag",
  "coda",
  "interlude",
  "instrumental",
  "hook",
  "refrain",
  "vamp",
  "ending",
  "turnaround",
  "solo",
  "break",
];

// Chord quality patterns
const CHORD_QUALITIES = {
  major: "",
  minor: "m",
  diminished: "dim",
  augmented: "aug",
  suspended2: "sus2",
  suspended4: "sus4",
  dominant7: "7",
  major7: "maj7",
  minor7: "m7",
  diminished7: "dim7",
  augmented7: "aug7",
  add9: "add9",
  add11: "add11",
  6: "6",
  m6: "m6",
  9: "9",
  m9: "m9",
  11: "11",
  13: "13",
};

// Regex to match chord patterns
const CHORD_REGEX =
  /^([A-G][#b]?)(m|min|maj|dim|aug|sus[24]?|add[0-9]+|[0-9]+)?([0-9]*)?(\/[A-G][#b]?)?$/i;

/**
 * Parse a potential chord string
 * @param {string} text - Text that might be a chord
 * @returns {object|null} Parsed chord object or null if not a valid chord
 */
export function parseChord(text) {
  const trimmed = text.trim();

  // Check if it's a section keyword
  if (
    SECTION_KEYWORDS.some((keyword) => trimmed.toLowerCase().includes(keyword))
  ) {
    return null;
  }

  const match = trimmed.match(CHORD_REGEX);
  if (!match) return null;

  const [, root, quality = "", extension = "", bass = ""] = match;

  return {
    root: normalizeNote(root),
    quality,
    extension,
    bass: bass ? normalizeNote(bass.slice(1)) : null,
    original: trimmed,
  };
}

/**
 * Normalize note name (handle enharmonics)
 * @param {string} note - Note name
 * @returns {string} Normalized note name
 */
function normalizeNote(note) {
  const upper = note.charAt(0).toUpperCase() + note.slice(1);
  // Convert flats to sharps for internal processing
  const flatIndex = FLAT_NOTES.indexOf(upper);
  if (flatIndex !== -1) {
    return NOTES[flatIndex];
  }
  return upper;
}

/**
 * Get the semitone index of a note
 * @param {string} note - Note name
 * @returns {number} Semitone index (0-11)
 */
function getNoteIndex(note) {
  const normalized = normalizeNote(note);
  const index = NOTES.indexOf(normalized);
  return index !== -1 ? index : FLAT_NOTES.indexOf(normalized);
}

/**
 * Transpose a single chord by a number of semitones
 * @param {string} chord - Chord string
 * @param {number} semitones - Number of semitones to transpose
 * @param {boolean} useFlats - Whether to use flats instead of sharps
 * @returns {string} Transposed chord string
 */
export function transposeChord(chord, semitones, useFlats = false) {
  const parsed = parseChord(chord);
  if (!parsed) return chord;

  const noteArray = useFlats ? FLAT_NOTES : NOTES;

  const rootIndex = getNoteIndex(parsed.root);
  const newRootIndex = (rootIndex + semitones + 12) % 12;
  const newRoot = noteArray[newRootIndex];

  let result = newRoot + parsed.quality + parsed.extension;

  if (parsed.bass) {
    const bassIndex = getNoteIndex(parsed.bass);
    const newBassIndex = (bassIndex + semitones + 12) % 12;
    result += "/" + noteArray[newBassIndex];
  }

  return result;
}

/**
 * Get semitones between two keys
 * @param {string} fromKey - Starting key
 * @param {string} toKey - Target key
 * @returns {number} Number of semitones
 */
export function getSemitonesBetween(fromKey, toKey) {
  const fromIndex = getNoteIndex(fromKey);
  const toIndex = getNoteIndex(toKey);
  return (toIndex - fromIndex + 12) % 12;
}

/**
 * Transpose all chords in a lyrics string
 * @param {string} lyrics - Lyrics with chords in brackets [C] or above words
 * @param {number} semitones - Number of semitones to transpose
 * @param {boolean} useFlats - Whether to use flats
 * @returns {string} Transposed lyrics
 */
export function transposeLyrics(lyrics, semitones, useFlats = false) {
  // Handle chords in brackets [C]
  const bracketPattern = /\[([^\]]+)\]/g;
  return lyrics.replace(bracketPattern, (match, chord) => {
    const transposed = transposeChord(chord, semitones, useFlats);
    return `[${transposed}]`;
  });
}

/**
 * Extract all unique chords from lyrics
 * @param {string} lyrics - Lyrics with chords
 * @returns {string[]} Array of unique chords
 */
export function extractChords(lyrics) {
  const bracketPattern = /\[([^\]]+)\]/g;
  const chords = new Set();
  let match;

  while ((match = bracketPattern.exec(lyrics)) !== null) {
    const chord = match[1];
    if (parseChord(chord)) {
      chords.add(chord);
    }
  }

  return Array.from(chords);
}

/**
 * Detect the likely key of a song based on its chords
 * @param {string[]} chords - Array of chords
 * @returns {string} Detected key
 */
export function detectKey(chords) {
  if (chords.length === 0) return "C";

  // Simple heuristic: first and last chords often indicate the key
  const firstChord = parseChord(chords[0]);
  const lastChord = parseChord(chords[chords.length - 1]);

  // If they match, high confidence
  if (firstChord && lastChord && firstChord.root === lastChord.root) {
    return firstChord.root + (firstChord.quality.includes("m") ? "m" : "");
  }

  // Otherwise use first chord
  if (firstChord) {
    return firstChord.root + (firstChord.quality.includes("m") ? "m" : "");
  }

  return "C";
}

/**
 * Format chord for display (convert internal format to display format)
 * @param {string} chord - Chord in internal format
 * @param {object} options - Display options
 * @returns {string} Formatted chord
 */
export function formatChord(chord, options = {}) {
  const { useFlats = false, useSuperscript = false } = options;

  let formatted = chord;

  if (useFlats) {
    NOTES.forEach((sharp, i) => {
      if (sharp.includes("#")) {
        formatted = formatted.replace(sharp, FLAT_NOTES[i]);
      }
    });
  }

  return formatted;
}

/**
 * Get all keys for key selector
 * @param {boolean} useFlats - Whether to use flats
 * @returns {string[]} Array of keys
 */
export function getAllKeys(useFlats = false) {
  const notes = useFlats ? FLAT_NOTES : NOTES;
  const keys = [];

  notes.forEach((note) => {
    keys.push(note); // Major
    keys.push(note + "m"); // Minor
  });

  return keys;
}

export default {
  parseChord,
  transposeChord,
  transposeLyrics,
  extractChords,
  detectKey,
  formatChord,
  getAllKeys,
  getSemitonesBetween,
  NOTES,
  FLAT_NOTES,
  SECTION_KEYWORDS,
};
