import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
export const SITE = '1688';
export const HOME_URL = 'https://www.1688.com/';
export const SEARCH_URL_PREFIX = 'https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=';
export const DETAIL_URL_PREFIX = 'https://detail.1688.com/offer/';
export const STORE_MOBILE_URL_PREFIX = 'https://winport.m.1688.com/page/index.html?memberId=';
export const STRATEGY = 'cookie';
export const SEARCH_LIMIT_DEFAULT = 20;
export const SEARCH_LIMIT_MAX = 100;
const STORE_GENERIC_HOSTS = new Set(['www', 'detail', 's', 'winport', 'work', 'air', 'dj']);
const TRACKING_QUERY_KEYS = new Set([
    'spm',
    'tracelog',
    'clickid',
    'source',
    'scene',
    'from',
    'src',
    'ns',
    'cna',
    'pvid',
]);
const CAPTCHA_URL_MARKER = '/_____tmd_____/punish';
const CAPTCHA_TEXT_PATTERNS = [
    '请拖动下方滑块完成验证',
    '请按住滑块，拖动到最右边',
    '通过验证以确保正常访问',
    '验证码拦截',
    '访问验证',
    '滑动验证',
];
const LOGIN_TEXT_PATTERNS = [
    '请登录',
    '登录后',
    '账号登录',
    '手机登录',
    '立即登录',
    '扫码登录',
    '请先完成登录',
    '请先登录后查看',
];
const LOGIN_URL_PATTERNS = ['/member/login', 'passport', 'login.taobao.com', 'account.1688.com'];
export const FACTORY_BADGE_PATTERNS = [
    '源头工厂',
    '深度验厂',
    '实力工厂',
    '工厂档案',
    '加工专区',
    '验厂报告',
    '厂家直销',
    '生产厂家',
    '工厂直供',
];
export const SERVICE_BADGE_PATTERNS = [
    '延期必赔',
    '品质保障',
    '破损包赔',
    '退货包运费',
    '晚发必赔',
    '7*24小时响应',
    '48小时发货',
    '72小时发货',
    '后天达',
    '包邮',
    '闪电拿样',
];
const CHINA_LOCATIONS = [
    '北京',
    '天津',
    '上海',
    '重庆',
    '河北',
    '山西',
    '辽宁',
    '吉林',
    '黑龙江',
    '江苏',
    '浙江',
    '安徽',
    '福建',
    '江西',
    '山东',
    '河南',
    '湖北',
    '湖南',
    '广东',
    '海南',
    '四川',
    '贵州',
    '云南',
    '陕西',
    '甘肃',
    '青海',
    '台湾',
    '内蒙古',
    '广西',
    '西藏',
    '宁夏',
    '新疆',
    '香港',
    '澳门',
];
export function cleanText(value) {
    return typeof value === 'string'
        ? value.replace(/\u00a0/g, ' ').replace(/\s+/g, ' ').trim()
        : '';
}
export function cleanMultilineText(value) {
    return typeof value === 'string'
        ? value
            .replace(/\u00a0/g, ' ')
            .split('\n')
            .map((line) => line.replace(/\s+/g, ' ').trim())
            .filter(Boolean)
            .join('\n')
        : '';
}
export function uniqueNonEmpty(values) {
    return [...new Set(values.map((value) => cleanText(value)).filter(Boolean))];
}
export function parseSearchLimit(input) {
    const parsed = Number.parseInt(String(input ?? SEARCH_LIMIT_DEFAULT), 10);
    if (!Number.isFinite(parsed) || parsed < 1) {
        throw new ArgumentError('1688 search --limit must be a positive integer', 'Example: opencli 1688 search "桌面置物架" --limit 20');
    }
    return Math.min(SEARCH_LIMIT_MAX, parsed);
}
export function buildSearchUrl(query) {
    const normalized = cleanText(query);
    if (!normalized) {
        throw new ArgumentError('1688 search query cannot be empty', 'Example: opencli 1688 search "桌面置物架" --limit 20');
    }
    return `${SEARCH_URL_PREFIX}${encodeURIComponent(normalized)}`;
}
export function buildDetailUrl(input) {
    const offerId = extractOfferId(input);
    if (!offerId) {
        throw new ArgumentError('1688 item expects an offer URL or offer ID', 'Example: opencli 1688 item 887904326744');
    }
    return `${DETAIL_URL_PREFIX}${offerId}.html`;
}
export function resolveStoreUrl(input) {
    const normalized = cleanText(input);
    if (!normalized) {
        throw new ArgumentError('1688 store expects a store URL or member ID', 'Example: opencli 1688 store https://yinuoweierfushi.1688.com/');
    }
    const memberId = extractMemberId(normalized);
    if (memberId) {
        return `${STORE_MOBILE_URL_PREFIX}${memberId}`;
    }
    if (/^https?:\/\//i.test(normalized)) {
        return canonicalizeStoreUrl(normalized);
    }
    if (normalized.endsWith('.1688.com')) {
        return canonicalizeStoreUrl(`https://${normalized}`);
    }
    if (/^[a-z0-9-]+$/i.test(normalized)) {
        return canonicalizeStoreUrl(`https://${normalized}.1688.com`);
    }
    throw new ArgumentError('1688 store expects a store URL or member ID', 'Example: opencli 1688 store b2b-22154705262941f196');
}
export function canonicalizeStoreUrl(input) {
    const url = parse1688Url(input);
    const memberId = extractMemberId(url.toString());
    if (memberId) {
        return `${STORE_MOBILE_URL_PREFIX}${memberId}`;
    }
    const host = normalizeStoreHost(url.hostname);
    if (!host) {
        throw new ArgumentError('Invalid 1688 store URL', 'Example: opencli 1688 store https://yinuoweierfushi.1688.com/');
    }
    return `https://${host}`;
}
export function canonicalizeItemUrl(input) {
    const offerId = extractOfferId(input);
    if (offerId) {
        return `${DETAIL_URL_PREFIX}${offerId}.html`;
    }
    const url = parse1688UrlOrNull(input);
    if (!url)
        return null;
    stripTrackingParams(url);
    url.hash = '';
    return url.toString();
}
export function canonicalizeSellerUrl(input) {
    const memberId = extractMemberId(input);
    if (memberId) {
        return `${STORE_MOBILE_URL_PREFIX}${memberId}`;
    }
    const url = parse1688UrlOrNull(input);
    if (!url)
        return null;
    const host = normalizeStoreHost(url.hostname);
    if (!host)
        return null;
    return `https://${host}`;
}
export function extractOfferId(input) {
    const normalized = cleanText(input);
    if (!normalized)
        return null;
    const directId = normalized.match(/^\d{6,}$/)?.[0];
    if (directId)
        return directId;
    const detailMatch = normalized.match(/\/offer\/(\d{6,})\.html/i);
    if (detailMatch)
        return detailMatch[1];
    const queryMatch = normalized.match(/[?&]offerId=(\d{6,})/i);
    if (queryMatch)
        return queryMatch[1];
    return null;
}
export function extractMemberId(input) {
    const normalized = cleanText(input);
    if (!normalized)
        return null;
    const direct = normalized.match(/\bb2b-[a-z0-9]+\b/i)?.[0];
    if (direct)
        return direct;
    const queryMatch = normalized.match(/[?&]memberId=(b2b-[a-z0-9]+)/i);
    if (queryMatch)
        return queryMatch[1];
    const mobileMatch = normalized.match(/\/winport\/(b2b-[a-z0-9]+)\.html/i);
    if (mobileMatch)
        return mobileMatch[1];
    return null;
}
export function extractShopId(input) {
    const normalized = cleanText(input);
    if (!normalized)
        return null;
    try {
        const url = new URL(/^https?:\/\//i.test(normalized) ? normalized : `https://${normalized}`);
        const host = normalizeStoreHost(url.hostname);
        if (!host)
            return null;
        return host.split('.')[0] ?? null;
    }
    catch {
        return /^[a-z0-9-]+$/i.test(normalized) ? normalized : null;
    }
}
export function buildProvenance(sourceUrl) {
    return {
        source_url: sourceUrl,
        fetched_at: new Date().toISOString(),
        strategy: STRATEGY,
    };
}
export function parsePriceText(text) {
    const normalized = normalizeNumericText(cleanText(text));
    const matches = normalized.match(/\d+(?:,\d{3})*(?:\.\d+)?/g) ?? [];
    const values = matches
        .map((value) => Number.parseFloat(value.replace(/,/g, '')))
        .filter((value) => Number.isFinite(value));
    if (values.length === 0) {
        return {
            price_text: normalized,
            price_min: null,
            price_max: null,
            currency: null,
        };
    }
    return {
        price_text: normalized,
        price_min: values[0] ?? null,
        price_max: values[values.length - 1] ?? values[0] ?? null,
        currency: normalized.includes('¥') || normalized.includes('元') ? 'CNY' : null,
    };
}
export function normalizePriceTiers(rawTiers, unit) {
    return rawTiers
        .map((tier) => {
        const quantityMin = toNumber(tier.beginAmount);
        const priceText = cleanText(tier.price);
        const price = toNumber(tier.price);
        return {
            quantity_text: quantityMin !== null ? `${quantityMin}${unit ?? ''}` : '',
            quantity_min: quantityMin,
            price_text: priceText,
            price,
            currency: priceText ? 'CNY' : null,
        };
    })
        .filter((tier) => tier.price_text);
}
export function parseMoqText(text) {
    const normalized = normalizeNumericText(cleanText(text));
    const match = normalized.match(/(\d+(?:\.\d+)?)\s*(件|个|套|箱|包|双|台|把|只|pcs|piece|pieces)?\s*起批/i)
        ?? normalized.match(/≥\s*(\d+(?:\.\d+)?)/);
    const rangeMatch = normalized.match(/(\d+(?:\.\d+)?)\s*(?:~|-|至|到)\s*\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只|pcs|piece|pieces)/i);
    if (!match && !rangeMatch) {
        return {
            moq_text: normalized,
            moq_value: null,
        };
    }
    return {
        moq_text: normalized,
        moq_value: Number.parseFloat((match ?? rangeMatch)[1]),
    };
}
export function extractLocation(text) {
    const normalized = cleanMultilineText(text);
    const primaryRegion = normalized.split(/送至|发往/)[0] ?? normalized;
    const lines = primaryRegion.split('\n');
    for (const line of lines) {
        const compact = cleanText(line);
        if (!compact || compact.length > 16)
            continue;
        if (CHINA_LOCATIONS.some((location) => compact.startsWith(location))) {
            return compact;
        }
    }
    const locationPattern = new RegExp(`(${CHINA_LOCATIONS.join('|')})[\\u4e00-\\u9fa5]{0,8}`);
    return primaryRegion.match(locationPattern)?.[0] ?? null;
}
export function extractAddress(text) {
    const normalized = cleanMultilineText(text);
    const lineMatch = normalized.match(/地址[:：]\s*([^\n]+)/);
    if (lineMatch)
        return cleanText(lineMatch[1]);
    return normalized
        .split('\n')
        .map((line) => cleanText(line))
        .find((line) => line.includes('省') || line.includes('市') || line.includes('区') || line.includes('县'))
        ?? null;
}
export function extractMetric(text, label) {
    const normalized = cleanMultilineText(text);
    const direct = normalized.match(new RegExp(`(?:^|\\n)\\s*${escapeForRegex(label)}[:：]?\\s*([^\\n]+)`));
    if (direct)
        return cleanText(direct[1]);
    const lineBased = normalized.match(new RegExp(`(?:^|\\n)\\s*${escapeForRegex(label)}\\n([^\\n]+)`));
    return lineBased ? cleanText(lineBased[1]) : null;
}
export function extractYearsOnPlatform(text) {
    return text.match(/入驻\d+年/)?.[0] ?? null;
}
export function extractMainBusiness(text) {
    const value = extractMetric(text, '主营');
    return value ? value.replace(/^：/, '').trim() : null;
}
export function extractBadges(text, candidates) {
    return uniqueNonEmpty(candidates.filter((candidate) => cleanMultilineText(text).includes(candidate)));
}
export function guessTopCategories(text) {
    const mainBusiness = extractMainBusiness(text);
    if (!mainBusiness)
        return [];
    return uniqueNonEmpty(mainBusiness.split(/[、,/|]/).map((value) => value.trim()));
}
export function isCaptchaState(state) {
    const href = cleanText(state.href).toLowerCase();
    const title = cleanText(state.title);
    const bodyText = cleanMultilineText(state.body_text);
    if (href.includes(CAPTCHA_URL_MARKER))
        return true;
    return CAPTCHA_TEXT_PATTERNS.some((pattern) => title.includes(pattern) || bodyText.includes(pattern));
}
export function isLoginState(state) {
    const href = cleanText(state.href).toLowerCase();
    const title = cleanText(state.title);
    const bodyText = cleanMultilineText(state.body_text);
    if (LOGIN_URL_PATTERNS.some((pattern) => href.includes(pattern)))
        return true;
    return LOGIN_TEXT_PATTERNS.some((pattern) => title.includes(pattern) || bodyText.includes(pattern));
}
export function buildCaptchaHint(action) {
    return [
        `Open a clean 1688 ${action} page in the shared Chrome profile and finish any slider challenge first.`,
        'If you run opencli via CDP, set OPENCLI_CDP_TARGET=1688.com or a more specific 1688 host before retrying.',
    ].join(' ');
}
export async function readPageState(page) {
    const result = await page.evaluate(`
    (() => ({
      href: window.location.href,
      title: document.title || '',
      body_text: document.body ? document.body.innerText || '' : '',
    }))()
  `);
    return {
        href: cleanText(result.href),
        title: cleanText(result.title),
        body_text: cleanMultilineText(result.body_text),
    };
}
export async function gotoAndReadState(page, url, settleMs = 2500, action = 'page') {
    try {
        await page.goto(url, { settleMs });
        await page.wait(1.5);
        return readPageState(page);
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        if (message.includes('Inspected target navigated or closed')
            || message.includes('Cannot find context with specified id')
            || message.includes('Target closed')) {
            throw new CommandExecutionError(`1688 ${action} navigation lost the current browser target`, `${buildCaptchaHint(action)} If CDP is attached to a stale or blocked tab, open a fresh 1688 tab and point OPENCLI_CDP_TARGET at that tab.`);
        }
        throw error;
    }
}
export async function ensure1688Session(page) {
    const state = await gotoAndReadState(page, HOME_URL, 1500, 'homepage');
    assertAuthenticatedState(state, 'homepage');
}
export function assertAuthenticatedState(state, action) {
    if (!isCaptchaState(state) && !isLoginState(state))
        return;
    throw new AuthRequiredError('1688.com', `请先在共享 Chrome 完成 1688 登录/验证，再重试（${action}）`);
}
export function assertNotCaptcha(state, action) {
    assertAuthenticatedState(state, action);
}
export function toNumber(value) {
    if (typeof value === 'number' && Number.isFinite(value)) {
        return value;
    }
    if (typeof value === 'string') {
        const normalized = value.replace(/,/g, '').trim();
        if (!normalized)
            return null;
        const parsed = Number.parseFloat(normalized);
        return Number.isFinite(parsed) ? parsed : null;
    }
    return null;
}
export function limitCandidates(values, limit) {
    const normalizedLimit = Math.max(1, Math.trunc(limit) || 1);
    return values.slice(0, normalizedLimit);
}
export function normalizeMediaUrl(input) {
    const raw = cleanText(input);
    if (!raw)
        return '';
    let value = raw
        .replace(/^url\((.*)\)$/i, '$1')
        .replace(/^['"]|['"]$/g, '')
        .replace(/\\u002F/g, '/')
        .replace(/&amp;/g, '&')
        .trim();
    if (!value || value.startsWith('data:') || value.startsWith('blob:'))
        return '';
    if (value.startsWith('//'))
        value = `https:${value}`;
    try {
        const url = new URL(value);
        return url.toString();
    }
    catch {
        return '';
    }
}
export function uniqueMediaSources(values) {
    const seen = new Set();
    const result = [];
    for (const value of values) {
        const url = normalizeMediaUrl(value.url);
        if (!url)
            continue;
        const key = `${value.type}:${url}`;
        if (seen.has(key))
            continue;
        seen.add(key);
        result.push({
            ...value,
            url,
            source: cleanText(value.source) || undefined,
        });
    }
    return result;
}
function normalizeNumericText(value) {
    return value
        .replace(/([¥$€])\s+(?=\d)/g, '$1')
        .replace(/(\d)\s*\.\s*(\d)/g, '$1.$2')
        .replace(/\s*([~-])\s*/g, '$1')
        .trim();
}
function escapeForRegex(value) {
    return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function parse1688Url(input) {
    const normalized = cleanText(input);
    try {
        const url = new URL(normalized);
        if (!url.hostname.endsWith('.1688.com') && url.hostname !== '1688.com' && url.hostname !== 'www.1688.com') {
            throw new Error('invalid-host');
        }
        stripTrackingParams(url);
        url.hash = '';
        return url;
    }
    catch {
        throw new ArgumentError('Invalid 1688 URL', 'Use a URL under 1688.com (for example: https://detail.1688.com/offer/887904326744.html)');
    }
}
function parse1688UrlOrNull(input) {
    try {
        return parse1688Url(input);
    }
    catch {
        return null;
    }
}
function normalizeStoreHost(hostname) {
    const lower = cleanText(hostname).toLowerCase();
    if (!lower.endsWith('.1688.com'))
        return null;
    const [subdomain] = lower.split('.');
    if (!subdomain || STORE_GENERIC_HOSTS.has(subdomain))
        return null;
    return lower;
}
function stripTrackingParams(url) {
    const keys = [...url.searchParams.keys()];
    for (const key of keys) {
        if (TRACKING_QUERY_KEYS.has(key) || key.toLowerCase().startsWith('utm_')) {
            url.searchParams.delete(key);
        }
    }
}
export const __test__ = {
    SEARCH_LIMIT_DEFAULT,
    SEARCH_LIMIT_MAX,
    parseSearchLimit,
    buildSearchUrl,
    buildDetailUrl,
    resolveStoreUrl,
    canonicalizeStoreUrl,
    canonicalizeItemUrl,
    canonicalizeSellerUrl,
    extractOfferId,
    extractMemberId,
    extractShopId,
    parsePriceText,
    normalizePriceTiers,
    parseMoqText,
    extractLocation,
    extractAddress,
    extractMetric,
    extractYearsOnPlatform,
    extractMainBusiness,
    extractBadges,
    guessTopCategories,
    isCaptchaState,
    isLoginState,
    cleanText,
    cleanMultilineText,
    uniqueNonEmpty,
    normalizeMediaUrl,
    uniqueMediaSources,
    limitCandidates,
};
