const express = require('express');
const multer = require('multer');
const axios = require('axios');
const FormData = require('form-data');

const app = express();
const port = process.env.PORT || 3000;

const config = {
    ncodbUrl: process.env.NOCODB_URL || 'https://noco.quadjourney.com',
    ncodbPublicUrl: process.env.NOCODB_PUBLIC_URL || process.env.NOCODB_URL || 'https://noco.quadjourney.com',
    apiToken: process.env.NOCODB_API_TOKEN || '',
    baseId: process.env.NOCODB_BASE_ID || 'pava9q9zccyihpt',
    tableId: process.env.NOCODB_TABLE_ID || 'mash7c5nx4unukc',
    columnName: process.env.NOCODB_COLUMN_NAME || 'photos',
    discordWebhookUrl: process.env.DISCORD_WEBHOOK_URL || '',
    discordUsername: process.env.DISCORD_USERNAME || 'NocoDB Uploader',
    discordAvatarUrl: process.env.DISCORD_AVATAR_URL || '',
    publicAppUrl: process.env.PUBLIC_APP_URL || `http://localhost:${process.env.EXTERNAL_PORT || port}`,
    sendToPhoneToken: process.env.SEND_TO_PHONE_TOKEN || '',
    workspaceId: ''  // fetched at startup
};

// CORS disabled — internal service accessed only via gateway
app.use(express.json());
// Allow form-encoded payloads from NocoDB webhook buttons
app.use(express.urlencoded({ extended: true }));

// Health check (before auth middleware)
app.get('/health', (req, res) => res.json({ status: 'ok' }));

// API key auth middleware — require X-API-Key header on all routes
const SERVICE_API_KEY = process.env.SERVICE_API_KEY || '';
if (SERVICE_API_KEY) {
    app.use((req, res, next) => {
        const key = req.headers['x-api-key'] || req.query.api_key;
        if (key !== SERVICE_API_KEY) {
            return res.status(401).json({ error: 'Unauthorized: invalid API key' });
        }
        next();
    });
}

const upload = multer({
    storage: multer.memoryStorage(),
    limits: { fileSize: 10 * 1024 * 1024 }
});

function extractRowFromPayload(body = {}) {
    if (!body || typeof body !== 'object') return null;
    // Common NocoDB webhook shapes
    if (body.data?.rows?.length) return body.data.rows[0];
    if (body.data?.records?.length) return body.data.records[0];
    if (body.data?.previous_rows?.length) return body.data.previous_rows[0];
    if (Array.isArray(body.rows) && body.rows.length) return body.rows[0];
    if (body.row) return body.row;
    return null;
}

// Optional per-request shared secret for send-to-phone endpoint
function isAuthorized(req) {
    if (!config.sendToPhoneToken) return true;
    const provided =
        (req.query && (req.query.token || req.query.auth)) ||
        (req.body && (req.body.token || req.body.auth)) ||
        req.headers['x-send-to-phone-token'];
    return provided === config.sendToPhoneToken;
}

function buildDeepLink(rowId, itemName) {
    const base = (config.publicAppUrl || '').replace(/\/$/, '') || `http://localhost:${port}`;
    const params = new URLSearchParams({ rowId: String(rowId) });
    if (itemName) {
        params.set('item', itemName);
    }
    return `${base}?${params.toString()}`;
}
const safeField = (value, fallback = 'Unknown') => (value ? String(value) : fallback);

async function fetchRowByFilter(rowId) {
    const headers = {
        'xc-token': config.apiToken,
        'Accept': 'application/json'
    };

    // Try row_id first (common UUID field), then Id as string
    const queries = [
        { where: `(row_id,eq,${rowId})`, limit: 1 },
        { where: `(Id,eq,${rowId})`, limit: 1 }
    ];

    for (const params of queries) {
        try {
            const resp = await axios.get(
                `${config.ncodbUrl}/api/v1/db/data/noco/${config.baseId}/${config.tableId}`,
                { headers, params }
            );
            const list = resp.data?.list || [];
            if (list.length > 0) return list[0];
        } catch (err) {
            // continue to next strategy
        }
    }
    return null;
}

async function fetchRowFlexible(rowId) {
    const headers = {
        'xc-token': config.apiToken,
        'Accept': 'application/json'
    };

    // First try direct PK fetch
    try {
        const resp = await axios.get(
            `${config.ncodbUrl}/api/v1/db/data/noco/${config.baseId}/${config.tableId}/${rowId}`,
            { headers }
        );
        return resp.data;
    } catch (err) {
        const code = err.response?.data?.error;
        const status = err.response?.status;
        // If PK fails (common when Noco button sends row_id UUID), try filtering
        if (code === 'INVALID_PK_VALUE' || status === 404 || status === 400) {
            const byFilter = await fetchRowByFilter(rowId);
            if (byFilter) return byFilter;
        }
        throw err;
    }
}

// Search for records by item name
app.get('/search-records', async (req, res) => {
    try {
        if (!config.apiToken) {
            return res.status(500).json({ error: 'NocoDB API token not configured' });
        }

        const searchTerm = req.query.q;
        if (!searchTerm) {
            return res.status(400).json({ error: 'Search term required' });
        }

        // Sanitize: strip NocoDB filter operators and special chars to prevent injection
        const term = searchTerm.replace(/[%_()~,]/g, '').trim();
        if (!term || term.length < 2) {
            return res.json({ results: [] });
        }

        // Server-side search across key fields using NocoDB like filter
        const where = [
            `(Item,like,%${term}%)`,
            `(Order Number,like,%${term}%)`,
            `(SKU,like,%${term}%)`,
            `(Serial Numbers,like,%${term}%)`,
            `(Name,like,%${term}%)`,
            `(Tracking Number,like,%${term}%)`
        ].join('~or');

        const response = await axios.get(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
            {
                headers: { 'xc-token': config.apiToken, 'Accept': 'application/json' },
                params: { where, limit: 200, sort: '-Id' }
            }
        );

        const rows = response.data.list || [];

        const searchResults = rows.map(row => {
            let displayText = row.Item || row.Name || 'Unknown Item';
            if (row['Order Number']) displayText += ' | Order: ' + row['Order Number'];
            if (row['Serial Numbers']) displayText += ' | SN: ' + row['Serial Numbers'];
            if (row.SKU) displayText += ' | SKU: ' + row.SKU;

            return {
                id: row.Id,
                item: displayText,
                received: row.Received || 'Unknown Status'
            };
        });

        res.json({ results: searchResults });

    } catch (error) {
        console.error('Search error:', error.response?.data || error.message);
        res.status(500).json({ error: 'Search failed', details: error.message });
    }
});

// Test endpoint
// Get single item details
app.get('/item-details/:id', async (req, res) => {
    try {
        const itemId = req.params.id;
        console.log('Fetching details for item ID:', itemId);
        
        const response = await axios.get(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId + '/' + itemId,
            {
                headers: {
                    'xc-token': config.apiToken,
                    'Accept': 'application/json'
                }
            }
        );
        
        const nocodbRowUrl = config.workspaceId
            ? `${config.ncodbPublicUrl}/dashboard/#/${config.workspaceId}/${config.baseId}/${config.tableId}?rowId=${itemId}`
            : config.ncodbPublicUrl;
        res.json({ success: true, item: response.data, nocodb_url: nocodbRowUrl });

    } catch (error) {
        console.error('Error fetching item details:', error.message);
        res.status(500).json({ error: 'Failed to fetch item details', details: error.message });
    }
});

