import { KGCore } from '../core/KGCore';
import type { KeySignature } from '../core/KGProject';
import type { ChordGuideData, ChordGuideItem, ResolvedChordGuideItem } from '../core/ChordGuideTypes';
import { getChordMidiPitches, parseChordSymbol } from './chordUtil';
import { getRootNoteFromKeySignature, noteNameToPitchClass } from './scaleUtil';

const SHARP_NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];

export type ChordGuideMode = 'ionian' | 'aeolian';
export type ChordGuideFunctionType = 'T' | 'S' | 'D';

export interface MatchingChordGuideCandidate {
  item: ResolvedChordGuideItem;
  matchedPitchIndex: number;
  displayPitchClasses: number[];
}

function getReferenceTonic(mode: ChordGuideMode): string {
  return mode === 'aeolian' ? 'A' : 'C';
}

function getChordGuideData(): ChordGuideData {
  return KGCore.CHORD_GUIDE_DATA ?? {
    ionian: { T: [], S: [], D: [] },
    aeolian: { T: [], S: [], D: [] },
  };
}

function transposePitchClass(pitchClass: number, semitones: number): number {
  return (pitchClass + semitones + 120) % 12;
}

function normalizeSemitoneOffset(semitones: number): number {
  return ((semitones % 12) + 12) % 12;
}

function toResolvedNoteNames(pitches: number[]): string[] {
  return pitches.map((pitch) => SHARP_NOTE_NAMES[((pitch % 12) + 12) % 12]);
}

function resolveChordRootPitchClass(item: ChordGuideItem, semitones: number): number | null {
  const descriptor = parseChordSymbol(item.name);
  if (!descriptor) {
    console.warn(`Unable to parse chord guide symbol: ${item.name}`);
    return null;
  }

  return transposePitchClass(noteNameToPitchClass(descriptor.root), semitones);
}

export function resolveChordGuideItems(
  keySignature: KeySignature,
  mode: ChordGuideMode,
  functionType: ChordGuideFunctionType
): ResolvedChordGuideItem[] {
  const data = getChordGuideData()[mode];
  if (!data) {
    return [];
  }

  const items = data[functionType] ?? [];
  const referenceTonic = getReferenceTonic(mode);
  const targetTonic = getRootNoteFromKeySignature(keySignature);
  const semitoneOffset = normalizeSemitoneOffset(
    noteNameToPitchClass(targetTonic) - noteNameToPitchClass(referenceTonic)
  );

  return items.flatMap((item) => {
    const rootPitchClass = resolveChordRootPitchClass(item, semitoneOffset);
    if (rootPitchClass === null) {
      return [];
    }

    const pitchClasses = getChordMidiPitches(item.name, rootPitchClass);
    if (pitchClasses.length === 0) {
      console.warn(`Unable to resolve pitch classes for chord guide symbol: ${item.name}`);
      return [];
    }

    return [{
      ...item,
      resolvedNotes: toResolvedNoteNames(pitchClasses),
      pitchClasses,
    }];
  });
}

export function getMatchingChordGuideChordsForPitch(
  hoverPitch: number,
  keySignature: KeySignature,
  mode: ChordGuideMode,
  functionType: ChordGuideFunctionType
): number[][] {
  return getMatchingChordGuideCandidatesForPitch(hoverPitch, keySignature, mode, functionType)
    .map((candidate) => candidate.displayPitchClasses);
}

export function getMatchingChordGuideCandidatesForPitch(
  hoverPitch: number,
  keySignature: KeySignature,
  mode: ChordGuideMode,
  functionType: ChordGuideFunctionType
): MatchingChordGuideCandidate[] {
  const resolvedChords = resolveChordGuideItems(keySignature, mode, functionType);
  if (resolvedChords.length === 0) {
    return [];
  }

  const hoverPitchClass = ((hoverPitch % 12) + 12) % 12;
  const matchesByPosition: MatchingChordGuideCandidate[][] = [];

  for (const item of resolvedChords) {
    for (let i = 0; i < item.pitchClasses.length; i++) {
      if (item.pitchClasses[i] % 12 !== hoverPitchClass) {
        continue;
      }

      if (!matchesByPosition[i]) {
        matchesByPosition[i] = [];
      }

      matchesByPosition[i].push({
        item,
        matchedPitchIndex: i,
        displayPitchClasses: item.pitchClasses[i] >= 12
          ? item.pitchClasses.map((pitch) => pitch - 12)
          : item.pitchClasses,
      });
      break;
    }
  }

  return matchesByPosition.flatMap((matches) => matches ?? []);
}
