import { Injectable } from '@nestjs/common';
import { TextToMusicDto } from './dto/text-to-music.dto';

type MusicLanguage = 'ar' | 'en';
type MusicInstrument = 'oud' | 'qanun' | 'ney' | 'violin' | 'darbuka' | 'riq' | 'mixed';
type MusicMaqam = 'hijaz' | 'bayati' | 'rast' | 'nahawand' | 'kurd' | 'saba';
type MusicMood =
  | 'sad'
  | 'calm'
  | 'energetic'
  | 'romantic'
  | 'spiritual'
  | 'traditional'
  | 'joyful';
type MusicStyle =
  | 'levantine'
  | 'egyptian'
  | 'gulf'
  | 'andalusian'
  | 'traditional_arabic'
  | 'classical_arabic'
  | 'cinematic_arabic';

export type AiMusicDetectedIntent = {
  language: MusicLanguage;
  instrument: MusicInstrument;
  solo: boolean;
  maqam: MusicMaqam | null;
  mood: MusicMood | null;
  style: MusicStyle | null;
  percussion: 'none' | 'light' | 'medium';
  vocals: boolean;
  tempo: 'slow' | 'medium' | 'fast';
  restrictions: string[];
};

export type AiMusicPromptEnhancement = {
  originalPrompt: string;
  enhancedPrompt: string;
  detectedMusicIntent: AiMusicDetectedIntent;
};

const SAFE_PRODUCTION_INSTRUCTIONS =
  'No artist imitation, no copyrighted melody imitation, clean studio recording, balanced mix.';

const maqamDescriptions: Record<MusicMaqam, string> = {
  hijaz: 'Hijaz maqam, emotional Middle Eastern Arabic sound',
  bayati: 'Bayati maqam, warm Levantine Arabic folk mood',
  rast: 'Rast maqam, classical Arabic optimistic character',
  nahawand: 'Nahawand maqam, dramatic minor Arabic mood',
  kurd: 'Kurd maqam, simple minor Arabic mood',
  saba: 'Saba maqam, deeply sad and expressive Arabic mood',
};

const moodDescriptions: Record<MusicMood, string> = {
  sad: 'sad, emotional, expressive',
  calm: 'calm, soft, slow',
  energetic: 'energetic, heroic, powerful',
  romantic: 'romantic, warm',
  spiritual: 'spiritual, meditative',
  traditional: 'traditional Arabic folk',
  joyful: 'joyful, uplifting',
};

const styleDescriptions: Record<MusicStyle, string> = {
  levantine: 'Levantine Arabic style',
  egyptian: 'Egyptian Arabic music style',
  gulf: 'Gulf Arabic music style',
  andalusian: 'North African and Andalusian Arabic influence',
  traditional_arabic: 'traditional Middle Eastern Arabic style',
  classical_arabic: 'classical Arabic music style',
  cinematic_arabic: 'cinematic Arabic soundtrack',
};

const instrumentDescriptions: Record<MusicInstrument, string> = {
  oud: 'oud',
  qanun: 'qanun',
  ney: 'ney flute',
  violin: 'violin',
  darbuka: 'darbuka',
  riq: 'riq frame drum',
  mixed: 'Arabic ensemble',
};

const instrumentTerms: Record<MusicInstrument, string[]> = {
  oud: ['عود', 'العود', 'عزف عود', 'oud'],
  qanun: ['قانون', 'القانون', 'qanun'],
  ney: ['ناي', 'الناي', 'ney', 'nay'],
  violin: ['كمان', 'كمنجة', 'violin'],
  darbuka: ['دربكة', 'طبلة', 'darbuka'],
  riq: ['رق', 'دف', 'riq'],
  mixed: ['فرقة', 'تخت', 'ensemble', 'mixed'],
};

const maqamTerms: Record<MusicMaqam, string[]> = {
  hijaz: ['حجاز', 'مقام حجاز', 'hijaz'],
  bayati: ['بياتي', 'مقام بياتي', 'bayati'],
  rast: ['راست', 'مقام راست', 'rast'],
  nahawand: ['نهاوند', 'مقام نهاوند', 'nahawand'],
  kurd: ['كرد', 'مقام كرد', 'kurd'],
  saba: ['صبا', 'مقام صبا', 'saba'],
};

