import type { Lang, Token } from '../core/types';
import { normalizeENKeyword } from '../core/index';

import type { LayoutCandidateLine, ScoreLayoutInput, ScoreTerm, ScoredLayout, Weights } from './types';
import { findPhraseSpans, isSpanSplitByBreaks, preparePhraseTokens } from './phraseMatch';
import { mergeLexicons } from './lexicons';
import { mergeWeights } from './weights';

function asInt(n: number): number {
  // 保证输出整数（避免后续误用浮点）
  return n | 0;
}

function absInt(n: number): number {
  return n < 0 ? -n : n;
}

function roundToInt(n: number): number {
  return Math.round(n);
}

function ratioToBp(ratio: number): number {
  // 把 0.90 转为 900（bp=1/1000），用于尽量规避浮点带来的跨端差异
  return Math.round(ratio * 1000);
}

function lineWidthOrApprox(line: LayoutCandidateLine, lang: Lang): number {
  if (typeof line.width === 'number' && Number.isFinite(line.width)) return roundToInt(line.width);
  // width unknown/approx：回退到 tokenCount / charCount（确定性）
  return lang === 'EN' ? asInt(line.tokenCount) : asInt(line.charCount);
}

function buildSet(arr: string[] | undefined, lang: Lang): Set<string> {
  if (!arr || arr.length === 0) return new Set();
  if (lang === 'EN') return new Set(arr.map((w) => normalizeENKeyword(w)));
  return new Set(arr);
}

function tokenHitSet(tokenText: string, set: Set<string>, lang: Lang): boolean {
  if (set.size === 0) return false;
  if (lang === 'EN') {
    // 全词等值匹配（允许两端标点包裹）：normalize 后做等值命中即可
    return set.has(normalizeENKeyword(tokenText));
  }
  return set.has(tokenText);
}

function isTcPunctuation(t: string, punctSet: Set<string>): boolean {
  return punctSet.has(t);
}

function computeEmotionTailFlags(tokens: Token[], lines: LayoutCandidateLine[], lang: Lang, emotionWordsSet: Set<string>): boolean[] {
  const flags: boolean[] = [];
  if (emotionWordsSet.size === 0) return lines.map(() => false);

  for (const line of lines) {
    const start = Math.max(0, line.start | 0);
    const end = Math.max(start, line.end | 0);
    const slice = tokens.slice(start, end);

    if (slice.length === 0) {
      flags.push(false);
      continue;
    }

    if (lang === 'EN') {
      const last = slice[slice.length - 1]?.text ?? '';
      flags.push(tokenHitSet(last, emotionWordsSet, 'EN'));
      continue;
    }

    // TC：“最后 2 个 grapheme 范围内命中”
    const last1 = slice[slice.length - 1]?.text ?? '';
    const last2 = slice.length >= 2 ? slice[slice.length - 2]?.text ?? '' : '';
    flags.push(tokenHitSet(last1, emotionWordsSet, 'TC') || tokenHitSet(last2, emotionWordsSet, 'TC'));
  }

  return flags;
}

function limitBreakdownForDebug(terms: ScoreTerm[]): ScoreTerm[] {
  // 裁决补充 10.4A：debug Top-3 按规则优先级（10.1 顺序）输出
  // 这里采用最稳妥的实现：直接取“按实现顺序生成的前 3 项”（确定性且满足优先级排序）
  return terms.slice(0, 3);
}

