import { Injectable, inject } from '@angular/core';
import { SettingsService } from './settings.service';

export interface ChordSegment {
  text: string;
  chord?: string;
}

export interface ParsedLine {
  segments: ChordSegment[];
  text: string;
}

export interface ParsedSection {
  type: 'verse' | 'chorus' | 'verse_num';
  lines: ParsedLine[];
  verseNumber?: number;
}

@Injectable({
  providedIn: 'root'
})
export class LyricsParserService {
  private settingsService = inject(SettingsService);

  /**
   * Parse plain text (campo 'testo') into structured sections.
   */
  parseText(raw: string): ParsedSection[] {
    if (!raw) return [];
    return this.parseSections(raw, false);
  }

  /**
   * Parse text with inline chords (campo 'accordi') into structured sections.
   */
  parseAccordi(raw: string): ParsedSection[] {
    if (!raw) return [];
    return this.parseSections(raw, true);
  }

  private parseSections(raw: string, withChords: boolean): ParsedSection[] {
    const sections: ParsedSection[] = [];
    let currentType: 'verse' | 'chorus' | 'verse_num' = 'verse';
    let currentLines: ParsedLine[] = [];
    let verseNumCounter = 0;

    const memories = new Map<string, string[]>();
    const copies = new Map<string, string[]>();

    let currentAction: { type: 'memorize' | 'replay' | 'copy' | 'paste'; key: string } | null = null;
    let currentRawLines: string[] = [];

    const lines = raw.split('\n');
    const startTagRegex = /^\{(start_verse|start_chorus|start_verse_num|sov|soc)\}(?:\{([a-z]+)\/([a-zA-Z0-9_-]+)\})?$/;

    for (const line of lines) {
      const trimmed = line.trim();

      const startMatch = trimmed.match(startTagRegex);
      if (startMatch) {
        this.pushSection(sections, currentType, currentLines);
        
        const tag = startMatch[1];
        if (tag === 'start_verse' || tag === 'sov') {
          currentType = 'verse';
        } else if (tag === 'start_chorus' || tag === 'soc') {
          currentType = 'chorus';
        } else if (tag === 'start_verse_num') {
          currentType = 'verse_num';
          verseNumCounter++;
        }

        currentLines = [];
        currentRawLines = [];

        if (startMatch[2] && startMatch[3]) {
          currentAction = {
            type: startMatch[2] as any,
            key: startMatch[3]
          };

          if (currentAction.type === 'paste') {
            const pastedRawLines = copies.get(currentAction.key) || [];
            for (const pLine of pastedRawLines) {
              if (withChords) {
                currentLines.push(this.parseChordLine(pLine));
              } else {
                const cleanLine = pLine.replace(/\[[^\]]*\]/g, '').trim();
                if (cleanLine.length > 0) {
                  currentLines.push({
                    text: cleanLine,
                    segments: [{ text: cleanLine }]
                  });
                }
              }
            }
          }
        } else {
          currentAction = null;
        }
        continue;
      }

      // Detect section end tags
      if (trimmed === '{end_verse}' || trimmed === '{eov}' || trimmed === '{end_chorus}' || trimmed === '{eoc}' || trimmed === '{end_verse_num}') {
        if (currentAction) {
          if (currentAction.type === 'memorize') {
            memories.set(currentAction.key, [...currentRawLines]);
          } else if (currentAction.type === 'copy') {
            copies.set(currentAction.key, [...currentRawLines]);
          }
        }

        this.pushSection(sections, currentType, currentLines, currentType === 'verse_num' ? verseNumCounter : undefined);
        currentLines = [];
        currentRawLines = [];
        currentAction = null;
        continue;
      }

      // Skip structural tags (already handled above)
      if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
        continue;
      }

      // Skip empty lines
      if (trimmed.length === 0) {
        continue;
      }