const moodTerms: Record<MusicMood, string[]> = {
  sad: ['حزين', 'حزن', 'مؤثر', 'عاطفي', 'sad'],
  calm: ['هادئ', 'هادي', 'رايق', 'ناعم', 'calm', 'soft'],
  energetic: ['حماسي', 'قوي', 'ملحمي', 'energetic', 'heroic'],
  romantic: ['رومانسي', 'romantic'],
  spiritual: ['صوفي', 'روحاني', 'spiritual'],
  traditional: ['تراثي', 'شعبي', 'traditional', 'folk'],
  joyful: ['فرح', 'مبهج', 'joyful', 'uplifting'],
};

const styleTerms: Record<MusicStyle, string[]> = {
  levantine: ['شامي', 'سوري', 'لبناني', 'فلسطيني', 'أردني', 'levantine'],
  egyptian: ['مصري', 'egyptian'],
  gulf: ['خليجي', 'gulf'],
  andalusian: ['مغربي', 'أندلسي', 'اندلسي', 'andalusian', 'north african'],
  traditional_arabic: ['شرقي', 'traditional middle eastern'],
  classical_arabic: ['كلاسيكي عربي', 'classical arabic'],
  cinematic_arabic: ['سينمائي', 'cinematic'],
};

@Injectable()
export class AiMusicPromptEnhancerService {
  enhance(dto: TextToMusicDto): AiMusicPromptEnhancement {
    const originalPrompt = dto.prompt.trim();
    const normalizedPrompt = this.normalize(originalPrompt);
    const language: MusicLanguage = /[\u0600-\u06ff]/.test(originalPrompt) ? 'ar' : 'en';
    const explicitInstrument = this.normalizeKnownKey(dto.instrument, instrumentDescriptions);
    const instrument =
      explicitInstrument ??
      this.detectByTerms(normalizedPrompt, instrumentTerms) ??
      this.detectByTerms(this.normalize((dto.instruments ?? []).join(' ')), instrumentTerms) ??
      'mixed';
    const maqam =
      this.normalizeKnownKey(dto.maqam, maqamDescriptions) ??
      this.detectByTerms(normalizedPrompt, maqamTerms);
    const mood =
      this.normalizeKnownKey(dto.mood, moodDescriptions) ?? this.detectByTerms(normalizedPrompt, moodTerms);
    const style =
      this.normalizeKnownKey(dto.style, styleDescriptions) ??
      this.detectByTerms(normalizedPrompt, styleTerms) ??
      (language === 'ar' ? 'traditional_arabic' : null);
    const restrictions = this.detectRestrictions(normalizedPrompt);
    const solo =
      dto.solo === true ||
      restrictions.includes('solo') ||
      (instrument !== 'mixed' && this.includesAny(normalizedPrompt, ['only', 'solo', 'فقط', 'بس', 'لوحده', 'لحاله']));
    const vocals =
      dto.vocals === true
        ? true
        : dto.vocals === false || restrictions.includes('no_vocals')
          ? false
          : false;
    const percussion =
      dto.percussion ??
      (solo || restrictions.includes('no_percussion')
        ? 'none'
        : this.inferPercussion(normalizedPrompt, instrument));
    const tempo = dto.tempo ?? this.inferTempo(normalizedPrompt, mood);

    const detectedMusicIntent: AiMusicDetectedIntent = {
      language,
      instrument,
      solo,
      maqam,
      mood,
      style,
      percussion,
      vocals,
      tempo,
      restrictions,
    };

    return {
      originalPrompt,
      enhancedPrompt: this.buildPrompt(originalPrompt, detectedMusicIntent),
      detectedMusicIntent,
    };
  }

  private buildPrompt(originalPrompt: string, intent: AiMusicDetectedIntent): string {
    const parts: string[] = [];
    const instrument = instrumentDescriptions[intent.instrument];

    if (intent.solo && intent.instrument !== 'mixed') {
      parts.push(`Solo ${instrument} performance only`);
      parts.push('No backing instruments, no ensemble, no strings, no piano, no other instruments');
    } else if (intent.instrument !== 'mixed') {
      parts.push(`Instrumental Arabic ${instrument} melody`);
    } else {
      parts.push('Instrumental Arabic Middle Eastern music piece');
    }

    if (intent.maqam) {
      parts.push(maqamDescriptions[intent.maqam]);
    }
    if (intent.mood) {
      parts.push(moodDescriptions[intent.mood]);
    }
    if (intent.style) {
      parts.push(styleDescriptions[intent.style]);
    }
    if (intent.restrictions.includes('taqsim') || (intent.solo && intent.instrument === 'oud')) {
      parts.push('Traditional Arabic taqsim improvisation');
    }
    if (intent.percussion === 'none') {
      parts.push('No percussion, no drums');
    } else if (intent.percussion === 'light') {
      parts.push('Light subtle percussion');
    } else {
      parts.push('Medium Arabic percussion groove');
    }
    parts.push(`${intent.tempo} tempo`);
    if (!intent.vocals) {
      parts.push('Instrumental only, no vocals, no lyrics');
    }
    parts.push(SAFE_PRODUCTION_INSTRUCTIONS);
    if (intent.language !== 'ar' && !intent.solo) {
      parts.push(`User idea: ${originalPrompt}`);
    }

    return this.dedupe(parts).join(', ');
  }

