import type { Email } from "@/lib/email-data"
import type { FullContact } from "@/lib/contacts/types"
import type { SearchParams } from "./search-params"

// ---------------------------------------------------------------------------
// Suggestion types
// ---------------------------------------------------------------------------

export type ContactSuggestion = {
  kind: "contact"
  contact: FullContact
  email: string
  displayName: string
  /** Score 0-1 (higher = better match). */
  score: number
}

export type EmailSuggestion = {
  kind: "email"
  email: string
  displayName: string
  score: number
}

export type SearchSuggestion = ContactSuggestion | EmailSuggestion

// ---------------------------------------------------------------------------
// Prefix matching helpers
// ---------------------------------------------------------------------------

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

function prefixScore(haystack: string, needle: string): number {
  const h = normalize(haystack)
  const n = normalize(needle)
  if (!n) return 0
  if (h === n) return 1
  if (h.startsWith(n)) return 0.9 + 0.1 * (n.length / h.length)
  const idx = h.indexOf(n)
  if (idx > 0) return 0.5 + 0.4 * (n.length / h.length)
  return 0
}

// ---------------------------------------------------------------------------
// matchContacts
// ---------------------------------------------------------------------------

export function matchContacts(
  query: string,
  contacts: FullContact[],
  limit = 5
): ContactSuggestion[] {
  if (!query.trim()) return []
  const results: ContactSuggestion[] = []
  for (const c of contacts) {
    const fullName = `${c.firstName} ${c.lastName}`.trim()
    let bestScore = 0
    let bestEmail = c.emails[0]?.value ?? ""

    bestScore = Math.max(bestScore, prefixScore(fullName, query))
    bestScore = Math.max(bestScore, prefixScore(c.firstName, query))
    bestScore = Math.max(bestScore, prefixScore(c.lastName, query))

    for (const e of c.emails) {
      const s = prefixScore(e.value, query)
      if (s > bestScore) {
        bestScore = s
        bestEmail = e.value
      }
    }

    if (bestScore > 0) {
      results.push({
        kind: "contact",
        contact: c,
        email: bestEmail,
        displayName: fullName,
        score: bestScore,
      })
    }
  }
  results.sort((a, b) => b.score - a.score)
  return results.slice(0, limit)
}

// ---------------------------------------------------------------------------
// matchEmails (unique sender emails from email data)
// ---------------------------------------------------------------------------

export function matchEmails(
  query: string,
  emails: Email[],
  limit = 5
): EmailSuggestion[] {
  if (!query.trim()) return []
  const seen = new Map<string, { name: string; score: number }>()

  for (const e of emails) {
    if (!e.senderEmail) continue
    const addr = e.senderEmail.toLowerCase()
    const name = e.sender
    const s1 = prefixScore(addr, query)
    const s2 = prefixScore(name, query)
    const s = Math.max(s1, s2)
    if (s > 0) {
      const existing = seen.get(addr)
      if (!existing || s > existing.score) {
        seen.set(addr, { name, score: s })
      }
    }
  }

  const results: EmailSuggestion[] = []
  for (const [addr, { name, score }] of seen) {
    results.push({ kind: "email", email: addr, displayName: name, score })
  }
  results.sort((a, b) => b.score - a.score)
  return results.slice(0, limit)
}

// ---------------------------------------------------------------------------
// Ghost text (best completion for the input)
// ---------------------------------------------------------------------------

export function bestCompletion(
  query: string,
  suggestions: SearchSuggestion[]
): string {
  if (!query.trim() || suggestions.length === 0) return ""
  const top = suggestions[0]!
  const q = normalize(query)
  const candidates =
    top.kind === "contact"
      ? [top.email, top.displayName]
      : [top.email, top.displayName]

  for (const c of candidates) {
    const cn = normalize(c)
    if (cn.startsWith(q)) {
      return c.slice(query.length)
    }
  }
  return ""
}

// ---------------------------------------------------------------------------
// filterEmailsBySearchParams
// ---------------------------------------------------------------------------

