#!/usr/bin/env node

import { createHash } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";

const [inputPath, outputPath] = process.argv.slice(2);
if (!inputPath || !outputPath) {
  console.error("usage: generate-reference-station-seed.mjs <input.geojson> <output.json>");
  process.exit(2);
}

const source = JSON.parse(await readFile(resolve(inputPath), "utf8"));
if (source?.type !== "FeatureCollection" || !Array.isArray(source.features)) {
  throw new Error("station_seed_feature_collection_required");
}

const generatedAt = isIso(source.timestamp) ? new Date(source.timestamp).toISOString() : new Date(0).toISOString();
const facts = source.features.map((feature, index) => normalizeFeature(feature, generatedAt, index));
facts.sort((left, right) => left.sourceId.localeCompare(right.sourceId));
const contentDigest = createHash("sha256")
  .update(JSON.stringify(facts))
  .digest("hex");
const counts = Object.fromEntries(["metro", "railway_station", "railway_terminal"].map((category) => [
  category,
  facts.filter((fact) => fact.attributes.category === category).length,
]));
const document = {
  schemaVersion: "nodedc.map-reference-seed/v1",
  profileId: "transport-stations.v1",
  sourceRevision: `seed-${contentDigest.slice(0, 16)}`,
  generatedAt,
  contentDigest,
  complete: true,
  facts,
  metadata: {
    factCount: facts.length,
    categoryCounts: counts,
  },
};

await mkdir(dirname(resolve(outputPath)), { recursive: true });
await writeFile(resolve(outputPath), `${JSON.stringify(document)}\n`, "utf8");
console.log(JSON.stringify({ output: resolve(outputPath), ...document.metadata, contentDigest }));

function normalizeFeature(feature, generatedAt, index) {
  if (feature?.type !== "Feature" || feature?.geometry?.type !== "Point") {
    throw new Error(`station_seed_feature_${index}_invalid`);
  }
  const coordinates = feature.geometry.coordinates;
  if (!Array.isArray(coordinates) || coordinates.length < 2) {
    throw new Error(`station_seed_feature_${index}_coordinates_invalid`);
  }
  const longitude = Number(coordinates[0]);
  const latitude = Number(coordinates[1]);
  if (!Number.isFinite(longitude) || longitude < -180 || longitude > 180
    || !Number.isFinite(latitude) || latitude < -90 || latitude > 90) {
    throw new Error(`station_seed_feature_${index}_coordinates_invalid`);
  }
  const properties = feature.properties && typeof feature.properties === "object" ? feature.properties : {};
  const category = normalizeCategory(properties.sort);
  const nativeId = String(feature.id || properties["@id"] || "").trim();
  const identity = nativeId.match(/^(node|way|relation)\/([1-9]\d*)$/);
  if (!identity) throw new Error(`station_seed_feature_${index}_identity_invalid`);
  const attributes = compact({
    name: firstString(properties.name, properties["name:ru"], properties.official_name, properties.loc_name),
    category,
    network: optionalString(properties.network),
    operator: optionalString(properties.operator),
    official_name: optionalString(properties.official_name),
    local_name: optionalString(properties.loc_name),
    uic_ref: optionalString(properties.uic_ref),
    wheelchair: optionalString(properties.wheelchair),
  });
  if (!attributes.name) throw new Error(`station_seed_feature_${index}_name_required`);
  return {
    sourceId: `osm.${identity[1]}.${identity[2]}`,
    semanticType: category === "railway_terminal" ? "map.terminal" : "map.station",
    observedAt: generatedAt,
    receivedAt: generatedAt,
    attributes,
    geometry: { type: "Point", coordinates: [longitude, latitude] },
    presentationStatus: "active",
  };
}

function normalizeCategory(value) {
  if (value === "metro") return "metro";
  if (value === "terminal") return "railway_terminal";
  if (value === "rzd_station") return "railway_station";
  throw new Error("station_seed_category_invalid");
}

function firstString(...values) {
  return values.map(optionalString).find(Boolean);
}

function optionalString(value) {
  if (typeof value !== "string") return undefined;
  const normalized = value.trim();
  return normalized && normalized.length <= 256 ? normalized : undefined;
}

function compact(value) {
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
}

function isIso(value) {
  return typeof value === "string" && !Number.isNaN(Date.parse(value));
}
