'use server'

import { revalidatePath } from 'next/cache'
import prisma from '@/lib/prisma'
import { Note, CheckItem } from '@/lib/types'
import { auth } from '@/auth'
import { getAIProvider } from '@/lib/ai/factory'
import { parseNote as parseNoteUtil, cosineSimilarity, validateEmbedding, calculateRRFK, detectQueryType, getSearchWeights } from '@/lib/utils'
import { getSystemConfig, getConfigNumber, getConfigBoolean, SEARCH_DEFAULTS } from '@/lib/config'
import { contextualAutoTagService } from '@/lib/ai/services/contextual-auto-tag.service'
import { cleanupNoteImages, parseImageUrls, deleteImageFileSafely } from '@/lib/image-cleanup'

/**
 * Champs sélectionnés pour les listes de notes (sans embedding pour économiser ~6KB/note).
 * L'embedding ne charge que pour la recherche sémantique.
 */
const NOTE_LIST_SELECT = {
  id: true,
  title: true,
  content: true,
  color: true,
  isPinned: true,
  isArchived: true,
  trashedAt: true,
  type: true,
  dismissedFromRecent: true,
  checkItems: true,
  labels: true,
  images: true,
  links: true,
  reminder: true,
  isReminderDone: true,
  reminderRecurrence: true,
  reminderLocation: true,
  isMarkdown: true,
  size: true,
  sharedWith: true,
  userId: true,
  order: true,
  notebookId: true,
  createdAt: true,
  updatedAt: true,
  contentUpdatedAt: true,
  autoGenerated: true,
  aiProvider: true,
  aiConfidence: true,
  language: true,
  languageConfidence: true,
  lastAiAnalysis: true,
  // embedding: false — volontairement omis (économise ~6KB JSON/note)
} as const

// Wrapper for parseNote that validates embeddings
function parseNote(dbNote: any): Note {
  const note = parseNoteUtil(dbNote)

  // Validate embedding if present
  if (note.embedding && Array.isArray(note.embedding)) {
    const validation = validateEmbedding(note.embedding)
    if (!validation.valid) {
      return {
        ...note,
        embedding: null
      }
    }
  }

  return note
}

// Helper to get hash color for labels (copied from utils)
function getHashColor(name: string): string {
  const colors = ['red', 'blue', 'green', 'yellow', 'purple', 'pink', 'orange', 'gray']
  let hash = 0
  for (let i = 0; i < name.length; i++) {
    hash = name.charCodeAt(i) + ((hash << 5) - hash)
  }
  return colors[Math.abs(hash) % colors.length]
}

/** Clé stable (carnet + nom) : les étiquettes sont uniques par (notebookId, name) côté Prisma */
function labelScopeKey(notebookId: string | null | undefined, rawName: string): string {
  const name = rawName.trim().toLowerCase()
  if (!name) return ''
  const nb = notebookId ?? ''
  return `${nb}\u0000${name}`
}

function collectLabelNamesFromNote(note: {
  labels: string | null
  labelRelations?: { name: string }[]
}): string[] {
  const names: string[] = []
  if (note.labels) {
    try {
      const parsed: unknown = JSON.parse(note.labels)
      if (Array.isArray(parsed)) {
        for (const l of parsed) {
          if (typeof l === 'string' && l.trim()) names.push(l.trim())
        }
      }
    } catch (e) {
      console.error('[SYNC] Failed to parse labels:', e)
    }
  }
  for (const rel of note.labelRelations ?? []) {
    if (rel.name?.trim()) names.push(rel.name.trim())
  }
  return names
}

/**
 * Sync Label rows with Note.labels.
 * Optimisé: createMany (bulk) + delete en parallèle — uniquement 3-4 requêtes au lieu de N+2.
 */
async function syncLabels(userId: string, noteLabels: string[] = [], notebookId?: string | null) {
  try {
    const nbScope = notebookId ?? null

    // 1. Bulk-upsert les nouveaux labels via upsert en transaction
    if (noteLabels.length > 0) {
      const trimmedNames = [...new Set(
        noteLabels.map(name => name?.trim()).filter((n): n is string => Boolean(n))
      )]

      if (trimmedNames.length > 0) {
        await prisma.$transaction(
          trimmedNames.map(name =>
            prisma.label.upsert({
              where: { notebookId_name: { notebookId: nbScope ?? '', name } as any },
              update: {},
              create: {
                userId,
                name,
                color: getHashColor(name),
                notebookId: nbScope,
              },
            })
          )
        )
      }
    }

    // 2. Récupérer les labels utilisés par toutes les notes de l'utilisateur
    const [allNotes, allLabels] = await Promise.all([
      prisma.note.findMany({
        where: { userId },
        select: {
          notebookId: true,
          labels: true,
          labelRelations: { select: { name: true } },
        },
      }),
      prisma.label.findMany({
        where: { userId },
        select: { id: true, name: true, notebookId: true },
      })
    ])

    const usedLabelsSet = new Set<string>()
    for (const note of allNotes) {
      for (const name of collectLabelNamesFromNote(note)) {
        const key = labelScopeKey(note.notebookId, name)
        if (key) usedLabelsSet.add(key)
      }
    }

    // 3. Supprimer les labels orphelins
    const orphanIds = allLabels
      .filter(label => {
        const key = labelScopeKey(label.notebookId, label.name)
        return key && !usedLabelsSet.has(key)
      })
      .map(label => label.id)

    if (orphanIds.length > 0) {
      // Dissocier les relations avant la suppression
      await prisma.label.updateMany({
        where: { id: { in: orphanIds } },
        data: {} // Nécessaire pour trigger le middleware
      })
      // Supprimer en une seule requête
      await prisma.label.deleteMany({
        where: { id: { in: orphanIds } }
      })
    }
  } catch (error) {
    console.error('Fatal error in syncLabels:', error)
  }
}

