geolite 0.0.17

open source geocode solution
use geo::Geometry;
use rusqlite::Connection;
use std::collections::HashMap;

use super::fields::{field, listed, osm_element, piece};
use super::map::drawing;
use super::tree::{breadcrumb, entry, folder, item};
use crate::address::{admin_level, query_match};
use crate::admin_level::geometry::admin_geometry;
use crate::admin_level::level;
use crate::admin_level::repository::geometry_by_ids;

pub struct leaf {
  pub path: Vec<item>,
  pub address: query_match,
  pub drawing: drawing,
}

fn areas_above(folder: &folder) -> Vec<i64> {
  folder
    .path
    .iter()
    .rev()
    .filter_map(|item| match &item.entry {
      entry::place(node) => Some(node.id),
      entry::missing { .. } | entry::base { .. } => None,
    })
    .collect()
}

pub fn open(conn: &Connection, folder: &folder, index: usize) -> Option<leaf> {
  let item = folder.items.get(index)?;
  let entry::place(node) = &item.entry else {
    return None;
  };
  let above = areas_above(folder);
  let address = crate::address::at_path(conn, node.id, &above)?;
  let drawn: Vec<i64> = std::iter::once(node.id)
    .chain(above.iter().copied())
    .collect();
  let geometries: HashMap<i64, admin_geometry> =
    geometry_by_ids(conn, &drawn).into_iter().collect();
  let areas: Vec<&Geometry<f64>> = above
    .iter()
    .filter_map(|area| geometries.get(area))
    .map(admin_geometry::geometry)
    .collect();
  let drawing = drawing::of(geometries.get(&node.id)?.geometry(), &areas)?;
  let mut path = folder.path.clone();
  path.push(item.clone());
  Some(leaf {
    path,
    address,
    drawing,
  })
}

fn osm_elements(level: &admin_level) -> Vec<osm_element> {
  if let Some(ways) = &level.osm_merged_way_ids {
    return ways.iter().copied().map(osm_element::way).collect();
  }
  level
    .osm_way_id
    .map(osm_element::way)
    .or(level.osm_relation_id.map(osm_element::relation))
    .into_iter()
    .collect()
}

fn kind_of(elements: &[osm_element]) -> Option<&'static str> {
  match elements {
    [] => None,
    [only] => Some(only.kind()),
    _ => Some("ways"),
  }
}

fn osm_field(own: &admin_level) -> Option<field> {
  let elements = osm_elements(own);
  let kind = kind_of(&elements)?;
  Some(field::of(&format!("osm {kind}"), listed(&elements)))
}

fn named(area: &admin_level) -> Vec<piece> {
  let elements = osm_elements(area);
  let name = &area.name;
  let Some(kind) = kind_of(&elements) else {
    return vec![piece::text(name.clone())];
  };
  let mut pieces = vec![piece::text(format!("{name} ({kind} "))];
  pieces.extend(listed(&elements));
  pieces.push(piece::text(")".to_string()));
  pieces
}

fn level_name(rung: u8) -> String {
  match level::new(rung) {
    Some(level) => format!("admin level {rung} ({})", level.name()),
    None => format!("admin level {rung}"),
  }
}

fn level_fields(rung: u8, path: &[admin_level]) -> Vec<field> {
  let name = level_name(rung);
  let areas: Vec<field> = path
    .iter()
    .filter(|area| area.level == rung)
    .map(|area| field::of(&name, named(area)))
    .collect();
  if areas.is_empty() {
    return vec![field::of(&name, vec![piece::absent])];
  }
  areas
}

fn rungs_of(path: &[admin_level]) -> Vec<u8> {
  let mut rungs: Vec<u8> = (0..=level::street.value())
    .filter_map(level::new)
    .map(level::value)
    .collect();
  rungs.extend(path.iter().map(|area| area.level));
  rungs.sort_unstable();
  rungs.dedup();
  rungs
}

impl leaf {
  pub fn breadcrumb(&self) -> String {
    breadcrumb(&self.path)
  }

  pub fn fields(&self) -> Vec<field> {
    let address = &self.address;
    let path = &address.admin_levels;
    let Some(own) = path.last() else {
      return Vec::new();
    };
    let optional = [
      ("post code", address.attributes.post_code.clone()),
      (
        "country code",
        address.attributes.country_iso_3166_1_alpha_2_code.clone(),
      ),
    ];
    let mut fields = vec![
      field::text("name", own.name.clone()),
      field::text("label", address.friendly_name.clone()),
    ];
    fields.extend(
      optional
        .into_iter()
        .filter_map(|(name, value)| Some(field::text(name, value?))),
    );
    fields.push(field::text(
      "point",
      format!("{:.5}, {:.5}", address.latitude, address.longitude),
    ));
    fields.extend(osm_field(own));
    fields.push(field::text("id", address.id.clone()));
    let rungs = rungs_of(path);
    fields.extend(rungs.into_iter().flat_map(|rung| level_fields(rung, path)));
    fields
  }
}