import { KGCore } from '../core/KGCore';
import { pianoRollIndexToPitch } from './midiUtil';
import type { KeySignature } from '../core/KGProject';

/**
 * Extracts the root note from a key signature string
 * @param keySignature - Key signature like "C major", "F# minor", "Bb major"
 * @returns Root note like "C", "F#", "Bb"
 */
export const getRootNoteFromKeySignature = (keySignature: KeySignature): string => {
  // Extract the note before " major" or " minor"
  const match = keySignature.match(/^([A-G][#b]?)\s+(major|minor)$/);
  if (!match) {
    console.warn(`Invalid key signature format: ${keySignature}, defaulting to C`);
    return 'C';
  }
  return match[1];
};

/**
 * Converts a note name (without octave) to pitch class (0-11)
 * @param noteName - Note name like "C", "C#", "Db", "F#"
 * @returns Pitch class (0=C, 1=C#/Db, 2=D, ..., 11=B)
 */
export const noteNameToPitchClass = (noteName: string): number => {
  const noteMap: { [key: string]: number } = {
    'C': 0, 'C#': 1, 'Db': 1,
    'D': 2, 'D#': 3, 'Eb': 3,
    'E': 4,
    'F': 5, 'F#': 6, 'Gb': 6,
    'G': 7, 'G#': 8, 'Ab': 8,
    'A': 9, 'A#': 10, 'Bb': 10,
    'B': 11
  };

  if (!(noteName in noteMap)) {
    console.warn(`Invalid note name: ${noteName}, defaulting to C (0)`);
    return 0;
  }

  return noteMap[noteName];
};

/**
 * Calculates the pitch classes (0-11) that belong to a scale
 * @param rootNote - Root note like "C", "F#", "Bb"
 * @param modeSteps - Mode interval steps (e.g., [2, 2, 1, 2, 2, 2, 1] for ionian)
 * @returns Array of pitch classes in the scale
 */
export const getScalePitchClasses = (rootNote: string, modeSteps: number[]): number[] => {
  const rootPitchClass = noteNameToPitchClass(rootNote);
  const scalePitchClasses: number[] = [rootPitchClass];

  let currentPitch = rootPitchClass;
  for (const step of modeSteps.slice(0, -1)) { // Exclude last step (returns to root)
    currentPitch = (currentPitch + step) % 12;
    scalePitchClasses.push(currentPitch);
  }

  return scalePitchClasses;
};

/**
 * Gets the mode steps for a given mode id
 * @param modeId - ID of the mode (e.g., "ionian", "aeolian")
 * @returns Array of interval steps, or default ionian if not found
 */
export const getModeSteps = (modeId: string): number[] => {
  const functionalChords = KGCore.FUNCTIONAL_CHORDS_DATA[modeId];
  if (!functionalChords || !functionalChords.steps) {
    console.warn(`Mode not found: ${modeId}, defaulting to ionian`);
    return [2, 2, 1, 2, 2, 2, 1]; // Default to ionian (major scale)
  }
  return functionalChords.steps;
};

/**
 * Transposes a note name by a given number of semitones
 * @param noteName - Note name like "C", "C#", "Db"
 * @param semitones - Number of semitones to transpose (positive or negative)
 * @returns Transposed note name
 */
const transposeNote = (noteName: string, semitones: number): string => {
  const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
  const pitchClass = noteNameToPitchClass(noteName);
  const newPitchClass = (pitchClass + semitones + 12) % 12;
  return noteNames[newPitchClass];
};

/**
 * Gets the list of suitable chords for a given key, mode, and functional harmony group
 * @param keySignature - Key signature like "C major", "F# minor", "Bb major"
 * @param modeId - Mode ID (e.g., "ionian", "aeolian", "dorian")
 * @param functionType - Functional harmony group: "T" (Tonic), "S" (Subdominant), or "D" (Dominant)
 * @returns Map of chord symbols to their transposed note arrays, e.g., { "I": ["C", "E", "G"], "vi": ["A", "C", "E"] }
 *         Returns empty object if mode or function not found
 *
 * Example:
 * - getSuitableChords("C major", "ionian", "T") returns { "I": ["C", "E", "G"], "vi": ["A", "C", "E"], ... }
 * - getSuitableChords("D major", "ionian", "T") returns { "I": ["D", "F#", "A"], "vi": ["B", "D", "F#"], ... }
 */
export const getSuitableChords = (
  keySignature: KeySignature,
  modeId: string,
  functionType: 'T' | 'S' | 'D'
): Record<string, string[]> => {
  // Get the functional chords for this mode
  const functionalChords = KGCore.FUNCTIONAL_CHORDS_DATA[modeId];
  if (!functionalChords) {
    console.warn(`No functional chords found for mode: ${modeId}`);
    return {};
  }

  // Get the chord symbols for the specified function (T/S/D)
  const chordSymbols = functionalChords[functionType];
  if (!chordSymbols || chordSymbols.length === 0) {
    console.warn(`No chords found for function ${functionType} in mode ${modeId}`);
    return {};
  }

  // Get the mode-specific chords data
  const modeChords = functionalChords.chords;
  if (!modeChords) {
    console.warn(`No chords data found for mode: ${modeId}`);
    return {};
  }

  // Get the root note from key signature
  const rootNote = getRootNoteFromKeySignature(keySignature);

  // Calculate the transposition interval from C to the root note
  const transposeSemitones = noteNameToPitchClass(rootNote);

  // Build a map of chord symbols to transposed notes
  const chordMap: Record<string, string[]> = {};

  for (const chordSymbol of chordSymbols) {
    // Get the chord notes from mode-specific chords data (in C key)
    const chordNotes = modeChords[chordSymbol];
    if (!chordNotes) {
      console.warn(`Chord symbol not found in mode ${modeId} chords: ${chordSymbol}`);
      continue; // Skip this chord if not found
    }

    // If the key is C, use notes as-is; otherwise transpose
    if (rootNote === 'C') {
      chordMap[chordSymbol] = chordNotes;
    } else {
      // Transpose each note in the chord
      const transposedNotes = chordNotes.map(note => transposeNote(note, transposeSemitones));
      chordMap[chordSymbol] = transposedNotes;
    }
  }

  return chordMap;
};

/**
 * Gets the transposed notes for a specific chord in a given key and mode
 * @param chordSymbol - Chord symbol (e.g., "I", "V7", "ii")
 * @param keySignature - Key signature like "C major", "F# minor"
 * @param modeId - Mode ID (e.g., "ionian", "aeolian")
 * @returns Array of note names for the chord in the specified key, or empty array if not found
 */
export const getChordNotesInKey = (
  chordSymbol: string,
  keySignature: KeySignature,
  modeId: string
): string[] => {
  // Get the functional chords for this mode
  const functionalChords = KGCore.FUNCTIONAL_CHORDS_DATA[modeId];
  if (!functionalChords) {
    console.warn(`No functional chords found for mode: ${modeId}`);
    return [];
  }

  // Get the mode-specific chords data
  const modeChords = functionalChords.chords;
  if (!modeChords) {
    console.warn(`No chords data found for mode: ${modeId}`);
    return [];
  }

  // Get the root note from key signature
  const rootNote = getRootNoteFromKeySignature(keySignature);

  // Get the chord notes from mode-specific chords data (in C key)
  const chordNotes = modeChords[chordSymbol];
  if (!chordNotes) {
    console.warn(`Chord symbol not found in mode ${modeId} chords: ${chordSymbol}`);
    return [];
  }

  // If the key is C, return the notes as-is
  if (rootNote === 'C') {
    return chordNotes;
  }

  // Calculate the transposition interval from C to the root note
  const transposeSemitones = noteNameToPitchClass(rootNote);

  // Transpose each note in the chord
  const transposedNotes = chordNotes.map(note => transposeNote(note, transposeSemitones));

  return transposedNotes;
};

/**
 * Gets matching chords for a given pitch, prioritized by which note in the chord matches
 * @param hoverPitch - The MIDI pitch being hovered over (0-127)
 * @param keySignature - Key signature like "C major", "F# minor"
 * @param modeId - Mode ID (e.g., "ionian", "aeolian")
 * @param functionType - Functional harmony group: "T" (Tonic), "S" (Subdominant), or "D" (Dominant)
 * @returns Array of chord pitch class arrays, prioritized by match position
 *          (root matches first, then 2nd note matches, then 3rd note, etc.)
 *          Each chord is adjusted so its pitch classes work in the same octave as the hover pitch
 *
 * Example:
 * - getMatchingChordsForPitch(60, "C major", "ionian", "T")
 *   Returns chords where C (pitch class 0) appears, with pitch classes adjusted for display
 */
export const getMatchingChordsForPitch = (
  hoverPitch: number,
  keySignature: KeySignature,
  modeId: string,
  functionType: 'T' | 'S' | 'D'
): number[][] => {
  // Get suitable chords for the given key, mode, and function
  const suitableChords = getSuitableChords(keySignature, modeId, functionType);

  if (Object.keys(suitableChords).length === 0) {
    return [];
  }

  // Convert chord note names to ascending pitch classes
  const chordsPitchClasses: Record<string, number[]> = {};

  for (const [chordSymbol, noteNames] of Object.entries(suitableChords)) {
    const pitchClasses: number[] = [];
    let previousPitch = -1;

    for (const noteName of noteNames) {
      let pitchClass = noteNameToPitchClass(noteName);
      // Ensure ascending order by adding 12 if pitch class <= previous
      if (previousPitch >= 0 && pitchClass <= previousPitch) {
        pitchClass += 12;
      }
      pitchClasses.push(pitchClass);
      previousPitch = pitchClass;
    }

    chordsPitchClasses[chordSymbol] = pitchClasses;
  }

  // Get the pitch class of the current hovering note (mod 12)
  const hoverPitchClass = hoverPitch % 12;

  // Group chords by which note position matches the hovering pitch class
  // matchesByPosition[0] = chords where 1st note matches
  // matchesByPosition[1] = chords where 2nd note matches, etc.
  const matchesByPosition: number[][][] = [];

  for (const pitchClasses of Object.values(chordsPitchClasses)) {
    // Check each note position in the chord
    for (let i = 0; i < pitchClasses.length; i++) {
      if (pitchClasses[i] % 12 === hoverPitchClass) {
        // Ensure the array exists for this position
        if (!matchesByPosition[i]) {
          matchesByPosition[i] = [];
        }

        // Offset the chord based on the matched pitch class
        // If the matched note is >= 12 (in the second octave), shift all notes down by 12
        if (pitchClasses[i] >= 12) {
          const offsetChord = pitchClasses.map(p => p - 12);
          matchesByPosition[i].push(offsetChord);
        } else {
          matchesByPosition[i].push(pitchClasses);
        }

        break; // Only count each chord once (at first matching position)
      }
    }
  }

  // Flatten the grouped matches: 1st position matches first, then 2nd, then 3rd, etc.
  const result: number[][] = [];
  for (const matches of matchesByPosition) {
    if (matches) {
      result.push(...matches);
    }
  }

  return result;
};

/**
 * Generates the CSS background-image string for the piano grid with scale highlighting
 * @param selectedMode - Current mode ID (e.g., "ionian", "dorian")
 * @param keySignature - Current key signature (e.g., "C major", "F# minor")
 * @returns CSS background-image string with highlighted scale notes
 */
export const generatePianoGridBackground = (
  selectedMode: string,
  keySignature: KeySignature
): string => {
  const majorGridLineColor = '#404040';
  const minorGridLineColor = '#343434';

  // Get root note and scale pitch classes
  const rootNote = getRootNoteFromKeySignature(keySignature);
  const modeSteps = getModeSteps(selectedMode);
  const scalePitchClasses = getScalePitchClasses(rootNote, modeSteps);

  // Generate horizontal lines for each of 96 rows (8 octaves)
  const horizontalLines = Array.from({ length: 96 }, (_, index) => {
    const pitch = pianoRollIndexToPitch(index);
    const pitchClass = pitch % 12;
    const isInScale = scalePitchClasses.includes(pitchClass);
    const isCRow = pitchClass === 0;

    // Calculate row positions using CSS calc() with --region-piano-key-height variable
    const rowTop = `calc(var(--region-piano-key-height) * ${index})`;
    const rowBottomMinusOne = `calc(var(--region-piano-key-height) * ${index + 1} - 1px)`;
    const rowBottom = `calc(var(--region-piano-key-height) * ${index + 1})`;
    const rowFillColor = isInScale ? '#303030' : '#282828';
    const horizontalLineColor = isCRow ? majorGridLineColor : minorGridLineColor;

    // Match the event list palette while preserving scale-aware row distinction.
    return `
      ${rowFillColor} ${rowTop},
      ${rowFillColor} ${rowBottomMinusOne},
      ${horizontalLineColor} ${rowBottomMinusOne},
      ${horizontalLineColor} ${rowBottom}
    `.trim();
  }).join(',\n');

  // Return complete background-image with vertical and horizontal gradients
  // Major bar lines sit above minor beat lines so bar boundaries remain the primary anchors.
  return `
    linear-gradient(to right,
      transparent calc(var(--region-grid-bar-width) - 1px),
      ${majorGridLineColor} calc(var(--region-grid-bar-width) - 1px),
      ${majorGridLineColor} var(--region-grid-bar-width)
    ),
    linear-gradient(to right,
      transparent calc(var(--region-grid-beat-width) - 1px),
      ${minorGridLineColor} calc(var(--region-grid-beat-width) - 1px),
      ${minorGridLineColor} var(--region-grid-beat-width)
    ),
    linear-gradient(to bottom, ${horizontalLines})
  `;
};

/**
 * Validation result for functional chords JSON
 */
export interface ValidationResult {
  valid: boolean;
  errors: string[];
}

/**
 * Validates functional chords JSON structure
 * @param jsonString - JSON string to validate
 * @returns Validation result with errors if any
 */
export const validateFunctionalChordsJSON = (jsonString: string): ValidationResult => {
  const errors: string[] = [];

  // Try to parse JSON
  let data: unknown;
  try {
    data = JSON.parse(jsonString);
  } catch (error) {
    return { valid: false, errors: ['Invalid JSON format'] };
  }

  // Check if data is an object
  if (typeof data !== 'object' || data === null || Array.isArray(data)) {
    return { valid: false, errors: ['Root must be an object'] };
  }

  // Type guard to treat data as a record
  const dataRecord = data as Record<string, unknown>;

  // Check if ionian mode exists
  if (!dataRecord.ionian) {
    errors.push('Missing required mode: "ionian"');
  }

  // Regex patterns
  const namePattern = /^[A-Za-z0-9_\- ]+$/;
  const romanNumeralPattern = /^♭?[ivIV]+[⁶°+0-9]*$/;
  const notePattern = /^[A-G][b#]?$/;

  // Validate each mode
  for (const [modeId, modeData] of Object.entries(dataRecord)) {
    const modePrefix = `Mode "${modeId}"`;

    // Validate mode structure
    if (typeof modeData !== 'object' || modeData === null || Array.isArray(modeData)) {
      errors.push(`${modePrefix}: must be an object`);
      continue;
    }

    // Type guard for mode object
    const mode = modeData as Record<string, unknown>;

    // Validate name
    if (typeof mode.name !== 'string' || !namePattern.test(mode.name)) {
      errors.push(`${modePrefix}: "name" must be a string with letters, numbers, underscores, dashes, and spaces`);
    }

    // Validate steps
    if (!Array.isArray(mode.steps)) {
      errors.push(`${modePrefix}: "steps" must be an array`);
    } else {
      if (mode.steps.length !== 7) {
        errors.push(`${modePrefix}: "steps" must contain exactly 7 integers`);
      }
      if (!mode.steps.every((step: unknown) => Number.isInteger(step))) {
        errors.push(`${modePrefix}: "steps" must contain only integers`);
      }
      const sum = mode.steps.reduce((acc: number, val: unknown) => acc + (typeof val === 'number' ? val : 0), 0);
      if (sum !== 12) {
        errors.push(`${modePrefix}: "steps" must sum to 12 (got ${sum})`);
      }
    }

    // Collect all chord symbols from T, S, D
    const allChordSymbols = new Set<string>();

    // Validate T, S, D arrays
    for (const functionType of ['T', 'S', 'D']) {
      if (!Array.isArray(mode[functionType])) {
        errors.push(`${modePrefix}: "${functionType}" must be an array`);
        continue;
      }

      for (const chordSymbol of mode[functionType]) {
        if (typeof chordSymbol !== 'string' || !romanNumeralPattern.test(chordSymbol)) {
          errors.push(`${modePrefix}: Invalid chord symbol "${chordSymbol}" in "${functionType}" (must be Roman numeral I-VII with optional ♭ prefix and/or ⁶°+digit suffixes)`);
        }
        allChordSymbols.add(chordSymbol);
      }
    }

    // Validate chords object
    if (typeof mode.chords !== 'object' || mode.chords === null || Array.isArray(mode.chords)) {
      errors.push(`${modePrefix}: "chords" must be an object`);
      continue;
    }

    // Type guard for chords object
    const chords = mode.chords as Record<string, unknown>;

    // Check if all chord symbols are defined in chords
    for (const chordSymbol of allChordSymbols) {
      if (!(chordSymbol in chords)) {
        errors.push(`${modePrefix}: Chord "${chordSymbol}" referenced in T/S/D but not defined in "chords"`);
      }
    }

    // Validate each chord definition
    for (const [chordSymbol, chordNotes] of Object.entries(chords)) {
      if (!Array.isArray(chordNotes)) {
        errors.push(`${modePrefix}: Chord "${chordSymbol}" must be an array of notes`);
        continue;
      }

      for (const note of chordNotes) {
        if (typeof note !== 'string' || !notePattern.test(note)) {
          errors.push(`${modePrefix}: Invalid note "${note}" in chord "${chordSymbol}" (must be A-G with optional b or #)`);
        }
      }
    }
  }

  return {
    valid: errors.length === 0,
    errors
  };
};
