/**
 * V7 §2.4.3 + §2.4.6 — classify a raw user feedback string into one of
 * eight shapes. The output drives whether the repair orchestrator needs
 * to run and what fields it can extract.
 *
 * We keep this deterministic + rule-based first. The feedback pipeline
 * must run in degraded mode (no LLM, no network) and tests must stay
 * trivially stable.
 *
 * Priority order (first hit wins):
 *
 *   1. `preference`  — explicit "use X instead of Y" / 用 X 代替 Y / ...
 *   2. `correction`  — "it should be X, not Y" / "应该是 X 不是 Y"
 *   3. `constraint`  — "also make sure N" / "还要 …" / "must keep …"
 *   4. `negative`    — blanket rejection ("wrong", "不对", "no")
 *   5. `positive`    — clear approval
 *   6. `confusion`   — user didn't understand ("what do you mean?" / "???")
 *   7. `instruction` — imperative next step
 *   8. `unknown`     — no signal
 */

import type { ClassifiedFeedback, UserFeedbackShape } from "./types.js";

export interface ClassifierOptions {
  /** Language hints are advisory; the classifier handles mixed text. */
  locales?: readonly ("en" | "zh")[];
}

export function classifyFeedback(
  raw: string,
  opts: ClassifierOptions = {},
): ClassifiedFeedback {
  const text = (raw ?? "").trim();
  if (!text) {
    return {
      shape: "unknown",
      confidence: 0,
      text: "",
    };
  }

  const normalized = text.toLowerCase();

  const preference = detectPreference(text, normalized);
  if (preference) return { text, ...preference };

  const correction = detectCorrection(text, normalized);
  if (correction) return { text, ...correction };

  const constraint = detectConstraint(text, normalized);
  if (constraint) return { text, ...constraint };

  if (matchesAny(normalized, NEGATIVE_PATTERNS)) {
    return {
      shape: "negative",
      confidence: 0.75,
      text,
    };
  }

  if (matchesAny(normalized, POSITIVE_PATTERNS)) {
    return {
      shape: "positive",
      confidence: 0.75,
      text,
    };
  }

  if (matchesAny(normalized, CONFUSION_PATTERNS)) {
    return {
      shape: "confusion",
      confidence: 0.7,
      text,
    };
  }

  if (looksLikeInstruction(text, normalized)) {
    return {
      shape: "instruction",
      confidence: 0.55,
      text,
    };
  }

  return { shape: "unknown", confidence: 0.3, text };
}

// ─── Preference extraction ────────────────────────────────────────────────

function detectPreference(
  raw: string,
  normalized: string,
): Omit<ClassifiedFeedback, "text"> | null {
  for (const pattern of PREFERENCE_PATTERNS) {
    const match = raw.match(pattern.regex);
    if (!match) continue;
    const prefer = pattern.prefer ? clean(match[pattern.prefer]) : undefined;
    const avoid = pattern.avoid ? clean(match[pattern.avoid]) : undefined;
    if (!prefer && !avoid) continue;
    return {
      shape: "preference",
      confidence: 0.8,
      prefer,
      avoid,
    };
  }
  // No capture groups, but still a soft preference signal
  if (/(prefer|instead|should use|下次用|改用|而不是)/.test(normalized)) {
    return { shape: "preference", confidence: 0.55 };
  }
  return null;
}

