// =============================================================================
// Repository — Search
// Server-side search across documents + metadata.
// =============================================================================

import type { SupabaseClient } from '@supabase/supabase-js'
import type { Document } from '@/lib/repository-types'

export interface SearchFilters {
  repositoryType?: 'project' | 'global_template' | 'masterdata_file'
  projectId?: string
  documentType?: string
  dateFrom?: string
  dateTo?: string
  supplierId?: string
  // Phase 5 junction-table filters
  supplierIds?: string[]
  departmentIds?: string[]
  unassigned?: boolean  // true = only docs with no supplier/dept assignments
}

export type RelevanceLevel = 'title' | 'keyword' | 'tag'

export interface SearchResult {
  document: Document
  relevance: RelevanceLevel
}

// ---------------------------------------------------------------------------
// searchDocuments
// ---------------------------------------------------------------------------
// Strategy:
//  1. Fetch metadata rows whose keywords/semantic_tags contain the query term(s)
//  2. Query documents with ILIKE on title, original_filename, description
//  3. Also match documents whose tags array contains any query token
//  4. Merge results, rank by relevance: title > description/tags > metadata
// ---------------------------------------------------------------------------

export async function searchDocuments(
  rawQuery: string,
  filters: SearchFilters,
  supabase: SupabaseClient
): Promise<SearchResult[]> {
  const q = rawQuery.trim()
  if (!q) return []

  const qLower = q.toLowerCase()

  // --- Step 1: metadata keyword / semantic_tag scan -----------------------
  // Fetch all metadata rows (practical for ≤10k documents)
  const { data: metaRows } = await supabase
    .from('document_metadata')
    .select('document_id, keywords, semantic_tags, supplier_id')

  const metaMatchIds = new Set<string>()
  const supplierMatchIds = new Set<string>()

  for (const row of metaRows ?? []) {
    const allTerms: string[] = [
      ...(row.keywords ?? []),
      ...(row.semantic_tags ?? []),
    ]
    if (allTerms.some((t) => (t as string).toLowerCase().includes(qLower))) {
      metaMatchIds.add(row.document_id as string)
    }
    if (filters.supplierId && row.supplier_id === filters.supplierId) {
      supplierMatchIds.add(row.document_id as string)
    }
  }

  // --- Step 1b: junction table filters ------------------------------------
  let junctionAllowedIds: Set<string> | null = null

  if (filters.supplierIds && filters.supplierIds.length > 0) {
    const { data: jRows } = await supabase
      .from('document_suppliers')
      .select('document_id')
      .in('supplier_id', filters.supplierIds)
    junctionAllowedIds = new Set((jRows ?? []).map((r) => r.document_id as string))
  }

  if (filters.departmentIds && filters.departmentIds.length > 0) {
    const { data: jRows } = await supabase
      .from('document_departments')
      .select('document_id')
      .in('department_id', filters.departmentIds)
    const deptIds = new Set((jRows ?? []).map((r) => r.document_id as string))
    // Intersect with existing junction filter if already set
    if (junctionAllowedIds) {
      for (const id of junctionAllowedIds) {
        if (!deptIds.has(id)) junctionAllowedIds.delete(id)
      }
    } else {
      junctionAllowedIds = deptIds
    }
  }

  // "unassigned" = docs that appear in neither document_suppliers nor document_departments
  if (filters.unassigned) {
    const [{ data: sRows }, { data: dRows }] = await Promise.all([
      supabase.from('document_suppliers').select('document_id'),
      supabase.from('document_departments').select('document_id'),
    ])
    const assignedIds = new Set<string>([
      ...(sRows ?? []).map((r) => r.document_id as string),
      ...(dRows ?? []).map((r) => r.document_id as string),
    ])
    // We'll post-filter against this set after fetching docs
    junctionAllowedIds = assignedIds // store temporarily — will be inverted below
  }

  // --- Step 2: document table query ---------------------------------------
  let dbQuery = supabase
    .from('documents')
    .select('*')
    .eq('is_deleted', false)
    .limit(100)

  if (filters.repositoryType) dbQuery = dbQuery.eq('repository_type', filters.repositoryType)
  if (filters.projectId)      dbQuery = dbQuery.eq('project_id', filters.projectId)
  if (filters.documentType)   dbQuery = dbQuery.eq('document_type', filters.documentType)
  if (filters.dateFrom)       dbQuery = dbQuery.gte('created_at', filters.dateFrom)
  if (filters.dateTo)         dbQuery = dbQuery.lte('created_at', filters.dateTo + 'T23:59:59Z')

  const { data: allDocs } = await dbQuery

  if (!allDocs) return []

  // Collect IDs we need from metadata (not yet in allDocs)
  const fetchedIds = new Set((allDocs as Document[]).map((d) => d.id))
  const metaOnlyIds = [...metaMatchIds, ...supplierMatchIds].filter(
    (id) => !fetchedIds.has(id)
  )

  let extraDocs: Document[] = []
  if (metaOnlyIds.length > 0) {
    let extraQuery = supabase
      .from('documents')
      .select('*')
      .eq('is_deleted', false)
      .in('id', metaOnlyIds)

    if (filters.repositoryType) extraQuery = extraQuery.eq('repository_type', filters.repositoryType)
    if (filters.projectId)      extraQuery = extraQuery.eq('project_id', filters.projectId)
    if (filters.documentType)   extraQuery = extraQuery.eq('document_type', filters.documentType)

    const { data: extra } = await extraQuery
    extraDocs = (extra ?? []) as Document[]
  }

  const combinedDocs = [...(allDocs as Document[]), ...extraDocs]

  // --- Step 3: Score and filter ------------------------------------------
  const results: SearchResult[] = []
  const seen = new Set<string>()

  for (const doc of combinedDocs) {
    if (seen.has(doc.id)) continue

    // Apply junction filter
    if (junctionAllowedIds !== null) {
      if (filters.unassigned) {
        // unassigned = NOT in the assigned set
        if (junctionAllowedIds.has(doc.id)) continue
      } else {
        if (!junctionAllowedIds.has(doc.id)) continue
      }
    }

    const titleHit =
      doc.title.toLowerCase().includes(qLower) ||
      doc.original_filename.toLowerCase().includes(qLower)
    const descHit = (doc.description ?? '').toLowerCase().includes(qLower)
    const tagHit = (doc.tags ?? []).some((t) => t.toLowerCase().includes(qLower))
    const metaHit = metaMatchIds.has(doc.id)
    const supplierHit = supplierMatchIds.has(doc.id)

    let relevance: RelevanceLevel | null = null
    if (titleHit) relevance = 'title'
    else if (descHit || tagHit) relevance = 'keyword'
    else if (metaHit || supplierHit) relevance = 'tag'

    if (relevance) {
      results.push({ document: doc, relevance })
      seen.add(doc.id)
    }
  }

  // Sort: title matches first, then keyword, then tag
  const ORDER: Record<RelevanceLevel, number> = { title: 0, keyword: 1, tag: 2 }
  results.sort((a, b) => ORDER[a.relevance] - ORDER[b.relevance])

  return results
}