// Send a Discord notification with a deep link for mobile upload
app.all('/send-to-phone', async (req, res) => {
    try {
        if (!config.discordWebhookUrl) {
            return res.status(500).json({ error: 'Discord webhook not configured' });
        }
        if (!config.apiToken) {
            return res.status(500).json({ error: 'NocoDB API token not configured' });
        }
        if (!isAuthorized(req)) {
            return res.status(403).json({ error: 'Unauthorized' });
        }

        const payloadRow = extractRowFromPayload(req.body);
        let rowId = req.body?.rowId || req.body?.id || req.query.rowId || req.query.id;
        if (!rowId && payloadRow) {
            rowId = payloadRow.Id || payloadRow.id || payloadRow.row_id || payloadRow.rowId;
        }
        const primaryId = payloadRow?.Id || rowId; // Prefer numeric Id when available
        console.log('Send-to-phone request payload:', {
            rowId,
            primaryId,
            query: req.query,
            body: req.body,
            payloadRow
        });
        if (!rowId) {
            return res.status(400).json({ error: 'Missing rowId/id' });
        }

        // Fetch row to confirm and enrich the message (with fallback for row_id UUIDs)
        const item = payloadRow || await fetchRowFlexible(primaryId);
        const itemName = item.Item || item.Name || '';
        const orderNumber = item['Order Number'] || '';
        const sku = item.SKU || '';
        const serials = item['Serial Numbers'] || '';
        const status = item.Received || item.received || 'Unknown';

        const deepLink = buildDeepLink(primaryId, itemName);
        const contentLines = [
            `📲 Upload photos from your phone`,
            `Item: ${safeField(itemName, 'Unknown')}`,
            `Row ID: ${primaryId}`,
            `Link: ${deepLink}`
        ];

        const embedFields = [
            { name: 'Row ID', value: String(primaryId), inline: true }
        ];
        if (orderNumber) embedFields.push({ name: 'Order #', value: String(orderNumber), inline: true });
        if (sku) embedFields.push({ name: 'SKU', value: String(sku), inline: true });
        if (serials) embedFields.push({ name: 'Serials', value: String(serials).slice(0, 1000) });
        if (status) embedFields.push({ name: 'Status', value: String(status), inline: true });

        const payload = {
            content: contentLines.join('\n'),
            username: config.discordUsername || undefined,
            avatar_url: config.discordAvatarUrl || undefined,
            embeds: [
                {
                    title: itemName || 'Item',
                    description: 'Tap the link to open the uploader on your phone.',
                    url: deepLink,
                    fields: embedFields,
                    footer: { text: 'NocoDB Photo Uploader' }
                }
            ]
        };

        await axios.post(config.discordWebhookUrl, payload);

        res.json({
            success: true,
            message: 'Sent to Discord',
            link: deepLink,
            item: {
                id: rowId,
                name: itemName,
                orderNumber,
                sku,
                serials,
                status
            }
        });
    } catch (error) {
        console.error('Send-to-phone error:', error.response?.data || error.message);
        res.status(500).json({ error: 'Failed to send link', details: error.message });
    }
});

// Summary endpoint — server-side filtered, fast single query
app.get('/summary', async (req, res) => {
    try {
        if (!config.apiToken) {
            return res.status(500).json({ error: 'NocoDB API token not configured' });
        }

        const response = await axios.get(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
            {
                headers: { 'xc-token': config.apiToken, 'Accept': 'application/json' },
                params: {
                    where: '(Received,eq,Issues)~or(Received,eq,Issue)~or(Received,eq,Needs Review)',
                    limit: 200
                }
            }
        );

        const rows = response.data.list || [];

        const mapItem = (row) => ({
            id: row.Id,
            item: row.Item || row.Name || 'Unknown',
            orderNumber: row['Order Number'] || '',
            serialNumbers: row['Serial Numbers'] || '',
            sku: row.SKU || '',
            received: row.Received || row.received || '',
            trackingNumber: row['Tracking Number'] || '',
            notes: row.Notes || ''
        });

        const issues = [];
        const needsReview = [];
        for (const row of rows) {
            const val = (row.Received || row.received || '').toLowerCase();
            if (val === 'issues' || val === 'issue') issues.push(mapItem(row));
            else if (val === 'needs review') needsReview.push(mapItem(row));
        }

        res.json({
            issues,
            issueCount: issues.length,
            needsReview,
            reviewCount: needsReview.length
        });
    } catch (error) {
        console.error('Summary fetch error:', error.message);
        res.status(500).json({ error: 'Failed to fetch summary', details: error.message });
    }
});

// Get items with issues (full details) — server-side filtered
app.get('/issues', async (req, res) => {
    try {
        if (!config.apiToken) {
            return res.status(500).json({ error: 'NocoDB API token not configured' });
        }
        const response = await axios.get(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
            {
                headers: { 'xc-token': config.apiToken, 'Accept': 'application/json' },
                params: {
                    where: '(Received,eq,Issues)~or(Received,eq,Issue)',
                    limit: 200,
                    sort: '-Id'
                }
            }
        );
        const issues = (response.data.list || []).map(row => ({
            id: row.Id,
            item: row.Item || row.Name || 'Unknown',
            orderNumber: row['Order Number'] || '',
            serialNumbers: row['Serial Numbers'] || '',
            sku: row.SKU || '',
            received: row.Received || row.received || '',
            trackingNumber: row['Tracking Number'] || '',
            notes: row.Notes || ''
        }));
        res.json({ issues, count: issues.length });
    } catch (error) {
        console.error('Issues fetch error:', error.message);
        res.status(500).json({ error: 'Failed to fetch issues', details: error.message });
    }
});

// Get count of rows that have issues
// Count of items with issues — server-side filtered, single query
app.get('/needs-review-count', async (req, res) => {
    try {
        if (!config.apiToken) {
            return res.status(500).json({ error: 'NocoDB API token not configured' });
        }
        const response = await axios.get(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
            {
                headers: { 'xc-token': config.apiToken, 'Accept': 'application/json' },
                params: {
                    where: '(Received,eq,Issues)~or(Received,eq,Issue)~or(Received,eq,Needs Review)',
                    limit: 1,
                    fields: 'Id'
                }
            }
        );
        const count = response.data.pageInfo?.totalRows || (response.data.list || []).length;
        res.json({ count });
    } catch (error) {
        console.error('Error fetching issues count:', error.message);
        res.status(500).json({ error: 'Failed to fetch count', details: error.message });
    }
});

