// Backgammon rules engine — ported from backgammonjs-master/lib/
// Supports Standard, Gul Bara, and Tapa variants

export const WHITE = 0;
export const BLACK = 1;

export const MOVE = 'move';
export const RECOVER = 'recover';
export const HIT = 'hit';
export const BEAR = 'bear';

// ===== STATE =====

export function createState() {
  const points = [];
  for (let i = 0; i < 24; i++) points.push([]);
  return {
    points,
    bar: [[], []],
    outside: [[], []],
    nextId: 1
  };
}

export function cloneState(state) {
  return JSON.parse(JSON.stringify(state));
}

function place(state, count, type, position) {
  for (let i = 0; i < count; i++) {
    const piece = { type, id: state.nextId++ };
    state.points[position].push(piece);
  }
}

function countAtPos(state, position, type) {
  return state.points[position].filter(p => p.type === type).length;
}

function countAllAtPos(state, position) {
  return state.points[position].length;
}

function getTopPiece(state, position) {
  const pt = state.points[position];
  return pt.length > 0 ? pt[pt.length - 1] : null;
}

function getPiecePos(state, piece) {
  for (let i = 0; i < state.points.length; i++) {
    for (let k = 0; k < state.points[i].length; k++) {
      if (state.points[i][k].id === piece.id) return i;
    }
  }
  return null;
}

function isPieceOnBar(state, piece) {
  return state.bar[piece.type].some(p => p.id === piece.id);
}

function isPieceOutside(state, piece) {
  return state.outside[piece.type].some(p => p.id === piece.id);
}

function havePiecesOnBar(state, type) {
  return state.bar[type].length > 0;
}

function getBarTopPiece(state, type) {
  const bar = state.bar[type];
  return bar.length > 0 ? bar[bar.length - 1] : null;
}

// ===== VARIANTS =====

const VARIANTS = {
  sheshbesh: {
    name: 'شيش بيش',
    nameEn: 'Shesh Besh',
    allowHit: true,
    resetState(state) {
      place(state, 5, WHITE, 5);
      place(state, 3, WHITE, 7);
      place(state, 5, WHITE, 12);
      place(state, 2, WHITE, 23);
      place(state, 5, BLACK, 18);
      place(state, 3, BLACK, 16);
      place(state, 5, BLACK, 11);
      place(state, 2, BLACK, 0);
    },
    incPos(pos, type, steps) {
      return type === WHITE ? pos - steps : pos + steps;
    },
    normPos(pos, type) {
      return type === BLACK ? 23 - pos : pos;
    },
    denormPos(pos, type) {
      return type === BLACK ? 23 - pos : pos;
    }
  },
  mahbousa: {
    name: 'محبوسة',
    nameEn: 'Mahbousa',
    allowHit: false,
    resetState(state) {
      place(state, 15, WHITE, 23);
      place(state, 15, BLACK, 0);
    },
    incPos(pos, type, steps) {
      return type === WHITE ? pos - steps : pos + steps;
    },
    normPos(pos, type) {
      return type === BLACK ? 23 - pos : pos;
    },
    denormPos(pos, type) {
      return type === BLACK ? 23 - pos : pos;
    }
  },
  thirtyone: {
    name: '٣١',
    nameEn: '31',
    allowHit: false,
    resetState(state) {
      place(state, 15, WHITE, 23);
      place(state, 15, BLACK, 11);
    },
    incPos(pos, type, steps) {
      if (type === WHITE) return pos - steps;
      let np = pos - steps;
      if (pos < 12 && np < 0) np = 24 + np;
      else if (pos >= 12 && np <= 11) np = np - 12;
      return np;
    },
    normPos(pos, type) {
      if (type === WHITE) return pos;
      if (pos < 0) return pos;
      if (pos >= 12) return pos - 12;
      return pos + 12;
    },
    denormPos(pos, type) {
      if (type === WHITE) return pos;
      if (pos < 0) return pos;
      if (pos >= 12) return pos - 12;
      return pos + 12;
    }
  }
};

export function getVariants() {
  return Object.keys(VARIANTS).map(k => ({ key: k, name: VARIANTS[k].name }));
}

// ===== GAME =====

export function createGame(variant = 'sheshbesh') {
  const rule = VARIANTS[variant];
  const state = createState();
  rule.resetState(state);

  return {
    variant,
    state,
    turn: WHITE,
    dice: null,
    movesLeft: [],
    movesPlayed: [],
    turnNumber: 0,
    isOver: false,
    winner: null,
    score: 0
  };
}

