export interface MatchableProfile {
  userId: string;
  gender: 'man' | 'woman';
  birthYear: number;
  country: string;
  religion: 'muslim' | 'non_muslim';
  eyeColor: string;
  hairColor: string;
  maritalStatus: 'single' | 'married' | 'divorced' | 'widowed';
  childrenCount: number;
  education: 'secondary' | 'diploma' | 'bachelor' | 'master' | 'doctorate' | 'other';
  occupation: string;
  bio: string;
  valuesText: string;
  appearanceNote: string;
  partnerDescription: string;
  preferredGender: 'man' | 'woman';
  preferredMinAge: number;
  preferredMaxAge: number;
  preferredCountry: string;
  preferredMaritalStatus: MatchableProfile['maritalStatus'] | 'any';
  preferredEducation: MatchableProfile['education'] | 'any';
  preferredReligion: MatchableProfile['religion'];
  preferredEyeColors: string[];
  preferredHairColors: string[];
  acceptsPartnerChildren: boolean;
  preferredMaxChildren: number;
  isComplete: boolean;
  isPublished: boolean;
}

const normalizeText = (value: string): string => value.normalize('NFKD').toLocaleLowerCase()
  .replace(/[\u0300-\u036f\u064B-\u065F\u0670]/g, '')
  .replace(/[أإآٱ]/g, 'ا').replace(/ى/g, 'ي').replace(/ة/g, 'ه').replace(/ؤ/g, 'و').replace(/ئ/g, 'ي')
  .replace(/[^\p{L}\p{N}]+/gu, ' ').trim();

const ignoredWords = new Set([
  'انا', 'اريد', 'ابحث', 'عن', 'في', 'من', 'مع', 'شريك', 'شريكه', 'شريكاً', 'يكون', 'تكون',
  'شخص', 'شخصيه', 'و', 'او', 'the', 'a', 'an', 'and', 'or', 'with', 'who', 'partner', 'person',
]);

const tokens = (value: string): string[] => normalizeText(value).split(/\s+/)
  .filter((word) => word.length >= 3 && !ignoredWords.has(word));

const distance = (first: string, second: string): number => {
  const previous = Array.from({ length: second.length + 1 }, (_, index) => index);
  for (let firstIndex = 1; firstIndex <= first.length; firstIndex += 1) {
    const current = [firstIndex];
    for (let secondIndex = 1; secondIndex <= second.length; secondIndex += 1) {
      current[secondIndex] = Math.min(
        current[secondIndex - 1] + 1,
        previous[secondIndex] + 1,
        previous[secondIndex - 1] + (first[firstIndex - 1] === second[secondIndex - 1] ? 0 : 1),
      );
    }
    previous.splice(0, previous.length, ...current);
  }
  return previous[second.length];
};

const wordsSimilar = (first: string, second: string): boolean => {
  if (first === second) return true;
  if (Math.min(first.length, second.length) >= 5 && (first.includes(second) || second.includes(first))) return true;
  const maximumDistance = Math.max(first.length, second.length) >= 8 ? 2 : 1;
  return distance(first, second) <= maximumDistance;
};

const conceptGroups: Record<string, string[]> = {
  respect: ['احترام', 'محترم', 'respect', 'respectful'],
  family: ['عائله', 'عايلي', 'اسره', 'family', 'familial'],
  calm: ['هادي', 'هادء', 'هدوء', 'calm', 'quiet'],
  honest: ['صادق', 'صدق', 'امين', 'امانه', 'honest', 'trustworthy'],
  kind: ['طيب', 'حنون', 'رحيم', 'ودود', 'kind', 'caring', 'gentle'],
  ambitious: ['طموح', 'ناجح', 'ambitious', 'driven'],
  educated: ['مثقف', 'متعلمين', 'تعليم', 'educated', 'intellectual'],
  religious: ['متدين', 'ملتزم', 'ديني', 'religious', 'practicing'],
  sociable: ['اجتماعي', 'مرح', 'sociable', 'funny', 'outgoing'],
};

const conceptsFor = (words: string[]): Set<string> => new Set(Object.entries(conceptGroups)
  .filter(([, aliases]) => words.some((word) => aliases.some((alias) => wordsSimilar(word, normalizeText(alias)))))
  .map(([concept]) => concept));