/** Après déplacement via API : rattacher les étiquettes de la note au bon carnet */
export async function reconcileLabelsAfterNoteMove(noteId: string, newNotebookId: string | null) {
  const session = await auth()
  if (!session?.user?.id) return
  const note = await prisma.note.findFirst({
    where: { id: noteId, userId: session.user.id },
    select: { labels: true },
  })
  if (!note) return
  let labels: string[] = []
  if (note.labels) {
    try {
      const raw = JSON.parse(note.labels) as unknown
      if (Array.isArray(raw)) {
        labels = raw.filter((x): x is string => typeof x === 'string')
      }
    } catch {
      /* ignore */
    }
  }
  await syncLabels(session.user.id, labels, newNotebookId)
}

// Get all notes (non-archived by default)
export async function getNotes(includeArchived = false) {
  const session = await auth();
  if (!session?.user?.id) return [];

  try {
    const notes = await prisma.note.findMany({
      where: {
        userId: session.user.id,
        trashedAt: null,
        ...(includeArchived ? {} : { isArchived: false }),
      },
      select: NOTE_LIST_SELECT,
      orderBy: [
        { isPinned: 'desc' },
        { order: 'asc' },
        { updatedAt: 'desc' }
      ]
    })

    return notes.map(parseNote)
  } catch (error) {
    console.error('Error fetching notes:', error)
    return []
  }
}

// Get notes with reminders (upcoming, overdue, done)
export async function getNotesWithReminders() {
  const session = await auth();
  if (!session?.user?.id) return [];

  try {
    const notes = await prisma.note.findMany({
      where: {
        userId: session.user.id,
        trashedAt: null,
        isArchived: false,
        reminder: { not: null }
      },
      select: NOTE_LIST_SELECT,
      orderBy: { reminder: 'asc' }
    })

    return notes.map(parseNote)
  } catch (error) {
    console.error('Error fetching notes with reminders:', error)
    return []
  }
}

// Mark a reminder as done / undone
export async function toggleReminderDone(noteId: string, done: boolean) {
  const session = await auth();
  if (!session?.user?.id) return { error: 'Unauthorized' }

  try {
    await prisma.note.update({
      where: { id: noteId, userId: session.user.id },
      data: { isReminderDone: done }
    })
    revalidatePath('/reminders')
    return { success: true }
  } catch (error) {
    console.error('Error toggling reminder done:', error)
    return { error: 'Failed to update reminder' }
  }
}

// Get archived notes only
export async function getArchivedNotes() {
  const session = await auth();
  if (!session?.user?.id) return [];

  try {
    const notes = await prisma.note.findMany({
      where: {
        userId: session.user.id,
        isArchived: true,
        trashedAt: null
      },
      select: NOTE_LIST_SELECT,
      orderBy: { updatedAt: 'desc' }
    })

    return notes.map(parseNote)
  } catch (error) {
    console.error('Error fetching archived notes:', error)
    return []
  }
}

// Search notes - DB-side filtering (fast) with optional semantic search
// Supports contextual search within notebook (IA5)
export async function searchNotes(query: string, useSemantic: boolean = false, notebookId?: string) {
  const session = await auth();
  if (!session?.user?.id) return [];

  try {
    // If query empty, return all notes
    if (!query || !query.trim()) {
      return await getAllNotes();
    }

    // If semantic search is requested, use the full implementation
    if (useSemantic) {
      return await semanticSearch(query, session.user.id, notebookId);
    }

    // DB-side keyword search using LIKE — much faster than loading all notes in memory
    const notes = await prisma.note.findMany({
      where: {
        userId: session.user.id,
        isArchived: false,
        trashedAt: null,
        OR: [
          { title: { contains: query } },
          { content: { contains: query } },
          { labels: { contains: query } },
        ],
      },
      select: NOTE_LIST_SELECT,
      orderBy: [
        { isPinned: 'desc' },
        { order: 'asc' },
        { updatedAt: 'desc' }
      ]
    });

    return notes.map(parseNote);
  } catch (error) {
    console.error('Search error:', error);
    return [];
  }
}

// Semantic search with AI embeddings - SIMPLE VERSION
// Supports contextual search within notebook (IA5)
async function semanticSearch(query: string, userId: string, notebookId?: string) {
  const allNotes = await prisma.note.findMany({
    where: {
      userId: userId,
      isArchived: false,
      trashedAt: null,
      ...(notebookId !== undefined ? { notebookId } : {})
    },
    include: { noteEmbedding: true }
  });

  const queryLower = query.toLowerCase().trim();

  // Get query embedding
  let queryEmbedding: number[] | null = null;
  try {
    const provider = getAIProvider(await getSystemConfig());
    queryEmbedding = await provider.getEmbeddings(query);
  } catch (e) {
    console.error('Failed to generate query embedding:', e);
    // Fallback to simple keyword search
    queryEmbedding = null;
  }

  // Filter notes: keyword match OR semantic match (threshold 30%)
  const results = allNotes.map(note => {
    const title = (note.title || '').toLowerCase();
    const content = note.content.toLowerCase();
    const labels = note.labels ? JSON.parse(note.labels) : [];

    // Keyword match
    const keywordMatch = title.includes(queryLower) ||
      content.includes(queryLower) ||
      labels.some((l: string) => l.toLowerCase().includes(queryLower));

    // Semantic match (if embedding available)
    let semanticMatch = false;
    let similarity = 0;
    if (queryEmbedding && note.noteEmbedding?.embedding) {
      similarity = cosineSimilarity(queryEmbedding, JSON.parse(note.noteEmbedding.embedding));
      semanticMatch = similarity > 0.3; // 30% threshold - works well for related concepts
    }

    return {
      note,
      keywordMatch,
      semanticMatch,
      similarity
    };
  }).filter(r => r.keywordMatch || r.semanticMatch);

  // Parse and add match info
  return results.map(r => {
    const parsed = parseNote(r.note);

    // Determine match type
    let matchType: 'exact' | 'related' | null = null;
    if (r.semanticMatch) {
      matchType = 'related';
    } else if (r.keywordMatch) {
      matchType = 'exact';
    }

    return {
      ...parsed,
      matchType
    };
  });
}

