/**
 * Project-intent detection from a chat message.
 *
 * Goal: when the user says "나 ConnectAI 프로젝트 진행할 거야" (or similar),
 * spot the intent + project handle so the sidebar can activate Project Mode
 * and auto-attach the architecture doc.
 *
 * Design philosophy:
 *   - Heuristic only. No LLM call. This is a routing decision, not a chat
 *     reply — false positives are cheap to correct (a chip the user can detach)
 *     but a 200 ms latency on every message would be unacceptable.
 *   - Multi-modal: pick up either an absolute project path OR a project NAME
 *     that matches an already-registered project. We avoid inventing brand
 *     new projects from arbitrary noun extraction — that produces too much
 *     noise.
 *   - Bilingual: Korean phrasing is the primary surface, English second.
 */

/** A registered project the detector can match a name against. */
export interface KnownProject {
    projectId: string;
    projectName: string;
    /** Optional aliases (lowercased) the user might say instead of the name. */
    aliases?: string[];
    projectRoot?: string;
}

export interface DetectionResult {
    /** Project entry the message refers to. */
    project: KnownProject;
    /** How we matched it — surfaced in logs so we can tune the regexes. */
    via: 'path' | 'name' | 'alias';
    /** The text fragment that triggered the match (for debugging). */
    matchedText: string;
}

// Korean activation verbs that strongly imply "start / continue working on X".
// We keep this small and high-precision rather than trying to enumerate every
// phrasing — a missed match just means the user has to click the activate chip.
const KO_INTENT_PATTERNS: RegExp[] = [
    /(?:나|이제|오늘은|이번엔)?\s*([\p{L}\p{N}_\-\.]+)\s*(?:프로젝트)?\s*(?:진행|작업|시작|할\s*거야|볼\s*거야|볼게|하자|시작하자)/u,
    /([\p{L}\p{N}_\-\.]+)\s*(?:프로젝트)\s*(?:열어|열자|확인)/u,
];

// English equivalents — same precision-first stance.
const EN_INTENT_PATTERNS: RegExp[] = [
    /\b(?:i'?m\s+working\s+on|let'?s\s+work\s+on|switching\s+to|i\s+want\s+to\s+work\s+on)\s+(?:the\s+)?([\w\-\.]+)\s*(?:project)?/i,
    /\bopen\s+(?:the\s+)?([\w\-\.]+)\s+project\b/i,
];

const STOPWORDS = new Set([
    // High-frequency Korean particles/verbs that show up where the regex
    // greedily captures a "noun" but shouldn't be treated as a project handle.
    '나', '이제', '오늘은', '이번엔', '나도', '나는',
    // English filler.
    'the', 'this', 'that', 'my', 'your', 'a',
]);

/**
 * Try to detect a project handle in `text` and resolve it against the list of
 * `known` projects. Returns `null` when no high-confidence match is found.
 */
export function detectProjectIntent(text: string, known: KnownProject[]): DetectionResult | null {
    const trimmed = (text || '').trim();
    if (!trimmed) return null;

    // 1) Direct absolute path (highest confidence). We also accept ~-prefixed
    //    paths because the chat history is full of them. The path doesn't have
    //    to match an existing project — sidebarProvider handles ephemeral
    //    project creation when needed.
    const pathMatch = _matchPath(trimmed);
    if (pathMatch) {
        const exact = known.find((k) => k.projectRoot && _samePath(k.projectRoot, pathMatch));
        if (exact) return { project: exact, via: 'path', matchedText: pathMatch };
        // Synthesise an ephemeral entry — caller decides whether to materialise it.
        return {
            project: {
                projectId: _slugify(pathMatch.split(/[\\/]/).filter(Boolean).pop() || pathMatch),
                projectName: pathMatch.split(/[\\/]/).filter(Boolean).pop() || pathMatch,
                projectRoot: pathMatch,
            },
            via: 'path',
            matchedText: pathMatch,
        };
    }

    // 2) Phrase-based extraction → match against known project names/aliases.
    //    We require an intent pattern AND a known-name match: this rules out
    //    "나는 글을 쓸 거야" → it has the verb but no project handle.
    const candidates: string[] = [];
    for (const re of KO_INTENT_PATTERNS) {
        const m = re.exec(trimmed);
        if (m && m[1] && !STOPWORDS.has(m[1].toLowerCase())) candidates.push(m[1]);
    }
    for (const re of EN_INTENT_PATTERNS) {
        const m = re.exec(trimmed);
        if (m && m[1] && !STOPWORDS.has(m[1].toLowerCase())) candidates.push(m[1]);
    }
    for (const candidate of candidates) {
        const hit = _findKnown(known, candidate);
        if (hit) return hit;
    }
    return null;
}

function _matchPath(text: string): string | null {
    // Absolute POSIX path (including macOS volumes) or Windows drive path.
    // We're permissive on what characters can appear — anything quoted or
    // surrounded by whitespace counts.
    const macVol = /\/Volumes\/[^\s`'"<>]+/;
    const posix = /(?:^|\s)(\/[^\s`'"<>]+)/;
    const win = /[A-Za-z]:[\\/][^\s`'"<>]+/;
    return (text.match(macVol) || [])[0]
        || (text.match(win) || [])[0]
        || ((): string | null => {
            const m = posix.exec(text);
            return m ? m[1] : null;
        })();
}

function _samePath(a: string, b: string): boolean {
    return a.replace(/[\\/]+$/, '').toLowerCase() === b.replace(/[\\/]+$/, '').toLowerCase();
}

function _findKnown(known: KnownProject[], handle: string): DetectionResult | null {
    const needle = _slugify(handle);
    if (!needle) return null;
    for (const k of known) {
        if (_slugify(k.projectName) === needle) {
            return { project: k, via: 'name', matchedText: handle };
        }
        for (const alias of k.aliases ?? []) {
            if (_slugify(alias) === needle) {
                return { project: k, via: 'alias', matchedText: handle };
            }
        }
    }
    return null;
}

/** Same slug logic the chronicle module uses — lowercase, non-word→hyphen. */
function _slugify(s: string): string {
    return (s || '')
        .toLowerCase()
        .replace(/[^a-z0-9가-힣]+/g, '-')
        .replace(/^-+|-+$/g, '')
        .slice(0, 60);
}
