import type {
  Album,
  AiRating,
  DuplicateGroup,
  ExploreTagEntry,
  Folder,
  FolderJobProgress,
  ImageExif,
  ImageRecord,
  ImageTag,
  SortOrder,
  VisualClusterEntry,
} from '../store'
import { isEmptyLibraryScenario, type MockScenario } from './mockScenarios'
import { fixtureMediaPath } from './mockMedia'

export interface MockDb {
  scenario: MockScenario
  folders: Folder[]
  images: ImageRecord[]
  albums: Album[]
  albumImageIds: Record<number, number[]>
  tagsByImageId: Record<number, ImageTag[]>
  visualClusters: VisualClusterEntry[]
  exploreTags: ExploreTagEntry[]
  backgroundJobs: FolderJobProgress[]
  duplicateGroups: DuplicateGroup[]
  duplicateScannedAt: number | null
}

const folderNames = ['Camera Roll', 'Client Selects', 'Video Archive']
const extremeFolderNames = [
  'Camera Roll',
  'Client Selects',
  'Video Archive',
  'Commercial Library',
  'Archive Vault',
  'Reference Pulls',
  'Delivery Selects',
  'Personal Work',
]
const extremeFolderCounts = [98_420, 76_880, 52_340, 44_900, 31_760, 24_180, 16_540, 12_920]
const mediaNames = [
  'alpine-lake',
  'city-after-rain',
  'coastal-walk',
  'studio-portrait',
  'market-neon',
  'forest-floor',
  'product-macro',
  'golden-hour',
  'night-drive',
  'desk-flatlay',
  'museum-hall',
  'wildflower',
]
const tags = [
  'landscape',
  'portrait',
  'street',
  'travel',
  'night',
  'product',
  'editorial',
  'architecture',
  'nature',
  'warm light',
  'blue hour',
  'select',
]

const hugeTagThemes = [
  'alpine',
  'archive',
  'botanical',
  'cinematic',
  'coastal',
  'editorial',
  'industrial',
  'interior',
  'macro',
  'night',
  'portrait',
  'street',
  'travel',
  'urban',
  'wildlife',
  'workspace',
]

const hugeTagSubjects = [
  'glass',
  'steel',
  'water',
  'stone',
  'mist',
  'foliage',
  'market',
  'signage',
  'neon',
  'shadow',
  'reflection',
  'texture',
  'silhouette',
  'motion',
  'symmetry',
  'detail',
  'warm light',
  'blue hour',
  'gold accent',
  'soft focus',
  'hard light',
  'negative space',
  'foreground',
  'background',
  'wide angle',
  'close crop',
  'environment',
  'still life',
  'natural light',
  'available light',
]

const hugeTags = [
  ...tags,
  ...Array.from({ length: hugeTagThemes.length * hugeTagSubjects.length }, (_, index) => {
    const theme = hugeTagThemes[index % hugeTagThemes.length]
    const subject =
      hugeTagSubjects[Math.floor(index / hugeTagThemes.length) % hugeTagSubjects.length]
    return `${theme} ${subject}`
  }),
  ...Array.from(
    { length: 320 },
    (_, index) => `fixture concept ${String(index + 1).padStart(3, '0')}`
  ),
]
const extremeTags = [
  ...tags,
  ...Array.from({ length: hugeTagThemes.length * hugeTagSubjects.length }, (_, index) => {
    const theme = hugeTagThemes[index % hugeTagThemes.length]
    const subject =
      hugeTagSubjects[Math.floor(index / hugeTagThemes.length) % hugeTagSubjects.length]
    return `${theme} ${subject}`
  }),
  ...Array.from(
    { length: 780 },
    (_, index) => `extreme concept ${String(index + 1).padStart(4, '0')}`
  ),
  ...Array.from(
    { length: 240 },
    (_, index) => `client taxonomy ${String(index + 1).padStart(3, '0')}`
  ),
]
const aiRatings: AiRating[] = ['general', 'sensitive', 'questionable']

function daysAgo(days: number): string {
  return new Date(Date.now() - days * 86_400_000).toISOString()
}

function mockPath(folder: number, name: string, index: number, ext: string): string {
  return `mock://full/${folder}-${name}-${index}.${ext}`
}

function mockThumb(index: number): string {
  return fixtureMediaPath(index)
}

