/* ============================================================
   斗地主 牌局引擎 v2
   - 54 张牌、牌型识别、比大小
   - findAllMoves：枚举所有合法出牌（供 AI / 提示循环 / 智能配牌）
   - aiMove：手牌拆解 + 策略（接牌 / 留炸弹 / 护队友 / 最优领出）
   挂到 window.DDZ
   ============================================================ */

const RANKS = ['3','4','5','6','7','8','9','10','J','Q','K','A','2'];
const RANK_VALUE = {'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'10':10,'J':11,'Q':12,'K':13,'A':14,'2':15};
const SUITS = ['S','H','C','D'];
const SUIT_SYMBOL = {S:'♠',H:'♥',C:'♣',D:'♦'};
const SUIT_COLOR = {S:'ink',C:'ink',H:'red',D:'red'};

function buildDeck() {
  const deck = []; let id = 0;
  for (const s of SUITS) for (const r of RANKS)
    deck.push({ id: id++, rank: r, suit: s, value: RANK_VALUE[r], symbol: SUIT_SYMBOL[s], color: SUIT_COLOR[s] });
  deck.push({ id: id++, rank: 'joker', suit: 'joker', value: 16, symbol: '小', color: 'ink', joker: 'small' });
  deck.push({ id: id++, rank: 'JOKER', suit: 'joker', value: 17, symbol: '大', color: 'red', joker: 'big' });
  return deck;
}
function shuffle(arr) {
  const a = arr.slice();
  for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; }
  return a;
}
function deal() {
  const deck = shuffle(buildDeck());
  const hands = [[], [], []];
  for (let i = 0; i < 51; i++) hands[i % 3].push(deck[i]);
  const bottom = deck.slice(51);
  hands.forEach(h => h.sort(sortCards));
  return { hands, bottom };
}
function sortCards(a, b) { return b.value - a.value; }
function isConsecutive(vals) { for (let i = 1; i < vals.length; i++) if (vals[i] !== vals[i - 1] + 1) return false; return true; }

/* ---------------- 牌型识别 ---------------- */
function getCombo(cards) {
  if (!cards || !cards.length) return null;
  const n = cards.length;
  const vals = cards.map(c => c.value).sort((a, b) => a - b);
  const counts = {}; vals.forEach(v => counts[v] = (counts[v] || 0) + 1);
  const uniq = Object.keys(counts).map(Number).sort((a, b) => a - b);
  const cv = Object.values(counts);

  if (n === 2 && vals[0] === 16 && vals[1] === 17) return { type: 'rocket', value: 100, len: 1 };
  if (n === 4 && cv.length === 1) return { type: 'bomb', value: uniq[0], len: 1 };
  if (n === 1) return { type: 'single', value: vals[0], len: 1 };
  if (n === 2 && counts[vals[0]] === 2) return { type: 'pair', value: vals[0], len: 1 };
  if (n === 3 && cv.length === 1) return { type: 'triple', value: uniq[0], len: 1 };
  if (n === 4) { const t = uniq.find(v => counts[v] === 3); if (t !== undefined) return { type: 'triple_single', value: t, len: 1 }; }
  if (n === 5) { const t = uniq.find(v => counts[v] === 3); const p = uniq.find(v => counts[v] === 2); if (t !== undefined && p !== undefined) return { type: 'triple_pair', value: t, len: 1 }; }
  if (n >= 5 && cv.every(c => c === 1) && uniq[uniq.length - 1] <= 14 && isConsecutive(uniq))
    return { type: 'straight', value: uniq[uniq.length - 1], len: n };
  if (n >= 6 && n % 2 === 0 && cv.every(c => c === 2) && uniq[uniq.length - 1] <= 14 && isConsecutive(uniq))
    return { type: 'straight_pair', value: uniq[uniq.length - 1], len: uniq.length };
  const triples = uniq.filter(v => counts[v] === 3).sort((a, b) => a - b);
  if (triples.length >= 2 && triples[triples.length - 1] <= 14 && isConsecutive(triples)) {
    const top = triples[triples.length - 1];
    if (n === triples.length * 3) return { type: 'airplane', value: top, len: triples.length };
    if (n === triples.length * 4) return { type: 'airplane_single', value: top, len: triples.length };
    if (n === triples.length * 5) { const rest = uniq.filter(v => counts[v] !== 3); if (rest.every(v => counts[v] === 2)) return { type: 'airplane_pair', value: top, len: triples.length }; }
  }
  return null;
}

