import type { ApiContact } from "@/lib/api/types"
import type { FullContact } from "./types"
import { fullContactDisplayName } from "./types"

export function normalizeContactSearchText(value: string): string {
  return value
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .trim()
}

function queryTokens(query: string): string[] {
  return normalizeContactSearchText(query).split(/\s+/).filter(Boolean)
}

/** Score 0-1. Strict substring match only (no fuzzy). Higher = closer match. */
export function fieldMatchScore(haystack: string, needle: string): number {
  const h = normalizeContactSearchText(haystack)
  const n = normalizeContactSearchText(needle)
  if (!n || !h.includes(n)) return 0
  if (h === n) return 1

  if (h.startsWith(n)) {
    return 0.95 + 0.05 * (n.length / h.length)
  }

  for (const word of h.split(/[\s@._+-]+/)) {
    if (!word) continue
    if (word.startsWith(n)) {
      return 0.88 + 0.07 * (n.length / word.length)
    }
  }

  const idx = h.indexOf(n)
  const positionBonus = 1 - (idx / Math.max(h.length, 1)) * 0.35
  const lengthBonus = n.length / Math.max(h.length, 1)
  return 0.42 + 0.28 * positionBonus + 0.22 * lengthBonus
}

function scoreAgainstFields(fields: string[], query: string): number {
  const tokens = queryTokens(query)
  if (tokens.length === 0) return 0

  let total = 0
  for (const token of tokens) {
    let best = 0
    for (const field of fields) {
      if (!field) continue
      best = Math.max(best, fieldMatchScore(field, token))
      best = Math.max(best, fieldMatchScore(field, query))
    }
    if (best === 0) return 0
    total += best
  }

  return total / tokens.length
}

function fullContactSearchFields(contact: FullContact): string[] {
  const fields = [
    fullContactDisplayName(contact),
    contact.firstName,
    contact.lastName,
    contact.middleName,
    contact.company,
    contact.department,
    contact.jobTitle,
    contact.website,
    contact.notes,
    ...(contact.nicknames ?? []),
    ...contact.emails.map((e) => e.value),
    ...contact.phones.map((p) => p.value),
    ...(contact.addresses ?? []).flatMap((a) => [
      a.street,
      a.city,
      a.region,
      a.postalCode,
      a.country,
    ]),
  ]
  return fields.filter((value): value is string => Boolean(value?.trim()))
}

function apiContactSearchFields(contact: ApiContact): string[] {
  return [contact.full_name, contact.email, contact.phone, contact.org].filter(
    (value): value is string => Boolean(value?.trim())
  )
}

export function scoreFullContact(contact: FullContact, query: string): number {
  return scoreAgainstFields(fullContactSearchFields(contact), query)
}

export function scoreApiContact(contact: ApiContact, query: string): number {
  return scoreAgainstFields(apiContactSearchFields(contact), query)
}

export function rankFullContacts(contacts: FullContact[], query: string): FullContact[] {
  const trimmed = query.trim()
  if (!trimmed) return contacts

  return contacts
    .map((contact) => ({ contact, score: scoreFullContact(contact, trimmed) }))
    .filter((entry) => entry.score > 0)
    .sort((a, b) => {
      if (b.score !== a.score) return b.score - a.score
      const nameA = fullContactDisplayName(a.contact) || a.contact.emails[0]?.value || ""
      const nameB = fullContactDisplayName(b.contact) || b.contact.emails[0]?.value || ""
      return nameA.localeCompare(nameB, "fr")
    })
    .map((entry) => entry.contact)
}

export function rankApiContacts(contacts: ApiContact[], query: string): ApiContact[] {
  const trimmed = query.trim()
  if (!trimmed) return contacts

  return contacts
    .map((contact) => ({ contact, score: scoreApiContact(contact, trimmed) }))
    .filter((entry) => entry.score > 0)
    .sort((a, b) => {
      if (b.score !== a.score) return b.score - a.score
      return a.contact.full_name.localeCompare(b.contact.full_name, "fr")
    })
    .map((entry) => entry.contact)
}