function makeImage(index: number, folderId: number, scenario: MockScenario): ImageRecord {
  const name = mediaNames[index % mediaNames.length]
  const isVideo = index % 11 === 6
  const failedEmbedding = scenario === 'errors' && index % 9 === 0
  const failedTagging = scenario === 'errors' && index % 7 === 0
  const ext = isVideo ? 'mp4' : 'jpg'
  return {
    id: index + 1,
    folder_id: folderId,
    path: isVideo ? mockPath(folderId, name, index + 1, ext) : mockThumb(index),
    filename: `${name}-${String(index + 1).padStart(3, '0')}.${ext}`,
    thumbnail_path:
      scenario === 'errors' && index % 10 === 3 ? 'mock://missing-thumb.svg' : mockThumb(index),
    width: isVideo ? 3840 : 3000 + (index % 5) * 180,
    height: isVideo ? 2160 : 2000 + (index % 4) * 160,
    file_size: 480_000 + index * 73_000,
    created_at: daysAgo(index + 3),
    modified_at: daysAgo(index + 1),
    taken_at: daysAgo(index + 10),
    mime_type: isVideo ? 'video/mp4' : 'image/jpeg',
    media_kind: isVideo ? 'video' : 'image',
    duration_ms: isVideo ? 42_000 + index * 1300 : null,
    video_codec: isVideo ? 'h264' : null,
    audio_codec: isVideo ? 'aac' : null,
    metadata_updated_at: daysAgo(index),
    metadata_error:
      scenario === 'errors' && index % 8 === 2 ? 'EXIF parse failed in UI fixture' : null,
    favorite: index % 5 === 0,
    rating: index % 6,
    embedding_status: failedEmbedding ? 'failed' : index % 13 === 0 ? 'processing' : 'ready',
    embedding_model: failedEmbedding ? null : 'clip-vit-b-32',
    embedding_updated_at: failedEmbedding ? null : daysAgo(index),
    embedding_error: failedEmbedding ? 'Fixture embedding failure' : null,
    generated_caption: `Fixture caption for ${name.replace(/-/g, ' ')}.`,
    caption_model: 'blip-base',
    caption_updated_at: daysAgo(index),
    caption_error: null,
    ai_rating: aiRatings[index % aiRatings.length],
    ai_tagger_model: failedTagging ? null : 'wd-vit-tagger-v3',
    ai_tagged_at: failedTagging ? null : daysAgo(index),
    ai_tagger_error: failedTagging ? 'Fixture tagger failure' : null,
  }
}

function makeFolders(scenario: MockScenario): Folder[] {
  if (isEmptyLibraryScenario(scenario)) return []
  const names = scenario === 'extreme' ? extremeFolderNames : folderNames
  return names.map((name, index) => ({
    id: index + 1,
    path: `C:\\Users\\phokus\\Pictures\\${name.replace(/ /g, '')}`,
    name,
    image_count: 0,
    indexed_at: daysAgo(index + 1),
    scan_error: scenario === 'errors' && index === 1 ? 'Folder moved or permission denied' : null,
    sort_order: index + 1,
  }))
}

function makeImages(scenario: MockScenario, folders: Folder[]): ImageRecord[] {
  if (isEmptyLibraryScenario(scenario)) return []
  const count =
    scenario === 'extreme' ? 1_440 : scenario === 'huge' ? 720 : scenario === 'errors' ? 54 : 72
  const images = Array.from({ length: count }, (_, index) =>
    makeImage(index, folders[index % folders.length].id, scenario)
  )
  for (const folder of folders) {
    folder.image_count =
      scenario === 'extreme'
        ? (extremeFolderCounts[folder.id - 1] ??
          images.filter((image) => image.folder_id === folder.id).length)
        : images.filter((image) => image.folder_id === folder.id).length
  }
  return images
}

function tagVocabularyForScenario(scenario: MockScenario): string[] {
  if (scenario === 'extreme') return extremeTags
  return scenario === 'huge' ? hugeTags : tags
}

function makeTags(images: ImageRecord[], scenario: MockScenario): Record<number, ImageTag[]> {
  const vocabulary = tagVocabularyForScenario(scenario)
  const tagCount = scenario === 'extreme' ? 14 : scenario === 'huge' ? 9 : 3
  return Object.fromEntries(
    images.map((image, index) => [
      image.id,
      Array.from({ length: tagCount }, (_, offset) => ({
        id: image.id * 10 + offset,
        image_id: image.id,
        tag: vocabulary[(index * (offset + 3) + offset * 29) % vocabulary.length],
        source: offset === 0 ? 'user' : 'ai',
        ai_model: offset === 0 ? null : 'wd-vit-tagger-v3',
        confidence: offset === 0 ? null : Math.min(0.99, 0.72 + offset * 0.03),
        created_at: daysAgo(index + offset),
      })),
    ])
  )
}