// Create a new note
export async function createNote(data: {
  title?: string
  content: string
  color?: string
  type?: 'text' | 'checklist'
  checkItems?: CheckItem[]
  labels?: string[]
  images?: string[]
  links?: any[]
  isArchived?: boolean
  reminder?: Date | null
  isMarkdown?: boolean
  size?: 'small' | 'medium' | 'large'
  autoGenerated?: boolean
  notebookId?: string | undefined // Assign note to a notebook if provided
  skipRevalidation?: boolean // Option to prevent full page refresh for smooth optimistic UI updates
}) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    // Save note to DB immediately (fast!) — AI operations run in background after
    const note = await prisma.note.create({
      data: {
        userId: session.user.id,
        title: data.title || null,
        content: data.content,
        color: data.color || 'default',
        type: data.type || 'text',
        checkItems: data.checkItems ? JSON.stringify(data.checkItems) : null,
        labels: data.labels && data.labels.length > 0 ? JSON.stringify(data.labels) : null,
        images: data.images ? JSON.stringify(data.images) : null,
        links: data.links ? JSON.stringify(data.links) : null,
        isArchived: data.isArchived || false,
        reminder: data.reminder || null,
        isMarkdown: data.isMarkdown || false,
        size: data.size || 'small',
        autoGenerated: data.autoGenerated || null,
        notebookId: data.notebookId || null,
      }
    })

    // Sync user-provided labels immediately (étiquettes rattachées au carnet de la note)
    if (data.labels && data.labels.length > 0) {
      await syncLabels(session.user.id, data.labels, data.notebookId ?? null)
    }

    if (!data.skipRevalidation) {
      // Revalidate main page (handles both inbox and notebook views via query params)
      revalidatePath('/')
    }

    // Fire-and-forget: run AI operations in background without blocking the response
    const userId = session.user.id
    const noteId = note.id
    const content = data.content
    const notebookId = data.notebookId
    const hasUserLabels = data.labels && data.labels.length > 0

    // Use setImmediate-like pattern to not block the response
    ;(async () => {
      try {
        // Background task 1: Generate embedding
        const provider = getAIProvider(await getSystemConfig())
        const embedding = await provider.getEmbeddings(content)
        if (embedding) {
          await prisma.noteEmbedding.upsert({
            where: { noteId: noteId },
            create: { noteId: noteId, embedding: JSON.stringify(embedding) },
            update: { embedding: JSON.stringify(embedding) }
          })
        }
      } catch (e) {
        console.error('[BG] Embedding generation failed:', e)
      }

      // Background task 2: Auto-labeling (only if no user labels and has notebook)
      if (!hasUserLabels && notebookId) {
        try {
          const autoLabelingEnabled = await getConfigBoolean('AUTO_LABELING_ENABLED', true)
          const autoLabelingConfidence = await getConfigNumber('AUTO_LABELING_CONFIDENCE_THRESHOLD', 70)

          if (autoLabelingEnabled) {
            const suggestions = await contextualAutoTagService.suggestLabels(
              content,
              notebookId,
              userId
            )

            const appliedLabels = suggestions
              .filter(s => s.confidence >= autoLabelingConfidence)
              .map(s => s.label)

            if (appliedLabels.length > 0) {
              await prisma.note.update({
                where: { id: noteId },
                data: { labels: JSON.stringify(appliedLabels) }
              })
              await syncLabels(userId, appliedLabels, notebookId ?? null)
              if (!data.skipRevalidation) {
                revalidatePath('/')
              }
            }
          }
        } catch (error) {
          console.error('[BG] Auto-labeling failed:', error)
        }
      }
    })()

    return parseNote(note)
  } catch (error) {
    console.error('Error creating note:', error)
    throw new Error('Failed to create note')
  }
}