      let resolvedLine = line;
      if (currentAction && currentAction.type === 'replay') {
        const memLines = memories.get(currentAction.key) || [];
        const lineIndex = currentRawLines.length;
        if (lineIndex < memLines.length) {
          const memLine = memLines[lineIndex];
          const chordMatches = [...memLine.matchAll(/\[([^\]]+)\]/g)].map(m => m[0]);
          let chordIdx = 0;
          resolvedLine = line.replace(/\*/g, () => {
            if (chordIdx < chordMatches.length) {
              return chordMatches[chordIdx++];
            }
            return '';
          });
        } else {
          resolvedLine = line.replace(/\*/g, '');
        }
      }

      currentRawLines.push(line);

      // Parse line
      if (withChords) {
        currentLines.push(this.parseChordLine(resolvedLine));
      } else {
        // CLEAN CHORDS in text-only mode: remove [anything]
        const cleanLine = resolvedLine.replace(/\[[^\]]*\]/g, '').trim();
        if (cleanLine.length > 0) {
          currentLines.push({
            text: cleanLine,
            segments: [{ text: cleanLine }]
          });
        }
      }
    }

    // Push any remaining lines
    this.pushSection(sections, currentType, currentLines);

    return sections;
  }

  private pushSection(sections: ParsedSection[], type: 'verse' | 'chorus' | 'verse_num', lines: ParsedLine[], verseNumber?: number): void {
    if (lines.length > 0) {
      sections.push({ type, lines, verseNumber });
    }
  }

  /**
   * Parse a single line containing inline chord tags.
   * Format: "[RE]Tu sei Re[LA]" → segments with chords positioned above text
   *
   * The chord tag appears BEFORE the text it belongs to:
   *   [RE]Tu sei Re → chord "RE" above "Tu sei Re"
   *
   * But a chord can also appear at the END of text:
   *   sei Re Gesù[SOL] → text "sei Re Gesù" then chord "SOL" with empty text
   */
  parseChordLine(line: string): ParsedLine {
    const segments: ChordSegment[] = [];
    // Clean up non-breaking spaces
    const cleaned = line.replace(/\u00a0/g, ' ').trim();

    // Regex to match [CHORD] tags and text between them
    const chordRegex = /\[([^\]]+)\]/g;
    let lastIndex = 0;
    let match: RegExpExecArray | null;

    while ((match = chordRegex.exec(cleaned)) !== null) {
      // Text before this chord tag
      const textBefore = cleaned.substring(lastIndex, match.index);
      if (textBefore.length > 0) {
        // This text has no chord above it (or belongs to previous chord)
        if (segments.length > 0 && segments[segments.length - 1].text === '') {
          // Previous segment had a chord but no text — attach this text to it
          segments[segments.length - 1].text = textBefore;
        } else {
          segments.push({ text: textBefore });
        }
      }

      // Add the chord as a new segment (text will be filled by next text chunk)
      segments.push({ chord: this.transposeChord(match[1], 0), text: '' });
      lastIndex = match.index + match[0].length;
    }

    // Remaining text after the last chord
    const remaining = cleaned.substring(lastIndex);
    if (remaining.length > 0) {
      if (segments.length > 0 && segments[segments.length - 1].text === '') {
        segments[segments.length - 1].text = remaining;
      } else {
        segments.push({ text: remaining });
      }
    }

    // If no chords found, just return plain text
    if (segments.length === 0) {
      segments.push({ text: cleaned });
    }

    // Build plain text
    const plainText = segments.map(s => s.text).join('');

    return { segments, text: plainText };
  }

  private readonly scale = ['DO', 'DO#', 'RE', 'RE#', 'MI', 'FA', 'FA#', 'SOL', 'SOL#', 'LA', 'LA#', 'SI'];
  private readonly flatScale = ['DO', 'REb', 'RE', 'MIb', 'MI', 'FA', 'SOLb', 'SOL', 'LAb', 'LA', 'SIb', 'SI'];

  /**
   * Transpose a single chord by a given number of semitones.
   * Handles Italian notation.
   */
  transposeChord(chord: string, semitones: number): string {
    if (!chord) return chord;

    // Handle slash chords (e.g., DO/SOL)
    if (chord.includes('/')) {
      return chord.split('/')
        .map(part => this.transposeChord(part.trim(), semitones))
        .join('/');
    }

    const possibleRoots = [...this.scale, ...this.flatScale].sort((a, b) => b.length - a.length);
    let root = '';
    let suffix = '';

    const upperChord = chord.toUpperCase();
    for (const r of possibleRoots) {
      if (upperChord.startsWith(r.toUpperCase())) {
        root = r;
        suffix = chord.substring(r.length);
        break;
      }
    }

    if (!root) return chord;

    const upperRoot = root.toUpperCase();
    let index = this.scale.indexOf(upperRoot);
    if (index === -1) {
      index = this.flatScale.findIndex(n => n.toUpperCase() === upperRoot);
    }
    if (index === -1) return chord;

    let newIndex = (index + semitones) % 12;
    if (newIndex < 0) newIndex += 12;

    // Decide flat vs sharp notation based on SettingsService preference:
    const pref = this.settingsService.chordNotationPreference();
    let useFlat = false;
    if (pref === 'diesis') {
      useFlat = false;
    } else if (pref === 'bemolle') {
      useFlat = true;
    } else {
      // Fallback/Default logic
      if (root.includes('#')) {
        useFlat = false;
      } else if (root.toLowerCase().includes('b')) {
        useFlat = true;
      } else {
        useFlat = semitones < 0;
      }
    }

    let newRoot = useFlat ? this.flatScale[newIndex] : this.scale[newIndex];
    if (newRoot === 'LA#') {
      newRoot = 'SIb';
    } else if (newRoot === 'RE#') {
      newRoot = 'MIb';
    }

    return newRoot + suffix;
  }

  /**
   * Transpose all chords in a parsed structure.
   */
  transposeSections(sections: ParsedSection[], semitones: number): ParsedSection[] {
    return sections.map(section => ({
      ...section,
      lines: section.lines.map(line => ({
        ...line,
        segments: line.segments.map(segment => ({
          ...segment,
          chord: segment.chord ? this.transposeChord(segment.chord, semitones) : undefined
        }))
      }))
    }));
  }

  /**
   * Helper to check if the segment at the given index is contiguous with the next segment.
   * This means the text is split within a word (e.g. by a chord tag like "rima[RE]ne").
   */
  isContiguousNext(segments: ChordSegment[], index: number): boolean {
    if (!segments || index >= segments.length - 1) return false;
    
    // Find the next segment with non-empty text
    let nextWithText: ChordSegment | null = null;
    for (let i = index + 1; i < segments.length; i++) {
      if (segments[i].text && segments[i].text.length > 0) {
        nextWithText = segments[i];
        break;
      }
    }
    
    const currentText = segments[index].text || '';
    if (!currentText) {
      // If current segment has no text, it should have no margin-right to not add extra spacing
      return true;
    }
    
    if (!nextWithText) return false;
    
    const endsWithNonSpace = !/\s$/.test(currentText);
    const startsWithNonSpace = !/^\s/.test(nextWithText.text);
    
    return endsWithNonSpace && startsWithNonSpace;
  }
}