function makeAlbums(
  images: ImageRecord[],
  scenario: MockScenario
): { albums: Album[]; albumImageIds: Record<number, number[]> } {
  if (isEmptyLibraryScenario(scenario)) return { albums: [], albumImageIds: {} }
  const selects = images
    .filter((image) => image.rating >= 4)
    .slice(0, 36)
    .map((image) => image.id)
  const motion = images.filter((image) => image.media_kind === 'video').map((image) => image.id)
  const favorites = images
    .filter((image) => image.favorite)
    .slice(0, 40)
    .map((image) => image.id)
  const albumImageIds = { 1: selects, 2: motion, 3: favorites }
  const portfolioCount = scenario === 'extreme' ? 18_450 : selects.length
  const motionCount = scenario === 'extreme' ? 7_820 : motion.length
  const favoritesCount = scenario === 'extreme' ? 42_610 : favorites.length
  const albums: Album[] = [
    {
      id: 1,
      name: 'Portfolio Shortlist',
      cover_image_id: selects[0] ?? null,
      cover_thumbnail_path: images.find((image) => image.id === selects[0])?.thumbnail_path ?? null,
      image_count: portfolioCount,
      sort_order: 1,
      created_at: daysAgo(40),
      updated_at: daysAgo(2),
    },
    {
      id: 2,
      name: 'Motion Tests',
      cover_image_id: motion[0] ?? null,
      cover_thumbnail_path: images.find((image) => image.id === motion[0])?.thumbnail_path ?? null,
      image_count: motionCount,
      sort_order: 2,
      created_at: daysAgo(38),
      updated_at: daysAgo(3),
    },
    {
      id: 3,
      name: 'Favorites',
      cover_image_id: favorites[0] ?? null,
      cover_thumbnail_path:
        images.find((image) => image.id === favorites[0])?.thumbnail_path ?? null,
      image_count: favoritesCount,
      sort_order: 3,
      created_at: daysAgo(20),
      updated_at: daysAgo(1),
    },
  ]
  return { albums, albumImageIds }
}

function makeProgress(folders: Folder[], scenario: MockScenario): FolderJobProgress[] {
  return folders.map((folder, index) => ({
    folder_id: folder.id,
    thumbnail_pending:
      scenario === 'extreme' ? 180 - index * 9 : scenario === 'busy' ? 18 - index * 3 : 0,
    metadata_pending:
      scenario === 'extreme' ? 140 - index * 7 : scenario === 'busy' ? 12 - index * 2 : 0,
    embedding_pending:
      scenario === 'extreme'
        ? 260 - index * 11
        : scenario === 'busy'
          ? 36 - index * 4
          : scenario === 'errors'
            ? 4
            : 0,
    embedding_ready: Math.max(0, folder.image_count - (scenario === 'busy' ? 20 : 2)),
    embedding_failed: scenario === 'errors' ? 3 + index : 0,
    caption_pending:
      scenario === 'extreme' ? 210 - index * 10 : scenario === 'busy' ? 24 - index * 2 : 0,
    caption_ready: Math.max(0, Math.floor(folder.image_count * 0.5)),
    caption_failed: scenario === 'errors' ? 2 : 0,
    tagging_pending:
      scenario === 'extreme' ? 320 - index * 13 : scenario === 'busy' ? 30 - index * 5 : 0,
    tagging_ready: Math.max(0, Math.floor(folder.image_count * 0.65)),
    tagging_failed: scenario === 'errors' ? 4 : 0,
  }))
}

function makeVisualClusters(images: ImageRecord[], scenario: MockScenario): VisualClusterEntry[] {
  if (images.length < 5) return []
  // Mirror the backend's k = (n / 20).clamp(5, 30). Force the full spread for the
  // "huge" scenario so Explore shows a realistic field of clusters rather than a
  // fixed handful, and size the counts off a large virtual library so the badges
  // read like a big collection.
  const clusterCount =
    scenario === 'huge' || scenario === 'extreme'
      ? 30
      : Math.min(30, Math.max(5, Math.round(images.length / 20)))
  const virtualTotal =
    scenario === 'extreme' ? 250_000 : scenario === 'huge' ? 12_000 : images.length
  // Long-tailed sizes (a few large clusters, many smaller), like real k-means output.
  const weights = Array.from({ length: clusterCount }, (_, i) => 1 / (i + 1.5))
  const weightSum = weights.reduce((sum, weight) => sum + weight, 0)
  return weights.map((weight, index) => {
    const group = images.filter((image) => image.id % (index + 2) === 0).slice(0, 28)
    const representative = group[0] ?? images[index % images.length]
    return {
      count: Math.max(1, Math.round((virtualTotal * weight) / weightSum)),
      representative_image_id: representative?.id ?? index + 1,
      thumbnail_path: representative?.thumbnail_path ?? null,
      image_ids: group.length ? group.map((image) => image.id) : [representative?.id ?? 1],
    }
  })
}

