import 'server-only'

import { createAdminClient } from '@/lib/supabase/admin'
import { emailPattern } from '@/lib/like'
import type { CarrierContact, ContactRole } from '@/types/db'

/**
 * Saved contacts (Tasks 98 & 99, 2026-09-09) — the dispatcher's own list of
 * drivers and brokers, so a trip invite is a pick instead of four fields
 * retyped every time.
 *
 * Every read tolerates migration 0014 not being applied yet (project rule
 * since Tasks 39–48): a missing table reads as "no contacts", never a 500.
 */

/** Postgres/PostgREST error for a table that does not exist yet. */
function tableMissing(error: { message?: string; code?: string } | null | undefined): boolean {
  if (!error) return false
  return (
    error.code === '42P01' ||
    error.code === 'PGRST205' ||
    /does not exist|schema cache|could not find the table/i.test(error.message ?? '')
  )
}

export interface ContactsResult {
  /** False when migration 0014 is not applied — surfaces an honest note. */
  available: boolean
  contacts: CarrierContact[]
}

/** This user's address book, most recently used first, then by name. */
export async function listContacts(ownerId: string): Promise<ContactsResult> {
  const admin = createAdminClient()
  const { data, error } = await admin
    .from('carrier_contacts')
    .select('*')
    .eq('owner_id', ownerId)
    .order('last_used_at', { ascending: false, nullsFirst: false })
    .order('name')
  if (tableMissing(error)) return { available: false, contacts: [] }
  return { available: true, contacts: (data ?? []) as CarrierContact[] }
}

/** Only what the invite picker needs, split the way Nash asked for. */
export interface ContactPickerData {
  available: boolean
  drivers: CarrierContact[]
  brokers: CarrierContact[]
  dispatchers: CarrierContact[]
}

export async function loadContactPicker(ownerId: string): Promise<ContactPickerData> {
  const { available, contacts } = await listContacts(ownerId)
  return {
    available,
    drivers: contacts.filter((c) => c.role === 'driver'),
    brokers: contacts.filter((c) => c.role === 'broker'),
    // A freight broker's saved carrier dispatchers (2026-09-13, migration 0016).
    dispatchers: contacts.filter((c) => c.role === 'dispatcher'),
  }
}

/**
 * Mark contacts as used when their people are invited to a trip, so the
 * picker keeps the drivers a dispatcher actually runs at the top.
 *
 * Deliberately does NOT create contacts — whether inviting someone on a trip
 * should also save them permanently is still Nash's call (open question Q6 in
 * docs/TASKS-2026-09-09-MEETING.md), so nothing is written on a guess.
 */
export async function touchContactsForInvites(
  ownerId: string,
  people: Array<{ email: string; role: string }>,
): Promise<void> {
  if (people.length === 0) return
  const admin = createAdminClient()
  const now = new Date().toISOString()
  await Promise.all(
    people
      .filter((p) => p.role === 'driver' || p.role === 'broker' || p.role === 'dispatcher')
      .map((p) =>
        admin
          .from('carrier_contacts')
          .update({ last_used_at: now })
          .eq('owner_id', ownerId)
          .eq('role', p.role)
          .ilike('email', emailPattern(p.email)),
      ),
  ).catch(() => {
    // Missing table or a transient failure must never break trip creation.
  })
}

export type { ContactRole }

/**
 * The freight broker's dispatchers for the New Trip Request picker (Nash,
 * 2026-09-13: "choose any of the dispatchers from his contact list").
 * Saved `dispatcher` contacts first (migration 0016), then every dispatcher
 * who is a participant on one of the broker's trips — de-duplicated by
 * email — so the list is useful from the first day, before any contact has
 * been saved. Trip-derived entries carry a synthetic id and are not stored.
 */
export async function loadBrokerDispatchers(user: {
  id: string
  email: string
  role: string
  internal: boolean
}): Promise<CarrierContact[]> {
  const { contacts } = await listContacts(user.id)
  const saved = contacts.filter((c) => c.role === 'dispatcher')
  const seen = new Set(saved.map((c) => c.email.toLowerCase()))
  const { getVisibleTrips } = await import('@/lib/data/trips')
  const { trips } = await getVisibleTrips(user as Parameters<typeof getVisibleTrips>[0])
  if (trips.length === 0) return saved
  const admin = createAdminClient()
  const { data } = await admin
    .from('trip_participants')
    .select('name, email, phone, phone_ext, user_id, created_at')
    .in('trip_id', trips.map((t) => t.id))
    .eq('role', 'dispatcher')
    .neq('status', 'removed')
    .order('created_at', { ascending: false })
  const derived: CarrierContact[] = []
  for (const p of (data ?? []) as Array<{ name: string; email: string; phone: string | null; phone_ext: string | null; user_id: string | null; created_at: string }>) {
    const key = p.email.toLowerCase()
    if (!p.email || seen.has(key)) continue
    seen.add(key)
    derived.push({
      id: `trip-${key}`,
      owner_id: user.id,
      name: p.name || p.email,
      email: p.email,
      phone: p.phone,
      phone_ext: p.phone_ext,
      role: 'dispatcher',
      user_id: p.user_id,
      invited_at: null,
      last_used_at: p.created_at,
      created_at: p.created_at,
    } as CarrierContact)
  }
  return [...saved, ...derived]
}
