#!/usr/bin/env tsx
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

/**
 * Augment the existing EPSG index with proj4 definition strings.
 *
 * Reads the generated EPSG index, fetches proj4 strings from epsg.io,
 * and writes the updated file back.
 *
 * Usage:
 *   npx tsx scripts/augment-epsg-proj4.ts
 *   npx tsx scripts/augment-epsg-proj4.ts --concurrency=30
 *
 * Alternative (when epsg.io is unavailable):
 *   pnpm add -D epsg epsg-index
 *   Then use the merge script in the PR description to combine both packages.
 */

import * as fs from 'node:fs';
import * as path from 'node:path';

const DEFAULT_CONCURRENCY = 30;
const GENERATED_PATH = path.resolve('packages/data/src/generated/epsg-index.generated.ts');

type EpsgEntry = {
  code: string;
  name: string;
  kind: string;
  area: string;
  scope: string;
  datum: string;
  projection: string;
  unit: string;
  deprecated: boolean;
  aliases: string[];
  searchText: string;
  proj4?: string;
};

function parseArgs(argv: string[]): { concurrency: number } {
  let concurrency = DEFAULT_CONCURRENCY;
  for (const arg of argv) {
    if (arg.startsWith('--concurrency=')) {
      const value = Number.parseInt(arg.slice('--concurrency='.length), 10);
      if (Number.isFinite(value) && value > 0) concurrency = value;
    }
  }
  return { concurrency };
}

function readExistingEntries(): { entries: EpsgEntry[]; version: string } {
  const content = fs.readFileSync(GENERATED_PATH, 'utf8');
  const jsonMatch = content.match(/EPSG_INDEX_JSON = (".*")/s);
  const versionMatch = content.match(/EPSG_INDEX_DATASET_VERSION = '([^']+)'/);
  if (!jsonMatch) throw new Error('Could not parse EPSG_INDEX_JSON from generated file');
  const json = JSON.parse(jsonMatch[1]) as string;
  return {
    entries: JSON.parse(json) as EpsgEntry[],
    version: versionMatch?.[1] ?? 'unknown',
  };
}

async function fetchProj4(code: string, attempts = 3): Promise<string | null> {
  for (let attempt = 1; attempt <= attempts; attempt++) {
    try {
      const resp = await fetch(`https://epsg.io/${code}.proj4`, {
        headers: { 'User-Agent': 'ifc-lite-epsg-generator/1.0' },
      });
      if (!resp.ok) return null;
      const text = (await resp.text()).trim();
      if (!text || text.startsWith('<') || text.startsWith('{') || !text.includes('+')) {
        return null;
      }
      return text;
    } catch {
      if (attempt < attempts) {
        await new Promise(r => setTimeout(r, 200 * attempt));
      }
    }
  }
  return null;
}

function renderModule(entries: EpsgEntry[], version: string): string {
  const json = JSON.stringify(entries);
  const stringLiteral = JSON.stringify(json);

  return `/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

/**
 * AUTO-GENERATED by \`scripts/generate-epsg-index.ts\`.
 *
 * Source: EPSG Registry API (https://apps.epsg.org/api/v1)
 * Dataset version: ${version}
 * Terms: https://epsg.org/terms-of-use.html
 *
 * Do not edit by hand. Re-run \`pnpm generate:epsg-index\` to refresh.
 */

import type { EpsgIndexEntry } from '../epsg-types.js';

export const EPSG_INDEX_DATASET_VERSION = '${version}';

const EPSG_INDEX_JSON = ${stringLiteral};

export const EPSG_INDEX: readonly EpsgIndexEntry[] = JSON.parse(EPSG_INDEX_JSON) as EpsgIndexEntry[];
`;
}

async function main(): Promise<void> {
  const { concurrency } = parseArgs(process.argv.slice(2));
  const { entries, version } = readExistingEntries();

  // Filter to entries that don't already have proj4
  const needsProj4 = entries.filter(e => !e.proj4);
  console.log(`Total entries: ${entries.length}, need proj4: ${needsProj4.length}`);

  const queue = [...needsProj4];
  let resolved = 0;
  let failed = 0;

  async function worker(): Promise<void> {
    while (queue.length > 0) {
      const entry = queue.shift();
      if (!entry) return;
      const def = await fetchProj4(entry.code);
      if (def) {
        entry.proj4 = def;
        resolved++;
      } else {
        failed++;
      }
      const total = resolved + failed;
      if (total % 500 === 0) {
        console.log(`  ${total}/${needsProj4.length} (${resolved} resolved, ${failed} missing)`);
      }
    }
  }

  const start = Date.now();
  await Promise.all(Array.from({ length: concurrency }, () => worker()));
  const elapsed = Date.now() - start;

  console.log(`Done in ${elapsed}ms: ${resolved} resolved, ${failed} missing`);
  console.log(`Total entries with proj4: ${entries.filter(e => e.proj4).length}/${entries.length}`);

  const moduleSource = renderModule(entries, version);
  fs.writeFileSync(GENERATED_PATH, `${moduleSource}\n`, 'utf8');
  console.log(`Written to ${GENERATED_PATH}`);
}

await main();
