;(function (window) {
    const SearchEngine = {};

    function normalizeText(text) {
        if (!text) return "";
        let s = String(text).toLowerCase();
        s = s.replace(/\s+/g, " ").trim();
        return s;
    }

    function normalizePolish(text) {
        if (!text) return "";
        const map = {
            ą: "a",
            ć: "c",
            ę: "e",
            ł: "l",
            ń: "n",
            ó: "o",
            ś: "s",
            ź: "z",
            ż: "z"
        };
        return text.replace(/[ąćęłńóśźż]/g, function (c) {
            return map[c] || c;
        });
    }

    function sanitizeQuery(raw) {
        if (!raw) return "";
        let q = String(raw);
        q = q.replace(/[\r\n\t]+/g, " ");
        q = q.replace(/['"\\<>]+/g, "");
        // Usuń końcowe przecinki, średniki, pipe (COMATSU dodaje ", " na końcu)
        q = q.replace(/[,;|\s]+$/g, "");
        q = q.replace(/\s+/g, " ").trim();
        return q;
    }

    function detectPostal(raw) {
        const m = raw.match(/\b(\d{2})[-\s]?(\d{3})\b/);
        if (!m) return null;
        return {
            original: m[0],
            normalized: m[1] + m[2]
        };
    }

    function stripPostal(raw) {
        return raw.replace(/\b\d{2}[-\s]?\d{3}\b/, " ").replace(/\s+/g, " ").trim();
    }

    function detectLockerId(raw) {
        if (!raw) return null;
        var re = /^[A-Z0-9]{4,10}$/;
        var str = String(raw);
        var compact = str.replace(/\s+/g, "").toUpperCase();
        if (re.test(compact)) {
            return compact;
        }
        var parts = str.split(/\s+/);
        for (var i = 0; i < parts.length; i++) {
            var token = parts[i].toUpperCase();
            if (re.test(token)) {
                return token;
            }
        }
        return null;
    }

    function splitByCityAndStreet(raw) {
        let txt = raw;
        const sepMatch = txt.match(/(.*?)(,|\||;)(.*)/);
        if (sepMatch) {
            return {
                cityPart: sepMatch[1],
                streetPart: sepMatch[3]
            };
        }
        const ulMatch = txt.match(/(.*?)(ul\.|ulica|al\.|aleja)(.*)/i);
        if (ulMatch) {
            return {
                cityPart: ulMatch[1],
                streetPart: ulMatch[3]
            };
        }
        return {
            cityPart: txt,
            streetPart: ""
        };
    }

    function tokensFrom(text) {
        if (!text) return [];
        return normalizePolish(normalizeText(text))
            .split(" ")
            .filter(function (t) {
                return t.length > 0;
            });
    }

    function filterNoiseTokens(tokens) {
        if (!tokens || !tokens.length) return [];
        var noise = {
            ul: true,
            "ul.": true,
            ulica: true,
            al: true,
            "al.": true,
            aleja: true,
            pl: true,
            plac: true
        };
        return tokens.filter(function (t) {
            return !noise[t];
        });
    }

    function parseQuery(raw) {
        const sanitized = sanitizeQuery(raw);
        const normalized = normalizeText(sanitized);
        const ascii = normalizePolish(normalized);

        const postal = detectPostal(normalized);
        const withoutPostal = postal ? stripPostal(normalized) : normalized;

        const lockerId = detectLockerId(normalized);

        const parts = splitByCityAndStreet(withoutPostal);
        const cityTokens = filterNoiseTokens(tokensFrom(parts.cityPart).slice(0, 3));
        const streetTokens = tokensFrom(parts.streetPart);

        let buildingNumber = null;
        if (streetTokens.length > 0) {
            const last = streetTokens[streetTokens.length - 1];
            if (/^\d+[a-zA-Z]?$/.test(last)) {
                buildingNumber = last;
                streetTokens.pop();
            }
        }

        return {
            raw,
            sanitized,
            normalized,
            ascii,
            lockerId,
            postal,
            cityTokens,
            streetTokens,
            buildingNumber
        };
    }

    function pointCountry(point) {
        if (!point) return null;
        if (point.address_details && point.address_details.country_code) {
            return String(point.address_details.country_code).toUpperCase();
        }
        if (point.country) {
            return String(point.country).toUpperCase();
        }
        return null;
    }

    function pointPostal(point) {
        const raw =
            (point.address_details && point.address_details.post_code) ||
            point.post_code ||
            "";
        return raw.replace(/[-\s]/g, "");
    }

    function pointCityTokens(point) {
        const raw =
            (point.address_details && point.address_details.city) ||
            point.city ||
            "";
        return tokensFrom(raw);
    }

    function pointStreetTokens(point) {
        const raw =
            (point.address_details && point.address_details.street) ||
            point.street ||
            "";
        return tokensFrom(raw);
    }

    function tokensMatchAll(queryTokens, targetTokens) {
        if (queryTokens.length === 0) return false;
        if (targetTokens.length === 0) return false;
        return queryTokens.every(function (qt) {
            return targetTokens.some(function (tt) {
                return tt.indexOf(qt) !== -1;
            });
        });
    }

    function tokensMatchAny(queryTokens, targetTokens) {
        if (queryTokens.length === 0) return false;
        if (targetTokens.length === 0) return false;
        return queryTokens.some(function (qt) {
            return targetTokens.some(function (tt) {
                return tt.indexOf(qt) !== -1;
            });
        });
    }

    function filterByCountry(points, countryCode) {
        if (!countryCode) return points.slice();
        const cc = String(countryCode).toUpperCase();
        return points.filter(function (p) {
            const pc = pointCountry(p);
            return !pc || pc === cc;
        });
    }

    function evaluateIntent(parsed, points, countryCode) {
        const inCountry = filterByCountry(points, countryCode);

        let lockerPoint = null;
        if (parsed.lockerId) {
            const lid = parsed.lockerId;
            lockerPoint =
                inCountry.find(function (p) {
                    return (
                        p.name &&
                        String(p.name).toUpperCase().replace(/\s+/g, "") === lid
                    );
                }) || null;
        }

        let postalPoints = [];
        if (parsed.postal) {
            const pnorm = parsed.postal.normalized;
            postalPoints = inCountry.filter(function (p) {
                return pointPostal(p) === pnorm;
            });
        }

        let cityPoints = [];
        if (parsed.cityTokens.length > 0) {
            cityPoints = inCountry.filter(function (p) {
                const ct = pointCityTokens(p);
                return tokensMatchAll(parsed.cityTokens, ct);
            });
        }

        let streetPoints = [];
        if (parsed.streetTokens.length > 0 && cityPoints.length > 0) {
            streetPoints = cityPoints.filter(function (p) {
                const st = pointStreetTokens(p);
                return tokensMatchAll(parsed.streetTokens, st);
            });
        }

        const hasLocker = !!lockerPoint;
        const hasPostal = postalPoints.length > 0;
        const hasCity = cityPoints.length > 0;
        const hasStreet = streetPoints.length > 0;

        let type = "FALLBACK";
        if (hasLocker) {
            type = "LOCKER_CODE_EXACT";
        } else if (hasPostal && hasCity) {
            type = "CITY_POSTAL";
        } else if (hasPostal) {
            type = "POSTAL_ONLY";
        } else if (hasCity && hasStreet) {
            type = "CITY_STREET";
        } else if (hasCity) {
            type = "CITY_ONLY";
        } else if (parsed.streetTokens.length > 0) {
            type = "STREET_ONLY";
        }

        let cityMatchesPostal = true;
        if (parsed.postal && hasPostal && hasCity) {
            const postalCities = postalPoints
                .map(function (p) {
                    return pointCityTokens(p).join(" ");
                })
                .filter(function (x) {
                    return x.length > 0;
                });
            const citySet = {};
            postalCities.forEach(function (c) {
                citySet[c] = true;
            });
            const uniqueCities = Object.keys(citySet);
            const parsedCity = parsed.cityTokens.join(" ");
            const parsedCityAscii = normalizePolish(parsedCity);
            const ok = uniqueCities.some(function (c) {
                const ca = normalizePolish(c);
                return ca.indexOf(parsedCityAscii) !== -1;
            });
            cityMatchesPostal = ok;
        }

        let streetExistsInCityOrPostal = true;
        if (parsed.streetTokens.length > 0) {
            const base = hasCity ? cityPoints : postalPoints;
            if (base.length > 0) {
                const matchAny = base.some(function (p) {
                    const st = pointStreetTokens(p);
                    return tokensMatchAll(parsed.streetTokens, st);
                });
                streetExistsInCityOrPostal = matchAny;
            }
        }

        return {
            inCountry,
            lockerPoint,
            postalPoints,
            cityPoints,
            streetPoints,
            type,
            consistency: {
                cityMatchesPostal,
                streetExistsInCityOrPostal
            }
        };
    }

    function sortByDistanceToLocker(points, lockerPoint) {
        if (!lockerPoint || !lockerPoint.location) return points.slice();
        const lat0 = lockerPoint.location.latitude;
        const lon0 = lockerPoint.location.longitude;
        return points
            .map(function (p) {
                if (!p.location) {
                    return { p, d: Number.MAX_VALUE };
                }
                const lat = p.location.latitude;
                const lon = p.location.longitude;
                const dx = lat - lat0;
                const dy = lon - lon0;
                const d = dx * dx + dy * dy;
                return { p, d };
            })
            .sort(function (a, b) {
                return a.d - b.d;
            })
            .map(function (x) {
                return x.p;
            });
    }

    function sortByRelevance(points, parsed) {
        const cityTokens = parsed.cityTokens;
        const streetTokens = parsed.streetTokens;
        const postalNorm = parsed.postal ? parsed.postal.normalized : null;
        const queryTokens = tokensFrom(parsed.normalized);

        function scorePoint(p) {
            let s = 0;
            const name = normalizePolish(normalizeText(p.name || ""));
            const cityT = pointCityTokens(p);
            const streetT = pointStreetTokens(p);
            const postal = pointPostal(p);

            if (postalNorm && postal === postalNorm) s += 400;
            if (cityTokens.length > 0 && tokensMatchAll(cityTokens, cityT)) s += 300;
            if (streetTokens.length > 0 && tokensMatchAll(streetTokens, streetT)) s += 200;
            if (streetTokens.length > 0 && tokensMatchAny(streetTokens, streetT)) s += 50;
            if (parsed.lockerId && name.indexOf(parsed.lockerId.toLowerCase()) !== -1) s += 500;
            if (tokensMatchAny(queryTokens, cityT)) s += 250;
            return s;
        }

        return points
            .map(function (p) {
                return { p, s: scorePoint(p) };
            })
            .sort(function (a, b) {
                return b.s - a.s;
            })
            .map(function (x) {
                return x.p;
            });
    }

    function pointHasBuildingNumber(point, buildingNumber) {
        if (!buildingNumber) return false;
        const bn = String(buildingNumber).toLowerCase();
        const ad = point.address_details || {};
        const fields = [
            ad.building_number,
            ad.building,
            ad.line1,
            ad.line2,
            ad.street,
            point.street
        ];
        for (var i = 0; i < fields.length; i++) {
            var f = fields[i];
            if (f && String(f).toLowerCase().indexOf(bn) !== -1) {
                return true;
            }
        }
        return false;
    }

    function search(points, rawQuery, options) {
        const opts = options || {};
        const countryCode = opts.countryCode || null;
        const sanitized = sanitizeQuery(rawQuery);
        if (!sanitized) {
            return {
                results: [],
                meta: {
                    type: "EMPTY",
                    intent: null,
                    conflict: false
                }
            };
        }

        const parsed = parseQuery(sanitized);

        const compact = parsed.normalized.replace(/\s+/g, "");
        if (
            compact &&
            /^\d+$/.test(compact) &&
            (!parsed.postal || !parsed.postal.normalized) &&
            compact.length < 5 &&
            parsed.cityTokens.length === 0 &&
            parsed.streetTokens.length === 0 &&
            !parsed.lockerId
        ) {
            return {
                results: [],
                meta: {
                    type: "NUMERIC_TOO_SHORT",
                    intent: null,
                    conflict: false
                }
            };
        }
        const intent = evaluateIntent(parsed, points, countryCode);

        let results = [];
        let searchType = intent.type;

        if (intent.type === "LOCKER_CODE_EXACT" && intent.lockerPoint) {
            results = [intent.lockerPoint];
        } else if (intent.type === "CITY_POSTAL") {
            // Najpierw: intersection (kod pocztowy + miasto)
            var intersection = intent.postalPoints.filter(function (p) {
                return intent.cityPoints.indexOf(p) !== -1;
            });
            if (intersection.length > 0) {
                results = sortByRelevance(intersection, parsed);
            } else {
                // Fallback: sam kod pocztowy (ważniejszy niż miasto!)
                // Bo "Warszawa, 02-495" → paczkomat w 02-495 może mieć dzielnicę zamiast "Warszawa"
                results = sortByRelevance(intent.postalPoints, parsed);
            }
        } else if (intent.type === "POSTAL_ONLY") {
            results = sortByRelevance(intent.postalPoints, parsed);
        } else if (intent.type === "CITY_STREET") {
            let base = intent.streetPoints.length > 0 ? intent.streetPoints : intent.cityPoints;
            if (parsed.buildingNumber && base.length > 0) {
                const narrowed = base.filter(function (p) {
                    return pointHasBuildingNumber(p, parsed.buildingNumber);
                });
                if (narrowed.length > 0) {
                    base = narrowed;
                }
            }
            results = sortByRelevance(base, parsed);
        } else if (intent.type === "CITY_ONLY") {
            results = sortByRelevance(intent.cityPoints, parsed);
        } else if (intent.type === "STREET_ONLY") {
            const inCountry = filterByCountry(points, countryCode);
            const matches = inCountry.filter(function (p) {
                const st = pointStreetTokens(p);
                return tokensMatchAll(parsed.streetTokens, st);
            });
            results = sortByRelevance(matches, parsed);
        } else {
            const inCountry = filterByCountry(points, countryCode);
            const qTokens = tokensFrom(parsed.normalized);
            const cityQueryTokens = qTokens.filter(function (t) {
                if (!t) return false;
                if (/^\d/.test(t)) return false;
                if (t === "ul" || t === "ul." || t === "ulica") return false;
                if (t === "al" || t === "al." || t === "aleja") return false;
                if (t === "pl" || t === "plac") return false;
                return t.length >= 3;
            });
            const fallback = inCountry.filter(function (p) {
                const name = tokensFrom(p.name || "");
                const ct = pointCityTokens(p);
                const st = pointStreetTokens(p);
                const cityMatch = cityQueryTokens.length
                    ? tokensMatchAny(cityQueryTokens, ct)
                    : true;
                if (!cityMatch) return false;
                return (
                    tokensMatchAny(qTokens, name) ||
                    tokensMatchAny(qTokens, ct) ||
                    tokensMatchAny(qTokens, st)
                );
            });
            results = sortByRelevance(fallback, parsed);
            searchType = "FALLBACK";
        }

        if (!results.length) {
            const inCountry = filterByCountry(points, countryCode);
            const qTokens = tokensFrom(parsed.normalized);
            const cityQueryTokens = qTokens.filter(function (t) {
                if (!t) return false;
                if (/^\d/.test(t)) return false;
                if (t === "ul" || t === "ul." || t === "ulica") return false;
                if (t === "al" || t === "al." || t === "aleja") return false;
                if (t === "pl" || t === "plac") return false;
                return t.length >= 3;
            });
            const fallback = inCountry.filter(function (p) {
                const name = tokensFrom(p.name || "");
                const ct = pointCityTokens(p);
                const st = pointStreetTokens(p);
                const cityMatch = cityQueryTokens.length
                    ? tokensMatchAny(cityQueryTokens, ct)
                    : true;
                if (!cityMatch) return false;
                return (
                    tokensMatchAny(qTokens, name) ||
                    tokensMatchAny(qTokens, ct) ||
                    tokensMatchAny(qTokens, st)
                );
            });
            results = sortByRelevance(fallback, parsed);
            searchType = "FALLBACK";
        }

        const maxResults = typeof opts.limit === "number" ? opts.limit : 500;
        if (results.length > maxResults) {
            results = results.slice(0, maxResults);
        }

        const conflict =
            intent.consistency &&
            (!intent.consistency.cityMatchesPostal ||
                !intent.consistency.streetExistsInCityOrPostal);

        return {
            results,
            meta: {
                type: searchType,
                intent,
                conflict
            }
        };
    }

    SearchEngine.search = search;
    SearchEngine.parseQuery = parseQuery;
    SearchEngine.evaluateIntent = evaluateIntent;
    SearchEngine.VERSION = 2;

    window.InpostSearchEngine = SearchEngine;
})(window);