function makeExploreTags(images: ImageRecord[], scenario: MockScenario): ExploreTagEntry[] {
  const vocabulary = tagVocabularyForScenario(scenario)
  return vocabulary
    .map((tag, index) => {
      const representative = images[index % Math.max(images.length, 1)]
      const divisor =
        scenario === 'extreme'
          ? 1 + Math.pow(index + 1, 0.42)
          : scenario === 'huge'
            ? 2 + Math.pow(index + 1, 0.58)
            : index + 3
      const sourceCycle = index % 3
      return {
        tag,
        count:
          scenario === 'extreme'
            ? Math.max(10_000, Math.min(100_000, Math.round(200_000 / divisor)))
            : Math.max(1, Math.round(images.length / divisor)),
        representative_image_id: representative?.id ?? index + 1,
        thumbnail_path: representative?.thumbnail_path ?? null,
        has_ai_source: sourceCycle !== 1,
        has_user_source: sourceCycle !== 0,
      }
    })
    .sort((left, right) => right.count - left.count || left.tag.localeCompare(right.tag))
}

function makeDuplicateGroups(images: ImageRecord[], scenario: MockScenario): DuplicateGroup[] {
  if (isEmptyLibraryScenario(scenario)) return []
  const candidates = images.filter((image) => image.media_kind === 'image')
  const groups = [0, 1, 2, 3]
    .map((groupIndex) => ({
      file_hash: `fixture-hash-${groupIndex}`,
      file_size: candidates[groupIndex]?.file_size ?? 1_200_000,
      images: candidates.slice(groupIndex * 4, groupIndex * 4 + (groupIndex === 1 ? 3 : 2)),
    }))
    .filter((group) => group.images.length > 1)
  return scenario === 'duplicates'
    ? groups.concat(
        groups
          .map((group, index) => ({
            ...group,
            file_hash: `${group.file_hash}-extra`,
            images: candidates.slice(20 + index * 3, 23 + index * 3),
          }))
          .filter((group) => group.images.length > 1)
      )
    : groups
}

export function createMockDb(scenario: MockScenario): MockDb {
  const folders = makeFolders(scenario)
  const images = makeImages(scenario, folders)
  const { albums, albumImageIds } = makeAlbums(images, scenario)
  return {
    scenario,
    folders,
    images,
    albums,
    albumImageIds,
    tagsByImageId: makeTags(images, scenario),
    visualClusters: makeVisualClusters(images, scenario),
    exploreTags: makeExploreTags(images, scenario),
    backgroundJobs: makeProgress(folders, scenario),
    duplicateGroups: makeDuplicateGroups(images, scenario),
    duplicateScannedAt: scenario === 'duplicates' ? Math.floor(Date.now() / 1000) - 420 : null,
  }
}

export function compareImages(sort: SortOrder): (left: ImageRecord, right: ImageRecord) => number {
  return (left, right) => {
    switch (sort) {
      case 'name_asc':
        return left.filename.localeCompare(right.filename)
      case 'name_desc':
        return right.filename.localeCompare(left.filename)
      case 'size_asc':
        return left.file_size - right.file_size
      case 'size_desc':
        return right.file_size - left.file_size
      case 'rating_asc':
        return left.rating - right.rating
      case 'rating_desc':
        return right.rating - left.rating
      case 'duration_asc':
        return (left.duration_ms ?? 0) - (right.duration_ms ?? 0)
      case 'duration_desc':
        return (right.duration_ms ?? 0) - (left.duration_ms ?? 0)
      case 'date_asc':
      case 'taken_asc':
        return (
          Date.parse(left.taken_at ?? left.created_at ?? '') -
          Date.parse(right.taken_at ?? right.created_at ?? '')
        )
      case 'date_desc':
      case 'taken_desc':
      default:
        return (
          Date.parse(right.taken_at ?? right.created_at ?? '') -
          Date.parse(left.taken_at ?? left.created_at ?? '')
        )
    }
  }
}

export function mockExif(imageId: number): ImageExif {
  return {
    make: 'Phokus Fixture Co.',
    model: `MockCam ${100 + imageId}`,
    lens: '35mm f/1.8',
    iso: String(100 + (imageId % 8) * 100),
    f_number: 'f/4',
    exposure_time: '1/250',
    focal_length: '35mm',
    datetime_original: daysAgo(imageId),
    gps_lat: imageId % 2 === 0 ? 51.5072 : null,
    gps_lon: imageId % 2 === 0 ? -0.1276 : null,
  }
}
