/**
 * pop-dashboard 수식 파싱 및 평가 유틸리티
 *
 * 보안: eval()/new Function() 사용 금지. 직접 사칙연산 파서 구현.
 */

import type { FormulaConfig, FormulaDisplayFormat } from "../../types";

// ===== 토큰 타입 =====

type TokenType = "number" | "variable" | "operator" | "lparen" | "rparen";

interface Token {
  type: TokenType;
  value: string;
}

// ===== 토크나이저 =====

/** 수식 문자열을 토큰 배열로 분리 */
function tokenize(expression: string): Token[] {
  const tokens: Token[] = [];
  let i = 0;
  const expr = expression.replace(/\s+/g, "");

  while (i < expr.length) {
    const ch = expr[i];

    // 숫자 (정수, 소수)
    if (/\d/.test(ch) || (ch === "." && i + 1 < expr.length && /\d/.test(expr[i + 1]))) {
      let num = "";
      while (i < expr.length && (/\d/.test(expr[i]) || expr[i] === ".")) {
        num += expr[i];
        i++;
      }
      tokens.push({ type: "number", value: num });
      continue;
    }

    // 변수 (A, B, C 등 알파벳)
    if (/[A-Za-z]/.test(ch)) {
      let varName = "";
      while (i < expr.length && /[A-Za-z0-9_]/.test(expr[i])) {
        varName += expr[i];
        i++;
      }
      tokens.push({ type: "variable", value: varName });
      continue;
    }

    // 연산자
    if ("+-*/".includes(ch)) {
      tokens.push({ type: "operator", value: ch });
      i++;
      continue;
    }

    // 괄호
    if (ch === "(") {
      tokens.push({ type: "lparen", value: "(" });
      i++;
      continue;
    }
    if (ch === ")") {
      tokens.push({ type: "rparen", value: ")" });
      i++;
      continue;
    }

    // 알 수 없는 문자는 건너뜀
    i++;
  }

  return tokens;
}

// ===== 재귀 하강 파서 =====

/**
 * 사칙연산 수식을 안전하게 평가 (재귀 하강 파서)
 *
 * 문법:
 *   expr   = term (('+' | '-') term)*
 *   term   = factor (('*' | '/') factor)*
 *   factor = NUMBER | VARIABLE | '(' expr ')'
 *
 * @param expression - 수식 문자열 (예: "A / B * 100")
 * @param values - 변수값 맵 (예: { A: 1234, B: 5678 })
 * @returns 계산 결과 (0으로 나누기 시 0 반환)
 */
export function evaluateFormula(
  expression: string,
  values: Record<string, number>
): number {
  const tokens = tokenize(expression);
  let pos = 0;

  function peek(): Token | undefined {
    return tokens[pos];
  }

  function consume(): Token {
    return tokens[pos++];
  }

  // factor = NUMBER | VARIABLE | '(' expr ')'
  function parseFactor(): number {
    const token = peek();
    if (!token) return 0;

    if (token.type === "number") {
      consume();
      return parseFloat(token.value);
    }

    if (token.type === "variable") {
      consume();
      return values[token.value] ?? 0;
    }

    if (token.type === "lparen") {
      consume(); // '(' 소비
      const result = parseExpr();
      if (peek()?.type === "rparen") {
        consume(); // ')' 소비
      }
      return result;
    }

    // 예상치 못한 토큰
    consume();
    return 0;
  }

  // term = factor (('*' | '/') factor)*
  function parseTerm(): number {
    let result = parseFactor();
    while (peek()?.type === "operator" && (peek()!.value === "*" || peek()!.value === "/")) {
      const op = consume().value;
      const right = parseFactor();
      if (op === "*") {
        result *= right;
      } else {
        // 0으로 나누기 방지
        result = right === 0 ? 0 : result / right;
      }
    }
    return result;
  }

  // expr = term (('+' | '-') term)*
  function parseExpr(): number {
    let result = parseTerm();
    while (peek()?.type === "operator" && (peek()!.value === "+" || peek()!.value === "-")) {
      const op = consume().value;
      const right = parseTerm();
      result = op === "+" ? result + right : result - right;
    }
    return result;
  }

  const result = parseExpr();
  return Number.isFinite(result) ? result : 0;
}

/**
 * 수식 결과를 displayFormat에 맞게 포맷팅
 *
 * @param config - 수식 설정
 * @param values - 변수값 맵 (예: { A: 1234, B: 5678 })
 * @returns 포맷된 문자열
 */
export function formatFormulaResult(
  config: FormulaConfig,
  values: Record<string, number>
): string {
  const formatMap: Record<FormulaDisplayFormat, () => string> = {
    value: () => {
      const result = evaluateFormula(config.expression, values);
      return formatNumber(result);
    },
    fraction: () => {
      // "1,234 / 5,678" 형태
      const ids = config.values.map((v) => v.id);
      if (ids.length >= 2) {
        return `${formatNumber(values[ids[0]] ?? 0)} / ${formatNumber(values[ids[1]] ?? 0)}`;
      }
      return formatNumber(evaluateFormula(config.expression, values));
    },
    percent: () => {
      const result = evaluateFormula(config.expression, values);
      return `${(result * 100).toFixed(1)}%`;
    },
    ratio: () => {
      // "1,234 : 5,678" 형태
      const ids = config.values.map((v) => v.id);
      if (ids.length >= 2) {
        return `${formatNumber(values[ids[0]] ?? 0)} : ${formatNumber(values[ids[1]] ?? 0)}`;
      }
      return formatNumber(evaluateFormula(config.expression, values));
    },
  };

  return formatMap[config.displayFormat]();
}

/**
 * 수식에 사용된 변수 ID가 모두 존재하는지 검증
 *
 * @param expression - 수식 문자열
 * @param availableIds - 사용 가능한 변수 ID 배열
 * @returns 유효 여부
 */
export function validateExpression(
  expression: string,
  availableIds: string[]
): boolean {
  const tokens = tokenize(expression);
  const usedVars = tokens
    .filter((t) => t.type === "variable")
    .map((t) => t.value);

  return usedVars.every((v) => availableIds.includes(v));
}

/**
 * 큰 숫자 축약 (Container Query 축소 시 사용)
 *
 * 1234 -> "1,234"
 * 12345 -> "1.2만"
 * 1234567 -> "123.5만"
 * 123456789 -> "1.2억"
 */
export function abbreviateNumber(value: number): string {
  const abs = Math.abs(value);
  const sign = value < 0 ? "-" : "";

  if (abs >= 100_000_000) {
    return `${sign}${(abs / 100_000_000).toFixed(1)}억`;
  }
  if (abs >= 10_000) {
    return `${sign}${(abs / 10_000).toFixed(1)}만`;
  }
  return `${sign}${formatNumber(abs)}`;
}

// ===== 내부 헬퍼 =====

/** 숫자를 천 단위 콤마 포맷 */
function formatNumber(value: number): string {
  if (Number.isInteger(value)) {
    return value.toLocaleString("ko-KR");
  }
  // 소수점 이하 최대 2자리
  return value.toLocaleString("ko-KR", {
    minimumFractionDigits: 0,
    maximumFractionDigits: 2,
  });
}