function canBeat(combo, prev) {
  if (!combo) return false;
  if (!prev) return true;
  if (combo.type === 'rocket') return true;
  if (prev.type === 'rocket') return false;
  if (combo.type === 'bomb') { if (prev.type === 'bomb') return combo.value > prev.value; return true; }
  if (prev.type === 'bomb') return false;
  return combo.type === prev.type && combo.len === prev.len && combo.value > prev.value;
}

function groupByValue(hand) { const g = {}; hand.forEach(c => { (g[c.value] = g[c.value] || []).push(c); }); return g; }
const comboType = (m) => { const c = getCombo(m); return c ? c.type : null; };
const isBombish = (m) => { const t = comboType(m); return t === 'bomb' || t === 'rocket'; };

/* ---------------- 移动枚举 ---------------- */
// 从 g 取若干“非指定值”的最低单牌作为带牌
function lowestAttachSingles(g, count, excludeVals) {
  const ex = new Set(excludeVals);
  const cards = [];
  const vals = Object.keys(g).map(Number).sort((a, b) => a - b);
  for (const v of vals) {
    if (ex.has(v)) continue;
    // 优先用零散牌（避免拆对/拆炸），但不足时也可拆
    for (const c of g[v]) { cards.push(c); if (cards.length >= count) return cards.slice(0, count); }
  }
  return cards.length >= count ? cards.slice(0, count) : null;
}
function lowestAttachPairs(g, count, excludeVals) {
  const ex = new Set(excludeVals);
  const out = [];
  const vals = Object.keys(g).map(Number).sort((a, b) => a - b);
  for (const v of vals) { if (ex.has(v)) continue; if (g[v].length >= 2) { out.push(g[v].slice(0, 2)); if (out.length >= count) break; } }
  if (out.length < count) return null;
  return out.flat();
}
function findStraights(g, exactLen, minTop) {
  const present = []; for (let v = 3; v <= 14; v++) if (g[v] && g[v].length >= 1) present.push(v);
  const res = [];
  for (let i = 0; i + exactLen <= present.length; i++) {
    const win = present.slice(i, i + exactLen);
    if (win[exactLen - 1] - win[0] === exactLen - 1 && (minTop == null || win[exactLen - 1] > minTop))
      res.push(win.map(v => g[v][0]));
  }
  return res;
}
function findStraightPairs(g, exactPairs, minTop) {
  const present = []; for (let v = 3; v <= 14; v++) if (g[v] && g[v].length >= 2) present.push(v);
  const res = [];
  for (let i = 0; i + exactPairs <= present.length; i++) {
    const win = present.slice(i, i + exactPairs);
    if (win[exactPairs - 1] - win[0] === exactPairs - 1 && (minTop == null || win[exactPairs - 1] > minTop))
      res.push(win.flatMap(v => g[v].slice(0, 2)));
  }
  return res;
}
function findAirplanes(g, numTriples, minTop, wing) {
  const present = []; for (let v = 3; v <= 14; v++) if (g[v] && g[v].length >= 3) present.push(v);
  const res = [];
  for (let i = 0; i + numTriples <= present.length; i++) {
    const win = present.slice(i, i + numTriples);
    if (win[numTriples - 1] - win[0] !== numTriples - 1) continue;
    if (minTop != null && win[numTriples - 1] <= minTop) continue;
    const core = win.flatMap(v => g[v].slice(0, 3));
    if (wing === 'none') { res.push(core); continue; }
    if (wing === 'single') { const w = lowestAttachSingles(g, numTriples, win); if (w) res.push([...core, ...w]); }
    if (wing === 'pair') { const w = lowestAttachPairs(g, numTriples, win); if (w) res.push([...core, ...w]); }
  }
  return res;
}