// Update a note
export async function updateNote(id: string, data: {
  title?: string | null
  content?: string
  color?: string
  isPinned?: boolean
  isArchived?: boolean
  type?: 'text' | 'checklist'
  checkItems?: CheckItem[] | null
  labels?: string[] | null
  images?: string[] | null
  links?: any[] | null
  reminder?: Date | null
  isMarkdown?: boolean
  size?: 'small' | 'medium' | 'large'
  autoGenerated?: boolean | null
  notebookId?: string | null
}, options?: { skipContentTimestamp?: boolean; skipRevalidation?: boolean }) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const oldNote = await prisma.note.findUnique({
      where: { id, userId: session.user.id },
      select: { labels: true, notebookId: true, reminder: true }
    })
    const oldLabels: string[] = oldNote?.labels ? JSON.parse(oldNote.labels) : []
    const oldNotebookId = oldNote?.notebookId

    const updateData: any = { ...data }

    // Reset isReminderDone only when reminder date actually changes (not on every save)
    if ('reminder' in data && data.reminder !== null) {
      const newTime = new Date(data.reminder as Date).getTime()
      const oldTime = oldNote?.reminder ? new Date(oldNote.reminder).getTime() : null
      if (newTime !== oldTime) {
        updateData.isReminderDone = false
      }
    }

    // Generate embedding in background — don't block the update
    if (data.content !== undefined) {
      const noteId = id
      const content = data.content
      ;(async () => {
        try {
          const provider = getAIProvider(await getSystemConfig());
          const embedding = await provider.getEmbeddings(content);
          if (embedding) {
            await prisma.noteEmbedding.upsert({
              where: { noteId: noteId },
              create: { noteId: noteId, embedding: JSON.stringify(embedding) },
              update: { embedding: JSON.stringify(embedding) }
            })
          }
        } catch (e) {
          console.error('[BG] Embedding regeneration failed:', e);
        }
      })()
    }

    if ('checkItems' in data) updateData.checkItems = data.checkItems ? JSON.stringify(data.checkItems) : null
    if ('labels' in data) updateData.labels = data.labels ? JSON.stringify(data.labels) : null
    if ('images' in data) updateData.images = data.images ? JSON.stringify(data.images) : null
    if ('links' in data) updateData.links = data.links ? JSON.stringify(data.links) : null
    if ('notebookId' in data) updateData.notebookId = data.notebookId
    // Explicitly handle size to ensure it propagates
    if ('size' in data && data.size) updateData.size = data.size

    // Only update contentUpdatedAt for actual content changes, NOT for property changes
    // (size, color, isPinned, isArchived are properties, not content)
    // skipContentTimestamp=true is used by the inline editor to avoid bumping "Récent" on every auto-save
    const contentFields = ['title', 'content', 'checkItems', 'images', 'links']
    const isContentChange = contentFields.some(field => field in data)
    if (isContentChange && !options?.skipContentTimestamp) {
      updateData.contentUpdatedAt = new Date()
    }

    const note = await prisma.note.update({
      where: { id, userId: session.user.id },
      data: updateData
    })

    // Sync Label rows (carnet + noms) quand les étiquettes changent ou que la note change de carnet
    const notebookMoved =
      data.notebookId !== undefined && data.notebookId !== oldNotebookId
    if (data.labels !== undefined || notebookMoved) {
      const labelsToSync = data.labels !== undefined ? (data.labels || []) : oldLabels
      const effectiveNotebookId =
        data.notebookId !== undefined ? data.notebookId : oldNotebookId
      await syncLabels(session.user.id, labelsToSync, effectiveNotebookId ?? null)
    }

    // Only revalidate for STRUCTURAL changes that affect the page layout/lists
    // Content edits (title, content, size, color) use optimistic UI — no refresh needed
    const structuralFields = ['isPinned', 'isArchived', 'labels', 'notebookId']
    const isStructuralChange = structuralFields.some(field => field in data)

    if (isStructuralChange && !options?.skipRevalidation) {
      revalidatePath('/')
      revalidatePath(`/note/${id}`)

      if (data.notebookId !== undefined && data.notebookId !== oldNotebookId) {
        if (oldNotebookId) {
          revalidatePath(`/notebook/${oldNotebookId}`)
        }
        if (data.notebookId) {
          revalidatePath(`/notebook/${data.notebookId}`)
        }
      }
    }

    return parseNote(note)
  } catch (error) {
    console.error('Error updating note:', error)
    throw error // Re-throw the REAL error, not a generic one
  }
}

// Soft-delete a note (move to trash)
export async function deleteNote(id: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    await prisma.note.update({
      where: { id, userId: session.user.id },
      data: { trashedAt: new Date() }
    })

    revalidatePath('/')
    return { success: true }
  } catch (error) {
    console.error('Error deleting note:', error)
    throw new Error('Failed to delete note')
  }
}

// Trash actions
export async function trashNote(id: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    await prisma.note.update({
      where: { id, userId: session.user.id },
      data: { trashedAt: new Date() }
    })
    revalidatePath('/')
    return { success: true }
  } catch (error) {
    console.error('Error trashing note:', error)
    throw new Error('Failed to trash note')
  }
}

export async function restoreNote(id: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    await prisma.note.update({
      where: { id, userId: session.user.id },
      data: { trashedAt: null }
    })
    revalidatePath('/')
    revalidatePath('/trash')
    return { success: true }
  } catch (error) {
    console.error('Error restoring note:', error)
    throw new Error('Failed to restore note')
  }
}

export async function getTrashedNotes() {
  const session = await auth();
  if (!session?.user?.id) return [];

  try {
    const notes = await prisma.note.findMany({
      where: {
        userId: session.user.id,
        trashedAt: { not: null }
      },
      select: NOTE_LIST_SELECT,
      orderBy: { trashedAt: 'desc' }
    })

    return notes.map(parseNote)
  } catch (error) {
    console.error('Error fetching trashed notes:', error)
    return []
  }
}

export async function permanentDeleteNote(id: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    // Fetch images before deleting so we can clean up files
    const note = await prisma.note.findUnique({
      where: { id, userId: session.user.id },
      select: { images: true }
    })
    const imageUrls = parseImageUrls(note?.images ?? null)

    await prisma.note.delete({ where: { id, userId: session.user.id } })

    // Clean up orphaned image files (safe: skips if referenced by other notes)
    if (imageUrls.length > 0) {
      await cleanupNoteImages(id, imageUrls)
    }

    await syncLabels(session.user.id, [])
    revalidatePath('/trash')
    revalidatePath('/')
    return { success: true }
  } catch (error) {
    console.error('Error permanently deleting note:', error)
    throw new Error('Failed to permanently delete note')
  }
}