export function rollDice(game) {
  const d1 = Math.floor(Math.random() * 6) + 1;
  const d2 = Math.floor(Math.random() * 6) + 1;
  const values = [d1, d2].sort((a, b) => b - a);

  let moves = [...values];
  if (d1 === d2) moves = [d1, d1, d1, d1];

  const playable = calculatePlayableMoves(game.state, moves, game.turn, game.variant);

  game.dice = values;
  game.movesLeft = [...playable];
  game.movesPlayed = [];
  return { values, moves: playable };
}

export function getValidMoves(game) {
  if (!game.dice || game.movesLeft.length === 0) return [];

  const rule = VARIANTS[game.variant];
  const state = game.state;
  const type = game.turn;
  const moves = [];

  const uniqueSteps = [...new Set(game.movesLeft)];

  for (const steps of uniqueSteps) {
    const pieces = getMovablePieces(state, type, rule);
    for (const piece of pieces) {
      const actions = getMoveActions(state, piece, steps, rule);
      if (actions.length > 0) {
        if (!isMoveRestricted(state, game.movesLeft, piece, steps, type, rule)) {
          const dest = getDestination(state, piece, steps, rule);
          moves.push({ piece, steps, actions, from: getPiecePos(state, piece), to: dest });
        }
      }
    }
  }
  return moves;
}

export function applyMove(game, pieceId, steps) {
  const rule = VARIANTS[game.variant];
  const state = game.state;
  const piece = findPiece(state, pieceId, game.turn);
  if (!piece) return null;

  const actions = getMoveActions(state, piece, steps, rule);
  if (actions.length === 0) return null;

  applyActions(state, actions, rule);

  const idx = game.movesLeft.indexOf(steps);
  if (idx >= 0) {
    game.movesLeft.splice(idx, 1);
    game.movesPlayed.push(steps);
  }

  if (hasWon(state, game.turn)) {
    game.isOver = true;
    game.winner = game.turn;
    game.score = getGameScore(state, game.turn, rule);
  }

  return actions;
}

export function nextTurn(game) {
  game.turn = game.turn === WHITE ? BLACK : WHITE;
  game.dice = null;
  game.movesLeft = [];
  game.movesPlayed = [];
  game.turnNumber++;
}

export function hasWon(state, type) {
  return state.outside[type].length === 15;
}

export function getGameScore(state, winner, rule) {
  const opp = winner === WHITE ? BLACK : WHITE;
  if (state.outside[opp].length === 0) {
    const r = rule || VARIANTS.standard;
    if (countAtHigherPos(state, 18, opp, r) > 0 || havePiecesOnBar(state, opp)) {
      return 3; // backgammon
    }
    return 2; // gammon
  }
  return 1; // normal
}

export function getPipCount(state, type) {
  const rule = VARIANTS.standard;
  let pips = 0;
  for (let i = 0; i < 24; i++) {
    const count = countAtPos(state, i, type);
    if (count > 0) {
      const norm = rule.normPos(i, type);
      pips += count * (norm + 1);
    }
  }
  pips += state.bar[type].length * 25;
  return pips;
}

// ===== INTERNAL HELPERS =====

function findPiece(state, pieceId, type) {
  for (let i = 0; i < 24; i++) {
    for (const p of state.points[i]) {
      if (p.id === pieceId && p.type === type) return p;
    }
  }
  for (const p of state.bar[type]) {
    if (p.id === pieceId) return p;
  }
  return null;
}

function getMovablePieces(state, type, rule) {
  if (havePiecesOnBar(state, type)) {
    const top = getBarTopPiece(state, type);
    return top ? [top] : [];
  }
  const pieces = [];
  for (let i = 0; i < 24; i++) {
    const pt = state.points[i];
    if (pt.length > 0 && pt[pt.length - 1].type === type) {
      pieces.push(pt[pt.length - 1]);
    }
  }
  return pieces;
}