function withinToMs(within: string): number {
  const map: Record<string, number> = {
    "1d": 86_400_000,
    "3d": 3 * 86_400_000,
    "1w": 7 * 86_400_000,
    "2w": 14 * 86_400_000,
    "1m": 30 * 86_400_000,
    "2m": 60 * 86_400_000,
    "6m": 180 * 86_400_000,
    "1y": 365 * 86_400_000,
  }
  return map[within] ?? 0
}

function textMatchesEmail(email: Email, text: string): boolean {
  const t = normalize(text)
  if (!t) return true

  const fields = [
    email.subject,
    email.preview,
    email.sender,
    email.senderEmail ?? "",
    email.body ?? "",
  ]
  for (const f of fields) {
    if (normalize(f).includes(t)) return true
  }

  if (email.conversation) {
    for (const msg of email.conversation) {
      if (normalize(msg.sender).includes(t)) return true
      if (normalize(msg.senderEmail).includes(t)) return true
      if (normalize(msg.preview).includes(t)) return true
      if (normalize(msg.body).includes(t)) return true
    }
  }

  return false
}

export function filterEmailsBySearchParams(
  emails: Email[],
  params: SearchParams,
  opts?: {
    starredIds?: string[]
    importantIds?: string[]
  }
): Email[] {
  const now = Date.now()
  return emails.filter((email) => {
    if (params.in === "all" && email.spam) return false
    if (params.in === "inbox" && !email.labels?.includes("inbox")) return false
    if (params.in === "sent" && !email.labels?.includes("sent")) return false
    if (params.in === "drafts" && !email.labels?.includes("drafts")) return false
    if (params.in === "spam" && !email.spam) return false
    if (params.in === "trash" && !email.deleted) return false
    if (params.in === "starred") {
      const isStarred =
        email.starred || (opts?.starredIds?.includes(email.id) ?? false)
      if (!isStarred) return false
    }

    if (params.q && !textMatchesEmail(email, params.q)) return false

    if (params.hasWords && !textMatchesEmail(email, params.hasWords))
      return false

    if (params.doesNotHave) {
      if (textMatchesEmail(email, params.doesNotHave)) return false
    }

    if (params.from) {
      const f = normalize(params.from)
      const senderMatch =
        normalize(email.sender).includes(f) ||
        normalize(email.senderEmail ?? "").includes(f)
      if (!senderMatch) return false
    }

    if (params.to) {
      const t = normalize(params.to)
      let found = false
      if (email.conversation) {
        for (const msg of email.conversation) {
          if (
            normalize(msg.sender).includes(t) ||
            normalize(msg.senderEmail).includes(t)
          )
            found = true
        }
      }
      if (
        normalize(email.sender).includes(t) ||
        normalize(email.senderEmail ?? "").includes(t)
      )
        found = true
      if (!found) return false
    }

    if (params.subject) {
      if (!normalize(email.subject).includes(normalize(params.subject)))
        return false
    }

    if (params.has.includes("attachment") && !email.hasAttachment) return false

    if (params.within) {
      const ms = withinToMs(params.within)
      if (ms > 0) {
        const emailDate = new Date(email.date).getTime()
        if (now - emailDate > ms) return false
      }
    }

    if (params.after) {
      const afterDate = new Date(params.after).getTime()
      if (Number.isFinite(afterDate) && new Date(email.date).getTime() < afterDate)
        return false
    }

    if (params.before) {
      const beforeDate = new Date(params.before).getTime()
      if (Number.isFinite(beforeDate) && new Date(email.date).getTime() > beforeDate)
        return false
    }

    if (params.size) {
      const sizeNum = parseFloat(params.size)
      if (Number.isFinite(sizeNum) && sizeNum > 0) {
        const multiplier = params.sizeUnit === "Mo" ? 1_048_576 : 1024
        const thresholdBytes = sizeNum * multiplier
        const totalSize =
          email.attachments?.reduce((a, att) => a + (att.sizeBytes ?? 0), 0) ?? 0
        if (params.sizeOp === "gt" && totalSize <= thresholdBytes) return false
        if (params.sizeOp === "lt" && totalSize >= thresholdBytes) return false
      }
    }

    return true
  })
}