export async function emptyTrash() {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    // Fetch trashed notes with images before deleting
    const trashedNotes = await prisma.note.findMany({
      where: {
        userId: session.user.id,
        trashedAt: { not: null }
      },
      select: { id: true, images: true }
    })

    await prisma.note.deleteMany({
      where: {
        userId: session.user.id,
        trashedAt: { not: null }
      }
    })

    // Clean up image files for all deleted notes
    for (const note of trashedNotes) {
      const imageUrls = parseImageUrls(note.images)
      if (imageUrls.length > 0) {
        await cleanupNoteImages(note.id, imageUrls)
      }
    }

    await syncLabels(session.user.id, [])
    revalidatePath('/trash')
    revalidatePath('/')
    return { success: true }
  } catch (error) {
    console.error('Error emptying trash:', error)
    throw new Error('Failed to empty trash')
  }
}

export async function removeImageFromNote(noteId: string, imageIndex: number) {
  const session = await auth()
  if (!session?.user?.id) throw new Error('Unauthorized')

  try {
    const note = await prisma.note.findUnique({
      where: { id: noteId, userId: session.user.id },
      select: { images: true },
    })
    if (!note) throw new Error('Note not found')

    const imageUrls = parseImageUrls(note.images)
    if (imageIndex < 0 || imageIndex >= imageUrls.length) throw new Error('Invalid image index')

    const removedUrl = imageUrls[imageIndex]
    const newImages = imageUrls.filter((_, i) => i !== imageIndex)

    await prisma.note.update({
      where: { id: noteId },
      data: { images: newImages.length > 0 ? JSON.stringify(newImages) : null },
    })

    // Clean up file if no other note references it
    await deleteImageFileSafely(removedUrl, noteId)

    return { success: true }
  } catch (error) {
    console.error('Error removing image:', error)
    throw new Error('Failed to remove image')
  }
}

export async function cleanupOrphanedImages(imageUrls: string[], noteId: string) {
  const session = await auth()
  if (!session?.user?.id) return

  try {
    for (const url of imageUrls) {
      await deleteImageFileSafely(url, noteId)
    }
  } catch {
    // Silent — best-effort cleanup
  }
}

export async function getTrashCount() {
  const session = await auth();
  if (!session?.user?.id) return 0;

  try {
    return await prisma.note.count({
      where: {
        userId: session.user.id,
        trashedAt: { not: null }
      }
    })
  } catch {
    return 0
  }
}

// Toggle functions
export async function togglePin(id: string, isPinned: boolean) { return updateNote(id, { isPinned }) }
export async function toggleArchive(id: string, isArchived: boolean) { return updateNote(id, { isArchived }) }
export async function updateColor(id: string, color: string) { return updateNote(id, { color }) }
export async function updateLabels(id: string, labels: string[]) { return updateNote(id, { labels }) }
export async function removeFusedBadge(id: string) { return updateNote(id, { autoGenerated: null }) }

// Update note size WITHOUT revalidation - client uses optimistic updates
export async function updateSize(id: string, size: 'small' | 'medium' | 'large') {

  const result = await updateNote(id, { size })

  return result
}

// Get all unique labels
export async function getAllLabels() {
  try {
    const notes = await prisma.note.findMany({ select: { labels: true } })
    const labelsSet = new Set<string>()
    notes.forEach((note: any) => {
      const labels = note.labels ? JSON.parse(note.labels) : null
      if (labels) labels.forEach((label: string) => labelsSet.add(label))
    })
    return Array.from(labelsSet).sort()
  } catch (error) {
    console.error('Error fetching labels:', error)
    return []
  }
}

// Reorder
export async function reorderNotes(draggedId: string, targetId: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');
  try {
    const draggedNote = await prisma.note.findUnique({ where: { id: draggedId, userId: session.user.id } })
    const targetNote = await prisma.note.findUnique({ where: { id: targetId, userId: session.user.id } })
    if (!draggedNote || !targetNote) throw new Error('Notes not found')
    const allNotes = await prisma.note.findMany({
      where: { userId: session.user.id, isPinned: draggedNote.isPinned, isArchived: false, trashedAt: null },
      orderBy: { order: 'asc' }
    })
    const reorderedNotes = allNotes.filter((n: any) => n.id !== draggedId)
    const targetIndex = reorderedNotes.findIndex((n: any) => n.id === targetId)
    reorderedNotes.splice(targetIndex, 0, draggedNote)
    const updates = reorderedNotes.map((note: any, index: number) =>
      prisma.note.update({ where: { id: note.id }, data: { order: index } })
    )
    await prisma.$transaction(updates)
    revalidatePath('/')
    return { success: true }
  } catch (error) {
    throw new Error('Failed to reorder notes')
  }
}

export async function updateFullOrder(ids: string[]) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');
  const userId = session.user.id;
  try {
    const updates = ids.map((id: string, index: number) =>
      prisma.note.update({ where: { id, userId }, data: { order: index } })
    )
    await prisma.$transaction(updates)
    revalidatePath('/')
    return { success: true }
  } catch (error) {
    throw new Error('Failed to update order')
  }
}

// Optimized version for drag & drop - no revalidation to prevent double refresh
export async function updateFullOrderWithoutRevalidation(ids: string[]) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');
  const userId = session.user.id;
  try {
    const updates = ids.map((id: string, index: number) =>
      prisma.note.update({ where: { id, userId }, data: { order: index } })
    )
    await prisma.$transaction(updates)
    return { success: true }
  } catch (error) {
    throw new Error('Failed to update order')
  }
}