// 所有能压过 prev 的出法（升序，炸弹/火箭排后）
function movesBeating(hand, prev) {
  const g = groupByValue(hand);
  const vals = Object.keys(g).map(Number).sort((a, b) => a - b);
  const res = [];
  const addBombs = (higherThan) => {
    for (const v of vals) if (g[v].length >= 4 && (higherThan == null || v > higherThan)) res.push(g[v].slice(0, 4));
    if (g[16] && g[17]) res.push([g[16][0], g[17][0]]);
  };
  switch (prev.type) {
    case 'single': for (const v of vals) if (v > prev.value) res.push([g[v][0]]); addBombs(null); break;
    case 'pair': for (const v of vals) if (v > prev.value && g[v].length >= 2) res.push(g[v].slice(0, 2)); addBombs(null); break;
    case 'triple': for (const v of vals) if (v > prev.value && g[v].length >= 3) res.push(g[v].slice(0, 3)); addBombs(null); break;
    case 'triple_single':
      for (const v of vals) if (v > prev.value && g[v].length >= 3) { const a = lowestAttachSingles(g, 1, [v]); if (a) res.push([...g[v].slice(0, 3), ...a]); }
      addBombs(null); break;
    case 'triple_pair':
      for (const v of vals) if (v > prev.value && g[v].length >= 3) { const a = lowestAttachPairs(g, 1, [v]); if (a) res.push([...g[v].slice(0, 3), ...a]); }
      addBombs(null); break;
    case 'straight': findStraights(g, prev.len, prev.value).forEach(m => res.push(m)); addBombs(null); break;
    case 'straight_pair': findStraightPairs(g, prev.len, prev.value).forEach(m => res.push(m)); addBombs(null); break;
    case 'airplane': findAirplanes(g, prev.len, prev.value, 'none').forEach(m => res.push(m)); addBombs(null); break;
    case 'airplane_single': findAirplanes(g, prev.len, prev.value, 'single').forEach(m => res.push(m)); addBombs(null); break;
    case 'airplane_pair': findAirplanes(g, prev.len, prev.value, 'pair').forEach(m => res.push(m)); addBombs(null); break;
    case 'bomb': addBombs(prev.value); break;
    case 'rocket': break;
  }
  return dedupe(res).sort(byStrength);
}

// 领出时的候选（升序友好排列）
function leadOptions(hand) {
  const g = groupByValue(hand);
  const vals = Object.keys(g).map(Number).sort((a, b) => a - b);
  const res = [];
  for (const v of vals) res.push([g[v][0]]);                                   // 单
  for (const v of vals) if (g[v].length >= 2) res.push(g[v].slice(0, 2));      // 对
  for (const v of vals) if (g[v].length >= 3) {                               // 三 / 三带
    res.push(g[v].slice(0, 3));
    const a1 = lowestAttachSingles(g, 1, [v]); if (a1) res.push([...g[v].slice(0, 3), ...a1]);
    const a2 = lowestAttachPairs(g, 1, [v]); if (a2) res.push([...g[v].slice(0, 3), ...a2]);
  }
  for (let L = 5; L <= 12; L++) findStraights(g, L, null).forEach(m => res.push(m));
  for (let L = 3; L <= 10; L++) findStraightPairs(g, L, null).forEach(m => res.push(m));
  for (let L = 2; L <= 6; L++) { findAirplanes(g, L, null, 'none').forEach(m => res.push(m)); findAirplanes(g, L, null, 'single').forEach(m => res.push(m)); findAirplanes(g, L, null, 'pair').forEach(m => res.push(m)); }
  for (const v of vals) if (g[v].length >= 4) res.push(g[v].slice(0, 4));      // 炸
  if (g[16] && g[17]) res.push([g[16][0], g[17][0]]);                          // 火箭
  return dedupe(res).sort(byStrength);
}

function signature(move) { return move.map(c => c.id).sort((a, b) => a - b).join(','); }
function dedupe(moves) { const seen = new Set(); const out = []; for (const m of moves) { const s = signature(m); if (!seen.has(s)) { seen.add(s); out.push(m); } } return out; }
function byStrength(a, b) {
  const ca = getCombo(a), cb = getCombo(b);
  const rank = (c) => c.type === 'rocket' ? 3 : c.type === 'bomb' ? 2 : 0;
  const ra = rank(ca), rb = rank(cb);
  if (ra !== rb) return ra - rb;
  if (ca.len !== cb.len) return ca.len - cb.len;
  if (ca.value !== cb.value) return ca.value - cb.value;
  return a.length - b.length;
}

// 统一入口：prev 为 null 时给领出候选，否则给压制候选
function findAllMoves(hand, prev) { return prev ? movesBeating(hand, prev) : leadOptions(hand); }

