/**
 * De server-only import (die geïnstalleerd moet worden via pnpm) zorgt ervoor dat de code in dit bestand enkel op de
 * server uitgevoerd kan worden.
 * Als één van de geëxporteerde functies geïmporteerd wordt in een client component of hook zal de applicatie niet
 * compileren en een foutmelding opgooien.
 */
import 'server-only'
import type {SexType} from '@faker-js/faker'
import {fakerNL_BE} from '@faker-js/faker'
import type {Contact} from '@/models/contact'
import {createRow, deleteRow, getRow, getTable, updateRow, writeTable} from '@/dal/databaseSimulation'

/**
 * Onderstaande code is illustratief, vanaf les 2 wordt dit vervangen met Prisma code.
 * Je mag iets als het onderstaande niet gebruiken in je eigen project.
 */

const tableName = 'contacts'

/**
 * Retrieve the contacts for the currently logged-in user.
 */
export async function getContacts(contactName: string): Promise<Contact[]> {
  // Wait for 1 second to demonstrate how suspense works in Next.js.
  await new Promise(resolve => setTimeout(resolve, 1000))
  let contacts = await getTable<Contact>(tableName)

  if (contacts.length === 0) {
    // Generate some test data.
    contacts = [...generateRandomContacts(), ...generateRandomContacts({n: 2, favorite: true})]
    await writeTable(tableName, contacts)
  }

  contactName = contactName.toLowerCase()
  return contacts
    .filter(c => c.firstName?.toLowerCase().includes(contactName) || c.lastName?.toLowerCase().includes(contactName))
    .sort((a, b) => `${a.firstName} ${a.lastName}`.localeCompare(`${b.firstName} ${b.lastName}`))
}

/**
 * Retrieve the contacts for the currently logged-in user.
 */
export async function getContact(id: string): Promise<Contact> {
  return getRow<Contact>(tableName, id)
}

/**
 * Retrieve the contacts which have been added to user's favorites.
 */
export async function getFavoriteContacts(): Promise<Contact[]> {
  // Wait for 0.5 seconds to demonstrate how suspense works in Next.js.
  await new Promise(resolve => setTimeout(resolve, 500))
  let contacts = await getTable<Contact>(tableName)

  if (contacts.length === 0) {
    // Generate some test data.
    contacts = [...generateRandomContacts(), ...generateRandomContacts({n: 2, favorite: true})]
    await writeTable(tableName, contacts)
  }

  return contacts.filter(c => c.favorite)
}

/**
 * Generate 10 random contacts.
 *
 * @param favorite Whether or the contact should be added to the favorites, defaults to false.
 * @param n The number of contacts to generate, defaults to 10.
 */
function generateRandomContacts({favorite, n}: {favorite?: boolean; n?: number} = {}): Contact[] {
  n ??= 10
  favorite ??= false
  const contacts: Contact[] = []
  for (let i = 0; i < n; i++) {
    const sex = fakerNL_BE.person.sex() as SexType
    const firstName = fakerNL_BE.person.firstName(sex)
    const lastName = fakerNL_BE.datatype.boolean() ? fakerNL_BE.person.lastName(sex) : null
    const description = fakerNL_BE.lorem.sentence()

    const contactInfoMap: Record<string, () => string> = {
      email: () => fakerNL_BE.internet.email({firstName: firstName, lastName: lastName ?? undefined}),
      phone: () => fakerNL_BE.phone.number(),
      address: () =>
        fakerNL_BE.location.streetAddress() + ' ' + fakerNL_BE.location.zipCode() + ' ' + fakerNL_BE.location.city(),
      'work phone': () => fakerNL_BE.phone.number(),
      'work email': () => fakerNL_BE.internet.email({firstName: firstName, lastName: lastName ?? undefined}),
      'work place': () => fakerNL_BE.company.name(),
      'job title': () => fakerNL_BE.person.jobTitle(),
    }

    contacts.push({
      id: fakerNL_BE.string.uuid(),
      firstName,
      lastName,
      description,
      avatar: `/avatars/${sex}/AV${sex === 'female' ? fakerNL_BE.number.int({min: 51, max: 100}) : fakerNL_BE.number.int({min: 1, max: 50})}.png`,
      contactInfo: Object.keys(contactInfoMap)
        .filter(() => fakerNL_BE.datatype.boolean())
        .map(k => ({type: k, value: contactInfoMap[k]()})),
      favorite,
    })
  }
  return contacts
}

/**
 * Update an existing contact.
 *
 * @param contact The updated contact info.
 */
export async function updateContact(contact: Contact): Promise<Contact> {
  return updateRow(tableName, contact)
}

/**
 * Delete an existing contact.
 *
 * @param id The id of the contact to delete.
 */
export async function deleteContact(id: string): Promise<void> {
  await deleteRow(tableName, id)
}

/**
 * Create a new contact.
 *
 * @param contact The information for the new contact.
 */
export async function createContact(contact: Omit<Contact, 'id'>): Promise<Contact> {
  return createRow(tableName, {
    ...contact,
    avatar: `https://avatar.iran.liara.run/username?username=${contact.firstName}+${contact.lastName}`,
  })
}

/**
 * Toggle the favorite option for a given contact.
 *
 * @param id The id of the contact for which the favorite column must be toggled.
 */
export async function toggleFavorite(id: string): Promise<Contact> {
  const contacts = await getTable<Contact>(tableName)
  const contact = contacts.find(c => c.id === id)!
  contact.favorite = !contact.favorite
  await writeTable(tableName, contacts)
  return {...contact}
}
