const PROCUREMENT_HINTS = [
    '招标',
    '采购',
    '公告',
    '项目',
    '中标',
    '成交',
    '流标',
    '终止',
    '询价',
    '竞价',
    '比选',
    '投标',
    'tender',
    'procurement',
    'bidding',
    'bid',
    'notice',
];
const RESULT_HINTS = [
    '中标',
    '成交',
    '结果',
    '候选人',
    '中选',
    '定标',
    '评标',
    'award',
    'winner',
];
const NOTICE_HINTS = [
    '招标',
    '采购',
    '询价',
    '比选',
    '公告',
    '竞争性',
    '邀请',
    '投标',
    'tender',
    'procurement',
    'notice',
];
const NEWS_HINTS = [
    '新闻',
    '资讯',
    '动态',
    '政策',
    '简讯',
    'news',
    'article',
];
const NAVIGATION_HINTS = [
    '首页',
    '官网',
    '网站地图',
    '联系我们',
    '帮助中心',
    'english',
    'login',
    '注册',
    '导航',
    '法规',
    '政策文件',
    '服务平台',
    '信用中国',
];
const DETAIL_URL_HINTS = [
    '/detail',
    '/content',
    '/jybx/',
    '/notice',
    '/article',
    '/view',
    '/project',
    '/bid',
    'detail=',
    'id=',
];
const LIST_URL_HINTS = [
    '/search',
    '/list',
    '/index',
    '/home',
    '/portal',
    '/channel',
    'page=',
];
const OWNER_PATTERNS = [
    /(?:招标人|采购人|业主|建设单位|项目单位)\s*[：:]\s*([^\n，。；]{2,60})/i,
];
const CODE_PATTERNS = [
    /(?:项目编号|招标编号|采购编号|项目编码|项目代码|编号)\s*[：:]\s*([A-Za-z0-9\-_/]{4,60})/i,
];
const BUDGET_PATTERNS = [
    /(?:预算(?:金额)?|控制价|最高限价|限价|采购金额|合同估算价)\s*[：:]\s*([^\n，。；]{2,80})/i,
];
const DEADLINE_PATTERNS = [
    /(?:报名截止时间|投标截止时间|开标时间|响应文件递交截止时间|截止时间|开标日期)\s*[：:]\s*([^\n，。；]{2,80})/i,
];
const DATE_PATTERN = /(20\d{2})[.\-/年](\d{1,2})[.\-/月](\d{1,2})/;
export function cleanText(value) {
    return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
}
export function normalizeDate(raw) {
    const normalized = cleanText(raw);
    const match = normalized.match(DATE_PATTERN);
    if (!match)
        return '';
    const year = match[1];
    const month = match[2].padStart(2, '0');
    const day = match[3].padStart(2, '0');
    return `${year}-${month}-${day}`;
}
function uniqueInOrder(values) {
    const ordered = [];
    const seen = new Set();
    for (const value of values) {
        const text = cleanText(value);
        if (!text || seen.has(text))
            continue;
        seen.add(text);
        ordered.push(text);
    }
    return ordered;
}
function containsAny(haystack, needles) {
    return needles.some((needle) => haystack.includes(needle.toLowerCase()));
}
function extractByPatterns(text, patterns) {
    for (const pattern of patterns) {
        const matched = text.match(pattern);
        if (matched?.[1])
            return cleanText(matched[1]);
    }
    return '';
}
function deriveSnippet(text) {
    const normalized = cleanText(text);
    if (!normalized)
        return '';
    return normalized.slice(0, 220);
}
function splitEvidenceBlocks(text, query) {
    const normalized = cleanText(text);
    if (!normalized)
        return [];
    const queryTokens = query
        .split(/\s+/)
        .map((item) => item.toLowerCase().trim())
        .filter(Boolean);
    const chunks = normalized
        .split(/[。！？；\n]/)
        .map((chunk) => cleanText(chunk))
        .filter(Boolean);
    const ranked = chunks
        .map((chunk) => {
        const lower = chunk.toLowerCase();
        const tokenScore = queryTokens.length === 0
            ? 0
            : queryTokens.reduce((score, token) => (lower.includes(token) ? score + 2 : score), 0);
        const procurementScore = containsAny(lower, PROCUREMENT_HINTS) ? 1 : 0;
        return {
            chunk,
            score: tokenScore + procurementScore,
        };
    })
        .sort((a, b) => b.score - a.score || b.chunk.length - a.chunk.length)
        .slice(0, 5)
        .map((item) => item.chunk);
    return uniqueInOrder(ranked);
}
function classifyContentType(title, url, contextText) {
    const haystack = `${title} ${contextText} ${url}`.toLowerCase();
    if (containsAny(haystack, RESULT_HINTS))
        return 'result';
    if (containsAny(haystack, NOTICE_HINTS))
        return 'notice';
    if (containsAny(haystack, NEWS_HINTS))
        return 'news';
    if (containsAny(haystack, NAVIGATION_HINTS))
        return 'navigation';
    return 'unknown';
}
function isDetailPage(url) {
    const lower = cleanText(url).toLowerCase();
    if (!lower)
        return false;
    const hasDetailToken = DETAIL_URL_HINTS.some((hint) => lower.includes(hint));
    if (!hasDetailToken)
        return false;
    const hasListToken = LIST_URL_HINTS.some((hint) => lower.includes(hint));
    return !hasListToken;
}
function buildQualityFlags(core) {
    const flags = [];
    if (!core.project_owner)
        flags.push('missing_project_owner');
    if (!core.project_code)
        flags.push('missing_project_code');
    if (!core.budget_or_limit)
        flags.push('missing_budget');
    if (!core.deadline_or_open_time)
        flags.push('missing_deadline');
    if (core.content_type === 'navigation')
        flags.push('navigation_risk');
    if (!core.is_detail_page)
        flags.push('list_page_url');
    return flags;
}
function queryMatched(text, query) {
    const tokenParts = query
        .split(/\s+/)
        .map((part) => part.toLowerCase().trim())
        .filter(Boolean);
    if (tokenParts.length === 0)
        return true;
    const lower = text.toLowerCase();
    return tokenParts.some((part) => lower.includes(part));
}
function normalizeCoreRecord(row, { sourceSite, }) {
    const title = cleanText(row.title);
    const url = cleanText(row.url);
    const contextText = cleanText(row.contextText);
    const date = normalizeDate(cleanText(row.date || contextText));
    const publishTime = date;
    const contentType = classifyContentType(title, url, contextText);
    const projectOwner = extractByPatterns(contextText, OWNER_PATTERNS);
    const projectCode = extractByPatterns(contextText, CODE_PATTERNS);
    const budget = extractByPatterns(contextText, BUDGET_PATTERNS);
    const deadline = extractByPatterns(contextText, DEADLINE_PATTERNS);
    const snippet = deriveSnippet(contextText || title);
    const core = {
        title,
        url,
        date,
        publish_time: publishTime,
        source_site: sourceSite,
        is_detail_page: isDetailPage(url),
        content_type: contentType,
        project_owner: projectOwner,
        project_code: projectCode,
        budget_or_limit: budget,
        deadline_or_open_time: deadline,
        snippet,
        summary: snippet,
        quality_flags: [],
    };
    core.quality_flags = buildQualityFlags(core);
    return core;
}
function qualityRejectReason(core, query) {
    if (!core.title || !core.url)
        return 'missing_identity';
    if (core.content_type === 'navigation')
        return 'navigation_only';
    const searchable = `${core.title} ${core.snippet} ${core.url}`.toLowerCase();
    const hasQuery = queryMatched(searchable, query);
    if (!hasQuery)
        return 'query_mismatch';
    return null;
}
function dedupeByTitleUrl(items) {
    const deduped = [];
    const seen = new Set();
    for (const item of items) {
        const key = `${item.title}\t${item.url}`;
        if (seen.has(key))
            continue;
        seen.add(key);
        deduped.push(item);
    }
    return deduped;
}
export function formatTaxonomyError(taxonomy, { site, command, detail, }) {
    return `[taxonomy=${taxonomy}] site=${site} command=${command} ${cleanText(detail)}`;
}
export function taxonomyError(taxonomy, context) {
    return new Error(formatTaxonomyError(taxonomy, context));
}
export function toProcurementSearchRecords(rows, { site, query, limit, }) {
    const normalizedRows = dedupeByTitleUrl(rows.map((row) => normalizeCoreRecord(row, { sourceSite: site })));
    const accepted = [];
    for (const row of normalizedRows) {
        const rejectReason = qualityRejectReason(row, query);
        if (rejectReason)
            continue;
        accepted.push(row);
    }
    if (normalizedRows.length > 0 && accepted.length === 0) {
        throw taxonomyError('extraction_drift', {
            site,
            command: 'search',
            detail: `all rows rejected by quality gate (raw=${normalizedRows.length})`,
        });
    }
    return accepted
        .slice(0, Math.max(1, limit))
        .map((row, index) => ({
        rank: index + 1,
        ...row,
    }));
}
export function toProcurementDetailRecord({ title, url, contextText, publishTime, }, { site, query = '', }) {
    const core = normalizeCoreRecord({
        title,
        url,
        date: publishTime,
        contextText,
    }, { sourceSite: site });
    const detailText = cleanText(contextText).slice(0, 6000);
    const evidenceBlocks = splitEvidenceBlocks(detailText, query);
    return {
        ...core,
        detail_text: detailText,
        evidence_blocks: evidenceBlocks,
    };
}
export const __test__ = {
    classifyContentType,
    isDetailPage,
    splitEvidenceBlocks,
    qualityRejectReason,
};