/* ---------------- 手牌拆解（估算需要几手出完） ---------------- */
function decompose(hand) {
  const g = {}; hand.forEach(c => { (g[c.value] = g[c.value] || 0) + 0; g[c.value]++; });
  const cnt = {}; hand.forEach(c => cnt[c.value] = (cnt[c.value] || 0) + 1);
  let groups = 0;
  const c = { ...cnt };
  // 火箭
  if (c[16] && c[17]) { groups++; c[16]--; c[17]--; }
  // 炸弹
  for (let v = 3; v <= 17; v++) if (c[v] === 4) { groups++; c[v] = 0; }
  // 顺子（尽量长）
  let found = true;
  while (found) {
    found = false;
    for (let L = 12; L >= 5; L--) {
      for (let s = 3; s + L - 1 <= 14; s++) {
        let ok = true; for (let v = s; v < s + L; v++) if (!c[v]) { ok = false; break; }
        if (ok) { for (let v = s; v < s + L; v++) c[v]--; groups++; found = true; break; }
      }
      if (found) break;
    }
  }
  // 连对
  found = true;
  while (found) {
    found = false;
    for (let L = 10; L >= 3; L--) {
      for (let s = 3; s + L - 1 <= 14; s++) {
        let ok = true; for (let v = s; v < s + L; v++) if ((c[v] || 0) < 2) { ok = false; break; }
        if (ok) { for (let v = s; v < s + L; v++) c[v] -= 2; groups++; found = true; break; }
      }
      if (found) break;
    }
  }
  // 三张
  for (let v = 3; v <= 15; v++) if (c[v] === 3) { groups++; c[v] = 0; }
  // 对子
  for (let v = 3; v <= 17; v++) if (c[v] === 2) { groups++; c[v] = 0; }
  // 单张
  for (let v = 3; v <= 17; v++) if (c[v] === 1) { groups++; c[v] = 0; }
  return groups;
}

/* ---------------- AI ----------------
   ctx = { me, landlord, counts:[n0,n1,n2], leaderSeat, difficulty }
*/
function aiMove(hand, prev, ctx = {}) {
  const { me = 0, landlord = null, counts = null, leaderSeat = null, difficulty = 'normal' } = ctx;
  const amLandlord = landlord != null && me === landlord;
  const minOpp = () => {
    if (!counts || landlord == null) return 99;
    if (amLandlord) return Math.min(...[0, 1, 2].filter(i => i !== me).map(i => counts[i]));
    return counts[landlord];
  };

  if (!prev) return chooseLead(hand, ctx, amLandlord);

  const moves = movesBeating(hand, prev);
  if (!moves.length) return null;
  const nonBomb = moves.filter(m => !isBombish(m));
  const bombs = moves.filter(isBombish);

  // 简单难度：常常选择不出（被动）、几乎不主动用炸弹
  if (difficulty === 'easy') {
    const teammate0 = !amLandlord && landlord != null && leaderSeat != null && leaderSeat !== me && leaderSeat !== landlord;
    if (teammate0 && hand.length > 3) return null;
    if (nonBomb.length) {
      if (Math.random() < 0.35) return null;           // 经常让牌
      // 随机选一手（不优化拆牌）
      return nonBomb[Math.floor(Math.random() * Math.min(2, nonBomb.length))];
    }
    if (bombs.length && minOpp() <= 1 && Math.random() < 0.4) return bombs[0];
    return null;
  }

  // 护队友
  const teammateLeads = !amLandlord && landlord != null && leaderSeat != null && leaderSeat !== me && leaderSeat !== landlord;
  if (teammateLeads) {
    if (hand.length <= 3 && nonBomb.length) return nonBomb[0];
    const grabChance = difficulty === 'hard' ? 0.15 : 0.25;
    if (prev.type === 'single' && prev.value <= 10 && Math.random() < grabChance && nonBomb.length) return nonBomb[0];
    return null;
  }

  if (nonBomb.length) {
    const bombVals = new Set(); const g = groupByValue(hand);
    Object.keys(g).forEach(v => { if (g[v].length >= 4) bombVals.add(+v); });
    const scored = nonBomb.map(m => {
      const usesBomb = m.some(c => bombVals.has(c.value));
      const rest = hand.filter(c => !m.includes(c));
      // hard 难度更看重出完后的剩余手数（更激进地追求一条龙）
      const restWeight = difficulty === 'hard' ? 1.4 : 1;
      return { m, key: (usesBomb ? 100 : 0) + decompose(rest) * restWeight + getCombo(m).value * 0.01 };
    }).sort((a, b) => a.key - b.key);
    return scored[0].m;
  }

  if (bombs.length) {
    const threat = minOpp() <= 2;
    if (threat || prev.type === 'bomb' || prev.type === 'rocket') return bombs[0];
    const aggressive = difficulty === 'hard';
    if (hand.length <= 5 && Math.random() < (aggressive ? 0.7 : 0.5)) return bombs[0];
    if (Math.random() < (aggressive ? 0.2 : 0.1)) return bombs[0];
  }
  return null;
}

