// ─── Country code lookup map ───────────────────────────────────────────────

const COUNTRY_CODES = {
    AD: 'Andorra', AE: 'United Arab Emirates', AF: 'Afghanistan',
    AG: 'Antigua and Barbuda', AI: 'Anguilla', AL: 'Albania',
    AM: 'Armenia', AO: 'Angola', AR: 'Argentina', AT: 'Austria',
    AU: 'Australia', AZ: 'Azerbaijan', BA: 'Bosnia and Herzegovina',
    BB: 'Barbados', BD: 'Bangladesh', BE: 'Belgium', BF: 'Burkina Faso',
    BG: 'Bulgaria', BH: 'Bahrain', BI: 'Burundi', BJ: 'Benin',
    BN: 'Brunei', BO: 'Bolivia', BR: 'Brazil', BS: 'Bahamas',
    BT: 'Bhutan', BW: 'Botswana', BY: 'Belarus', BZ: 'Belize',
    CA: 'Canada', CD: 'Democratic Republic of the Congo',
    CF: 'Central African Republic', CG: 'Republic of the Congo',
    CH: 'Switzerland', CI: "Côte d'Ivoire", CL: 'Chile', CM: 'Cameroon',
    CN: 'China', CO: 'Colombia', CR: 'Costa Rica', CU: 'Cuba',
    CV: 'Cabo Verde', CY: 'Cyprus', CZ: 'Czech Republic', DE: 'Germany',
    DJ: 'Djibouti', DK: 'Denmark', DM: 'Dominica', DO: 'Dominican Republic',
    DZ: 'Algeria', EC: 'Ecuador', EE: 'Estonia', EG: 'Egypt',
    EH: 'Western Sahara', ER: 'Eritrea', ES: 'Spain', ET: 'Ethiopia',
    FI: 'Finland', FJ: 'Fiji', FM: 'Micronesia', FR: 'France',
    GA: 'Gabon', GB: 'United Kingdom', GD: 'Grenada', GE: 'Georgia',
    GH: 'Ghana', GI: 'Gibraltar', GL: 'Greenland', GM: 'Gambia',
    GN: 'Guinea', GQ: 'Equatorial Guinea', GR: 'Greece', GT: 'Guatemala',
    GW: 'Guinea-Bissau', GY: 'Guyana', HK: 'Hong Kong', HN: 'Honduras',
    HR: 'Croatia', HT: 'Haiti', HU: 'Hungary', ID: 'Indonesia',
    IE: 'Ireland', IL: 'Israel', IN: 'India', IQ: 'Iraq', IR: 'Iran',
    IS: 'Iceland', IT: 'Italy', JM: 'Jamaica', JO: 'Jordan', JP: 'Japan',
    KE: 'Kenya', KG: 'Kyrgyzstan', KH: 'Cambodia', KI: 'Kiribati',
    KM: 'Comoros', KN: 'Saint Kitts and Nevis', KP: 'North Korea',
    KR: 'South Korea', KW: 'Kuwait', KZ: 'Kazakhstan', LA: 'Laos',
    LB: 'Lebanon', LC: 'Saint Lucia', LI: 'Liechtenstein', LK: 'Sri Lanka',
    LR: 'Liberia', LS: 'Lesotho', LT: 'Lithuania', LU: 'Luxembourg',
    LV: 'Latvia', LY: 'Libya', MA: 'Morocco', MC: 'Monaco', MD: 'Moldova',
    ME: 'Montenegro', MG: 'Madagascar', MH: 'Marshall Islands',
    MK: 'North Macedonia', ML: 'Mali', MM: 'Myanmar', MN: 'Mongolia',
    MR: 'Mauritania', MT: 'Malta', MU: 'Mauritius', MV: 'Maldives',
    MW: 'Malawi', MX: 'Mexico', MY: 'Malaysia', MZ: 'Mozambique',
    NA: 'Namibia', NE: 'Niger', NG: 'Nigeria', NI: 'Nicaragua',
    NL: 'Netherlands', NO: 'Norway', NP: 'Nepal', NR: 'Nauru',
    NZ: 'New Zealand', OM: 'Oman', PA: 'Panama', PE: 'Peru',
    PG: 'Papua New Guinea', PH: 'Philippines', PK: 'Pakistan',
    PL: 'Poland', PT: 'Portugal', PW: 'Palau', PY: 'Paraguay',
    QA: 'Qatar', RO: 'Romania', RS: 'Serbia', RU: 'Russia', RW: 'Rwanda',
    SA: 'Saudi Arabia', SB: 'Solomon Islands', SC: 'Seychelles',
    SD: 'Sudan', SE: 'Sweden', SG: 'Singapore', SI: 'Slovenia',
    SK: 'Slovakia', SL: 'Sierra Leone', SM: 'San Marino', SN: 'Senegal',
    SO: 'Somalia', SR: 'Suriname', ST: 'São Tomé and Príncipe',
    SV: 'El Salvador', SY: 'Syria', SZ: 'Eswatini', TD: 'Chad',
    TG: 'Togo', TH: 'Thailand', TJ: 'Tajikistan', TL: 'Timor-Leste',
    TM: 'Turkmenistan', TN: 'Tunisia', TO: 'Tonga', TR: 'Turkey',
    TT: 'Trinidad and Tobago', TV: 'Tuvalu', TW: 'Taiwan', TZ: 'Tanzania',
    UA: 'Ukraine', UG: 'Uganda', US: 'United States', UY: 'Uruguay',
    UZ: 'Uzbekistan', VA: 'Vatican City', VC: 'Saint Vincent and the Grenadines',
    VE: 'Venezuela', VN: 'Vietnam', VU: 'Vanuatu', WS: 'Samoa',
    YE: 'Yemen', ZA: 'South Africa', ZM: 'Zambia', ZW: 'Zimbabwe',
};

