import type { CarrierContact, TripRole } from '@/types/db'

/**
 * Who counts as "my contacts" when inviting someone to a trip (Nash,
 * 2026-09-18): "their contacts is anyone that they previously worked with —
 * any people that had relations to any of the trips where they were a part
 * of. They automatically become a contact."
 *
 * Two sources, merged here, pure so the rule is testable:
 *   saved   — the address book a person keeps by hand (carrier_contacts)
 *   trip    — everyone who shares a trip with them, derived at read time and
 *             never stored (the trip itself is the record of having worked
 *             together)
 *
 * The person themselves is never a contact. A saved entry wins over a
 * derived one for the same email and role, so hand-corrected details stick.
 * Pilots can be contacts (a dispatcher re-hires the same escort) even though
 * the saved address book does not store that role yet.
 */
export type InviteContactRole = Extract<TripRole, 'driver' | 'broker' | 'dispatcher' | 'pilot'>
export const INVITE_CONTACT_ROLES: readonly InviteContactRole[] = ['driver', 'broker', 'dispatcher', 'pilot']

export interface InviteContact {
  /** Saved: the carrier_contacts id. Derived: `trip:<role>:<email>` — not a row. */
  id: string
  name: string
  email: string
  phone: string | null
  phone_ext: string | null
  role: InviteContactRole
  source: 'saved' | 'trip'
  /** Orders the list — last time they were on a trip with you, or last picked. */
  last_used_at: string | null
  /**
   * The company this person is related to, when known (their account's
   * company). Nash, 2026-09-18: "what if I'm looking for Andre and there is
   * like 14 different Andres?… at least we show what company he's related
   * to… let's show the email too, makes it easier."
   */
  company: string | null
  /** The most recent trip you shared — one more way to tell two people apart. */
  last_trip_ref: string | null
}

export interface DerivedPerson {
  name: string
  email: string
  phone: string | null
  phone_ext: string | null
  role: string
  /** When they joined that trip — the newest wins as last_used_at. */
  created_at: string
  company?: string | null
  trip_ref?: string | null
}

const key = (email: string, role: string) => `${role}:${email.trim().toLowerCase()}`

export function mergeInviteContacts(
  saved: (Pick<CarrierContact, 'id' | 'name' | 'email' | 'phone' | 'phone_ext' | 'role' | 'last_used_at' | 'created_at'> & { company?: string | null })[],
  derived: DerivedPerson[],
  selfEmail: string | null | undefined,
): InviteContact[] {
  const self = (selfEmail ?? '').trim().toLowerCase()
  const out = new Map<string, InviteContact>()

  for (const c of saved) {
    if (!c.email || c.email.trim().toLowerCase() === self) continue
    if (!(INVITE_CONTACT_ROLES as readonly string[]).includes(c.role)) continue
    out.set(key(c.email, c.role), {
      id: c.id,
      name: c.name || c.email,
      email: c.email.trim(),
      phone: c.phone,
      phone_ext: c.phone_ext,
      role: c.role as InviteContactRole,
      source: 'saved',
      last_used_at: c.last_used_at ?? c.created_at,
      company: c.company ?? null,
      last_trip_ref: null,
    })
  }

  for (const p of derived) {
    if (!p.email || p.email.trim().toLowerCase() === self) continue
    if (!(INVITE_CONTACT_ROLES as readonly string[]).includes(p.role)) continue
    const k = key(p.email, p.role)
    const existing = out.get(k)
    if (existing) {
      // A saved entry keeps its details; a derived duplicate only refreshes
      // "last worked together" (and the trip it was) when that is newer, and
      // fills in a company the saved entry did not have.
      const newer = !existing.last_used_at || p.created_at > existing.last_used_at
      if (existing.source === 'trip' && newer) existing.last_used_at = p.created_at
      if (newer || !existing.last_trip_ref) existing.last_trip_ref = p.trip_ref ?? existing.last_trip_ref
      if (!existing.company && p.company) existing.company = p.company
      continue
    }
    out.set(k, {
      id: `trip:${k}`,
      name: p.name || p.email,
      email: p.email.trim(),
      phone: p.phone,
      phone_ext: p.phone_ext,
      role: p.role as InviteContactRole,
      source: 'trip',
      last_used_at: p.created_at,
      company: p.company ?? null,
      last_trip_ref: p.trip_ref ?? null,
    })
  }

  return [...out.values()].sort((a, b) => {
    const t = (b.last_used_at ?? '').localeCompare(a.last_used_at ?? '')
    return t !== 0 ? t : a.name.localeCompare(b.name)
  })
}

/**
 * The groups the picker shows: one per role this inviter may invite, in
 * that order, each with its contacts. Nash's one firm rule (Task 99): "keep
 * them distinguished — separate drivers and brokers — when I go to choose."
 * A role with nobody in it is still listed, so the inviter sees the empty
 * state rather than a missing tab.
 */
export function inviteContactGroups(
  contacts: InviteContact[],
  roles: readonly string[],
): Array<{ role: InviteContactRole; contacts: InviteContact[] }> {
  return INVITE_CONTACT_ROLES.filter((r) => roles.includes(r)).map((role) => ({
    role,
    contacts: contacts.filter((c) => c.role === role),
  }))
}

/**
 * Search-as-you-type over a contact list (Nash, 2026-09-18: "what if I have
 * 400 drivers in my contact book?… a smart search as you type"). Every word
 * of the query must appear somewhere in the name, email, company, phone or
 * last trip number — so "andre pan" finds the Andre at PAN LOGISTICS, and an
 * email fragment tells two people with the same name apart.
 */
export function filterInviteContacts<T extends { name: string; email: string; company?: string | null; phone?: string | null; last_trip_ref?: string | null }>(
  list: T[],
  query: string,
): T[] {
  const words = query.trim().toLowerCase().split(/\s+/).filter(Boolean)
  if (words.length === 0) return list
  return list.filter((c) => {
    const hay = `${c.name} ${c.email} ${c.company ?? ''} ${c.phone ?? ''} ${c.last_trip_ref ?? ''}`.toLowerCase()
    return words.every((w) => hay.includes(w))
  })
}
