/**
 * Chord Transposition Utility
 * Uses tonal.js for accurate music theory-based transposition
 */
import { Note, Interval, Chord } from "tonal";

// All chromatic notes for transposition
const CHROMATIC_SCALE = [
  "C",
  "C#",
  "D",
  "D#",
  "E",
  "F",
  "F#",
  "G",
  "G#",
  "A",
  "A#",
  "B",
];
const CHROMATIC_FLATS = [
  "C",
  "Db",
  "D",
  "Eb",
  "E",
  "F",
  "Gb",
  "G",
  "Ab",
  "A",
  "Bb",
  "B",
];

// Common key signatures (for UI display)
export const KEY_OPTIONS = [
  { value: "C", label: "C Major" },
  { value: "C#", label: "C# / Db Major" },
  { value: "D", label: "D Major" },
  { value: "D#", label: "D# / Eb Major" },
  { value: "E", label: "E Major" },
  { value: "F", label: "F Major" },
  { value: "F#", label: "F# / Gb Major" },
  { value: "G", label: "G Major" },
  { value: "G#", label: "G# / Ab Major" },
  { value: "A", label: "A Major" },
  { value: "A#", label: "A# / Bb Major" },
  { value: "B", label: "B Major" },
  { value: "Cm", label: "C Minor" },
  { value: "Dm", label: "D Minor" },
  { value: "Em", label: "E Minor" },
  { value: "Fm", label: "F Minor" },
  { value: "Gm", label: "G Minor" },
  { value: "Am", label: "A Minor" },
  { value: "Bm", label: "B Minor" },
];

/**
 * Get the semitone value of a note (0-11)
 */
