const ATTRIBUTION = '© OpenStreetMap contributors'

/** Longest match first — Flock, Genetec, Leonardo, etc. */
const KNOWN_ALPR_MODELS = [
  'Falcon Flex',
  'AutoVu CR-H2',
  'AutoVu Sharp',
  'UnicamVELOCITY3',
  'Falcon',
  'Sparrow',
  'Raven',
  'Condor',
  'Wing',
  'Pelican',
]

const GENERIC_CAMERA_NAMES = new Set([
  'flock alpr camera',
  'flock alpr cameera',
  'flock camera',
  'flock alpr',
  'flock',
  'flock camera',
  'automatic license plate reader (alpr)',
  'alpr camera',
  'alpr',
])

const SKIP_EXTRA_TAGS = new Set([
  'surveillance:type',
  'man_made',
  'camera:direction',
  'direction',
  'camera:angle',
  'surveillance:angle',
  'manufacturer',
  'brand',
  'model',
  'operator',
  'name',
  'ref',
  'surveillance',
  'camera:type',
  'description',
  'note',
  'colour',
  'color',
  'manufacturer:wikidata',
  'model:wikidata',
  'operator:wikidata',
])

function escapeRegex(text) {
  return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}

/** @param {string} value */
export function normalizeModelName(value) {
  const trimmed = value.trim()
  if (!trimmed) return null
  const lower = trimmed.toLowerCase()
  for (const model of KNOWN_ALPR_MODELS) {
    if (model.toLowerCase() === lower) return model
  }
  return trimmed.split(/\s+/).map(word =>
    word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(),
  ).join(' ')
}

/** @param {string} text */
function matchKnownModel(text) {
  for (const model of KNOWN_ALPR_MODELS) {
    const re = new RegExp(`\\b${escapeRegex(model)}\\b`, 'i')
    if (re.test(text)) return model
  }
  const flock = text.match(/\bFlock\s+([a-z][\w -]*)/i)
  if (flock) {
    const fragment = flock[1].trim()
    if (!fragment || /^safety$/i.test(fragment)) return null
    return matchKnownModel(fragment)
  }
  return null
}

/** @param {Record<string, string>} tags */
export function inferModelFromTags(tags) {
  const t = tags ?? {}
  if (t.model?.trim()) return normalizeModelName(t.model)
  for (const key of ['name', 'description', 'note']) {
    const value = t[key]
    if (!value?.trim()) continue
    const model = matchKnownModel(value)
    if (model) return model
  }
  return null
}

/** @param {string | null | undefined} name */
export function isGenericCameraName(name) {
  if (!name?.trim()) return false
  const normalized = name.trim().toLowerCase()
  if (GENERIC_CAMERA_NAMES.has(normalized)) return true
  if (/^flock\s+alpr\b/i.test(name) && !matchKnownModel(name)) return true
  if (matchKnownModel(name) && /\bflock\b/i.test(name)) return true
  return false
}

/** @param {Record<string, string>} tags */
export function displayNameFromTags(tags) {
  const name = tags?.name?.trim()
  if (!name || isGenericCameraName(name)) return null
  if (matchKnownModel(name)) return null
  return name
}

/** @param {Record<string, string>} tags */
export function identifyingProperties(tags, manufacturer = null) {
  const t = tags ?? {}
  const mfr = manufacturer ?? t.manufacturer ?? t.brand ?? null
  const model = inferModelFromTags(t)
  const name = displayNameFromTags(t)
  /** @type {Record<string, string | boolean>} */
  const props = {}
  if (mfr) props.manufacturer = mfr
  if (model) props.model = model
  if (mfr && !model) props.modelUnknown = true
  if (t.brand && t.brand !== mfr) props.brand = t.brand
  if (t.operator) props.operator = t.operator
  if (name) props.name = name
  if (t.ref) props.ref = t.ref
  if (t.surveillance) props.surveillance = t.surveillance
  if (t['camera:type']) props.cameraType = t['camera:type']
  if (t.description) props.description = t.description
  if (t.note) props.note = t.note
  const colour = t.colour ?? t.color
  if (colour) props.colour = colour
  if (t['manufacturer:wikidata']) props.manufacturerWikidata = t['manufacturer:wikidata']
  if (t['model:wikidata']) props.modelWikidata = t['model:wikidata']
  if (t['operator:wikidata']) props.operatorWikidata = t['operator:wikidata']

  const extra = {}
  for (const [key, value] of Object.entries(t)) {
    if (SKIP_EXTRA_TAGS.has(key) || !value?.trim()) continue
    extra[key] = value
  }
  if (Object.keys(extra).length) props.tags = extra
  return props
}

/** @param {{ osmId: number, lat: number, lng: number, manufacturer: string | null, direction: number | null, fov: number | null, tags: Record<string, string> }} camera */
export function cameraToFeature(camera) {
  return {
    type: 'Feature',
    id: camera.osmId,
    geometry: { type: 'Point', coordinates: [camera.lng, camera.lat] },
    properties: {
      osmId: camera.osmId,
      direction: camera.direction,
      fov: camera.fov,
      ...identifyingProperties(camera.tags, camera.manufacturer),
    },
  }
}

/** @param {ReturnType<typeof cameraToFeature>[]} features */
export function featureCollection(features, source) {
  return {
    type: 'FeatureCollection',
    features,
    attribution: ATTRIBUTION,
    source,
  }
}

export { ATTRIBUTION }
