"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.cutsService = void 0;
const client_1 = require("@prisma/client");
const database_1 = require("../../../config/database");
const error_handler_1 = require("../../../middleware/error-handler");
const spatial_1 = require("../../../utils/spatial");
const logger_1 = require("../../../utils/logger");
exports.cutsService = {
    async findAll(filters) {
        const { page, limit, category, search } = filters;
        const skip = (page - 1) * limit;
        const where = {};
        if (search) {
            where.OR = [
                { name: { contains: search, mode: 'insensitive' } },
                { description: { contains: search, mode: 'insensitive' } },
            ];
        }
        if (category)
            where.category = category;
        const [cuts, total] = await Promise.all([
            database_1.prisma.cut.findMany({
                where,
                skip,
                take: limit,
                orderBy: { createdAt: 'desc' },
            }),
            database_1.prisma.cut.count({ where }),
        ]);
        return {
            cuts,
            pagination: {
                page,
                limit,
                total,
                totalPages: Math.ceil(total / limit),
            },
        };
    },
    async findById(id) {
        const cut = await database_1.prisma.cut.findUnique({ where: { id } });
        if (!cut) {
            throw new error_handler_1.AppError(404, 'Cut not found', 'CUT_NOT_FOUND');
        }
        return cut;
    },
    async create(data, userId) {
        // Auto-calculate bounds from geojson if not provided
        let boundsStr = data.bounds;
        if (!boundsStr) {
            try {
                const rings = (0, spatial_1.parseGeoJsonPolygon)(data.geojson);
                const allCoords = rings.flat();
                const bounds = (0, spatial_1.calculateBounds)(allCoords);
                boundsStr = JSON.stringify(bounds);
            }
            catch {
                // Bounds calculation optional
            }
        }
        const cut = await database_1.prisma.cut.create({
            data: {
                name: data.name,
                description: data.description,
                color: data.color,
                opacity: data.opacity,
                category: data.category,
                isPublic: data.isPublic,
                isOfficial: data.isOfficial,
                geojson: data.geojson,
                bounds: boundsStr,
                showLocations: data.showLocations,
                exportEnabled: data.exportEnabled,
                assignedTo: data.assignedTo,
                createdByUserId: userId,
            },
        });
        return cut;
    },
    async update(id, data) {
        const existing = await database_1.prisma.cut.findUnique({ where: { id } });
        if (!existing) {
            throw new error_handler_1.AppError(404, 'Cut not found', 'CUT_NOT_FOUND');
        }
        // Recalculate bounds if geojson changed
        const updateData = { ...data };
        if (data.geojson && !data.bounds) {
            try {
                const rings = (0, spatial_1.parseGeoJsonPolygon)(data.geojson);
                const allCoords = rings.flat();
                const bounds = (0, spatial_1.calculateBounds)(allCoords);
                updateData.bounds = JSON.stringify(bounds);
            }
            catch {
                // Bounds calculation optional
            }
        }
        if (data.lastCanvassed !== undefined) {
            updateData.lastCanvassed = data.lastCanvassed ? new Date(data.lastCanvassed) : null;
        }
        const cut = await database_1.prisma.cut.update({
            where: { id },
            data: updateData,
        });
        return cut;
    },
    async delete(id) {
        const existing = await database_1.prisma.cut.findUnique({ where: { id } });
        if (!existing) {
            throw new error_handler_1.AppError(404, 'Cut not found', 'CUT_NOT_FOUND');
        }
        await database_1.prisma.cut.delete({ where: { id } });
    },
    async getPublicCuts() {
        const cuts = await database_1.prisma.cut.findMany({
            where: { isPublic: true },
            select: {
                id: true,
                name: true,
                description: true,
                color: true,
                opacity: true,
                category: true,
                geojson: true,
                bounds: true,
            },
            orderBy: { name: 'asc' },
        });
        return cuts;
    },
    async getLocationsInCut(id) {
        const cut = await database_1.prisma.cut.findUnique({ where: { id } });
        if (!cut) {
            throw new error_handler_1.AppError(404, 'Cut not found', 'CUT_NOT_FOUND');
        }
        const rings = (0, spatial_1.parseGeoJsonPolygon)(cut.geojson);
        // Get all locations (latitude/longitude are non-nullable)
        const locations = await database_1.prisma.location.findMany();
        // Filter to those inside the polygon
        const inside = locations.filter((loc) => {
            const lat = Number(loc.latitude);
            const lng = Number(loc.longitude);
            return rings.some((ring) => (0, spatial_1.isPointInPolygon)(lat, lng, ring));
        });
        return inside;
    },
    async exportSingleGeoJson(id) {
        const cut = await database_1.prisma.cut.findUnique({ where: { id } });
        if (!cut)
            throw new error_handler_1.AppError(404, 'Cut not found', 'CUT_NOT_FOUND');
        const geojson = JSON.parse(cut.geojson);
        return {
            type: 'Feature',
            properties: {
                name: cut.name,
                description: cut.description,
                color: cut.color,
                'fill-opacity': Number(cut.opacity),
                fill: cut.color,
                category: cut.category,
                isPublic: cut.isPublic,
                isOfficial: cut.isOfficial,
                assignedTo: cut.assignedTo,
            },
            geometry: geojson,
        };
    },
    async exportAllGeoJson() {
        const cuts = await database_1.prisma.cut.findMany({ orderBy: { name: 'asc' } });
        const features = cuts.map((cut) => {
            const geojson = JSON.parse(cut.geojson);
            return {
                type: 'Feature',
                properties: {
                    name: cut.name,
                    description: cut.description,
                    color: cut.color,
                    'fill-opacity': Number(cut.opacity),
                    fill: cut.color,
                    category: cut.category,
                    isPublic: cut.isPublic,
                    isOfficial: cut.isOfficial,
                    assignedTo: cut.assignedTo,
                },
                geometry: geojson,
            };
        });
        return {
            type: 'FeatureCollection',
            features,
        };
    },
    async importGeoJson(buffer, userId) {
        let parsed;
        try {
            parsed = JSON.parse(buffer.toString('utf-8'));
        }
        catch {
            throw new error_handler_1.AppError(400, 'Invalid JSON file', 'INVALID_JSON');
        }
        // Collect features from various GeoJSON shapes
        const features = [];
        const type = parsed.type;
        if (type === 'FeatureCollection' && Array.isArray(parsed.features)) {
            for (const f of parsed.features) {
                if (f.geometry)
                    features.push({ geometry: f.geometry, properties: (f.properties ?? {}) });
            }
        }
        else if (type === 'Feature' && parsed.geometry) {
            features.push({ geometry: parsed.geometry, properties: (parsed.properties ?? {}) });
        }
        else if (type === 'Polygon' || type === 'MultiPolygon') {
            features.push({ geometry: parsed, properties: {} });
        }
        else {
            throw new error_handler_1.AppError(400, 'Unsupported GeoJSON type. Expected Feature, FeatureCollection, Polygon, or MultiPolygon.', 'UNSUPPORTED_GEOJSON');
        }
        let success = 0;
        let failed = 0;
        const errors = [];
        for (let i = 0; i < features.length; i++) {
            try {
                const { geometry, properties } = features[i];
                const geoType = geometry.type;
                if (geoType !== 'Polygon' && geoType !== 'MultiPolygon') {
                    errors.push(`Feature ${i + 1}: Unsupported geometry type "${geoType}"`);
                    failed++;
                    continue;
                }
                const geojsonStr = JSON.stringify(geometry);
                // Extract properties
                const name = properties?.name || `Imported Cut ${i + 1}`;
                const color = properties?.color || properties?.fill || '#3388ff';
                const opacity = typeof properties?.['fill-opacity'] === 'number' ? properties['fill-opacity'] : 0.3;
                const categoryRaw = properties?.category?.toUpperCase();
                const category = categoryRaw && Object.values(client_1.CutCategory).includes(categoryRaw)
                    ? categoryRaw
                    : undefined;
                // Calculate bounds
                let boundsStr;
                try {
                    const rings = (0, spatial_1.parseGeoJsonPolygon)(geojsonStr);
                    const allCoords = rings.flat();
                    const bounds = (0, spatial_1.calculateBounds)(allCoords);
                    boundsStr = JSON.stringify(bounds);
                }
                catch { /* optional */ }
                await database_1.prisma.cut.create({
                    data: {
                        name,
                        description: properties?.description || undefined,
                        color: /^#[0-9a-fA-F]{6}$/.test(color) ? color : '#3388ff',
                        opacity,
                        category,
                        isPublic: properties?.isPublic === true,
                        isOfficial: properties?.isOfficial === true,
                        geojson: geojsonStr,
                        bounds: boundsStr,
                        assignedTo: properties?.assignedTo || undefined,
                        createdByUserId: userId,
                    },
                });
                success++;
            }
            catch (err) {
                const msg = err instanceof Error ? err.message : 'Unknown error';
                errors.push(`Feature ${i + 1}: ${msg}`);
                failed++;
                logger_1.logger.warn(`GeoJSON import feature ${i + 1} failed:`, err);
            }
        }
        return { total: features.length, success, failed, errors: errors.slice(0, 50) };
    },
    async getStatistics(id) {
        const cut = await database_1.prisma.cut.findUnique({ where: { id } });
        if (!cut) {
            throw new error_handler_1.AppError(404, 'Cut not found', 'CUT_NOT_FOUND');
        }
        const rings = (0, spatial_1.parseGeoJsonPolygon)(cut.geojson);
        // Get all locations with addresses (latitude/longitude are non-nullable)
        const locations = await database_1.prisma.location.findMany({
            select: {
                id: true,
                latitude: true,
                longitude: true,
                addresses: {
                    select: {
                        id: true,
                        supportLevel: true,
                        sign: true,
                    },
                },
            },
        });
        // Filter locations inside polygon and flatten addresses
        const addressesInCut = [];
        for (const loc of locations) {
            const lat = Number(loc.latitude);
            const lng = Number(loc.longitude);
            if (rings.some((ring) => (0, spatial_1.isPointInPolygon)(lat, lng, ring))) {
                addressesInCut.push(...loc.addresses);
            }
        }
        const byLevel = {
            LEVEL_1: 0,
            LEVEL_2: 0,
            LEVEL_3: 0,
            LEVEL_4: 0,
            NONE: 0,
        };
        let withSign = 0;
        for (const addr of addressesInCut) {
            const level = addr.supportLevel || 'NONE';
            byLevel[level] = (byLevel[level] || 0) + 1;
            if (addr.sign)
                withSign++;
        }
        return {
            total: addressesInCut.length,
            byLevel,
            withSign,
        };
    },
};
//# sourceMappingURL=cuts.service.js.map