// Maintenance - Sync all labels and clean up orphans (par carnet, aligné sur syncLabels)
export async function cleanupAllOrphans() {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');
  const userId = session.user.id;
  let createdCount = 0;
  let deletedCount = 0;
  let errors: any[] = [];
  try {
    const allNotes = await prisma.note.findMany({
      where: { userId },
      select: {
        notebookId: true,
        labels: true,
        labelRelations: { select: { name: true } },
      },
    })

    const usedSet = new Set<string>()
    for (const note of allNotes) {
      for (const name of collectLabelNamesFromNote(note)) {
        const key = labelScopeKey(note.notebookId, name)
        if (key) usedSet.add(key)
      }
    }

    let allScoped = await prisma.label.findMany({
      where: { userId },
      select: { id: true, name: true, notebookId: true },
    })

    const ensuredPairs = new Set<string>()
    for (const note of allNotes) {
      for (const name of collectLabelNamesFromNote(note)) {
        const key = labelScopeKey(note.notebookId, name)
        if (!key || ensuredPairs.has(key)) continue
        ensuredPairs.add(key)
        const trimmed = name.trim()
        const nb = note.notebookId ?? null
        const exists = allScoped.some(
          l => (l.notebookId ?? null) === nb && l.name.toLowerCase() === trimmed.toLowerCase()
        )
        if (exists) continue
        try {
          const created = await prisma.label.create({
            data: {
              userId,
              name: trimmed,
              color: getHashColor(trimmed),
              notebookId: nb,
            },
          })
          allScoped.push(created)
          createdCount++
        } catch (e: any) {
          console.error(`Failed to create label:`, e)
          errors.push({ label: trimmed, notebookId: nb, error: e.message, code: e.code })
          allScoped = await prisma.label.findMany({
            where: { userId },
            select: { id: true, name: true, notebookId: true },
          })
        }
      }
    }

    allScoped = await prisma.label.findMany({
      where: { userId },
      select: { id: true, name: true, notebookId: true },
    })
    for (const label of allScoped) {
      const key = labelScopeKey(label.notebookId, label.name)
      if (!key || usedSet.has(key)) continue
      try {
        await prisma.label.update({
          where: { id: label.id },
          data: { notes: { set: [] } },
        })
        await prisma.label.delete({ where: { id: label.id } })
        deletedCount++
      } catch (e: any) {
        console.error(`Failed to delete orphan ${label.id}:`, e)
        errors.push({ labelId: label.id, name: label.name, error: e?.message, code: e?.code })
      }
    }

    revalidatePath('/')
    revalidatePath('/settings')
    return {
      success: true,
      created: createdCount,
      deleted: deletedCount,
      errors,
      message: `Created ${createdCount} missing label records, deleted ${deletedCount} orphan labels${errors.length > 0 ? ` (${errors.length} errors)` : ''}`
    }
  } catch (error) {
    console.error('[CLEANUP] Fatal error:', error);
    throw new Error('Failed to cleanup database')
  }
}

export async function syncAllEmbeddings() {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');
  const userId = session.user.id;
  let updatedCount = 0;
  try {
    const notesToSync = await prisma.note.findMany({
      where: {
        userId,
        trashedAt: null,
        noteEmbedding: { is: null }
      }
    })
    const provider = getAIProvider(await getSystemConfig());
    for (const note of notesToSync) {
      if (!note.content) continue;
      try {
        const embedding = await provider.getEmbeddings(note.content);
        if (embedding) {
          await prisma.noteEmbedding.upsert({
            where: { noteId: note.id },
            create: { noteId: note.id, embedding: JSON.stringify(embedding) },
            update: { embedding: JSON.stringify(embedding) }
          })
          updatedCount++;
        }
      } catch (e) { }
    }
    return { success: true, count: updatedCount }
  } catch (error: any) {
    throw new Error(`Failed to sync embeddings: ${error.message}`)
  }
}

// Get all notes including those shared with the user
export async function getAllNotes(includeArchived = false) {
  const session = await auth();
  if (!session?.user?.id) return [];

  const userId = session.user.id;

  try {
    // Fetch own notes + shared notes in parallel — no embedding to keep transfer fast
    const [ownNotes, acceptedShares] = await Promise.all([
      prisma.note.findMany({
        where: {
          userId,
          trashedAt: null,
          ...(includeArchived ? {} : { isArchived: false }),
        },
        select: NOTE_LIST_SELECT,
        orderBy: [
          { isPinned: 'desc' },
          { order: 'asc' },
          { updatedAt: 'desc' }
        ]
      }),
      prisma.noteShare.findMany({
        where: { userId, status: 'accepted' },
        include: { note: { select: NOTE_LIST_SELECT } }
      })
    ])

    const sharedNotes = acceptedShares
      .map(share => share.note)
      .filter(note => includeArchived || !note.isArchived)
      .map(note => ({ ...note, _isShared: true }))

    return [...ownNotes.map(parseNote), ...sharedNotes.map(parseNote)]
  } catch (error) {
    console.error('Error fetching notes:', error)
    return []
  }
}

// Get pinned notes only
export async function getPinnedNotes(notebookId?: string) {
  const session = await auth();
  if (!session?.user?.id) return [];

  const userId = session.user.id;

  try {
    const notes = await prisma.note.findMany({
      where: {
        userId: userId,
        isPinned: true,
        isArchived: false,
        trashedAt: null,
        ...(notebookId !== undefined ? { notebookId } : {})
      },
      orderBy: [
        { order: 'asc' },
        { updatedAt: 'desc' }
      ]
    })

    return notes.map(parseNote)
  } catch (error) {
    console.error('Error fetching pinned notes:', error)
    return []
  }
}