app.get('/', (req, res) => {
    res.send(`<!DOCTYPE html>
<html>
<head>
    <title>NocoDB Photo Uploader</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <style>
        * { 
            box-sizing: border-box; 
            margin: 0;
            padding: 0;
        }
        body { 
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 10px;
            position: relative;
        }
        body::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.03'%3E%3Ccircle cx='30' cy='30' r='4'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
            pointer-events: none;
        }
        .container { 
            background: rgba(255, 255, 255, 0.95);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            padding: 30px; 
            border-radius: 20px; 
            box-shadow: 0 20px 40px rgba(0,0,0,0.1), 0 0 0 1px rgba(255,255,255,0.2);
            width: 100%;
            max-width: 800px;
            margin: 0 auto;
            border: 1px solid rgba(255,255,255,0.18);
            transition: all 0.3s ease;
        }
        .container:hover {
            transform: translateY(-2px);
            box-shadow: 0 25px 50px rgba(0,0,0,0.15), 0 0 0 1px rgba(255,255,255,0.2);
        }
        h1 {
            font-size: clamp(1.8rem, 5vw, 2.5rem);
            margin-bottom: 30px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            background-clip: text;
            font-weight: 700;
            text-align: center;
        }
        .issues-counter { 
            background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
            color: white;
            padding: 20px; 
            border-radius: 16px; 
            margin-bottom: 25px; 
            text-align: center;
            box-shadow: 0 8px 25px rgba(255,107,107,0.3);
            border: none;
            transition: transform 0.2s ease;
        }
        .issues-counter:hover {
            transform: translateY(-1px);
        }
        .issues-counter h3 { 
            margin: 0 0 8px 0; 
            color: white; 
            font-weight: 600;
        }
        .issues-counter .count { 
            font-size: 28px; 
            font-weight: 800; 
            color: white;
            text-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }
        .config-info { 
            background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
            color: white;
            padding: 20px; 
            border-radius: 16px; 
            margin-bottom: 30px;
            box-shadow: 0 8px 25px rgba(116,185,255,0.3);
        }
        .config-info h3 {
            color: white;
            margin-bottom: 10px;
            font-weight: 600;
        }
        .config-info p {
            color: rgba(255,255,255,0.9);
            margin: 5px 0;
        }
        .form-group { 
            margin-bottom: 25px; 
        }
        .form-group label {
            display: block;
            margin-bottom: 8px;
            font-weight: 600;
            color: #2d3748;
            font-size: 14px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        .search-container { 
            position: relative; 
            margin-bottom: 10px; 
        }
        .search-input { 
            width: 100%; 
            padding: 16px 20px; 
            border: 2px solid rgba(102, 126, 234, 0.1); 
            border-radius: 12px; 
            box-sizing: border-box; 
            font-size: 16px;
            background: rgba(255,255,255,0.8);
            transition: all 0.3s ease;
            backdrop-filter: blur(10px);
        }
        .search-input:focus {
            outline: none;
            border-color: #667eea;
            background: rgba(255,255,255,0.95);
            box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
            transform: translateY(-1px);
        }
        .search-results { 
            position: absolute; 
            top: 100%; 
            left: 0; 
            right: 0; 
            background: rgba(255,255,255,0.95); 
            backdrop-filter: blur(10px);
            border: 2px solid rgba(102, 126, 234, 0.2); 
            border-top: none; 
            border-radius: 0 0 12px 12px; 
            max-height: 250px; 
            overflow-y: auto; 
            z-index: 100; 
            display: none;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
        }
        .search-result-item { 
            padding: 15px 20px; 
            cursor: pointer; 
            border-bottom: 1px solid rgba(102, 126, 234, 0.1); 
            transition: all 0.2s ease;
        }
        .search-result-item:hover { 
            background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
            transform: translateX(5px);
        }
        .search-result-item:last-child { 
            border-bottom: none; 
        }
        .scan-buttons {
            display: flex;
            gap: 10px;
            margin-top: 8px;
            flex-wrap: wrap;
        }
        .scan-btn {
            flex: 1 1 140px;
            background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
            color: white;
            border: none;
            border-radius: 10px;
            padding: 12px 14px;
            font-weight: 600;
            cursor: pointer;
            box-shadow: 0 8px 20px rgba(0, 184, 148, 0.3);
            transition: transform 0.2s ease, box-shadow 0.2s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        .scan-btn:hover {
            transform: translateY(-1px);
            box-shadow: 0 12px 24px rgba(0, 184, 148, 0.35);
        }
        .scan-btn:active {
            transform: translateY(0);
        }
        .result-id { 
            font-weight: 700; 
            color: #667eea;
            font-size: 14px;
        }
        .result-item { 
            color: #2d3748;
            font-weight: 600;
            margin: 4px 0;
        }
        .result-status { 
            font-size: 12px; 
            color: #718096;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        input[type="text"] { 
            width: 100%; 
            padding: 16px 20px; 
            border: 2px solid rgba(102, 126, 234, 0.1); 
            border-radius: 12px; 
            box-sizing: border-box; 
            font-size: 16px;
            background: rgba(255,255,255,0.8);
            transition: all 0.3s ease;
            backdrop-filter: blur(10px);
        }
        input[type="text"]:focus {
            outline: none;
            border-color: #667eea;
            background: rgba(255,255,255,0.95);
            box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1);
            transform: translateY(-1px);
        }
        .drop-zone { 
            border: 3px dashed rgba(102, 126, 234, 0.3); 
            padding: 50px 30px; 
            text-align: center; 
            cursor: pointer; 
            border-radius: 20px; 
            margin: 25px 0;
            background: linear-gradient(135deg, rgba(102, 126, 234, 0.05) 0%, rgba(118, 75, 162, 0.05) 100%);
            transition: all 0.3s ease;
            position: relative;
            overflow: hidden;
        }
        .drop-zone::before {
            content: '📸';
            font-size: 3rem;
            display: block;
            margin-bottom: 15px;
            opacity: 0.7;
        }
        .drop-zone:hover {
            border-color: #667eea;
            background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
            transform: translateY(-2px);
            box-shadow: 0 10px 30px rgba(102, 126, 234, 0.2);
        }
        .drop-zone p:first-of-type {
            font-size: 1.2rem;
            font-weight: 600;
            color: #2d3748;
            margin-bottom: 8px;
        }
        .upload-btn { 
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white; 
            padding: 18px 35px; 
            border: none; 
            border-radius: 12px; 
            width: 100%; 
            cursor: pointer; 
            font-size: 18px;
            font-weight: 600;
            transition: all 0.3s ease;
            box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
            text-transform: uppercase;
            letter-spacing: 1px;
        }
        .upload-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 12px 35px rgba(102, 126, 234, 0.4);
        }
        .upload-btn:active {
            transform: translateY(0);
        }
        #fileInput { display: none; }
        .preview-container { margin-top: 20px; }
        .preview-image { 
            width: 100%; 
            height: 280px; 
            object-fit: cover; 
            margin: 15px 0 5px 0; 
            border-radius: 16px; 
            border: none;
            box-shadow: 0 15px 35px rgba(0,0,0,0.1), 0 0 0 1px rgba(102, 126, 234, 0.1); 
            display: block; 
            transition: all 0.3s ease;
        }
        .preview-image:hover {
            transform: translateY(-5px) scale(1.02);
            box-shadow: 0 25px 50px rgba(0,0,0,0.15), 0 0 0 1px rgba(102, 126, 234, 0.2);
        }
        .file-preview { 
            display: block; 
            margin: 25px 0; 
            position: relative; 
            width: 100%; 
            animation: fadeInUp 0.3s ease;
        }
        
        @keyframes fadeInUp {
            from {
                opacity: 0;
                transform: translateY(30px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
        .file-name { 
            font-size: 16px; 
            color: #4a5568; 
            margin-top: 12px; 
            word-wrap: break-word; 
            font-weight: 600; 
            padding: 15px 20px;
            background: rgba(102, 126, 234, 0.05);
            border-radius: 12px;
            border-left: 4px solid #667eea;
        }
        .remove-btn { 
            position: absolute; 
            top: 20px; 
            right: 20px; 
            background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
            color: white; 
            border: none; 
            border-radius: 50%; 
            width: 45px; 
            height: 45px; 
            cursor: pointer; 
            font-size: 24px; 
            box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); 
            z-index: 10;
            transition: all 0.2s ease;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .remove-btn:hover {
            transform: scale(1.1);
            box-shadow: 0 10px 25px rgba(255, 107, 107, 0.5);
        }
        .file-count { 
            font-weight: 700; 
            color: white;
            margin-bottom: 20px; 
            font-size: 18px;
            padding: 12px 20px;
            background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
            border-radius: 25px;
            display: inline-block;
            box-shadow: 0 8px 20px rgba(0, 184, 148, 0.3);
            animation: slideIn 0.3s ease;
        }
        .status { 
            margin-top: 25px; 
            padding: 18px 25px; 
            border-radius: 12px; 
            text-align: center; 
            font-size: 16px;
            font-weight: 600;
            backdrop-filter: blur(10px);
            animation: slideIn 0.3s ease;
        }
        .status.success { 
            background: linear-gradient(135deg, #00b894 0%, #00a085 100%);
            color: white;
            box-shadow: 0 8px 25px rgba(0, 184, 148, 0.3);
        }
        .status.error { 
            background: linear-gradient(135deg, #ff6b6b 0%, #ee5a24 100%);
            color: white;
            box-shadow: 0 8px 25px rgba(255, 107, 107, 0.3);
        }
        
        @keyframes slideIn {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }
        
        /* Modal styles for item details */
        .modal {
            display: none;
            position: fixed;
            z-index: 1000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            overflow: auto;
            background-color: rgba(0,0,0,0.4);
        }
        .modal-content {
            background-color: #fefefe;
            margin: 20px auto;
            padding: 0;
            border: 1px solid #888;
            width: calc(100% - 20px);
            max-width: 800px;
            max-height: calc(100vh - 40px);
            overflow: hidden;
            border-radius: 10px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
        }
        .modal-header {
            padding: 20px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border-radius: 10px 10px 0 0;
            position: relative;
        }
        .modal-header h2 {
            margin: 0;
            font-size: 24px;
        }
        .close {
            color: white;
            position: absolute;
            right: 20px;
            top: 20px;
            font-size: 28px;
            font-weight: bold;
            cursor: pointer;
        }
        .close:hover { opacity: 0.8; }
        .modal-body {
            padding: 20px;
            max-height: calc(100vh - 140px);
            overflow-y: auto;
            -webkit-overflow-scrolling: touch;
        }
        .detail-row {
            display: flex;
            padding: 12px 0;
            border-bottom: 1px solid #eee;
            flex-wrap: wrap;
        }
        .detail-row:last-child {
            border-bottom: none;
        }
        .detail-label {
            font-weight: bold;
            width: 150px;
            color: #555;
            flex-shrink: 0;
        }
        .detail-value {
            flex: 1;
            color: #333;
            word-wrap: break-word;
            word-break: break-word;
            min-width: 0;
        }
        .detail-value.empty {
            color: #999;
            font-style: italic;
        }
        .status-badge {
            display: inline-block;
            padding: 4px 12px;
            border-radius: 20px;
            font-size: 14px;
            font-weight: bold;
        }
        .status-badge.closed {
            background: #d4edda;
            color: #155724;
        }
        .status-badge.issues {
            background: #f8d7da;
            color: #721c24;
        }
        .status-badge.open {
            background: #fff3cd;
            color: #856404;
        }
        .select-button {
            background: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
            margin-top: 20px;
            width: 100%;
        }
        .select-button:hover {
            background: #45a049;
        }
        /* Scanner modal */
        .scanner-modal {
            display: none;
            position: fixed;
            z-index: 1200;
            left: 0; top: 0; right:0; bottom:0;
            background: rgba(0,0,0,0.6);
            align-items: center;
            justify-content: center;
            padding: 15px;
        }
        .scanner-content {
            background: #fff;
            max-width: 420px;
            width: 100%;
            border-radius: 16px;
            padding: 18px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.25);
        }
        .scanner-header {
            display: flex;
            align-items: center;
            justify-content: space-between;
            margin-bottom: 10px;
        }
        .scanner-close {
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #666;
        }
        .scanner-area {
            border: 2px dashed rgba(102,126,234,0.25);
            border-radius: 12px;
            min-height: 280px;
            overflow: hidden;
        }
        .scanner-hint {
            margin-top: 8px;
            font-size: 14px;
            color: #555;
        }
        /* Modern theme overrides (light, Jotform-inspired) */
        .modern-theme {
            font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
            background: radial-gradient(circle at 20% 20%, rgba(255, 138, 101, 0.15) 0, transparent 25%), radial-gradient(circle at 80% 0%, rgba(16, 185, 129, 0.12) 0, transparent 22%), #f7f7fb;
            color: #1f2937;
        }
        .modern-theme body::before { display: none; }
        .modern-theme .container {
            background: #ffffff;
            border: 1px solid #e5e7eb;
            box-shadow: 0 12px 40px rgba(15, 23, 42, 0.1);
        }
        .modern-theme h1 {
            background: linear-gradient(135deg, #ff7a59, #10b981);
        }
        .modern-theme .issues-counter {
            background: linear-gradient(135deg, #ff7a59, #f97316);
            box-shadow: 0 10px 30px rgba(249, 115, 22, 0.25);
        }
        .modern-theme .search-input,
        .modern-theme input[type="text"],
        .modern-theme input[type="number"] {
            background: #fff;
            color: #111827;
            border-color: #e5e7eb;
            box-shadow: inset 0 1px 2px rgba(0,0,0,0.04);
        }
        .modern-theme .search-input:focus,
        .modern-theme input[type="text"]:focus,
        .modern-theme input[type="number"]:focus {
            border-color: #ff7a59;
            box-shadow: 0 0 0 4px rgba(255, 122, 89, 0.2);
        }
        .modern-theme .search-results {
            background: #ffffff;
            border-color: #e5e7eb;
            box-shadow: 0 12px 30px rgba(0,0,0,0.08);
        }
        .modern-theme .search-result-item:hover {
            background: linear-gradient(135deg, rgba(255, 122, 89, 0.08), rgba(16, 185, 129, 0.08));
        }
        .modern-theme .drop-zone {
            background: linear-gradient(135deg, rgba(16, 185, 129, 0.08), rgba(255, 122, 89, 0.08));
            border-color: rgba(255, 122, 89, 0.35);
        }
        .modern-theme .upload-btn {
            background: linear-gradient(135deg, #ff7a59, #f97316);
            box-shadow: 0 10px 25px rgba(249, 115, 22, 0.25);
        }
        .modern-theme .scan-btn {
            background: linear-gradient(135deg, #10b981, #0ea271);
            box-shadow: 0 10px 22px rgba(16, 185, 129, 0.25);
        }
        .modern-theme .file-name {
            background: rgba(255, 122, 89, 0.08);
            border-left-color: #ff7a59;
            color: #1f2937;
        }
        .modern-theme .status.success { background: linear-gradient(135deg, #10b981, #0ea271); }
        .modern-theme .status.error { background: linear-gradient(135deg, #f43f5e, #f97316); }
        
        /* Mobile optimizations */
        @media (max-width: 768px) {
            body { 
                padding: 5px;
            }
            .container { 
                padding: 20px;
                border-radius: 16px;
            }
            .config-info {
                font-size: 14px;
                padding: 10px;
            }
            .preview-image { 
                height: 200px; 
            }
            input[type="text"], .search-input { 
                padding: 12px; 
                font-size: 16px; /* Prevents zoom on iOS */
            }
            .drop-zone { 
                padding: 30px 15px; 
            }
            .remove-btn { 
                width: 40px; 
                height: 40px; 
                font-size: 22px; 
                top: 20px; 
                right: 10px; 
            }
            .modal-content {
                width: calc(100% - 10px);
                margin: 5px auto;
                max-height: calc(100vh - 10px);
            }
            .modal-header {
                padding: 15px;
            }
            .modal-header h2 {
                font-size: 20px;
            }
            .modal-body {
                padding: 15px;
                max-height: calc(100vh - 100px);
            }
            .detail-label {
                width: 100%;
                margin-bottom: 5px;
            }
            .detail-value {
                width: 100%;
                padding-left: 0;
            }
            .detail-row {
                flex-direction: column;
            }
        }
        
        /* Tablet styles */
        @media (min-width: 769px) and (max-width: 1024px) {
            .container {
                max-width: 700px;
            }
        }
        
        /* Ensure inputs don't zoom on mobile */
        @media (max-width: 768px) {
            input[type="text"],
            input[type="file"],
            select,
            textarea {
                font-size: 16px !important;
            }
        }
    </style>
</head>
<body>
    <script>
        (function() {
            const params = new URLSearchParams(window.location.search);
            if (params.get('theme') === 'modern') {
                document.body.classList.add('modern-theme');
            }
        })();
    </script>
    <div class="container">
        <h1>📷 NocoDB Photo Uploader</h1>
        
        <div class="issues-counter">
            <h3>Items with Issues</h3>
            <div class="count" id="issuesCount">Loading...</div>
        </div>
        
        <div class="form-group">
            <label>Search for Item:</label>
            <div class="search-container">
                <input type="text" id="searchInput" class="search-input" placeholder="Type item name to search..." />
                <div id="searchResults" class="search-results"></div>
            </div>
            <div class="scan-buttons">
                <button class="scan-btn" onclick="openScanner('search')">📷 Scan for Search</button>
            </div>
        </div>
        
        <div class="form-group">
            <label>Row ID:</label>
            <input type="text" id="rowId" placeholder="Enter row ID or search above" />
        </div>
        <div id="nocoLinkBox" style="display:none; margin-bottom:15px;">
            <a id="nocoLink" href="#" target="_blank" rel="noopener" style="color:#2563eb;font-weight:700;">Open in NocoDB</a>
        </div>

        <div class="form-group">
            <label>Item Details:</label>
            <div id="editForm"></div>
        </div>
        
        <div class="drop-zone" onclick="document.getElementById('fileInput').click()">
            <p>📁 Click to add photos</p>
            <p style="font-size: 14px; color: #888;">Each click adds more photos - they accumulate until you upload</p>
            <input type="file" id="fileInput" multiple accept="image/*" />
        </div>
        
        <div id="previewContainer"></div>
        <button class="upload-btn" onclick="uploadPhotos()">Upload Photos</button>
        <div id="status"></div>
    </div>
    
    <!-- Item Details Modal -->
    <div id="itemModal" class="modal">
        <div class="modal-content">
            <div class="modal-header">
                <h2 id="modalTitle">Item Details</h2>
                <span class="close" onclick="closeModal()">&times;</span>
            </div>
            <div class="modal-body" id="modalBody">
                <!-- Details will be inserted here -->
            </div>
        </div>
    </div>

    <!-- Scanner Modal -->
    <div id="scannerModal" class="scanner-modal">
        <div class="scanner-content">
            <div class="scanner-header">
                <h3 id="scannerTitle">Scan</h3>
                <button class="scanner-close" onclick="closeScanner()">&times;</button>
            </div>
            <div id="scannerArea" class="scanner-area"></div>
            <div class="scanner-hint">Align the barcode in the frame. Scans close automatically on success.</div>
        </div>
    </div>
    
    <script>
        let selectedFiles = [];
        let scannerInstance = null;
        let scannerTarget = null;
        let detectorStream = null;
        let detectorInterval = null;
        let detectorRunning = false;
        const nocoLinkBase = '${(config.ncodbPublicUrl || '').endsWith('/') ? config.ncodbPublicUrl.slice(0, -1) : (config.ncodbPublicUrl || '')}/#/nc/${config.baseId}/${config.tableId}';
        const fieldDefs = [
            { key: 'Id', label: 'ID', readonly: true },
            { key: 'Item', label: 'Item' },
            { key: 'Order Number', label: 'Order Number' },
            { key: 'Serial Numbers', label: 'Serial Numbers' },
            { key: 'SKU', label: 'SKU' },
            { key: 'Vendor', label: 'Vendor' },
            { key: 'Date', label: 'Date' },
            { key: 'Tracking Number', label: 'Tracking Number' },
            { key: 'Received', label: 'Status/Received' },
            { key: 'QTY', label: 'Quantity' },
            { key: 'Price Per Item', label: 'Price Per Item' },
            { key: 'Tax', label: 'Tax' },
            { key: 'Total', label: 'Total' },
            { key: 'Name', label: 'Name' }
        ];
        
        // Prefill from query params (rowId/item) for deep links
        (function prefillFromQuery() {
            try {
                const params = new URLSearchParams(window.location.search);
                const rowId = params.get('rowId') || params.get('id');
                const itemName = params.get('item');
                if (rowId) {
                    selectItem(rowId, itemName || '');
                    showItemDetails(rowId);
                }
            } catch (e) {
                console.warn('Prefill error:', e);
            }
        })();
        
        // Load issues count
        loadIssuesCount();
        renderEditForm({});
        
        // Search functionality
        let searchTimeout;
        document.getElementById('searchInput').addEventListener('input', function(e) {
            clearTimeout(searchTimeout);
            const query = e.target.value.trim();
            
            if (query.length < 2) {
                document.getElementById('searchResults').style.display = 'none';
                return;
            }
            
            searchTimeout = setTimeout(() => searchItems(query), 300);
        });
        
        // Hide search results when clicking outside
        document.addEventListener('click', function(e) {
            if (!e.target.closest('.search-container')) {
                document.getElementById('searchResults').style.display = 'none';
            }
        });
        
        async function searchItems(query) {
            try {
                const response = await fetch('/search-records?q=' + encodeURIComponent(query));
                const data = await response.json();
                
                const resultsDiv = document.getElementById('searchResults');
                resultsDiv.innerHTML = '';
                
                if (data.results && data.results.length > 0) {
                    data.results.forEach(item => {
                        const itemDiv = document.createElement('div');
                        itemDiv.className = 'search-result-item';
                        
                        const resultId = document.createElement('div');
                        resultId.className = 'result-id';
                        resultId.textContent = 'ID: ' + item.id;
                        
                        const resultItem = document.createElement('div');
                        resultItem.className = 'result-item';
                        resultItem.textContent = item.item;
                        
                        const resultStatus = document.createElement('div');
                        resultStatus.className = 'result-status';
                        resultStatus.textContent = 'Status: ' + item.received;
                        
                        itemDiv.appendChild(resultId);
                        itemDiv.appendChild(resultItem);
                        itemDiv.appendChild(resultStatus);
                        
                        itemDiv.onclick = () => {
                            selectItem(item.id, item.item);
                            showItemDetails(item.id);
                        };
                        resultsDiv.appendChild(itemDiv);
                    });
                    resultsDiv.style.display = 'block';
                } else {
                    resultsDiv.innerHTML = '<div class="search-result-item">No results found</div>';
                    resultsDiv.style.display = 'block';
                }
            } catch (error) {
                console.error('Search error:', error);
            }
        }
        
        function selectItem(id, itemName) {
            document.getElementById('rowId').value = id;
            document.getElementById('searchInput').value = itemName;
            document.getElementById('searchResults').style.display = 'none';
            updateNocoLink(id);
            // Clear search box so next search can start fresh
            document.getElementById('searchInput').value = '';
        }

        function renderEditForm(item = {}) {
            const container = document.getElementById('editForm');
            if (!container) return;
            container.innerHTML = '';
            fieldDefs.forEach(def => {
                const wrap = document.createElement('div');
                wrap.style.marginBottom = '15px';
                const label = document.createElement('label');
                label.textContent = def.label;
                label.style.display = 'block';
                label.style.marginBottom = '6px';
                label.style.fontWeight = '600';

                const input = document.createElement('input');
                input.id = 'edit-' + def.key;
                input.type = (def.key === 'Price Per Item' || def.key === 'Tax' || def.key === 'Total' || def.key === 'QTY') ? 'number' : 'text';
                input.value = item[def.key] !== undefined && item[def.key] !== null ? item[def.key] : '';
                input.style.width = '100%';
                input.style.padding = '12px';
                input.style.borderRadius = '10px';
                input.style.border = '2px solid rgba(102, 126, 234, 0.1)';
                input.style.background = 'rgba(255,255,255,0.8)';
                if (def.readonly) {
                    input.readOnly = true;
                    input.style.opacity = '0.7';
                }

                wrap.appendChild(label);
                wrap.appendChild(input);

                // Inline scan buttons for specific fields
                if (def.key === 'Serial Numbers' || def.key === 'Tracking Number') {
                    const btnRow = document.createElement('div');
                    btnRow.className = 'scan-buttons';
                    btnRow.style.marginTop = '8px';
                    const btn = document.createElement('button');
                    btn.className = 'scan-btn';
                    btn.type = 'button';
                    btn.textContent = def.key === 'Serial Numbers' ? '🔍 Scan Serial' : '🧭 Scan Tracking';
                    btn.onclick = () => openScanner(def.key === 'Serial Numbers' ? 'serial' : 'tracking');
                    btnRow.appendChild(btn);
                    wrap.appendChild(btnRow);
                }

                container.appendChild(wrap);
            });
        }

        function collectEditValues() {
            const values = {};
            fieldDefs.forEach(def => {
                if (def.readonly) return;
                const el = document.getElementById('edit-' + def.key);
                if (!el) return;
                values[def.key] = el.value ?? '';
            });
            return values;
        }
        
        async function showItemDetails(id) {
            try {
                const response = await fetch('/item-details/' + id);
                const data = await response.json();
                
                if (data.success && data.item) {
                    renderEditForm(data.item);
                    const modal = document.getElementById('itemModal');
                    const modalBody = document.getElementById('modalBody');
                    const modalTitle = document.getElementById('modalTitle');
                    
                    // Set title
                    modalTitle.textContent = data.item.Item || data.item.Name || 'Item Details';
                    
                    // Build details HTML
                    let detailsHTML = '';
                    
                    // Define the order and labels for fields
                    const fieldOrder = [
                        { key: 'Id', label: 'ID' },
                        { key: 'Item', label: 'Item' },
                        { key: 'Name', label: 'Name' },
                        { key: 'Received', label: 'Status' },
                        { key: 'Order Number', label: 'Order Number' },
                        { key: 'SKU', label: 'SKU' },
                        { key: 'Serial Numbers', label: 'Serial Numbers' },
                        { key: 'QTY', label: 'Quantity' },
                        { key: 'Price Per Item', label: 'Price Per Item' },
                        { key: 'Tax', label: 'Tax' },
                        { key: 'Total', label: 'Total' },
                        { key: 'Vendor', label: 'Vendor' },
                        { key: 'Date', label: 'Date' },
                        { key: 'Tracking Number', label: 'Tracking Number' },
                        { key: 'CreatedAt', label: 'Created' },
                        { key: 'UpdatedAt', label: 'Updated' }
                    ];
                    
                    fieldOrder.forEach(field => {
                        const value = data.item[field.key];
                        if (field.key === 'CreatedAt' || field.key === 'UpdatedAt') {
                            // Skip timestamp fields or format them nicely
                            if (value) {
                                const date = new Date(value);
                                detailsHTML += '<div class="detail-row">';
                                detailsHTML += '<div class="detail-label">' + field.label + ':</div>';
                                detailsHTML += '<div class="detail-value">' + date.toLocaleString() + '</div>';
                                detailsHTML += '</div>';
                            }
                        } else if (field.key === 'Received') {
                            // Special formatting for status
                            detailsHTML += '<div class="detail-row">';
                            detailsHTML += '<div class="detail-label">' + field.label + ':</div>';
                            detailsHTML += '<div class="detail-value">';
                            const status = value || 'Unknown';
                            const statusClass = status.toLowerCase() === 'closed' ? 'closed' : 
                                              status.toLowerCase() === 'issues' ? 'issues' : 'open';
                            detailsHTML += '<span class="status-badge ' + statusClass + '">' + status + '</span>';
                            detailsHTML += '</div></div>';
                        } else if (value !== null && value !== undefined && value !== '') {
                            detailsHTML += '<div class="detail-row">';
                            detailsHTML += '<div class="detail-label">' + field.label + ':</div>';
                            detailsHTML += '<div class="detail-value">' + value + '</div>';
                            detailsHTML += '</div>';
                        } else if (field.key === 'Item' || field.key === 'Name' || field.key === 'Order Number') {
                            // Show important fields even if empty
                            detailsHTML += '<div class="detail-row">';
                            detailsHTML += '<div class="detail-label">' + field.label + ':</div>';
                            detailsHTML += '<div class="detail-value empty">Not provided</div>';
                            detailsHTML += '</div>';
                        }
                    });
                    
                    modalBody.innerHTML = detailsHTML;
                    modal.style.display = 'none'; // keep hidden
                    updateNocoLink(id);
                }
            } catch (error) {
                console.error('Error fetching item details:', error);
            }
        }
        
        function selectAndClose(id, itemName) {
            selectItem(id, itemName);
            closeModal();
        }
        
        function closeModal() {
            document.getElementById('itemModal').style.display = 'none';
        }

        function updateNocoLink(id) {
            const box = document.getElementById('nocoLinkBox');
            const link = document.getElementById('nocoLink');
            if (!box || !link) return;
            if (!id) {
                box.style.display = 'none';
                link.href = '#';
                return;
            }
            link.href = nocoLinkBase + '?rowId=' + encodeURIComponent(id);
            box.style.display = 'block';
        }
        
        // Close modal when clicking outside
        window.onclick = function(event) {
            const modal = document.getElementById('itemModal');
            if (event.target == modal) {
                modal.style.display = 'none';
            }
        }
        
        async function loadIssuesCount() {
            try {
                const response = await fetch('/needs-review-count');
                const data = await response.json();
                document.getElementById('issuesCount').textContent = data.count;
            } catch (error) {
                document.getElementById('issuesCount').textContent = 'Error';
            }
        }
        
        document.getElementById('fileInput').addEventListener('change', function(e) {
            const newFiles = Array.from(e.target.files);
            selectedFiles = [...selectedFiles, ...newFiles];
            this.value = '';
            displayPreviews();
        });
        
        function displayPreviews() {
            const container = document.getElementById('previewContainer');
            container.innerHTML = '';
            
            if (selectedFiles.length > 0) {
                const fileCount = document.createElement('div');
                fileCount.className = 'file-count';
                fileCount.textContent = selectedFiles.length + ' file(s) selected';
                container.appendChild(fileCount);
                
                selectedFiles.forEach((file, index) => {
                    const fileDiv = document.createElement('div');
                    fileDiv.className = 'file-preview';
                    
                    if (file.type.startsWith('image/')) {
                        const img = document.createElement('img');
                        img.className = 'preview-image';
                        img.src = URL.createObjectURL(file);
                        fileDiv.appendChild(img);
                    }
                    
                    const fileName = document.createElement('div');
                    fileName.className = 'file-name';
                    fileName.textContent = file.name;
                    fileDiv.appendChild(fileName);
                    
                    const removeBtn = document.createElement('button');
                    removeBtn.className = 'remove-btn';
                    removeBtn.textContent = '×';
                    removeBtn.onclick = () => removeFile(index);
                    fileDiv.appendChild(removeBtn);
                    
                    container.appendChild(fileDiv);
                });
            }
        }
        
        function removeFile(index) {
            selectedFiles.splice(index, 1);
            displayPreviews();
        }

        // --- Scanner handling ---
        function ensureScannerScript() {
            return new Promise((resolve, reject) => {
                if (window.Html5Qrcode) return resolve();
                const script = document.createElement('script');
                script.src = 'https://unpkg.com/html5-qrcode@2.3.8/html5-qrcode.min.js';
                script.async = true;
                script.onload = () => resolve();
                script.onerror = () => reject(new Error('Failed to load scanner lib'));
                document.head.appendChild(script);
            });
        }

        async function startBarcodeDetector(target) {
            if (!('BarcodeDetector' in window)) return false;
            const formats = ['qr_code','code_128','code_39','codabar','ean_13','ean_8','upc_a','upc_e','itf','data_matrix','pdf417'];
            let detector;
            try {
                detector = new BarcodeDetector({ formats });
            } catch (e) {
                return false;
            }

            const area = document.getElementById('scannerArea');
            area.innerHTML = '';
            const video = document.createElement('video');
            video.setAttribute('playsinline', 'true');
            video.style.width = '100%';
            video.style.borderRadius = '8px';
            area.appendChild(video);

            try {
                detectorStream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
            } catch (e) {
                console.warn('Camera permission error:', e);
                return false;
            }

            video.srcObject = detectorStream;
            await video.play();
            detectorRunning = true;

            const canvas = document.createElement('canvas');
            const ctx = canvas.getContext('2d');

            const scanFrame = async () => {
                if (!detectorRunning) return;
                if (video.readyState === video.HAVE_ENOUGH_DATA) {
                    canvas.width = video.videoWidth;
                    canvas.height = video.videoHeight;
                    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                    try {
                        const barcodes = await detector.detect(canvas);
                        if (barcodes && barcodes.length > 0) {
                            const text = barcodes[0].rawValue || barcodes[0].rawdata || '';
                            if (text) {
                                onScanSuccess(text);
                                return;
                            }
                        }
                    } catch (e) {
                        // ignore detection errors per frame
                    }
                }
                detectorInterval = requestAnimationFrame(scanFrame);
            };

            detectorInterval = requestAnimationFrame(scanFrame);
            return true;
        }

        function stopBarcodeDetector() {
            detectorRunning = false;
            if (detectorInterval) {
                cancelAnimationFrame(detectorInterval);
                detectorInterval = null;
            }
            if (detectorStream) {
                detectorStream.getTracks().forEach(t => t.stop());
                detectorStream = null;
            }
            const area = document.getElementById('scannerArea');
            if (area) area.innerHTML = '';
        }

        async function openScanner(target) {
            scannerTarget = target;
            document.getElementById('scannerModal').style.display = 'flex';
            document.getElementById('scannerTitle').textContent =
                target === 'serial' ? 'Scan Serial' : target === 'tracking' ? 'Scan Tracking' : 'Scan for Search';
            try {
                // Try native detector first
                const detectorStarted = await startBarcodeDetector(target);
                if (detectorStarted) return;

                // Fallback to html5-qrcode
                await ensureScannerScript();
                if (scannerInstance) {
                    await scannerInstance.stop();
                }
                scannerInstance = new Html5Qrcode('scannerArea');
                await scannerInstance.start(
                    { facingMode: 'environment' },
                    { fps: 10, qrbox: { width: 260, height: 260 } },
                    onScanSuccess,
                    onScanError
                );
            } catch (err) {
                console.error('Scanner error:', err);
                alert('Camera not available: ' + err.message);
                closeScanner();
            }
        }

        async function closeScanner() {
            document.getElementById('scannerModal').style.display = 'none';
            stopBarcodeDetector();
            if (scannerInstance) {
                try { await scannerInstance.stop(); } catch (e) {}
                try { scannerInstance.clear(); } catch (e) {}
                scannerInstance = null;
            }
            document.getElementById('scannerArea').innerHTML = '';
        }

        function onScanSuccess(decodedText) {
            if (!decodedText) return;
            const trimmed = decodedText.trim();
            if (scannerTarget === 'serial') {
                const serialInput = document.getElementById('edit-Serial Numbers');
                if (serialInput) {
                    if (serialInput.value) {
                        serialInput.value = serialInput.value + ', ' + trimmed;
                    } else {
                        serialInput.value = trimmed;
                    }
                }
            } else if (scannerTarget === 'tracking') {
                const trackingInput = document.getElementById('edit-Tracking Number');
                if (trackingInput) {
                    trackingInput.value = trimmed;
                }
                const searchInput = document.getElementById('searchInput');
                if (searchInput) {
                    searchInput.value = trimmed;
                    searchItems(trimmed);
                }
            } else {
                const searchInput = document.getElementById('searchInput');
                searchInput.value = trimmed;
                searchItems(trimmed);
            }
            closeScanner();
        }

        function onScanError(err) {
            // Quietly ignore; library calls this frequently during scan
            return;
        }
        
        async function uploadPhotos() {
            const rowId = document.getElementById('rowId').value;
            const fieldUpdates = collectEditValues();
            const hasFields = Object.keys(fieldUpdates).length > 0;
            if (!rowId || (!selectedFiles.length && !hasFields)) {
                document.getElementById('status').innerHTML = '<div class="status error">Please enter row ID and select files or fields to update</div>';
                return;
            }
            
            const formData = new FormData();
            formData.append('rowId', rowId);
            formData.append('fields', JSON.stringify(fieldUpdates));
            selectedFiles.forEach(file => formData.append('photos', file));
            
            try {
                const response = await fetch('/upload', { method: 'POST', body: formData });
                const result = await response.json();
                if (response.ok) {
                    document.getElementById('status').innerHTML = '<div class="status success">Upload successful!</div>';
                    loadIssuesCount(); // Refresh count
                    
                    // Clear all fields and files
                    selectedFiles = [];
                    document.getElementById('fileInput').value = '';
                    document.getElementById('rowId').value = '';
                    document.getElementById('searchInput').value = ''; // Clear search field
                    document.getElementById('searchResults').style.display = 'none'; // Hide search results
                    document.getElementById('previewContainer').innerHTML = '';
                    renderEditForm({}); // Clear form
                } else {
                    throw new Error(result.error);
                }
            } catch (error) {
                document.getElementById('status').innerHTML = '<div class="status error">Error: ' + error.message + '</div>';
            }
        }
    </script>
</body>
</html>`);
});