// ─── Legal entity suffixes ──────────────────────────────────────────────────

const ENTITY_SUFFIXES = [
    'gmbh', 'ltd', 'co.', 'ag', 'inc', 'corp', 'corporation', 'limited',
    'llc', 'llp', 'plc', 'lp', 'lllp', 'ulc', 's.à r.l', 'sarl', 'srl',
    's.r.l.', 's.r.l', 's.l.', 's.l', 's.a.', 's.a', 'spa', 's.p.a', 'sa',
    'sas', 'gmbh & co. kg', 'kgaa', 'kg', 'bv', 'nv', 'oy', 'ab', 'as',
    'a/s', 'pte.', 'pvt.', 'pty.', 'pte', 'pvt', 'pty', 'kabushiki kaisha',
    'zrt', 'gongsi', 'limitada', 'sociedad limitada',
];

// ─── Helpers ────────────────────────────────────────────────────────────────

const isEmail = str => str.includes('@');
const isUrl = str => str.includes('http');
const isPhone = str => !isEmail(str) && !isUrl(str) && /^(\+?\d[\d\s\-()]+)$/.test(str);

function isCompanyName(line) {
    if (!line) return false;
    const tokens = line.toLowerCase().split(/[^a-z0-9.]+/).filter(Boolean);
    const stripTrailing = t => t.replace(/[^a-z0-9.]+$/g, '');
    for (let token of tokens) {
        token = stripTrailing(token);
        for (const suffix of ENTITY_SUFFIXES) {
            if (token === suffix.toLowerCase()) return true;
        }
    }
    return false;
}

function findCountryCode(text) {
    const words = text
        .split(/\s+/)
        .map(w => w.replace(/[^A-Za-z]/g, ''))
        .filter(Boolean);

    for (const word of words) {
        const upper = word.toUpperCase();
        if (COUNTRY_CODES[upper]) return upper;

        const entry = Object.entries(COUNTRY_CODES).find(
            ([, name]) => name.toUpperCase() === upper
        );
        if (entry) return entry[0];
    }
    return null;
}