const countryGroups: Record<string, string[]> = {
  germany: ['المانيا', 'المانيه', 'germany', 'deutschland'],
  syria: ['سوريا', 'سوريه', 'syria', 'syrien'],
  turkey: ['تركيا', 'تركيه', 'turkey', 'turkiye', 'turkei'],
  austria: ['النمسا', 'austria', 'osterreich'],
  switzerland: ['سويسرا', 'switzerland', 'schweiz'],
  france: ['فرنسا', 'france', 'frankreich'],
  netherlands: ['هولندا', 'netherlands', 'nederland'],
  sweden: ['السويد', 'sweden', 'schweden'],
};

const countryKeys = (value: string): Set<string> => {
  const words = tokens(value);
  const keys = new Set(words);
  for (const [country, aliases] of Object.entries(countryGroups)) {
    if (words.some((word) => aliases.some((alias) => wordsSimilar(word, normalizeText(alias))))) keys.add(country);
  }
  return keys;
};

const countryMatches = (preferred: string, actual: string): boolean => {
  if (preferred.toLocaleLowerCase() === 'any') return true;
  const preferredKeys = countryKeys(preferred);
  const actualKeys = countryKeys(actual);
  return [...preferredKeys].some((word) => [...actualKeys].some((other) => wordsSimilar(word, other)));
};

const descriptionAffinity = (seeker: MatchableProfile, candidate: MatchableProfile): number => {
  const desired = tokens(seeker.partnerDescription);
  if (!desired.length) return 0;
  const candidateWords = tokens([
    candidate.bio, candidate.valuesText, candidate.appearanceNote, candidate.occupation,
  ].join(' '));
  const candidateConcepts = conceptsFor(candidateWords);
  const desiredConcepts = conceptsFor(desired);
  const directMatches = desired.filter((word) => candidateWords.some((other) => wordsSimilar(word, other))).length;
  const conceptMatches = [...desiredConcepts].filter((concept) => candidateConcepts.has(concept)).length;
  const required = Math.max(1, Math.ceil(desired.length * 0.2));
  return Math.min(1, (directMatches + conceptMatches) / required);
};

const acceptsCandidate = (seeker: MatchableProfile, candidate: MatchableProfile): boolean => {
  const age = new Date().getUTCFullYear() - candidate.birthYear;
  return seeker.preferredGender === candidate.gender
    && age >= seeker.preferredMinAge && age <= seeker.preferredMaxAge
    && countryMatches(seeker.preferredCountry, candidate.country)
    && (seeker.preferredMaritalStatus === 'any' || seeker.preferredMaritalStatus === candidate.maritalStatus)
    && (seeker.preferredEducation === 'any' || seeker.preferredEducation === candidate.education)
    && seeker.religion === candidate.religion
    && (seeker.preferredEyeColors.includes('any') || seeker.preferredEyeColors.includes(candidate.eyeColor))
    && (seeker.preferredHairColors.includes('any') || seeker.preferredHairColors.includes(candidate.hairColor))
    && (seeker.acceptsPartnerChildren
      ? candidate.childrenCount <= seeker.preferredMaxChildren : candidate.childrenCount === 0);
};

export const profilesMatch = (first: MatchableProfile, second: MatchableProfile): boolean =>
  first.isComplete && first.isPublished && second.isComplete && second.isPublished
  && acceptsCandidate(first, second) && acceptsCandidate(second, first);

export const profileCompatibilityScore = (first: MatchableProfile, second: MatchableProfile): number =>
  descriptionAffinity(first, second) + descriptionAffinity(second, first);

export const matchReasons = (first: MatchableProfile, second: MatchableProfile): string[] => {
  const reasons = [
    'العمر ضمن المجال الذي حدده الطرفان.',
    first.preferredCountry === 'any' && second.preferredCountry === 'any'
      ? 'لا يشترط أي منكما دولة إقامة محددة.' : 'مكان الإقامة متوافق بمرونة مع تفضيلاتكما.',
    first.preferredMaritalStatus === 'any' && second.preferredMaritalStatus === 'any'
      ? 'الحالة الاجتماعية لا تعارض تفضيلاتكما.' : 'الحالة الاجتماعية توافق ما حدده الطرفان.',
    'الديانة متطابقة تلقائيًا، وتفضيلات المظهر والأطفال متوافقة.',
  ];
  const descriptionScore = profileCompatibilityScore(first, second);
  if (descriptionScore >= 1.5) reasons.push('يوجد تقارب واضح بين الصفات المرغوبة وبيانات الطرفين.');
  else if (descriptionScore > 0) reasons.push('يوجد تقارب جزئي بين الصفات المرغوبة وبيانات الطرفين.');
  return reasons;
};