// Proxy NocoDB images
app.get('/noco-image/*', async (req, res) => {
    try {
        const imagePath = req.params[0];
        if (!imagePath) return res.status(400).json({ error: 'Missing path' });

        const imageUrl = config.ncodbUrl + '/' + imagePath;
        const response = await axios.get(imageUrl, {
            headers: { 'xc-token': config.apiToken },
            responseType: 'stream',
            timeout: 15000
        });

        res.set('Content-Type', response.headers['content-type'] || 'image/jpeg');
        res.set('Cache-Control', 'public, max-age=86400');
        response.data.pipe(res);
    } catch (error) {
        console.error('Image proxy error:', error.message);
        res.status(404).json({ error: 'Image not found' });
    }
});

// Create new record
app.post('/create', async (req, res) => {
    try {
        const fields = req.body || {};
        if (!fields.Item) {
            return res.status(400).json({ error: 'Item name is required' });
        }
        // Only send known fields, skip junk
        const allowed = ['Item', 'Order Number', 'Serial Numbers', 'SKU', 'Received',
                         'Price Per Item', 'Tax', 'Total', 'QTY', 'Notes',
                         'Tracking Number', 'Source', 'Platform', 'Category'];
        const record = {};
        for (const key of allowed) {
            if (fields[key] !== undefined && fields[key] !== null && fields[key] !== '') {
                record[key] = fields[key];
            }
        }
        // Default status
        if (!record.Received) record.Received = 'Pending';

        const createResp = await axios.post(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
            record,
            { headers: { 'Content-Type': 'application/json', 'xc-token': config.apiToken } }
        );
        const newId = createResp.data?.Id || createResp.data?.id;
        console.log('Created new record:', newId, record.Item);
        res.json({ success: true, id: newId, item: createResp.data });
    } catch (error) {
        console.error('Create error:', error.response?.data || error.message);
        res.status(500).json({ error: 'Create failed', details: error.message });
    }
});