// ─── Parser 1: general multi-line address block ─────────────────────────────
// Expects newline-separated text; finds country from a comma-separated location
// line, then extracts name, address lines, city, postal code, state, contact info.

function parseAddressBlock(input) {
    const emptyResult = {
        name: null, country: null, address1: null, address2: null,
        city: null, postalCode: null, stateOrProvince: null,
        phoneNumber: null, emailAddress: null, website: null,
    };

    // Collect lines up to the first blank line
    const rawLines = input.split('\n');
    const trimmedLines = [];
    for (const line of rawLines) {
        if (line.trim() === '') break;
        trimmedLines.push(line.trim());
    }

    const lines = trimmedLines.filter(l => l !== '');
    if (lines.length === 0) return emptyResult;

    // Find country by scanning comma-separated lines
    let foundCountry = false;
    let countryCode = null;

    for (const line of lines) {
        if (!line.includes(',')) continue;
        const parts = line.split(',').map(p => p.trim()).filter(Boolean);
        for (const part of parts) {
            const code = findCountryCode(part);
            if (code) {
                foundCountry = true;
                countryCode = code;
                break;
            }
        }
        if (foundCountry) break;
    }

    if (!foundCountry) return emptyResult;

    const result = { ...emptyResult, country: countryCode };

    // Find the location line index (a line with a country part and 3+ comma tokens)
    let locationLineIndex = -1;
    const preLocationLines = [];

    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        const parts = line.split(',').map(p => p.trim()).filter(Boolean);
        const hasCountry = parts.some(p => findCountryCode(p));

        if (hasCountry && parts.length >= 3) {
            locationLineIndex = i;
            break;
        }
        preLocationLines.push(line);
    }

    // Detect company name from pre-location lines
    let name = preLocationLines.find(isCompanyName);
    if (!name) {
        name = preLocationLines.find(line => {
            const words = line.split(/\s+/);
            let titleCaseCount = 0;
            for (const w of words) {
                if (/^[A-Z][a-z]/.test(w)) titleCaseCount++;
            }
            return titleCaseCount >= 2;
        }) || (preLocationLines.length > 0 ? preLocationLines[0] : null);
    }
    if (!name && lines.length > 0) name = lines[0];
    result.name = name || null;

    // Address lines (lines between name and location line)
    const nameIndex = preLocationLines.indexOf(result.name);
    const afterName = nameIndex !== -1
        ? preLocationLines.slice(nameIndex + 1)
        : preLocationLines;

    if (afterName.length > 0) result.address1 = afterName[0];
    else {
        const ni = lines.indexOf(result.name);
        if (ni !== -1 && ni + 1 < lines.length) result.address1 = lines[ni + 1];
    }

    if (afterName.length > 1) {
        const addr2 = afterName.slice(1).find(l => !isEmail(l) && !isUrl(l));
        if (addr2) result.address2 = addr2;
    }

    // Parse city / postal code / state from the location line
    let city = null, postalCode = null, stateOrProvince = null;

    if (locationLineIndex !== -1) {
        const locationParts = lines[locationLineIndex]
            .split(',').map(p => p.trim()).filter(Boolean);

        let countryPartIndex = -1;
        for (let i = locationParts.length - 1; i >= 0; i--) {
            if (findCountryCode(locationParts[i])) {
                countryPartIndex = i;
                break;
            }
        }

        if (countryPartIndex !== -1) {
            const beforeCountry = locationParts.slice(0, countryPartIndex);
            const afterCountry = locationParts.slice(countryPartIndex + 1);

            if (afterCountry.length > 0 && /\d/.test(afterCountry[0])) {
                postalCode = afterCountry[0];
            }

            if (beforeCountry.length > 2) {
                city = beforeCountry.slice(0, -1).join(', ');
                stateOrProvince = beforeCountry[beforeCountry.length - 1];
            } else if (beforeCountry.length === 2) {
                city = beforeCountry[0];
                stateOrProvince = beforeCountry[1];
            } else if (beforeCountry.length === 1) {
                city = beforeCountry[0];
            }
        }
    }

    result.city = city || result.country;
    result.postalCode = postalCode || result.country;
    result.stateOrProvince = stateOrProvince || result.country;

    if (result.postalCode && result.postalCode.length > 9) {
        result.postalCode = result.postalCode.substring(0, 9);
    }

    // Contact info
    for (const line of lines) {
        if (!result.emailAddress && isEmail(line)) result.emailAddress = line;
        if (!result.website && isUrl(line)) result.website = line;
        if (!result.phoneNumber && isPhone(line)) result.phoneNumber = line;
        if (result.emailAddress && result.website && result.phoneNumber) break;
    }

    // Fix city if it's suspiciously long (> 50 chars)
    try {
        if (result.city && result.city.length > 50) {
            const fixLine = lines.find(l => l.includes(result.country + ','));
            if (fixLine) result.city = fixLine.split(',')[0].trim();
        }
    } catch (e) {
        console.error('Error processing city:', e);
    }

    // Fix city if it looks like a 2-letter country code
    try {
        if (result.city && result.city.length <= 2) {
            const phoneIndex = lines.indexOf(result.phoneNumber);
            const emailIndex = lines.indexOf(result.emailAddress);
            const prevLine = phoneIndex > -1 ? lines[phoneIndex - 1]
                : emailIndex > -1 ? lines[emailIndex - 1]
                    : null;
            if (prevLine) {
                const parts = prevLine.split(',');
                if (parts.length > 0) {
                    result.city = parts[0].trim();
                    result.country = parts[1].trim();
                    result.postalCode = parts[parts.length - 1].trim();
                }
            }
        }
    } catch (e) { /* ignore */ }

    return result;
}

