Source: backend/server.js

/**
 * Main Express server application file.
 * Handles API endpoints, static file serving, and core business logic execution.
 * @module server
 */
import express from 'express';
import cors from 'cors';
import path from 'path';
import { fileURLToPath } from 'url';
import validator from 'validator'; // [NEW] External library for robust validation

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.join(__dirname, '..');

// Import data and utility classes
import { products, Bouquet } from './data/products/products.js';
import { User } from './data/entities/User.js';
import { Order } from './data/entities/Order.js';
import { Payment } from './data/entities/Payment.js';
import { NovaPoshtaShipping } from './data/entities/Shipping.js';
import { createMasterGraph } from './utils/compatibilityGraph.js';

const compatibilityGraph = createMasterGraph(products);

const app = express();
const PORT = 3001;

app.use(cors());
app.use(express.json());

app.use(express.static(path.join(projectRoot, 'frontend')));
app.use('/images', express.static(path.join(projectRoot, 'images')));
app.use('/entities', express.static(path.join(projectRoot, 'backend/data/entities')));

/**
 * Helper function to transform complex product objects (class instances) 
 * into simple Data Transfer Objects (DTOs) for API response.
 * @function
 * @param {Object} product - An instance of a product class (Flower, Bouquet, etc.).
 * @returns {Object} A simplified object containing key product data.
 */
function hydrateProduct(product) {
    const data = {
        id: product.getId(),
        name: product.getName(),
        price: product.getPrice(),
        image: product.getImage(),
        description: product.getDescription(),
        isPricy: product.isPricy(),
        type: 'ShopItem'
    };

    if (typeof product.isSuitableForTallVase === 'function') {
        data.type = 'Flower';
        data.isSuitableForTallVase = product.isSuitableForTallVase();
    } else if (typeof product.addFlower === 'function') {
        data.type = 'Bouquet';
        data.mainColor = product.getMainColor();
        data.season = product.getSeason();
    } else if (product.constructor.name === 'DecorItem') {
        data.type = 'DecorItem';
    }
    
    return data;
}

// === CORE API ENDPOINTS ===

/**
 * GET /api/products
 * Returns a list of all products (hydrated DTOs).
 */
app.get('/api/products', (req, res) => {
    const hydratedProducts = products.map(hydrateProduct); 
    res.json(hydratedProducts);
});

/**
 * GET /api/products/:id
 * Returns a single product by ID.
 */
app.get('/api/products/:id', (req, res) => {
    const id = parseInt(req.params.id);
    const product = products.find(p => p.getId() === id);
    if (product) {
        res.json(hydrateProduct(product)); 
    } else {
        res.status(404).json({ error: 'Product not found' });
    }
});

/**
 * POST /api/order
 * Processes a new customer order. Implements robust validation using the 'validator' library.
 * @param {Object} req.body - Order details including user info, items, and payment method.
 */
app.post('/api/order', (req, res) => {
    const orderData = req.body;
    try {
        if (!orderData || !orderData.items || !orderData.user) {
            return res.status(400).json({ error: 'Empty or incorrect order data' });
        }
        
        // --- LAB 3: ROBUST INPUT VALIDATION (validator library) ---
        const { name, email, address } = orderData.user;
        
        if (!validator.isEmail(email)) {
            throw new Error('Invalid email format provided.');
        }

        const sanitizedName = validator.escape(name); // Prevents XSS injection on name
        
        // Basic address length check, now using sanitized name
        if (address.length < 5) {
             throw new Error('Address must be longer than 5 characters.');
        }

        // Use sanitized name for User creation
        const user = new User(Date.now(), sanitizedName, email);
        const addressSet = user.setShippingAddress(address);
        if (!addressSet) { throw new Error('Address setting failed.'); }

        const order = new Order(Date.now() + 1, user);
        orderData.items.forEach(item => order.addItem(item));
        const orderTotal = order.calculateTotal();
        
        const shipping = new NovaPoshtaShipping(1, user.getShippingAddress(), 1);
        const shippingCost = shipping.calculateCost();
        const finalTotal = orderTotal + shippingCost;
        
        const payment = new Payment(Date.now() + 2, finalTotal, orderData.paymentMethod);
        payment.processPayment();
        const trackingNumber = shipping.generateTrackingNumber();

        res.status(201).json({
            success: true,
            message: 'Order processed successfully!',
            orderId: order.getOrderId(),
            customerName: user.getName(),
            totalPaid: finalTotal,
            trackingNumber: trackingNumber,
            estimatedDelivery: order.getEstimatedDeliveryDate()
        });
   
    } catch (error) {
        console.error('Order processing failed:', error.message);
        res.status(400).json({ success: false, error: error.message });
    }
});

/**
 * POST /api/build-bouquet
 * Calculates the optimal assembly cost using the MST graph algorithm.
 * @param {Array<string>} req.body.componentNames - Names of the flowers selected for the bouquet.
 */
app.post('/api/build-bouquet', (req, res) => {
     const { componentNames } = req.body;
     if (!componentNames || componentNames.length < 2) {
        return res.status(400).json({ success: false, error: 'Bouquet must have at least 2 components.' });
     }
     try {
        const components = products.filter(p => componentNames.includes(p.getName()));
        const tempBouquet = new Bouquet(999, 'Temp Builder', 0, '', components);
        const result = tempBouquet.findOptimalFlowerConnections(compatibilityGraph, 'Kruskal');
        
        res.status(200).json({
            success: true,
            assemblyCost: result.weight,
            connections: result.mst.length,
            algorithm: result.algorithm
        });
     } catch (error) {
        console.error('Bouquet build error:', error.message);
        res.status(500).json({ success: false, error: 'Error processing bouquet logic.' });
     }
});


// ... (Static routes for HTML5 navigation remain unchanged)
app.get(['/', '/index.html'], (req, res) => {
    res.sendFile(path.join(projectRoot, 'frontend', 'index.html'));
});
// ... (other static routes: /cart.html, /details.html, /checkout.html, etc.)

/**
 * GET /api/components
 * API endpoint used by the Bouquet Builder.
 * Returns a filtered list of individual flowers suitable for custom bouquets.
 * @returns {Array<Object>} List of hydrated flower objects.
 */
app.get('/api/components', (req, res) => {
    const components = products
        .filter(p => typeof p.isSuitableForTallVase === 'function')
        .map(hydrateProduct);
    
    res.json(components);
});

app.listen(PORT, () => {
    console.log(`Server running. Frontend accessible at http://localhost:${PORT}`);
});