// Update item fields
app.patch('/item/:id', async (req, res) => {
    try {
        const rowId = req.params.id;
        const fields = req.body;
        if (!rowId || !fields || Object.keys(fields).length === 0) {
            return res.status(400).json({ error: 'Missing rowId or fields' });
        }
        await axios.patch(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId + '/' + rowId,
            fields,
            {
                headers: {
                    'Content-Type': 'application/json',
                    'xc-token': config.apiToken
                }
            }
        );
        res.json({ success: true });
    } catch (error) {
        console.error('Update error:', error.response?.data || error.message);
        res.status(500).json({ error: 'Update failed', details: error.message });
    }
});

app.post('/duplicate/:id', async (req, res) => {
    try {
        const rowId = req.params.id;
        // Fetch original record
        const response = await axios.get(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId + '/' + rowId,
            { headers: { 'xc-token': config.apiToken, 'Accept': 'application/json' } }
        );
        const original = response.data;
        // Strip fields that shouldn't be duplicated
        const skip = ['Id', 'id', 'CreatedAt', 'UpdatedAt', 'nc_', 'photos', 'Photos', 'row_id',
                       'Print', 'Print SKU', 'send to phone'];
        const newRecord = {};
        for (const [key, value] of Object.entries(original)) {
            if (skip.some(s => key === s || key.startsWith(s))) continue;
            if (value && typeof value === 'object' && !Array.isArray(value)) continue; // skip webhook buttons etc
            if (value !== null && value !== undefined) {
                newRecord[key] = value;
            }
        }
        // Create new record
        const createResp = await axios.post(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
            newRecord,
            { headers: { 'Content-Type': 'application/json', 'xc-token': config.apiToken } }
        );
        console.log('Duplicated record', rowId, '-> new ID:', createResp.data?.Id || createResp.data?.id);
        res.json({ success: true, newId: createResp.data?.Id || createResp.data?.id, item: createResp.data });
    } catch (error) {
        console.error('Duplicate error:', error.response?.data || error.message);
        res.status(500).json({ error: 'Duplicate failed', details: error.message });
    }
});