// Get recent notes (notes modified in the last 7 days)
// Get recent notes (notes modified in the last 7 days)
export async function getRecentNotes(limit: number = 3) {
  const session = await auth();
  if (!session?.user?.id) return [];

  const userId = session.user.id;

  try {
    const sevenDaysAgo = new Date()
    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
    sevenDaysAgo.setHours(0, 0, 0, 0) // Set to start of day

    const notes = await prisma.note.findMany({
      where: {
        userId: userId,
        contentUpdatedAt: { gte: sevenDaysAgo },
        isArchived: false,
        trashedAt: null,
        dismissedFromRecent: false // Filter out dismissed notes
      },
      orderBy: { contentUpdatedAt: 'desc' },
      take: limit
    })

    return notes.map(parseNote)
  } catch (error) {
    console.error('Error fetching recent notes:', error)
    return []
  }
}

// Dismiss a note from Recent section
export async function dismissFromRecent(id: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    await prisma.note.update({
      where: { id, userId: session.user.id },
      data: { dismissedFromRecent: true }
    })

    // revalidatePath('/') // Removed to prevent immediate refill of the list
    return { success: true }
  } catch (error) {
    console.error('Error dismissing note from recent:', error)
    throw new Error('Failed to dismiss note')
  }
}

export async function getNoteById(noteId: string) {
  const session = await auth();
  if (!session?.user?.id) return null;

  const userId = session.user.id;

  try {
    const note = await prisma.note.findFirst({
      where: {
        id: noteId,
        OR: [
          { userId: userId },
          {
            shares: {
              some: {
                userId: userId,
                status: 'accepted'
              }
            }
          }
        ]
      }
    })

    if (!note) return null

    return parseNote(note)
  } catch (error) {
    console.error('Error fetching note:', error)
    return null
  }
}

// Add a collaborator to a note (updated to use new share request system)
export async function addCollaborator(noteId: string, userEmail: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    // Use the new share request system
    const result = await createShareRequest(noteId, userEmail, 'view');

    // Get user info for response
    const targetUser = await prisma.user.findUnique({
      where: { email: userEmail },
      select: { id: true, name: true, email: true, image: true }
    });

    if (!targetUser) {
      throw new Error('User not found')
    }

    return { success: true, user: targetUser }
  } catch (error: any) {
    console.error('Error adding collaborator:', error)
    throw error
  }
}

// Remove a collaborator from a note
export async function removeCollaborator(noteId: string, userId: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const note = await prisma.note.findUnique({
      where: { id: noteId }
    })

    if (!note) {
      throw new Error('Note not found')
    }

    if (note.userId !== session.user.id) {
      throw new Error('You can only manage collaborators on your own notes')
    }

    // Delete the NoteShare record (cascades to remove access)
    await prisma.noteShare.deleteMany({
      where: {
        noteId,
        userId
      }
    })

    // Don't revalidatePath here - it would close all open dialogs!
    // The UI will update via optimistic updates in the dialog
    return { success: true }
  } catch (error: any) {
    console.error('Error removing collaborator:', error)
    throw new Error(error.message || 'Failed to remove collaborator')
  }
}

// Get collaborators for a note
export async function getNoteCollaborators(noteId: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const note = await prisma.note.findUnique({
      where: { id: noteId },
      select: { userId: true }
    })

    if (!note) {
      throw new Error('Note not found')
    }

    // Owner can always see collaborators
    // Shared users can also see collaborators if they have accepted access
    if (note.userId !== session.user.id) {
      const share = await prisma.noteShare.findUnique({
        where: {
          noteId_userId: {
            noteId,
            userId: session.user.id
          }
        }
      })
      if (!share || share.status !== 'accepted') {
        throw new Error('You do not have access to this note')
      }
    }

    // Get all users who have been shared this note (any status)
    const shares = await prisma.noteShare.findMany({
      where: {
        noteId
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
            image: true
          }
        }
      }
    })

    return shares.map(share => share.user)
  } catch (error: any) {
    console.error('Error fetching collaborators:', error)
    throw new Error(error.message || 'Failed to fetch collaborators')
  }
}

// Get all users associated with a note (owner + collaborators)
export async function getNoteAllUsers(noteId: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const note = await prisma.note.findUnique({
      where: { id: noteId },
      select: { userId: true }
    })

    if (!note) {
      throw new Error('Note not found')
    }

    // Check access via NoteShare
    const share = await prisma.noteShare.findUnique({
      where: {
        noteId_userId: {
          noteId,
          userId: session.user.id
        }
      }
    })

    const hasAccess = note.userId === session.user.id || (share && share.status === 'accepted')

    if (!hasAccess) {
      throw new Error('You do not have access to this note')
    }

    // Get owner
    const owner = await prisma.user.findUnique({
      where: { id: note.userId! },
      select: {
        id: true,
        name: true,
        email: true,
        image: true
      }
    })

    if (!owner) {
      throw new Error('Owner not found')
    }

    // Get collaborators (accepted shares)
    const shares = await prisma.noteShare.findMany({
      where: {
        noteId,
        status: 'accepted'
      },
      include: {
        user: {
          select: {
            id: true,
            name: true,
            email: true,
            image: true
          }
        }
      }
    })

    const collaborators = shares.map(share => share.user)

    // Return owner + collaborators
    return [owner, ...collaborators]
  } catch (error: any) {
    console.error('Error fetching note users:', error)
    throw new Error(error.message || 'Failed to fetch note users')
  }
}

// ============================================================
// NEW: Share Request System with Accept/Decline Workflow
// ============================================================