// ─── Parser 2: European-style address (DE/ES/AT/CH/FR/GB) ──────────────────
// Uses city lookups, postal-code patterns, and keyword hints to identify
// country and state without requiring an explicit country token.

const CITY_TO_COUNTRY_STATE = {
    frankfurt: { c: 'DE', s: 'HE' },
    hamburg: { c: 'DE', s: 'HH' },
    berlin: { c: 'DE', s: 'BE' },
    munich: { c: 'DE', s: 'BY' },
    stuttgart: { c: 'DE', s: 'BW' },
    leipzig: { c: 'DE', s: 'SN' },
    madrid: { c: 'ES', s: 'M' },
    barcelona: { c: 'ES', s: 'B' },
    fuenlabrada: { c: 'ES', s: 'M' },
    valencia: { c: 'ES', s: 'V' },
    sevilla: { c: 'ES', s: 'SE' },
    bilbao: { c: 'ES', s: 'BI' },
    vienna: { c: 'AT', s: 'W' },
    graz: { c: 'AT', s: 'ST' },
    zurich: { c: 'CH', s: 'ZH' },
};

const POSTAL_CODE_RULES = [
    // German postal codes
    {
        rx: /^\d{5}$/,
        handler: code => {
            const n = +code;
            if (n >= 60000 && n <= 69999) return { c: 'DE', s: 'HE' };
            if (n >= 10000 && n <= 19999) return { c: 'DE', s: 'SN' };
            if (n >= 20000 && n <= 29999) return { c: 'DE', s: 'SH' };
            return { c: 'DE' };
        },
    },
    // Spanish postal codes
    {
        rx: /^\d{5}$/,
        handler: code => {
            const stateMap = { 28: 'M', '08': 'B', 46: 'V', 41: 'SE', 48: 'BI' };
            const state = stateMap[code.slice(0, 2)];
            return state ? { c: 'ES', s: state } : { c: 'ES' };
        },
    },
    // 4-digit codes: Austria or Switzerland
    {
        rx: /^\d{4}$/,
        handler: code => {
            const n = +code;
            if (n >= 100 && n <= 1999) return { c: 'AT', s: 'W' };
            if (n >= 8000 && n <= 8999) return { c: 'CH', s: 'ZH' };
            return null;
        },
    },
    // UK postcodes
    { rx: /^[A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2}$/i, handler: () => ({ c: 'GB' }) },
    // US ZIP codes
    { rx: /^\d{5}(?:-\d{4})?$/, handler: () => ({ c: 'US' }) },
];