export function scoreLayout(input: ScoreLayoutInput): ScoredLayout {
  const weights: Weights = mergeWeights(input.config.weights);
  const lexicons = mergeLexicons(input.lexicons);

  const tokens = input.tokens ?? [];
  const breaks = (input.layoutCandidate.breaks ?? []).slice().sort((a, b) => a - b);
  const lines = input.layoutCandidate.lines ?? [];

  const terms: ScoreTerm[] = [];
  let score = 0;

  const flags: ScoredLayout['flags'] = {
    overflowed: Boolean(input.layoutCandidate.meta?.overflowed),
    fallback: Boolean(input.layoutCandidate.meta?.fallback),
  };

  // ------------------------------
  // 10.1-1 情绪短语拆分惩罚（最高优先）
  // ------------------------------
  {
    let deltaEmotion = 0;
    let deltaProtected = 0;

    const listEmotion = input.lang === 'EN' ? lexicons.emotionPhrasesEN : lexicons.emotionPhrasesTC;
    const listProtected = lexicons.protectedPhrases ?? [];

    const pushSplit = (key: 'EMOTION_SPLIT' | 'PROTECTED_SPLIT', phrase: string, spanLen: number) => {
      const base = key === 'EMOTION_SPLIT' ? weights.P_EMOTION_SPLIT : weights.P_PROTECTED_SPLIT;
      const d = -asInt(base) - asInt(spanLen); // 方案 A：追加“被拆分短语长度”惩罚（更长短语更强保护）
      if (key === 'EMOTION_SPLIT') deltaEmotion += d;
      else deltaProtected += d;

      flags.emotionSplit = true;
      // 记录 detail 便于治理
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      terms.push({ key, delta: d, detail: { phrase, spanLen } });
    };

    // emotion phrases
    for (const phrase of listEmotion) {
      const phraseTokens = preparePhraseTokens(phrase, input.lang);
      const spans = findPhraseSpans(tokens, phraseTokens, input.lang);
      for (const span of spans) {
        if (isSpanSplitByBreaks(span, breaks)) pushSplit('EMOTION_SPLIT', phrase, span.length);
      }
    }

    // protected phrases
    for (const phrase of listProtected) {
      const phraseTokens = preparePhraseTokens(phrase, input.lang);
      const spans = findPhraseSpans(tokens, phraseTokens, input.lang);
      for (const span of spans) {
        if (isSpanSplitByBreaks(span, breaks)) pushSplit('PROTECTED_SPLIT', phrase, span.length);
      }
    }

    // 注意：这里已经把 term 推入了 terms（保持“优先级最高的项”最先出现）
    score += deltaEmotion + deltaProtected;
  }

  // ------------------------------
  // 10.1-2 行超长惩罚 + 理想长度奖励
  // ------------------------------
  {
    const idealBp = ratioToBp(input.config.idealWidthRatio[input.context]);
    const idealWidth = roundToInt((input.availableWidth * idealBp) / 1000);
    const minPreferredRatio = input.config.minPreferredRatio ?? 0.6;
    const minPreferredWidth = roundToInt((idealWidth * ratioToBp(minPreferredRatio)) / 1000);

    let overMaxDelta = 0;
    let idealDelta = 0;
    let tooShortDelta = 0;

    for (const line of lines) {
      const w = lineWidthOrApprox(line, input.lang);

      if (w > input.availableWidth) {
        overMaxDelta -= asInt(weights.P_OVER_MAXLEN) * asInt(w - input.availableWidth);
      }

      idealDelta -= absInt(w - idealWidth);

      if (w < minPreferredWidth) {
        tooShortDelta -= asInt(weights.P_TOO_SHORT) * asInt(minPreferredWidth - w);
      }
    }

    if (overMaxDelta !== 0) terms.push({ key: 'OVER_MAXLEN', delta: overMaxDelta, detail: { availableWidth: input.availableWidth } });
    if (idealDelta !== 0) terms.push({ key: 'IDEAL_LEN', delta: idealDelta, detail: { idealWidth, idealBp } });
    if (tooShortDelta !== 0) terms.push({ key: 'TOO_SHORT', delta: tooShortDelta, detail: { minPreferredWidth } });

    score += overMaxDelta + idealDelta + tooShortDelta;
  }

  // ------------------------------
  // 10.1-3 widow（EN）/ 单字行（TC）
  // ------------------------------
  {
    const lastLine = lines[lines.length - 1];
    if (lastLine) {
      const lastWidth = lineWidthOrApprox(lastLine, input.lang);

      if (input.lang === 'EN') {
        const tokenCount = asInt(lastLine.tokenCount);
        if (tokenCount === 1) {
          const d = -asInt(weights.P_WIDOW_LINE);
          terms.push({ key: 'WIDOW_LINE', delta: d });
          score += d;

          const start = Math.max(0, lastLine.start | 0);
          const end = Math.max(start, lastLine.end | 0);
          const only = tokens.slice(start, end)[0]?.text ?? '';
          const norm = normalizeENKeyword(only);
          const widowMaxLen = asInt(input.config.widowMaxLen ?? 3);
          if (norm.length > 0 && norm.length <= widowMaxLen) {
            const d2 = -asInt(weights.P_WIDOW_WORD);
            terms.push({ key: 'WIDOW_WORD', delta: d2, detail: { word: norm, widowMaxLen } });
            score += d2;
          }
        }
      } else {
        // TC：最后一行只有 1 个 grapheme（或 charCount<=1）视为“单字行”
        const c = asInt(lastLine.charCount);
        if (c <= 1) {
          const d = -asInt(weights.P_WIDOW_LINE);
          terms.push({ key: 'WIDOW_LINE', delta: d, detail: { tcSingleChar: true, lastWidth } });
          score += d;
        }
      }
    }
  }

  // ------------------------------
  // 10.2F TC 助词孤立（在 10.1-3 后落地，保证顺序确定性）
  // ------------------------------
  if (input.lang === 'TC' && lines.length > 0) {
    const particles = new Set(input.config.tcParticles ?? ['啊', '喔', '呢', '啦', '吗', '吧', '呀']);
    const whitelist = new Set(input.config.tcParticleWhitelist ?? ['啊', '喔', '呢', '啦']);

    let delta = 0;

    for (const line of lines) {
      const start = Math.max(0, line.start | 0);
      const end = Math.max(start, line.end | 0);
      const slice = tokens.slice(start, end);
      if (slice.length === 0) continue;

      const first = slice[0]?.text ?? '';
      const last = slice[slice.length - 1]?.text ?? '';

      const hitFirst = particles.has(first);
      const hitLast = particles.has(last);
      if (!hitFirst && !hitLast) continue;

      const base = asInt(weights.P_PARTICLE_ISO);
      const half = Math.floor(base / 2);

      if (hitFirst) delta -= whitelist.has(first) ? half : base;
      if (hitLast) delta -= whitelist.has(last) ? half : base;
    }

    if (delta !== 0) {
      terms.push({ key: 'PARTICLE_ISO', delta, detail: { particles: Array.from(particles), whitelist: Array.from(whitelist) } });
      score += delta;
    }
  }

  // ------------------------------
  // 10.1-4 标点断点奖励（TC）
  // ------------------------------
  if (input.lang === 'TC') {
    const puncts = new Set(input.config.tcPunctuations ?? ['，', '。', '！', '？', '；', '：', '、']);
    let delta = 0;

    for (const b of breaks) {
      const prev = tokens[b - 1]?.text ?? '';
      if (isTcPunctuation(prev, puncts)) delta += asInt(weights.R_PUNCT_BREAK);
    }

    if (delta !== 0) {
      terms.push({ key: 'PUNCT_BREAK', delta, detail: { tcPunctuations: Array.from(puncts) } });
      score += delta;
    }
  }

  // ------------------------------
  // 10.1-5 Shift/Accum/Self 断点奖励（含 EN 行首/行尾感知）
  // ------------------------------
  {
    const shiftSet = buildSet(input.lang === 'EN' ? lexicons.shiftWordsEN : lexicons.shiftWordsTC, input.lang);
    const accumSet = buildSet(input.lang === 'EN' ? lexicons.accumWordsEN : lexicons.accumWordsTC, input.lang);
    const selfSet = buildSet(input.lang === 'EN' ? lexicons.selfWordsEN : lexicons.selfWordsTC, input.lang);
    const emotionSet = buildSet(input.lang === 'EN' ? lexicons.emotionWordsEN : lexicons.emotionWordsTC, input.lang);

    const emotionTailByLine = computeEmotionTailFlags(tokens, lines, input.lang, emotionSet);

    // EmotionWord 落点强化（默认权重为 0，不影响结果；但用于实现 10.2G 的“Accum 减半”规则）
    let emotionTailDelta = 0;
    for (let i = 0; i < emotionTailByLine.length; i++) {
      if (emotionTailByLine[i]) emotionTailDelta += asInt(weights.R_EMOTION_TAIL);
    }
    if (emotionTailDelta !== 0) {
      terms.push({ key: 'EMOTION_TAIL', delta: emotionTailDelta });
      score += emotionTailDelta;
    }

    let shiftDelta = 0;
    let accumDelta = 0;
    let selfDelta = 0;

    // 为 Accum 减半规则准备：把每个 break 映射到其左右行索引
    // 假设 lines 与 breaks 一一对应：breaks[i] 分隔 lines[i] 与 lines[i+1]
    for (let i = 0; i < breaks.length; i++) {
      const b = breaks[i]!;
      const prevLast = tokens[b - 1]?.text ?? '';
      const nextFirst = tokens[b]?.text ?? '';

      const hitShift = tokenHitSet(nextFirst, shiftSet, input.lang) || tokenHitSet(prevLast, shiftSet, input.lang);
      const hitAccum = tokenHitSet(nextFirst, accumSet, input.lang) || tokenHitSet(prevLast, accumSet, input.lang);
      const hitSelf = tokenHitSet(nextFirst, selfSet, input.lang) || tokenHitSet(prevLast, selfSet, input.lang);

      if (hitShift) shiftDelta += asInt(weights.R_SHIFT_BREAK);
      if (hitSelf) selfDelta += asInt(weights.R_SELF_BREAK);

      if (hitAccum) {
        // 10.2G：若同一行（或同一断点）同时满足 EmotionWord 强化与 Accum 奖励 -> Accum 减半
        const leftLineIdx = i;
        const rightLineIdx = i + 1;
        const emotionHitNearby = Boolean(emotionTailByLine[leftLineIdx]) || Boolean(emotionTailByLine[rightLineIdx]);
        const base = asInt(weights.R_ACCUM_BREAK);
        const reward = emotionHitNearby ? Math.floor(base / 2) : base;
        accumDelta += reward;
      }
    }

    if (shiftDelta !== 0) {
      terms.push({ key: 'SHIFT_BREAK', delta: shiftDelta });
      score += shiftDelta;
    }
    if (accumDelta !== 0) {
      terms.push({ key: 'ACCUM_BREAK', delta: accumDelta });
      score += accumDelta;
    }
    if (selfDelta !== 0) {
      terms.push({ key: 'SELF_BREAK', delta: selfDelta });
      score += selfDelta;
    }
  }

  // ------------------------------
  // 10.1-6 多行视觉均衡（尾行过短惩罚）
  // ------------------------------
  {
    const lastLine = lines[lines.length - 1];
    if (lastLine) {
      const idealBp = ratioToBp(input.config.idealWidthRatio[input.context]);
      const idealWidth = roundToInt((input.availableWidth * idealBp) / 1000);
      const shortRatio = input.config.shortLastLineRatio ?? 0.5;
      const shortThreshold = roundToInt((idealWidth * ratioToBp(shortRatio)) / 1000);

      const lastW = lineWidthOrApprox(lastLine, input.lang);
      if (lastW < shortThreshold) {
        const d = -asInt(weights.P_SHORT_LASTLINE);
        terms.push({ key: 'SHORT_LASTLINE', delta: d, detail: { shortThreshold, lastW, idealWidth } });
        score += d;
      }
    }
  }

  const scoreBreakdown = input.debug ? { total: score, terms: limitBreakdownForDebug(terms) } : undefined;

  // tieKey 在 tieKey.ts 单独构造（此处返回占位，调用方可覆盖）
  const tieKey: Array<number | string> = [
    flags.emotionSplit ? 1 : 0,
    flags.overflowed ? 1 : 0,
    // lastLineWidth/spread/idealDistance 等由 buildTieKey 计算；这里仅保证结构存在
  ];

  return { score, flags, tieKey, scoreBreakdown };
}