function chooseLead(hand, ctx = {}, amLandlord = false) {
  // 整手能一次走完
  if (getCombo(hand)) return hand.slice();
  const g = groupByValue(hand);
  const bombVals = new Set(); Object.keys(g).forEach(v => { if (g[v].length >= 4) bombVals.add(+v); });
  const opts = leadOptions(hand);
  const score = (m) => {
    const c = getCombo(m); const top = c.value; let s;
    if (c.type === 'straight') s = 5 + top * 0.1 - m.length * 0.5;
    else if (c.type === 'straight_pair') s = 6 + top * 0.1 - m.length * 0.4;
    else if (c.type.startsWith('airplane')) s = 7 + top * 0.1;
    else if (c.type === 'triple' || c.type === 'triple_single' || c.type === 'triple_pair') s = 28 + top;
    else if (c.type === 'pair') s = 20 + top;
    else if (c.type === 'single') s = 40 + top;
    else s = 999; // 炸弹/火箭
    if (c.type === 'single' && top >= 15) s += 100;   // 留 2/王 控场
    if (c.type === 'pair' && top >= 15) s += 60;
    if (m.some(card => bombVals.has(card.value)) && c.type !== 'bomb') s += 90; // 别拆炸弹
    return s;
  };
  opts.sort((a, b) => score(a) - score(b));
  const best = opts.find(m => !isBombish(m)) || opts[0];
  return best || [[...hand].sort((x, y) => x.value - y.value)[0]];
}

/* 提示：返回所有可出（供 UI 循环切换） */
function findHint(hand, prev) { const all = findAllMoves(hand, prev); return all.length ? all[0] : null; }

/* ---------------- 癞子（红桃3 万能牌） ----------------
   只有一张癞子。玩家选牌后在出牌时把癞子"变"成具体牌（保留 id）。
   resolveLaizi(cards, prev) → 具体牌数组 或 null（无法组成合法牌型）
*/
const isLaizi = (c) => !!c && c.laizi === true;
function cloneAs(card, v) {
  const rank = RANKS[v - 3] || '2';
  // 找一个花色符号（沿用原癞子花色 ♥）
  return { ...card, value: v, rank, symbol: card.symbol || '♥', color: 'red', laizi: true, asValue: v };
}
function resolveLaizi(cards, prev) {
  const wilds = cards.filter(isLaizi);
  const normals = cards.filter(c => !isLaizi(c));
  if (wilds.length === 0) { return getCombo(cards) ? cards.slice() : null; }
  if (wilds.length > 1) { return getCombo(cards) ? cards.slice() : null; } // 理论上只有一张
  const wild = wilds[0];
  const candidates = [];
  for (let v = 3; v <= 15; v++) {
    const trial = [...normals, cloneAs(wild, v)];
    const combo = getCombo(trial);
    if (combo) candidates.push({ trial, combo, v });
  }
  if (!candidates.length) return null;
  if (prev) {
    const beating = candidates.filter(c => canBeat(c.combo, prev));
    if (!beating.length) return null;
    // 选能压过且代价最小（value 最小）的一手
    beating.sort((a, b) => a.combo.value - b.combo.value);
    return beating[0].trial.slice().sort(sortCards);
  }
  // 领出：优先炸弹，其次取最高 value 的解（对玩家更有利）
  candidates.sort((a, b) => {
    const ra = (a.combo.type === 'bomb' || a.combo.type === 'rocket') ? 1 : 0;
    const rb = (b.combo.type === 'bomb' || b.combo.type === 'rocket') ? 1 : 0;
    if (ra !== rb) return rb - ra;
    return b.combo.value - a.combo.value;
  });
  return candidates[0].trial.slice().sort(sortCards);
}

window.DDZ = {
  RANKS, RANK_VALUE, SUIT_SYMBOL, buildDeck, shuffle, deal, sortCards,
  getCombo, canBeat, groupByValue, findAllMoves, movesBeating, leadOptions,
  aiMove, chooseLead, findHint, decompose, isLaizi, resolveLaizi,
};