const KEYWORD_COUNTRY_HINTS = [
    { word: /straße|str\./i, c: 'DE' },
    { word: /sociedad limitada|s\.l\./i, c: 'ES' },
];

const COUNTRY_NAME_ENDINGS = {
    ES: 'spain', DE: 'germany', AT: 'austria',
    CH: 'switzerland', FR: 'france', GB: 'united kingdom',
};

const ENTITY_SUFFIX_PATTERN = /\b(S\.?L\.?U?\.?|S\.?A\.?|GMBH|AG|LLC|INC)\b\.?/gi;

function normalizeForSearch(str) {
    return str.toLowerCase()
        .normalize('NFD')
        .replace(/[\u0300-\u036f]/g, '')
        .replace(/[\s\-]/g, '');
}

function parseEuropeanAddress(input) {
    const isEmailEu = str => /@/.test(str);
    const isUrlEu = str => /https?:\/\//i.test(str);
    const isPhoneEu = str => /^\+?\d[\d\s\-()]{5,}$/.test(str);

    const lines = input
        .split('\n')
        .map(l => l.replace(/\s+/g, ' ').trim())
        .filter(Boolean);

    const contact = { email: null, phone: null, url: null };
    lines.forEach(line => {
        if (!contact.email && isEmailEu(line)) contact.email = line;
        else if (!contact.url && isUrlEu(line)) contact.url = line;
        else if (!contact.phone && isPhoneEu(line)) contact.phone = line;
    });

    // Non-contact lines; strip entity suffixes
    const contentLines = lines.filter(l => !(isEmailEu(l) || isUrlEu(l) || isPhoneEu(l)));
    const cleanedLines = contentLines.map(l => l.replace(ENTITY_SUFFIX_PATTERN, '').trim());

    let location = { c: null, s: null, city: null, postal: null };

    // 1. Country name at end of line
    for (const line of cleanedLines) {
        for (const [code, ending] of Object.entries(COUNTRY_NAME_ENDINGS)) {
            if (line.toLowerCase().endsWith(ending)) {
                location.c = code;
                break;
            }
        }
        if (location.c) break;
    }

    // 2. Known city name
    if (!location.c) {
        outer: for (const line of cleanedLines) {
            const norm = normalizeForSearch(line);
            for (const [city, info] of Object.entries(CITY_TO_COUNTRY_STATE)) {
                if (norm.includes(city)) {
                    location = { ...location, ...info, city: city };
                    break outer;
                }
            }
        }
    }

    // 3. Postal code pattern
    if (!location.c) {
        for (const line of cleanedLines) {
            for (const { rx, handler } of POSTAL_CODE_RULES) {
                const match = line.match(rx);
                if (match) {
                    const info = handler(match[0]);
                    if (info) {
                        location = { ...location, ...info, postal: match[0] };
                        break;
                    }
                }
            }
            if (location.c) break;
        }
    }

    // 4. Keyword hints
    if (!location.c) {
        for (const { word, c } of KEYWORD_COUNTRY_HINTS) {
            if (cleanedLines.some(l => word.test(l))) {
                location.c = c;
                break;
            }
        }
    }

    const result = {
        name: null,
        country: location.c || null,
        stateOrProvince: location.s || null,
        city: location.city
            ? location.city.replace(/\b\w/g, ch => ch.toUpperCase())
            : null,
        postalCode: location.postal || null,
        address1: null,
        address2: null,
        phoneNumber: contact.phone,
        emailAddress: contact.email,
        website: contact.url,
    };

    if (!contentLines.length) return result;
    result.name = contentLines[0];

    const remainingLines = contentLines.slice(1);
    if (!remainingLines.length) return result;

    // Find the "location line" (contains postal code or known city)
    let locationLineIdx = remainingLines.findIndex(line =>
        (result.postalCode && line.includes(result.postalCode)) ||
        (location.city && line.toLowerCase().includes(location.city.toLowerCase()))
    );
    if (locationLineIdx === -1) locationLineIdx = remainingLines.length - 1;

    const locationLine = remainingLines[locationLineIdx];
    const addressPrefix = remainingLines.slice(0, locationLineIdx).join(', ');
    const locationParts = locationLine.split(',').map(p => p.trim()).filter(Boolean);

    const postalIdx = locationParts.findIndex(p => /\d{4,6}/.test(p));

    if (postalIdx > -1) {
        const beforePostal = locationParts.slice(0, postalIdx);
        const afterPostal = locationParts.slice(postalIdx + 1);
        const postalMatch = locationParts[postalIdx].match(/(\d{4,6})/);
        const postal = postalMatch[1];

        result.postalCode = result.postalCode || postal;

        // Try to infer country from tokens after postal code
        if (!result.country && afterPostal.length) {
            const last = afterPostal[afterPostal.length - 1].toLowerCase();
            if (/spain|españa/.test(last)) result.country = 'ES';
            else if (/germany|deutschland/.test(last)) result.country = 'DE';
            else if (/austria|österreich/.test(last)) result.country = 'AT';
        }

        if (!result.city && beforePostal.length) result.city = beforePostal.pop();
        if (!result.stateOrProvince && beforePostal.length) result.stateOrProvince = beforePostal.pop();

        result.address1 = addressPrefix ||
            locationParts.slice(0, postalIdx
                - (result.stateOrProvince ? 1 : 0)
                - (result.city ? 1 : 0)
            ).join(', ');
    } else {
        result.city = result.city || locationLine;
        result.address1 = addressPrefix || '';
    }

    if (!result.address1?.trim()) {
        result.address1 = addressPrefix || locationParts.slice(0, postalIdx).join(', ');
    }

    return result;
}