app.post('/upload', upload.array('photos'), async (req, res) => {
    try {
        const { rowId } = req.body;
        const files = req.files || [];
        const rawFields = req.body.fields;
        let fieldUpdates = {};
        if (rawFields) {
            try { fieldUpdates = JSON.parse(rawFields); } catch (e) { fieldUpdates = {}; }
        }
        
        if (!rowId || !config.apiToken) {
            return res.status(400).json({ error: 'Missing required data' });
        }
        
        const hasFiles = files.length > 0;
        const hasFields = fieldUpdates && Object.keys(fieldUpdates).length > 0;
        if (!hasFiles && !hasFields) {
            return res.status(400).json({ error: 'No files or fields to update' });
        }
        
        const uploadedFiles = [];
        
        for (let file of files) {
            const formData = new FormData();
            formData.append('file', file.buffer, {
                filename: file.originalname,
                contentType: file.mimetype
            });
            
            const uploadResponse = await axios.post(
                config.ncodbUrl + '/api/v1/db/storage/upload',
                formData,
                {
                    headers: {
                        ...formData.getHeaders(),
                        'xc-token': config.apiToken
                    }
                }
            );
            
            uploadedFiles.push(...uploadResponse.data);
        }
        
        const updateData = { ...fieldUpdates };
        if (hasFiles) {
            updateData[config.columnName] = uploadedFiles;
        }
        
        await axios.patch(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId + '/' + rowId,
            updateData,
            {
                headers: {
                    'Content-Type': 'application/json',
                    'xc-token': config.apiToken
                }
            }
        );
        
        res.json({ success: true, message: 'Upload successful' });
        
    } catch (error) {
        const status = error.response?.status;
        const data = error.response?.data;
        const details = typeof data === 'string' ? data.slice(0, 500) : data || error.message;
        console.error('Upload error:', status || '', details);
        res.status(500).json({ error: 'Upload failed', status, details });
    }
});

