// Production rule-based appointment parser from ShopProQuote project.
// Extracts multiple appointments from OCR text with no API dependencies.
// Handles: phones, VINs (OCR-tolerant), dates, times, durations, vehicles,
// advisor+RO stripping, cross-block name carryover, table format detection.
// See SKILL.md for the full architecture and pitfall documentation.

function parseWithRules(ocrText) {
    var t = ocrText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
    if (!t) return [];

    // Strip separator lines and short all-caps headers, KEEP blank lines
    t = t.split('\n').map(function(line) {
        var s = line.trim();
        if (s.length === 0) return ''; // keep blank lines for block splitting
        if (/^[\-\u2013=_*#]{3,}$/.test(s)) return '';
        if (s.length < 25 && s === s.toUpperCase() && /^[A-Z\s]+$/.test(s) && s.length > 1) return '';
        return line;
    }).join('\n');

    var appointments = [];
    var phoneRe = /\(?\d{3}\)?[\s.\-]*\d{3}[\s.\-]*\d{4}/;
    var vinRe = /\b[A-HJ-NPR-Z0-9OIQ]{17}\b/i;
    function fixVin(v) { return v.toUpperCase().replace(/O/g,'0').replace(/I/g,'1').replace(/Q/g,'0'); }
    function parseDate(str) { /* see SKILL.md for full implementation */ return null; }
    var dateRe = /\b(?:\d{4}[\/\-.]\d{1,2}[\/\-.]\d{1,2}|\d{1,2}[\/]\d{1,2}(?:[\/]\d{4})?|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{1,2}(?:[,\s]+\d{4})?)\b/i;
    var timeRe = /\b(\d{1,2}):(\d{2})\s*(AM|PM|am|pm)?\b|\b(\d{1,2})\s*(AM|PM|am|pm)\b/;
    var durRe = /\b(\d+)\s*(?:min|minutes?|hrs?|hours?)\b/i;
    var makesRe = /\b(?:ford|chevy|chevrolet|toyota|honda|nissan|bmw|mercedes|audi|dodge|jeep|gmc|ram|hyundai|kia|subaru|mazda|vw|volkswagen|lexus|acura|infiniti|cadillac|buick|chrysler|lincoln|tesla|volvo)\b/i;
    var yrRe = /\b(19|20)\d{2}\b/;

    function parseOne(raw, carryName) {
        var b = raw, appt = {};
        if (carryName) { appt.customerName = carryName; }
        // Extract structured fields
        var pm = b.match(phoneRe); if (pm) { appt.customerPhone = pm[0].replace(/[^\d]/g, ''); b = b.replace(pm[0], ' '); }
        var vm = b.match(vinRe); if (vm) { appt.vin = fixVin(vm[0]); b = b.replace(vm[0], ' '); }
        var dm = b.match(dateRe); if (dm) { var pd = parseDate(dm[0]); if (pd) { appt.appointmentDate = pd; b = b.replace(dm[0], ' '); } }
        var tm = b.match(timeRe);
        if (tm) { /* parse time to HH:MM, see SKILL.md */ b = b.replace(tm[0], ' '); }
        var dum = b.match(durRe); if (dum) { var val = parseInt(dum[1]); if (/hr|hour/i.test(dum[0])) val *= 60; appt.duration = val; b = b.replace(dum[0], ' '); }

        // Boundary detection
        var np = b.match(phoneRe), nv = b.match(vinRe);
        var myText = b, nextText = '', nextName = null;
        if (np || nv) {
            var ni = b.length;
            if (np) ni = Math.min(ni, b.indexOf(np[0]));
            if (nv) ni = Math.min(ni, b.indexOf(nv[0]));
            if (ni > 0 && ni < b.length) {
                var before = b.substring(0, ni);
                // Walk back to last multi-space gap before last 1-2 words (tolerate trailing spaces)
                var gap = before.match(/\s{2,}(?=\S+(?:\s+\S+){0,1}\s*$)/);
                var splitAt = ni;
                if (gap) { splitAt = before.lastIndexOf(gap[0]) + gap[0].length; }
                else {
                    // Fallback: split before last 1-2 capitalized words
                    var nw = before.match(/([A-Z][A-Za-z]+(?:\s+[A-Z][A-Za-z]+){0,1})\s*$/);
                    if (nw && nw[1].length > 2) splitAt = before.length - nw[0].length;
                }
                myText = b.substring(0, splitAt);
                nextText = b.substring(splitAt);
            }
        }

        // Split on multi-spaces (preserves column structure)
        var parts = myText.split(/\s{2,}/).map(function(p) { return p.replace(/\s+/g,' ').trim(); }).filter(function(p) { return p.length > 1; });

        // Strip advisor name + RO code
        var roCode = null;
        parts = parts.map(function(p) {
            var m = p.match(/^([A-Z][a-z]+)\s+(\[[^\]]+\])\s*(.*)/);
            if (m) { roCode = roCode || m[2]; return m[3] || ''; }
            return p;
        }).filter(function(p) { return p.length > 0; });

        // Clean VIN artifacts
        parts = parts.map(function(p) { return p.replace(/VIN\s*VIN:?\s*/gi,'').replace(/VIN:?\s*VIN\s*/gi,'').trim(); }).filter(function(p) { return p.length > 1; });

        // Assign name (first part, unless carried from previous block)
        if (!carryName && parts.length > 0) { appt.customerName = parts[0]; parts.shift(); }

        // Vehicle detection: prefer part with BOTH year AND make
        var vi = -1;
        for (var i = 0; i < parts.length; i++) {
            if (yrRe.test(parts[i]) && makesRe.test(parts[i])) { vi = i; break; }
        }
        if (vi < 0) for (var i = 0; i < parts.length; i++) {
            if (yrRe.test(parts[i]) || makesRe.test(parts[i])) { vi = i; break; }
        }
        if (vi >= 0) { appt.vehicleInfo = parts[vi]; parts.splice(vi, 1); }
        if (parts.length > 0) { appt.serviceType = parts.join(' ').replace(/^\s+|\s+$/g, ''); }
        if (roCode) appt.notes = (appt.notes ? appt.notes + ' ' : '') + 'RO: ' + roCode;

        // Cross-block name carryover: strip trailing all-caps name from service
        if (appt.serviceType) {
            var tn = appt.serviceType.match(/\s+([A-Z]{2,}(?:\s+[A-Z]{2,}){1,2})\s*$/);
            if (tn && tn[1].length > 3) {
                appt.serviceType = appt.serviceType.substring(0, appt.serviceType.length - tn[0].length).trim();
                nextName = tn[1];
            }
        }

        if (appt.customerName && appt.customerName.length > 1) appointments.push(appt);
        if (nextText.length > 10) return parseOne(nextText, nextName);
        return nextName; // return for cross-block carry
    }

    // Split into blocks, detect tables, parse
    var blocks = t.split(/\n\s*\n/).filter(function(b) { return b.trim().length > 3; });
    if (blocks.length === 0) blocks = [t];

    // Table format detection
    if (blocks.length === 1) {
        var lines = t.split('\n').filter(function(l) { return l.trim().length > 0; });
        if (lines.length >= 3 && lines.length <= 40) {
            var fl = lines[0].trim();
            var hw = (fl.match(/\b(name|phone|date|time|service|vehicle|vin|customer|appointment|mileage|advisor|status)\b/gi) || []).length;
            var hd = phoneRe.test(fl) || vinRe.test(fl);
            // Only strip if 3+ header words AND no phone/VIN (data guard)
            if (hw >= 3 && !hd && lines.length > 2) lines = lines.slice(1);
            var wc = lines.map(function(l) { return l.trim().split(/\s+/).length; });
            var avg = wc.reduce(function(a,b) { return a+b; }, 0) / wc.length;
            if (wc.every(function(w) { return Math.abs(w - avg) <= 3; }) && lines.length >= 2) {
                lines.forEach(function(line) { parseOne(line.replace(/\t/g, '    ').replace(/\n/g, ' ')); });
                return appointments;
            }
        }
    }

    // Standard block-by-block with cross-block name carry
    var carry = null;
    blocks.forEach(function(block) {
        carry = parseOne(block.replace(/\t/g, '    ').replace(/\n/g, ' '), carry);
    });

    return appointments;
}
