import { z } from '@zod/zod/mini'
import { Lat, Lng } from '../schema/v0.ts'
import type { BookmarkProperties } from '../schema/v0.ts'
import type { ImportResult, ParsedBookmark } from './types.ts'

const GeoJsonPoint = z.object({
  type: z.literal('Feature'),
  geometry: z.object({
    type: z.literal('Point'),
    coordinates: z.tuple([Lng, Lat]),
  }),
  properties: z.nullable(z.record(z.string(), z.unknown())),
})
type GeoJsonPoint = z.infer<typeof GeoJsonPoint>

const GeoJsonInput = z.union([
  z.object({
    type: z.literal('FeatureCollection'),
    features: z.array(z.unknown()),
  }),
  GeoJsonPoint,
])

/** Extract BookmarkProperties from a GeoJSON feature's property bag. */
function extractProperties(
  raw: Record<string, unknown>,
): Partial<BookmarkProperties> {
  // Support flat address strings or nested location objects (e.g. Google Takeout)
  const loc = (typeof raw.location === 'object' && raw.location !== null)
    ? raw.location as Record<string, unknown>
    : undefined

  const name = (raw.name ?? raw.Name ?? loc?.name) as string | undefined
  const displayName = (raw.displayName ?? raw.display_name) as
    | string
    | undefined

  // Address may be a string (display text) or an object with a displayText field
  let addressText: string | undefined
  if (typeof raw.address === 'string') {
    addressText = raw.address || undefined
  } else if (typeof loc?.address === 'string') {
    addressText = loc.address || undefined
  }

  const description = (raw.description ?? raw.desc) as string | undefined

  // Collect any link-like fields into the links map
  const links: Record<string, string> = {}
  const gmapsUrl = (raw.googleMapsUrl ?? raw.google_maps_url) as
    | string
    | undefined
  if (gmapsUrl?.trim()) links['Google Maps'] = gmapsUrl.trim()

  // Nominatim-style fields that may appear in exported GeoJSON
  const nominatimId = typeof raw.nominatimId === 'number'
    ? raw.nominatimId
    : typeof raw.place_id === 'number'
    ? raw.place_id
    : undefined
  const nominatimCategory =
    (raw.nominatimCategory ?? raw.class ?? raw.category) as string | undefined
  const nominatimType = (raw.nominatimType ?? raw.type) as string | undefined
  const nominatimPlaceRank = (raw.nominatimPlaceRank ?? raw.place_rank) as
    | number
    | undefined
  const importance = raw.importance as number | undefined
  const osmId = (raw.osmId ?? raw.osm_id) as number | undefined
  const osmType = (raw.osmType ?? raw.osm_type) as string | undefined

  return {
    name: name?.trim() || undefined,
    displayName: displayName?.trim() || undefined,
    description: description?.trim() || undefined,
    links: Object.keys(links).length > 0 ? links : undefined,
    nominatimId,
    nominatimCategory,
    nominatimType,
    nominatimPlaceRank,
    importance,
    osmId,
    osmType,
    address: addressText ? { displayText: addressText.trim() } : undefined,
  }
}

export function parseGeoJson(text: string): ImportResult {
  const raw = GeoJsonInput.parse(JSON.parse(text))
  const rawFeatures = raw.type === 'FeatureCollection' ? raw.features : [raw]

  const bookmarks: ParsedBookmark[] = []
  for (const feature of rawFeatures) {
    const result = GeoJsonPoint.safeParse(feature)
    if (!result.success) continue
    const { geometry, properties } = result.data
    const [lng, lat] = geometry.coordinates
    bookmarks.push({
      lat,
      lng,
      folderTempId: null,
      properties: extractProperties(properties ?? {}),
    })
  }

  return { folders: [], bookmarks }
}