function clean(s: string | undefined): string | undefined {
  if (!s) return undefined;
  return s.trim().replace(/^["'`]|["'`]$/g, "").trim() || undefined;
}

// ─── Patterns ─────────────────────────────────────────────────────────────

const PREFERENCE_PATTERNS: readonly {
  regex: RegExp;
  prefer?: number;
  avoid?: number;
}[] = [
  // "use X instead of Y"
  {
    regex: /use\s+(?<prefer>.+?)\s+instead\s+of\s+(?<avoid>.+?)([.。!?\n]|$)/i,
    prefer: 1,
    avoid: 2,
  },
  // "prefer X over Y"
  {
    regex: /prefer\s+(?<prefer>.+?)\s+over\s+(?<avoid>.+?)([.。!?\n]|$)/i,
    prefer: 1,
    avoid: 2,
  },
  // "X instead of Y" (no leading verb)
  {
    regex: /([^,.!?\n]+)\s+instead\s+of\s+([^,.!?\n]+)/i,
    prefer: 1,
    avoid: 2,
  },
  // Chinese: "用 X 代替 Y" / "用 X 而不是 Y"
  {
    regex: /用\s*(.+?)\s*(代替|而不是)\s*(.+?)([。!?\n]|$)/,
    prefer: 1,
    avoid: 3,
  },
  // Chinese: "别/不要 Y，用 X"
  {
    regex: /(别|不要|不能)\s*(.+?)[，,]\s*(要)?\s*(用|改用)\s*(.+?)([。!?\n]|$)/,
    prefer: 5,
    avoid: 2,
  },
  // "next time: do X"
  {
    regex: /next time\s*[:：]?\s*(.+)/i,
    prefer: 1,
  },
];

const NEGATIVE_PATTERNS: readonly RegExp[] = [
  /\bwrong\b/,
  /\bnot\s+(right|correct|what|that)\b/,
  /\bdon't\s+do\b/,
  /\bdo\s+not\s+do\b/,
  /\bstop\s+that\b/,
  /\bno[,.!? ]/,
  /^(no|nope|nah)$/,
  /不对/,
  /错(了)?/,
  /不要这样/,
  /别这样/,
];

const POSITIVE_PATTERNS: readonly RegExp[] = [
  /\b(great|perfect|awesome|nice work|well done|works|fixed)\b/,
  /\bthanks?\b/,
  /^(yes|ok|okay|sure)[.!?]?$/,
  /好的|太棒了|不错|完美|搞定/,
];

/**
 * V7 §2.4.3 — user expresses confusion or asks why the agent did
 * something. Drives a UI "explain-further" path, not decision repair.
 */
const CONFUSION_PATTERNS: readonly RegExp[] = [
  /\bwhat\s+(do\s+you\s+mean|are\s+you\s+(doing|saying))\b/,
  /\bwhy\s+(did|are)\s+you\b/,
  /\bi\s+don'?t\s+(understand|get|follow)\b/,
  /\bnot\s+sure\s+what\b/,
  /\bconfus(ed|ing)\b/,
  /什么意思/,
  /没(看|搞)懂/,
  /为什么(这样|要)/,
  /\?{2,}\s*$/,
];

/**
 * V7 §2.4.3 — user corrects a specific part of the answer ("it should be X,
 * not Y"). Captured separately from `preference` because corrections
 * target the PREVIOUS answer whereas preferences target FUTURE behavior.
 */
const CORRECTION_PATTERNS: readonly {
  regex: RegExp;
  /** 1-based index of the "should be" clause in the match. */
  should: number;
}[] = [
  // "it should be X" / "should be X" / "actually X"
  { regex: /\b(?:it\s+should\s+be|should\s+be|it\s*'?s?\s+actually)\s+(?<should>.{3,120})/i, should: 1 },
  // "not X, (it's) Y" — take Y as correction
  { regex: /\bnot\s+.{2,80}[,，]\s*(?:it'?s\s+|its\s+|actually\s+)?(?<should>.{3,120})/i, should: 1 },
  // "the answer is X" right after negation
  { regex: /\b(?:answer|result|value|output)\s+(?:is|=)\s+(?<should>.{2,120})/i, should: 1 },
  // Chinese: "应该是 X"
  { regex: /应该是\s*(?<should>.{2,80})/, should: 1 },
  // Chinese: "不是 X 是 Y" → take Y as correction
  { regex: /不是\s*.{1,40}\s*[，,]?\s*是\s*(?<should>.{2,80})/, should: 1 },
];

/**
 * V7 §2.4.3 — user keeps the direction but tightens scope ("also add N",
 * "keep X but also Y", "but it must …"). Constraints flow into L2/L3
 * scope refinement, so we need to capture them separately from
 * preferences (which are about *which tool* to pick).
 */
const CONSTRAINT_PATTERNS: readonly {
  regex: RegExp;
  /** 1-based index of the constraint text. */
  constraint: number;
}[] = [
  { regex: /\b(?:also|additionally|on top of that)\s+(?<c>.{3,120})/i, constraint: 1 },
  { regex: /\b(?:must|has to|needs to)\s+(?<c>.{3,120})/i, constraint: 1 },
  { regex: /\bmake sure (?:to |that )?(?<c>.{3,120})/i, constraint: 1 },
  { regex: /\bbut\s+(?:make sure|don'?t forget|keep)\s+(?<c>.{3,120})/i, constraint: 1 },
  // Chinese
  { regex: /还要\s*(?<c>.{2,80})/, constraint: 1 },
  { regex: /别忘了\s*(?<c>.{2,80})/, constraint: 1 },
  { regex: /加(一个|个)?\s*(?<c>.{2,80}(条件|限制|要求|约束))/, constraint: 1 },
  { regex: /必须\s*(?<c>.{2,80})/, constraint: 1 },
];

function detectCorrection(
  raw: string,
  normalized: string,
): Omit<ClassifiedFeedback, "text"> | null {
  for (const pat of CORRECTION_PATTERNS) {
    const m = raw.match(pat.regex);
    if (!m) continue;
    const correction = clean(m[pat.should]);
    if (!correction) continue;
    return {
      shape: "correction",
      confidence: 0.75,
      correction,
    };
  }
  // Softer signal: "not quite" / "close but" — correction intent without
  // an explicit "should be X".
  if (/\b(?:not quite|close but|almost|kind of)\b/.test(normalized)) {
    return { shape: "correction", confidence: 0.5 };
  }
  return null;
}

function detectConstraint(
  raw: string,
  _normalized: string,
): Omit<ClassifiedFeedback, "text"> | null {
  for (const pat of CONSTRAINT_PATTERNS) {
    const m = raw.match(pat.regex);
    if (!m) continue;
    const c = clean(m[pat.constraint]);
    if (!c) continue;
    return {
      shape: "constraint",
      confidence: 0.7,
      constraint: c,
    };
  }
  return null;
}

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

function matchesAny(s: string, patterns: readonly RegExp[]): boolean {
  for (const p of patterns) if (p.test(s)) return true;
  return false;
}

function looksLikeInstruction(raw: string, normalized: string): boolean {
  // Heuristic: starts with an imperative verb OR contains a "then/also"
  // connective with a verb.
  const firstWord = (raw.match(/[A-Za-z\u4e00-\u9fff]+/)?.[0] ?? "").toLowerCase();
  if (IMPERATIVE_VERBS.has(firstWord)) return true;
  return /\b(then|also|next)\s+(run|delete|create|install|try|use|call)\b/.test(
    normalized,
  );
}

const IMPERATIVE_VERBS = new Set([
  "run",
  "delete",
  "create",
  "install",
  "try",
  "use",
  "call",
  "build",
  "deploy",
  "test",
  "add",
  "remove",
  "restart",
  "停止",
  "启动",
  "运行",
  "删除",
  "创建",
  "安装",
  "试试",
  "改成",
]);

// ─── Export for re-use ────────────────────────────────────────────────────

export type { UserFeedbackShape };