// ─── Parser 3: Chinese-style address ────────────────────────────────────────
// Handles "District, City, Province" patterns and Chinese punctuation.

const CHINESE_PROVINCES = [
    'anhui', 'beijing', 'chongqing', 'fujian', 'gansu', 'guangdong',
    'guangxi', 'guizhou', 'hainan', 'hebei', 'heilongjiang', 'henan',
    'hubei', 'hunan', 'jiangsu', 'jiangxi', 'jilin', 'liaoning',
    'inner mongolia', 'neimenggu', 'ningxia', 'qinghai', 'shaanxi',
    'shandong', 'shanghai', 'shanxi', 'sichuan', 'tianjin', 'xinjiang',
    'tibet', 'xizang', 'yunnan', 'zhejiang', 'hong kong', 'macau', 'taiwan',
];

const COUNTRY_CODE_MAP = {
    CN: 'China', DE: 'Germany', ES: 'Spain', US: 'United States',
};
const COUNTRY_NAME_TO_CODE = Object.fromEntries(
    Object.entries(COUNTRY_CODE_MAP).map(([code, name]) => [name.toLowerCase(), code])
);

function parseChineseAddress(input) {
    const normalize = str => str.replace(/[，。]/g, ',').replace(/\s+/g, ' ').trim();
    const isEmailCN = str => /^[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}$/i.test(str);
    const isPhoneCN = str => /^\+?\d[\d\s\-()]{5,}$/.test(str);

    const resolveCountry = token => {
        const clean = token.replace(/[^A-Za-z]/g, '').toLowerCase();
        if (COUNTRY_CODE_MAP[clean.toUpperCase()]) return clean.toUpperCase();
        return COUNTRY_NAME_TO_CODE[clean] || null;
    };

    // Strip HTML, split on newlines and separators, normalize
    const tokens = input
        .replace(/<[^>]*>/g, '')
        .split(/[\r\n]+/)
        .flatMap(l => l.split(/·|•|\|/))
        .map(normalize)
        .filter(Boolean);

    const result = {
        name: null, address1: null, address2: null, city: null,
        stateOrProvince: null, postalCode: null, country: null,
        emailAddress: null, phoneNumber: null,
    };

    // Extract and remove contact lines
    for (const token of [...tokens]) {
        if (!result.emailAddress && isEmailCN(token)) {
            result.emailAddress = token;
            tokens.splice(tokens.indexOf(token), 1);
        } else if (!result.phoneNumber && isPhoneCN(token)) {
            result.phoneNumber = token;
            tokens.splice(tokens.indexOf(token), 1);
        }
    }

    if (tokens.length) result.name = tokens.shift();

    const addressStr = tokens.join(', ');

    // Pattern: "Street, District, City, Province"
    let match = addressStr.match(
        /(.+?),\s*([\w\s]+?)\s+District,\s*([\w\s]+?)\s+City,\s*([\w\s]+?)\s+Province/i
    );

    if (!match) {
        const alt = addressStr.match(
            /(.+?),\s*district,\s*([\w\s]+?)(?:\s+City)?,\s*([\w\s]+?)(?:\s+Province)?,\s*China/i
        );
        if (alt) match = [alt[0], alt[1], null, alt[2], alt[3]];
    }

    if (match) {
        result.address1 = normalize(match[1]).replace(/,\s*$/, '').replace(/\bDistrict$/i, 'District');
        result.city = normalize(match[3] || match[2] || '');
        result.stateOrProvince = normalize(match[4] || '');
        result.country = 'CN';
        return result;
    }

    // Generic fallback
    const words = addressStr.split(/[\s,]+/).filter(Boolean);
    for (let i = words.length - 1; i >= 0; i--) {
        const code = resolveCountry(words[i]);
        if (code) { result.country = code; break; }
    }

    const postalMatch = addressStr.match(/\b\d{4,6}\b/);
    if (postalMatch) result.postalCode = postalMatch[0];

    const lastComma = addressStr.lastIndexOf(',');
    if (lastComma !== -1) {
        result.address1 = addressStr.slice(0, lastComma).trim();
        const tail = addressStr.slice(lastComma + 1).trim().split(',');
        result.city = tail[0].trim();
        if (tail[1]) result.stateOrProvince = tail[1].trim();
    } else {
        result.address1 = addressStr;
    }

    if (!result.country && result.stateOrProvince) {
        if (CHINESE_PROVINCES.includes(result.stateOrProvince.toLowerCase().trim())) {
            result.country = 'CN';
        }
    }

    return result;
}

// ─── Main dispatcher ─────────────────────────────────────────────────────────
// Tries the general parser first; if it produces a valid country + city,
// returns that result. Otherwise falls back to the European-style parser.

function parseAddress(input) {
    const result = parseAddressBlock(input);
    if (result && result.country && result.city) return result;
    return parseEuropeanAddress(input);
}

// ─── DOM wiring ──────────────────────────────────────────────────────────────

document.addEventListener('DOMContentLoaded', () => {
    const runBtn = document.getElementById('runParserBtn');
    const inputEl = document.getElementById('inputText');
    const outputEl = document.getElementById('output');

    runBtn.addEventListener('click', () => {
        const parsed = parseAddressBlock(inputEl.value);
        outputEl.textContent = JSON.stringify(parsed, null, 2);
    });
});



