var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
import vine from '@vinejs/vine';
import { parse } from 'csv-parse';
import fs from 'fs';
import fsPromise from 'fs/promises';
import Fuse from 'fuse.js';
import _ from 'lodash';
import path from 'path';
import { method } from '../observability/tracing.js';
export const AKOpenDataCityVineSchema = {
    name: vine.string(),
    postalCode: vine.string().postalCode(),
    latitude: vine.number().optional(),
    longitude: vine.number().optional(),
    inseeCode: vine.string(),
};
const AKOpenDataCityVineSchemaObject = vine.object(AKOpenDataCityVineSchema);
export default class AKOpenDataCityService {
    #options;
    fuse = new Fuse([]);
    outputValidator = vine.compile(AKOpenDataCityVineSchemaObject);
    static async create(options) {
        const service = new AKOpenDataCityService(options);
        await service.loadCities();
        return service;
    }
    constructor(options) {
        this.#options = options;
    }
    search(query) {
        const result = this.fuse.search(_.deburr(query), { limit: 10 });
        return _.map(result, (r) => ({
            ..._.pick(r.item, ['name', 'latitude', 'longitude', 'inseeCode']),
            postalCode: r.item.postalCodes[0],
        }));
    }
    getByCodes(postalCode, inseeCode) {
        const result = this.fuse
            .search({ inseeCode: inseeCode }, { limit: 1 })
            .filter((r) => r.item.postalCodes.includes(postalCode));
        if (_.isEmpty(result))
            throw Error(`Could not find city of insee code ${inseeCode} and postal code ${postalCode}`);
        else {
            const item = result[0].item;
            if (!item.postalCodes.includes(postalCode) || item.inseeCode !== inseeCode)
                throw Error(`Mismatch in find city (requested insee = ${inseeCode}, postal code ${postalCode} but found insee = ${item.inseeCode}, postal codes = ${item.postalCodes}})`);
            return {
                ..._.pick(item, ['name', 'latitude', 'longitude', 'inseeCode']),
                postalCode: item.postalCodes[0],
            };
        }
    }
    async loadCities() {
        const cities = await this.loadDatagouvCities();
        const validPostalCodes = await this.loadOpenDataJuridictionsPostalCodes();
        const selectedCities = cities.filter((c) => c.postalCodes.some((p) => validPostalCodes.includes(p)));
        this.fuse = new Fuse(selectedCities, {
            keys: [
                { name: 'name', getFn: (city) => _.deburr(city.name), weight: 2 },
                'postalCodes',
                { name: 'inseeCode', weight: 0.01 },
            ],
            isCaseSensitive: false,
        });
    }
    async loadDatagouvCities() {
        const cities = await this.#loadCSV('20230823-communes-departement-region.csv', [
            ['nom_commune_complet', 'name'],
            ['code_postal', 'postalCode', this.normalizePostalCode.bind(this)],
            ['latitude', 'latitude', parseFloat],
            ['longitude', 'longitude', parseFloat],
            ['code_commune_INSEE', 'inseeCode'],
        ]);
        for (const city of cities) {
            if (!_.isNumber(city.latitude))
                city.latitude = undefined;
            if (!_.isNumber(city.longitude))
                city.longitude = undefined;
        }
        const hasArrondissement = (c) => Number.isInteger(Number(_.last(c.name.split(' '))));
        const citiesWithArrondissement = _(cities)
            .filter(hasArrondissement)
            .groupBy((c) => c.postalCode.substring(0, 3) + '00')
            .map((sameCities, postalCode) => {
            const { name, latitude, longitude, inseeCode } = sameCities[0];
            return {
                name: _(name).split(' ').dropRight().join(' '),
                latitude,
                longitude,
                inseeCode,
                postalCodes: [...sameCities.map((c) => c.postalCode), postalCode],
            };
        })
            .value();
        const citiesWithoutArrondissement = _(cities)
            .filter(_.negate(hasArrondissement))
            .groupBy((c) => c.inseeCode)
            .map((sameCities) => {
            const { name, latitude, longitude, inseeCode } = sameCities[0];
            return {
                name,
                latitude,
                longitude,
                postalCodes: sameCities.map((c) => c.postalCode),
                inseeCode,
            };
        })
            .value();
        return [...citiesWithArrondissement, ...citiesWithoutArrondissement];
    }
    async loadOpenDataJuridictionsPostalCodes() {
        const juridictions = await this.#loadJSON('liste-des-structures-de-la-justice-france.json');
        const cedexToPostalCode = await this.loadCedexToPostalCode();
        const postalCodes = _(juridictions)
            .map((r) => _.get(r, 'postal_code'))
            .map((postalCode) => _.get(cedexToPostalCode, postalCode, postalCode))
            .filter((c) => c)
            .uniq()
            .value();
        return postalCodes;
    }
    async loadCedexToPostalCode() {
        const cedexes = await this.#loadCSV('correspondance-code-cedex-code-insee.csv', [
            ['﻿Code Postal / CEDEX', 'postalCode'],
            ['Code INSEE', 'inseeCode'],
            ['Libellé', 'display'],
            ['Type de code', 'type'],
            ['Nom de la commune', 'city'],
        ], ';');
        const inseeToPostalCode = Object.fromEntries(cedexes
            .filter((c) => c.type === 'Code Postal')
            .map((e) => [e.inseeCode, e.postalCode])
            .filter((e) => e[1]));
        const cedexToPostalCode = Object.fromEntries(cedexes
            .filter((c) => c.type === 'Code CEDEX')
            .filter((c) => _(c.display).deburr().toLowerCase().includes(_(c.city).deburr().toLocaleLowerCase()))
            .map((e) => [e.postalCode, inseeToPostalCode[e.inseeCode]])
            .filter((e) => e[1]));
        return cedexToPostalCode;
    }
    normalizePostalCode(code) {
        return _.padStart(code, 5, '0');
    }
    async #loadJSON(fileName) {
        const filePath = path.join(this.#options.assetsPath.toString(), fileName);
        const rawData = await fsPromise.readFile(filePath, 'utf8');
        const jsonData = JSON.parse(rawData);
        return jsonData;
    }
    async #loadCSV(fileName, fields, delimiter) {
        const filePath = path.join(this.#options.assetsPath.toString(), fileName);
        const parser = fs.createReadStream(filePath).pipe(parse({
            skipEmptyLines: true,
            delimiter,
        }));
        const parserIterator = parser[Symbol.asyncIterator]();
        const columns = (await parserIterator.next()).value;
        const results = [];
        for await (const record of parserIterator) {
            const result = {};
            for (const [column, fieldname, transformer] of fields) {
                const value = record[columns.indexOf(column)];
                result[fieldname] = transformer ? transformer(value) : value;
            }
            results.push(result);
        }
        return results;
    }
}
__decorate([
    method(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String]),
    __metadata("design:returntype", Array)
], AKOpenDataCityService.prototype, "search", null);
__decorate([
    method(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [String, String]),
    __metadata("design:returntype", Object)
], AKOpenDataCityService.prototype, "getByCodes", null);
__decorate([
    method(),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", Promise)
], AKOpenDataCityService, "create", null);
//# sourceMappingURL=opendata_city_service.js.map