import {
  filterPois,
  type PoiCategory,
  type PoiDataset,
  type PoiDatasetMeta,
  type PoiFilter,
  type PoiRepository,
  type PoiResolved,
  type PoiSummary,
  type PoiTag,
  resolveDatasetPois,
  sortCategories,
  sortTags,
  toPoiSummary,
  validatePoiDataset,
} from '@/domain/poi'
import { poiDataset } from './dataset'

function cloneMeta(meta: PoiDatasetMeta): PoiDatasetMeta {
  return {
    ...meta,
    reviewedBy: [...meta.reviewedBy],
    defaultViewport: { ...meta.defaultViewport },
  }
}

function cloneCategory(category: PoiCategory): PoiCategory {
  return { ...category }
}

function cloneTag(tag: PoiTag): PoiTag {
  return { ...tag, synonyms: tag.synonyms ? [...tag.synonyms] : undefined }
}

function cloneResolvedPoi(poi: PoiResolved): PoiResolved {
  return {
    ...poi,
    aliases: poi.aliases ? [...poi.aliases] : undefined,
    tagCodes: [...poi.tagCodes],
    imageIds: [...poi.imageIds],
    openingHours: { ...poi.openingHours },
    category: { ...poi.category },
    tags: poi.tags.map(cloneTag),
    coverImage: { ...poi.coverImage },
    images: poi.images.map(image => ({ ...image })),
  }
}

export function createPoiRepository(dataset: PoiDataset = poiDataset): PoiRepository {
  const validation = validatePoiDataset(dataset)
  if (!validation.isDatasetUsable) {
    const errors = validation.issues
      .filter(issue => issue.severity === 'error')
      .map(issue => `${issue.code}@${issue.path}`)
      .join(', ')
    throw new Error(`POI dataset is unusable: ${errors}`)
  }

  const publishedPois = validation.validPois.filter(poi => poi.publishStatus === 'published')
  const resolvedPois = resolveDatasetPois(dataset, publishedPois)
  const byId = new Map(resolvedPois.map(poi => [poi.id, poi]))

  function getPublishedPois(filter: PoiFilter = {}): PoiResolved[] {
    return filterPois(resolvedPois, filter)
      .map(poi => byId.get(poi.id))
      .filter((poi): poi is PoiResolved => Boolean(poi))
      .map(cloneResolvedPoi)
  }

  return {
    getDatasetMeta(): PoiDatasetMeta {
      return cloneMeta(dataset.meta)
    },
    getCategories(): PoiCategory[] {
      const categoryCodesWithPois = new Set(resolvedPois.map(poi => poi.categoryCode))
      return sortCategories(dataset.categories)
        .filter(category => categoryCodesWithPois.has(category.code))
        .map(cloneCategory)
    },
    getTags(): PoiTag[] {
      return sortTags(dataset.tags).map(cloneTag)
    },
    getPublishedPois,
    getPoiSummaries(filter: PoiFilter = {}): PoiSummary[] {
      return getPublishedPois(filter).map(toPoiSummary)
    },
    getPoiById(poiId: string): PoiResolved | null {
      const poi = byId.get(poiId.trim())
      return poi ? cloneResolvedPoi(poi) : null
    },
  }
}

export const poiRepository = createPoiRepository()

export function getPoiRepository(): PoiRepository {
  return poiRepository
}

export const getDatasetMeta = poiRepository.getDatasetMeta
export const getCategories = poiRepository.getCategories
export const getTags = poiRepository.getTags
export const getPublishedPois = poiRepository.getPublishedPois
export const getPoiSummaries = poiRepository.getPoiSummaries
export const getPoiById = poiRepository.getPoiById