  private detectRestrictions(normalizedPrompt: string): string[] {
    const restrictions = new Set<string>();
    if (this.includesAny(normalizedPrompt, ['فقط', 'بس', 'لوحده', 'لحاله', 'only', 'solo'])) {
      restrictions.add('solo');
    }
    if (
      this.includesAny(normalizedPrompt, [
        'بدون غناء',
        'لا غناء',
        'من غير صوت',
        'بدون صوت',
        'no vocals',
        'no lyrics',
      ])
    ) {
      restrictions.add('no_vocals');
    }
    if (
      this.includesAny(normalizedPrompt, [
        'بدون ايقاع',
        'بدون إيقاع',
        'بدون طبله',
        'بدون طبلة',
        'بدون درامز',
        'no percussion',
        'no drums',
      ])
    ) {
      restrictions.add('no_percussion');
    }
    if (this.includesAny(normalizedPrompt, ['بدون الات ثانيه', 'بدون آلات ثانية', 'no other instruments'])) {
      restrictions.add('no_other_instruments');
      restrictions.add('solo');
    }
    if (this.includesAny(normalizedPrompt, ['تقاسيم', 'تقسيم', 'taqsim'])) {
      restrictions.add('taqsim');
    }
    return [...restrictions];
  }

  private inferPercussion(
    normalizedPrompt: string,
    instrument: MusicInstrument,
  ): 'none' | 'light' | 'medium' {
    if (this.includesAny(normalizedPrompt, ['خفيف', 'light', 'ناعم'])) {
      return 'light';
    }
    if (
      instrument === 'darbuka' ||
      instrument === 'riq' ||
      this.includesAny(normalizedPrompt, ['ايقاع', 'إيقاع', 'دربكه', 'دربكة', 'طبله', 'طبلة', 'percussion'])
    ) {
      return 'medium';
    }
    return 'none';
  }

  private inferTempo(normalizedPrompt: string, mood: MusicMood | null): 'slow' | 'medium' | 'fast' {
    if (this.includesAny(normalizedPrompt, ['سريع', 'fast']) || mood === 'energetic') {
      return 'fast';
    }
    if (this.includesAny(normalizedPrompt, ['بطي', 'هاد', 'slow']) || mood === 'sad' || mood === 'calm') {
      return 'slow';
    }
    return 'medium';
  }

  private detectByTerms<T extends string>(
    normalizedPrompt: string,
    termMap: Record<T, string[]>,
  ): T | null {
    for (const [key, terms] of Object.entries(termMap) as Array<[T, string[]]>) {
      if (this.includesAny(normalizedPrompt, terms.map((term) => this.normalize(term)))) {
        return key;
      }
    }
    return null;
  }

  private normalizeKnownKey<T extends string>(value: string | undefined, known: Record<T, string>): T | null {
    if (!value) {
      return null;
    }
    const normalized = this.normalize(value).replace(/\s+/g, '_');
    return normalized in known ? (normalized as T) : null;
  }

  private includesAny(value: string, terms: string[]): boolean {
    return terms.some((term) => value.includes(this.normalize(term)));
  }

  private normalize(value: string): string {
    return value
      .trim()
      .toLowerCase()
      .replace(/[أإآ]/g, 'ا')
      .replace(/ى/g, 'ي')
      .replace(/ة/g, 'ه')
      .replace(/[ًٌٍَُِّْـ]/g, '');
  }

  private dedupe(parts: string[]): string[] {
    const seen = new Set<string>();
    const cleanParts: string[] = [];
    for (const part of parts) {
      const clean = part.trim().replace(/\s+/g, ' ');
      const key = clean.toLowerCase();
      if (clean && !seen.has(key)) {
        cleanParts.push(clean);
        seen.add(key);
      }
    }
    return cleanParts;
  }
}