function getMoveActions(state, piece, steps, rule) {
  const actions = [];

  function addAction(type, piece, from, to) {
    actions.push({ type, piece: { id: piece.id, type: piece.type }, from, to });
  }

  try {
    if (havePiecesOnBar(state, piece.type)) {
      if (!isPieceOnBar(state, piece)) return [];

      const dest = piece.type === WHITE ? (24 - steps) : (steps - 1);
      const top = getTopPiece(state, dest);
      const topType = top ? top.type : null;

      if (topType === null || topType === piece.type) {
        addAction(RECOVER, piece, dest);
      } else if (rule.allowHit && countAtPos(state, dest, topType) === 1) {
        addAction(HIT, top, dest);
        addAction(RECOVER, piece, dest);
      }
      return actions;
    }

    const position = getPiecePos(state, piece);
    if (position === null) return [];

    if (allPiecesAreHome(state, piece.type, rule)) {
      const dest = rule.incPos(position, piece.type, steps);
      const normDest = rule.normPos(dest, piece.type);

      if (normDest >= 0) {
        const top = getTopPiece(state, dest);
        const topType = top ? top.type : null;

        if (topType === null || topType === piece.type) {
          addAction(MOVE, piece, position, dest);
        } else if (rule.allowHit && countAtPos(state, dest, topType) === 1) {
          addAction(HIT, top, dest);
          addAction(MOVE, piece, position, dest);
        } else if (!rule.allowHit && countAllAtPos(state, dest) === 1 && topType !== piece.type) {
          // Tapa: can land on single opponent piece
          addAction(MOVE, piece, position, dest);
        }
      } else if (normDest === -1) {
        addAction(BEAR, piece, position);
      } else {
        const normSource = rule.normPos(position, piece.type);
        if (countAtHigherPos(state, normSource + 1, piece.type, rule) <= 0) {
          addAction(BEAR, piece, position);
        }
      }
    } else {
      const dest = rule.incPos(position, piece.type, steps);
      if (dest < 0 || dest > 23) return [];

      const top = getTopPiece(state, dest);
      const topType = top ? top.type : null;

      if (topType === null || topType === piece.type) {
        addAction(MOVE, piece, position, dest);
      } else if (rule.allowHit && countAtPos(state, dest, topType) === 1) {
        addAction(HIT, top, dest);
        addAction(MOVE, piece, position, dest);
      } else if (!rule.allowHit && countAllAtPos(state, dest) === 1 && topType !== piece.type) {
        addAction(MOVE, piece, position, dest);
      }
    }
  } catch (e) {
    return [];
  }

  return actions;
}

function getDestination(state, piece, steps, rule) {
  if (isPieceOnBar(state, piece)) {
    return piece.type === WHITE ? (24 - steps) : (steps - 1);
  }
  const pos = getPiecePos(state, piece);
  if (pos === null) return null;
  if (allPiecesAreHome(state, piece.type, rule)) {
    const dest = rule.incPos(pos, piece.type, steps);
    const norm = rule.normPos(dest, piece.type);
    if (norm < 0) return 'off';
    return dest;
  }
  return rule.incPos(pos, piece.type, steps);
}

function applyActions(state, actions, rule) {
  for (const action of actions) {
    const piece = findPieceById(state, action.piece.id, action.piece.type);
    if (!piece) continue;

    if (action.type === MOVE) {
      const pos = getPiecePos(state, piece);
      if (pos !== null) {
        const idx = state.points[pos].findIndex(p => p.id === piece.id);
        if (idx >= 0) {
          state.points[pos].splice(idx, 1);
          state.points[action.to].push(piece);
        }
      }
    } else if (action.type === RECOVER) {
      const idx = state.bar[piece.type].findIndex(p => p.id === piece.id);
      if (idx >= 0) {
        state.bar[piece.type].splice(idx, 1);
        state.points[action.from].push(piece);
      }
    } else if (action.type === HIT) {
      const pos = action.from;
      const idx = state.points[pos].findIndex(p => p.id === piece.id);
      if (idx >= 0) {
        state.points[pos].splice(idx, 1);
        state.bar[piece.type].push(piece);
      }
    } else if (action.type === BEAR) {
      const pos = action.from;
      const idx = state.points[pos].findIndex(p => p.id === piece.id);
      if (idx >= 0) {
        state.points[pos].splice(idx, 1);
        state.outside[piece.type].push(piece);
      }
    }
  }
}

function findPieceById(state, id, type) {
  for (let i = 0; i < 24; i++) {
    for (const p of state.points[i]) {
      if (p.id === id) return p;
    }
  }
  for (const p of state.bar[type]) {
    if (p.id === id) return p;
  }
  return null;
}

function allPiecesAreHome(state, type, rule) {
  return countAtHigherPos(state, 6, type, rule) <= 0;
}

function countAtHigherPos(state, normPosition, type, rule) {
  let cnt = 0;
  for (let i = 23; i >= normPosition; i--) {
    const denorm = rule.denormPos(i, type);
    if (denorm >= 0 && denorm <= 23) {
      cnt += countAtPos(state, denorm, type);
    }
  }
  return cnt;
}

function calculatePlayableMoves(state, moves, type, variant) {
  const rule = VARIANTS[variant];
  const weight = calcMoveWeights(state, moves, type, rule, null, true);
  return weight.playableMoves.length > 0 ? weight.playableMoves : [];
}