// Create a share request (invite user)
export async function createShareRequest(noteId: string, recipientEmail: string, permission: 'view' | 'comment' | 'edit' = 'view') {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    // Find note
    const note = await prisma.note.findUnique({
      where: { id: noteId }
    });

    if (!note) {
      throw new Error('Note not found');
    }

    // Check ownership
    if (note.userId !== session.user.id) {
      throw new Error('Only the owner can share notes');
    }

    // Find recipient by email
    const recipient = await prisma.user.findUnique({
      where: { email: recipientEmail }
    });

    if (!recipient) {
      throw new Error('User not found');
    }

    // Don't share with yourself
    if (recipient.id === session.user.id) {
      throw new Error('You cannot share with yourself');
    }

    // Check if share already exists
    const existingShare = await prisma.noteShare.findUnique({
      where: {
        noteId_userId: {
          noteId,
          userId: recipient.id
        }
      }
    });

    if (existingShare) {
      if (existingShare.status === 'declined') {
        // Reactivate declined share
        await prisma.noteShare.update({
          where: { id: existingShare.id },
          data: {
            status: 'pending',
            notifiedAt: new Date(),
            permission
          }
        });
      } else if (existingShare.status === 'removed') {
        // Reactivate removed share
        await prisma.noteShare.update({
          where: { id: existingShare.id },
          data: {
            status: 'pending',
            notifiedAt: new Date(),
            permission
          }
        });
      } else {
        throw new Error('Note already shared with this user');
      }
    } else {
      // Create new share request
      await prisma.noteShare.create({
        data: {
          noteId,
          userId: recipient.id,
          sharedBy: session.user.id,
          status: 'pending',
          permission,
          notifiedAt: new Date()
        }
      });
    }

    // Don't revalidatePath here - it would close all open dialogs!
    // The UI will update via optimistic updates in the dialog
    return { success: true };
  } catch (error: any) {
    console.error('Error creating share request:', error);
    throw error;
  }
}

// Get pending share requests for current user
export async function getPendingShareRequests() {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const pendingRequests = await prisma.noteShare.findMany({
      where: {
        userId: session.user.id,
        status: 'pending'
      },
      include: {
        note: {
          select: {
            id: true,
            title: true,
            content: true,
            color: true,
            createdAt: true
          }
        },
        sharer: {
          select: {
            id: true,
            name: true,
            email: true,
            image: true
          }
        }
      },
      orderBy: {
        createdAt: 'desc'
      }
    });

    return pendingRequests;
  } catch (error: any) {
    console.error('Error fetching pending share requests:', error);
    throw error;
  }
}

// Respond to share request (accept/decline)
export async function respondToShareRequest(shareId: string, action: 'accept' | 'decline') {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const share = await prisma.noteShare.findUnique({
      where: { id: shareId },
      include: {
        note: true,
        sharer: true
      }
    });

    if (!share) {
      throw new Error('Share request not found');
    }

    // Verify this share belongs to current user
    if (share.userId !== session.user.id) {
      throw new Error('Unauthorized');
    }

    // Update share status
    const updatedShare = await prisma.noteShare.update({
      where: { id: shareId },
      data: {
        status: action === 'accept' ? 'accepted' : 'declined',
        respondedAt: new Date()
      },
      include: {
        note: {
          select: {
            title: true
          }
        }
      }
    });

    // Revalidate all relevant cache tags
    revalidatePath('/');

    return { success: true, share: updatedShare };
  } catch (error: any) {
    console.error('Error responding to share request:', error);
    throw error;
  }
}

// Get all accepted shares for current user (notes shared with me)
export async function getAcceptedSharedNotes() {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const acceptedShares = await prisma.noteShare.findMany({
      where: {
        userId: session.user.id,
        status: 'accepted'
      },
      include: {
        note: true
      },
      orderBy: {
        createdAt: 'desc'
      }
    });

    return acceptedShares.map(share => share.note);
  } catch (error: any) {
    console.error('Error fetching accepted shared notes:', error);
    throw error;
  }
}

// Remove share from my view (hide the note)
export async function removeSharedNoteFromView(shareId: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    const share = await prisma.noteShare.findUnique({
      where: { id: shareId }
    });

    if (!share) {
      throw new Error('Share not found');
    }

    // Verify this share belongs to current user
    if (share.userId !== session.user.id) {
      throw new Error('Unauthorized');
    }

    // Update status to 'removed' (note is hidden from user's view)
    await prisma.noteShare.update({
      where: { id: shareId },
      data: {
        status: 'removed'
      }
    });

    revalidatePath('/');
    return { success: true };
  } catch (error: any) {
    console.error('Error removing shared note from view:', error);
    throw error;
  }
}

// Get notification count for pending shares
export async function getPendingShareCount() {
  const session = await auth();
  if (!session?.user?.id) return 0;

  try {
    const count = await prisma.noteShare.count({
      where: {
        userId: session.user.id,
        status: 'pending'
      }
    });

    return count;
  } catch (error) {
    console.error('Error getting pending share count:', error);
    return 0;
  }
}

// Leave a shared note (remove from my list)
export async function leaveSharedNote(noteId: string) {
  const session = await auth();
  if (!session?.user?.id) throw new Error('Unauthorized');

  try {
    // Find the NoteShare record for this user and note
    const share = await prisma.noteShare.findUnique({
      where: {
        noteId_userId: {
          noteId,
          userId: session.user.id
        }
      }
    });

    if (!share) {
      throw new Error('Share not found');
    }

    // Verify this share belongs to current user
    if (share.userId !== session.user.id) {
      throw new Error('Unauthorized');
    }

    // Update status to 'removed' (note is hidden from user's view)
    await prisma.noteShare.update({
      where: { id: share.id },
      data: {
        status: 'removed'
      }
    });

    revalidatePath('/');
    return { success: true };
  } catch (error: any) {
    console.error('Error leaving shared note:', error);
    throw error;
  }
}