function getNoteIndex(note) {
  const normalized = note.replace(/b/g, "").replace(/#/g, "");
  let index = CHROMATIC_SCALE.indexOf(normalized.toUpperCase());

  // Handle sharps
  if (note.includes("#")) {
    index = (index + 1) % 12;
  }
  // Handle flats
  if (note.includes("b") || note.includes("♭")) {
    index = (index - 1 + 12) % 12;
  }

  return index;
}

/**
 * Calculate semitone difference between two keys
 */
export function getSemitoneDistance(fromKey, toKey) {
  const fromRoot = fromKey.replace(/m$/, ""); // Remove minor suffix
  const toRoot = toKey.replace(/m$/, "");

  const fromIndex = getNoteIndex(fromRoot);
  const toIndex = getNoteIndex(toRoot);

  return (toIndex - fromIndex + 12) % 12;
}

/**
 * Transpose a single chord by semitones
 * @param {string} chord - The chord to transpose (e.g., "Am7", "F#m", "Cmaj7")
 * @param {number} semitones - Number of semitones to transpose
 * @param {boolean} useFlats - Whether to use flats instead of sharps
 * @returns {string} - The transposed chord
 */
export function transposeChord(chord, semitones, useFlats = false) {
  if (!chord || semitones === 0) return chord;

  // Parse the chord to extract root and quality
  const match = chord.match(/^([A-Ga-g][#b♯♭]?)(.*)$/);
  if (!match) return chord;

  const [, root, quality] = match;

  // Get current note index
  const currentIndex = getNoteIndex(root);

  // Calculate new index
  const newIndex = (currentIndex + semitones + 12) % 12;

  // Get new root note
  const scale = useFlats ? CHROMATIC_FLATS : CHROMATIC_SCALE;
  const newRoot = scale[newIndex];

  // Preserve original case
  const finalRoot =
    root === root.toLowerCase() ? newRoot.toLowerCase() : newRoot;

  return finalRoot + quality;
}

/**
 * Transpose all chords in a string
 * @param {string} text - Text containing chords in brackets or standalone
 * @param {number} semitones - Semitones to transpose
 * @param {boolean} useFlats - Use flats instead of sharps
 */
export function transposeText(text, semitones, useFlats = false) {
  if (!text || semitones === 0) return text;

  // Match chords in various formats: [Am7], (Cmaj7), or standalone chord patterns
  const chordPattern =
    /(\[?)([A-G][#b♯♭]?(?:m|maj|min|dim|aug|sus|add)?[0-9]?(?:\/[A-G][#b♯♭]?)?)(\]?)/g;

  return text.replace(chordPattern, (match, open, chord, close) => {
    // Handle slash chords (e.g., C/G)
    if (chord.includes("/")) {
      const [main, bass] = chord.split("/");
      const transposedMain = transposeChord(main, semitones, useFlats);
      const transposedBass = transposeChord(bass, semitones, useFlats);
      return `${open}${transposedMain}/${transposedBass}${close}`;
    }
    return `${open}${transposeChord(chord, semitones, useFlats)}${close}`;
  });
}

/**
 * Parse lyrics with inline chords
 * Chord format: [Chord] before the word/syllable it applies to
 * Example: "[Am]Amazing [G]grace how [D]sweet the [Am]sound"
 *
 * @param {string} lyrics - Raw lyrics with chords
 * @returns {Array} - Array of lines, each containing segments with chord/text pairs
 */
export function parseLyricsWithChords(lyrics) {
  if (!lyrics) return [];

  const lines = lyrics.split("\n");

  return lines.map((line) => {
    const segments = [];
    let currentPosition = 0;

    // Match chord patterns like [Am], [G7], [F#m], etc.
    const chordRegex =
      /\[([A-G][#b♯♭]?(?:m|maj|min|dim|aug|sus|add|M)?[0-9]*(?:\/[A-G][#b♯♭]?)?)\]/g;
    let match;

    while ((match = chordRegex.exec(line)) !== null) {
      // Add text before this chord (if any)
      if (match.index > currentPosition) {
        const textBefore = line.substring(currentPosition, match.index);
        if (textBefore) {
          // This text has no chord above it
          segments.push({ chord: null, text: textBefore });
        }
      }

      // Find the text after the chord until the next chord or end of line
      const chordEnd = match.index + match[0].length;
      const nextChordMatch = line.substring(chordEnd).match(/\[[A-G]/);
      const nextChordIndex = nextChordMatch
        ? chordEnd + nextChordMatch.index
        : line.length;

      const textAfter = line.substring(chordEnd, nextChordIndex);

      segments.push({
        chord: match[1],
        text: textAfter || " ", // At least a space for positioning
      });

      currentPosition = nextChordIndex;
    }

    // Add remaining text without chord
    if (currentPosition < line.length) {
      const remaining = line.substring(currentPosition);
      if (remaining) {
        segments.push({ chord: null, text: remaining });
      }
    }

    // If no chords found, the whole line is text
    if (segments.length === 0) {
      segments.push({ chord: null, text: line });
    }

    return segments;
  });
}

/**
 * Convert plain lyrics with chord lines to inline format
 * Handles the common format where chords are on their own line above lyrics
 *
 * Example input:
 *   Am         G        D
 *   Amazing grace how sweet
 *
 * Output:
 *   [Am]Amazing [G]grace how [D]sweet
 */
export function convertChordLinestoInline(lyrics) {
  if (!lyrics) return lyrics;

  const lines = lyrics.split("\n");
  const result = [];

  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    const nextLine = lines[i + 1];

    // Check if this line is a chord line (contains mostly chord patterns)
    if (isChordLine(line) && nextLine && !isChordLine(nextLine)) {
      // Merge chord line with lyric line below
      const merged = mergeChordAndLyricLines(line, nextLine);
      result.push(merged);
      i++; // Skip the lyric line since we merged it
    } else if (!isChordLine(line)) {
      // Regular lyric or section header
      result.push(line);
    }
    // Skip standalone chord lines without lyrics below
  }

  return result.join("\n");
}

/**
 * Check if a line contains primarily chords
 */
function isChordLine(line) {
  if (!line || line.trim().length === 0) return false;

  // Remove all chord patterns and see what's left
  const withoutChords = line
    .replace(
      /[A-G][#b♯♭]?(?:m|maj|min|dim|aug|sus|add|M)?[0-9]*(?:\/[A-G][#b♯♭]?)?/g,
      "",
    )
    .trim();
  const originalContent = line.replace(/\s+/g, "");

  // If removing chords leaves very little, it's a chord line
  return withoutChords.length < originalContent.length * 0.3;
}

/**
 * Merge a chord line with the lyric line below it
 */
function mergeChordAndLyricLines(chordLine, lyricLine) {
  const chordPositions = [];

  // Find all chords and their positions
  const chordRegex =
    /([A-G][#b♯♭]?(?:m|maj|min|dim|aug|sus|add|M)?[0-9]*(?:\/[A-G][#b♯♭]?)?)/g;
  let match;

  while ((match = chordRegex.exec(chordLine)) !== null) {
    chordPositions.push({
      chord: match[1],
      position: match.index,
    });
  }

  // Sort by position (in case regex finds them out of order)
  chordPositions.sort((a, b) => a.position - b.position);

  // Build the merged line by inserting chords into the lyric
  let result = "";
  let lastPos = 0;

  for (const { chord, position } of chordPositions) {
    // Add lyrics up to this chord position
    const lyricsBefore = lyricLine.substring(
      lastPos,
      Math.min(position, lyricLine.length),
    );
    result += lyricsBefore + `[${chord}]`;
    lastPos = Math.min(position, lyricLine.length);
  }

  // Add remaining lyrics
  result += lyricLine.substring(lastPos);

  return result;
}

/**
 * Extract the original key from song data
 */
export function extractOriginalKey(song) {
  if (!song) return "C";

  // Check various possible fields
  if (song.key_chord)
    return song.key_chord.replace(/\s+/g, "").split(/[,\/]/)[0];
  if (song.chords) return song.chords.replace(/\s+/g, "").split(/[,\/\s]/)[0];
  if (song.original_key) return song.original_key;
  if (song.key) return song.key;

  // Try to detect from lyrics
  const lyrics = song.lyrics || "";
  const firstChord = lyrics.match(/\[([A-G][#b♯♭]?m?)\]/);
  if (firstChord) return firstChord[1];

  return "C"; // Default
}

/**
 * Transpose entire lyrics to a new key
 */
export function transposeLyrics(lyrics, fromKey, toKey) {
  if (!lyrics || fromKey === toKey) return lyrics;

  const semitones = getSemitoneDistance(fromKey, toKey);

  // Determine if we should use flats based on the target key
  const flatKeys = [
    "F",
    "Bb",
    "Eb",
    "Ab",
    "Db",
    "Gb",
    "Dm",
    "Gm",
    "Cm",
    "Fm",
    "Bbm",
    "Ebm",
  ];
  const useFlats = flatKeys.includes(toKey);

  return transposeText(lyrics, semitones, useFlats);
}

export default {
  KEY_OPTIONS,
  transposeChord,
  transposeText,
  parseLyricsWithChords,
  convertChordLinestoInline,
  getSemitoneDistance,
  transposeLyrics,
  extractOriginalKey,
};