function isMoveRestricted(state, movesLeft, piece, steps, type, rule) {
  const weight = calcMoveWeights(state, movesLeft, type, rule, piece, false);
  const maxW = weight.max;
  const pieceW = weight[piece.id];
  if (!pieceW || pieceW.max < maxW) return true;
  return false;
}

function calcMoveWeights(state, movesLeft, type, rule, rootPiece, stopAtMax) {
  const weight = { max: 0, playableMoves: [] };
  const movesSum = movesLeft.reduce((a, b) => a + b, 0);

  function branch(st, moves, id, sum, level, branchMoves) {
    if (stopAtMax && weight.max >= movesSum) return;

    const ml = [...moves];
    const steps = ml.shift();
    if (!steps) return;

    const pieces = getMovablePiecesFromState(st, type, rule);

    for (const piece of pieces) {
      if (level === 0 && rootPiece && rootPiece.id !== piece.id) continue;
      if (isPieceOutsideState(st, piece)) continue;

      if (havePiecesOnBarState(st, type)) {
        const barTop = getBarTopPieceState(st, type);
        if (!barTop || barTop.id !== piece.id) continue;
      } else {
        const pos = getPiecePosState(st, piece);
        if (pos === null) continue;
        const top = st.points[pos].length > 0 ? st.points[pos][st.points[pos].length - 1] : null;
        if (!top || top.id !== piece.id) continue;
      }

      const tempState = JSON.parse(JSON.stringify(st));
      const tempPiece = findPieceInState(tempState, piece.id, type);
      if (!tempPiece) continue;

      const actions = getMoveActionsFromState(tempState, tempPiece, steps, rule);
      if (actions.length === 0) continue;

      applyActionsToState(tempState, actions, rule);

      const tempMoves = [...branchMoves, steps];
      const pieceID = id !== 0 ? id : piece.id;

      if (!weight[pieceID]) weight[pieceID] = { max: 0, moves: [] };

      const w = sum + steps;
      if (w > weight[pieceID].max) weight[pieceID].max = w;
      weight[pieceID].moves = tempMoves;
      if (w > weight.max) {
        weight.max = w;
        weight.playableMoves = tempMoves;
      }

      if (stopAtMax && weight.max >= movesSum) return;
      if (ml.length > 0) branch(tempState, ml, pieceID, w, level + 1, tempMoves);
    }
  }

  branch(state, movesLeft, 0, 0, 0, []);

  if (movesLeft.length > 1 && movesLeft[0] !== movesLeft[movesLeft.length - 1]) {
    const reversed = [...movesLeft].reverse();
    branch(state, reversed, 0, 0, 0, []);
  }

  return weight;
}

// State-only helpers for weight calculation (no mutation of original)
function getMovablePiecesFromState(state, type, rule) {
  if (state.bar[type].length > 0) {
    const top = state.bar[type].length > 0 ? state.bar[type][state.bar[type].length - 1] : null;
    return top ? [top] : [];
  }
  const pieces = [];
  for (let i = 0; i < 24; i++) {
    const pt = state.points[i];
    if (pt.length > 0 && pt[pt.length - 1].type === type) {
      pieces.push(pt[pt.length - 1]);
    }
  }
  return pieces;
}

function isPieceOutsideState(state, piece) {
  return state.outside[piece.type].some(p => p.id === piece.id);
}

function havePiecesOnBarState(state, type) {
  return state.bar[type].length > 0;
}

function getBarTopPieceState(state, type) {
  const bar = state.bar[type];
  return bar.length > 0 ? bar[bar.length - 1] : null;
}

function getPiecePosState(state, piece) {
  for (let i = 0; i < state.points.length; i++) {
    if (state.points[i].some(p => p.id === piece.id)) return i;
  }
  return null;
}

function findPieceInState(state, id, type) {
  for (let i = 0; i < 24; i++) {
    for (const p of state.points[i]) {
      if (p.id === id) return p;
    }
  }
  for (const p of state.bar[type]) {
    if (p.id === id) return p;
  }
  return null;
}

function getMoveActionsFromState(state, piece, steps, rule) {
  return getMoveActions(state, piece, steps, rule);
}

function applyActionsToState(state, actions, rule) {
  applyActions(state, actions, rule);
}

// ===== SERIALIZATION (for multiplayer sync) =====

export function serializeState(game) {
  return {
    variant: game.variant,
    state: game.state,
    turn: game.turn,
    dice: game.dice,
    movesLeft: game.movesLeft,
    movesPlayed: game.movesPlayed,
    turnNumber: game.turnNumber,
    isOver: game.isOver,
    winner: game.winner,
    score: game.score
  };
}

export function deserializeState(data) {
  return { ...data };
}