// Recent items — sorted by last updated
app.get('/recent', async (req, res) => {
    try {
        if (!config.apiToken) {
            return res.status(500).json({ error: 'NocoDB API token not configured' });
        }
        const limit = Math.min(parseInt(req.query.limit) || 30, 100);
        const response = await axios.get(
            config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId,
            {
                headers: { 'xc-token': config.apiToken, 'Accept': 'application/json' },
                params: { limit, sort: '-UpdatedAt' }
            }
        );
        const items = (response.data.list || []).map(row => ({
            id: row.Id,
            item: row.Item || row.Name || 'Unknown',
            received: row.Received || '',
            updatedAt: row.UpdatedAt || ''
        }));
        res.json({ items, count: items.length });
    } catch (error) {
        console.error('Recent fetch error:', error.message);
        res.status(500).json({ error: 'Failed to fetch recent items', details: error.message });
    }
});

// Upload from Immich — server-to-server, no browser involved
app.post('/upload-from-immich', async (req, res) => {
    try {
        const { rowId, assetIds, deleteAfter } = req.body;
        const immichUrl = process.env.IMMICH_URL;
        const immichApiKey = process.env.IMMICH_API_KEY;
        if (!rowId || !assetIds?.length) {
            return res.status(400).json({ error: 'Missing rowId or assetIds' });
        }
        if (!immichUrl || !immichApiKey) {
            return res.status(500).json({ error: 'Immich not configured on server' });
        }

        const uploaded = [];
        for (const assetId of assetIds) {
            try {
                // Download original from Immich
                const imgRes = await axios.get(`${immichUrl}/api/assets/${assetId}/original`, {
                    headers: { 'x-api-key': immichApiKey },
                    responseType: 'arraybuffer'
                });

                // Get filename from Immich
                const assetInfo = await axios.get(`${immichUrl}/api/assets/${assetId}`, {
                    headers: { 'x-api-key': immichApiKey }
                });
                const filename = assetInfo.data.originalFileName || `immich-${assetId}.jpg`;
                const mimetype = assetInfo.data.originalMimeType || 'image/jpeg';

                // Upload to NocoDB storage
                const formData = new FormData();
                formData.append('file', Buffer.from(imgRes.data), {
                    filename,
                    contentType: mimetype
                });

                const uploadResponse = await axios.post(
                    config.ncodbUrl + '/api/v1/db/storage/upload',
                    formData,
                    {
                        headers: {
                            ...formData.getHeaders(),
                            'xc-token': config.apiToken
                        }
                    }
                );

                uploaded.push(...uploadResponse.data);
                console.log(`Uploaded ${filename} from Immich to NocoDB`);
            } catch (e) {
                console.error(`Failed to upload asset ${assetId}:`, e.message);
            }
        }

        if (uploaded.length > 0) {
            // Attach to row
            await axios.patch(
                config.ncodbUrl + '/api/v1/db/data/noco/' + config.baseId + '/' + config.tableId + '/' + rowId,
                { [config.columnName]: uploaded },
                {
                    headers: {
                        'Content-Type': 'application/json',
                        'xc-token': config.apiToken
                    }
                }
            );
        }

        // Delete from Immich if requested
        const deletedIds = [];
        if (deleteAfter && uploaded.length > 0) {
            try {
                await axios.delete(`${immichUrl}/api/assets`, {
                    headers: { 'x-api-key': immichApiKey, 'Content-Type': 'application/json' },
                    data: { ids: assetIds.slice(0, uploaded.length), force: false }
                });
                deletedIds.push(...assetIds.slice(0, uploaded.length));
                console.log(`Trashed ${deletedIds.length} assets from Immich`);
            } catch (e) {
                console.error('Failed to delete from Immich:', e.message);
            }
        }

        res.json({ success: true, uploadedCount: uploaded.length, deletedCount: deletedIds.length });
    } catch (error) {
        console.error('Immich upload error:', error.message);
        res.status(500).json({ error: 'Upload failed', details: error.message });
    }
});

// Fetch workspace ID for NocoDB deep links
(async () => {
    try {
        const res = await axios.get(config.ncodbUrl + '/api/v1/db/meta/projects/', {
            headers: { 'xc-token': config.apiToken }
        });
        const base = (res.data.list || []).find(b => b.id === config.baseId);
        if (base && base.fk_workspace_id) {
            config.workspaceId = base.fk_workspace_id;
            console.log('   Workspace ID: ' + config.workspaceId);
        }
    } catch (e) {
        console.log('   Could not fetch workspace ID:', e.message);
    }
})();

app.listen(port, () => {
    const externalPort = process.env.EXTERNAL_PORT || port;
    console.log('📷 NocoDB Photo Uploader running on port ' + port);
    console.log('🔧 Configuration:');
    console.log('   NocoDB URL: ' + config.ncodbUrl);
    console.log('   Base ID: ' + config.baseId);
    console.log('   Table ID: ' + config.tableId);
    console.log('   Column Name: ' + config.columnName);
    console.log('   API Token: ' + (config.apiToken ? '✅ Set' : '❌ Missing'));
    console.log('');
    console.log('🌐 Open http://localhost:' + externalPort + ' to use the uploader');
    console.log('🔒 API Key: ' + (process.env.SERVICE_API_KEY ? '✅ Required' : '⚠️ Not set'));
    console.log('🎨 Modern theme preview: http://localhost:' + externalPort + '/preview');
});

// Preview route that enables modern theme without affecting default
app.get('/preview', (req, res) => {
    res.redirect('/?theme=modern');
});
