import { createHash } from "node:crypto";
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import vm from "node:vm";

const scriptDirectory = dirname(fileURLToPath(import.meta.url));
const projectRoot = resolve(scriptDirectory, "..");
const sourcePath = resolve(projectRoot, "src/utils/match.min.js");
const runtimePath = resolve(projectRoot, "src/utils/matchProtocol.js");
const generatedStartMarker = "// <match-schema-generated>";
const generatedEndMarker = "// </match-schema-generated>";

const supportedScalarKinds = new Set([
  "int32",
  "int64",
  "float",
  "double",
  "bool",
  "string",
  "bytes",
]);
const supportedMapKeyKinds = new Set(["int32", "int64", "bool", "string"]);
// 新版描述文件未携带旧协议的 oneof 元数据，保留现有解码结果中的 payload 标识。
const compatibilityOneofs = {
  ServerMessage: {
    match_info: "payload",
    shoot_data: "payload",
    practice_info: "payload",
  },
};

function isRecord(value) {
  return value !== null && typeof value === "object" && !Array.isArray(value);
}

function extractRootCreateArgument(source) {
  const match = /(?:\$protobuf\.)?Root\.create\s*\(/.exec(source);
  if (!match) {
    throw new Error("match.min.js 中未找到 Root.create(...) 协议描述");
  }

  const openIndex = source.indexOf("(", match.index);
  let depth = 0;
  let quote = "";
  let escaped = false;
  let lineComment = false;
  let blockComment = false;

  for (let index = openIndex; index < source.length; index += 1) {
    const char = source[index];
    const next = source[index + 1];

    if (lineComment) {
      if (char === "\n") lineComment = false;
      continue;
    }
    if (blockComment) {
      if (char === "*" && next === "/") {
        blockComment = false;
        index += 1;
      }
      continue;
    }
    if (quote) {
      if (escaped) {
        escaped = false;
      } else if (char === "\\") {
        escaped = true;
      } else if (char === quote) {
        quote = "";
      }
      continue;
    }
    if (char === '"' || char === "'" || char === "`") {
      quote = char;
      continue;
    }
    if (char === "/" && next === "/") {
      lineComment = true;
      index += 1;
      continue;
    }
    if (char === "/" && next === "*") {
      blockComment = true;
      index += 1;
      continue;
    }
    if (char === "(") {
      depth += 1;
      continue;
    }
    if (char === ")") {
      depth -= 1;
      if (depth === 0) {
        return source.slice(openIndex + 1, index).trim();
      }
    }
  }

  throw new Error("match.min.js 中的 Root.create(...) 括号不完整");
}

function stripQuotedText(source) {
  let output = "";
  let quote = "";
  let escaped = false;

  for (const char of source) {
    if (quote) {
      output += " ";
      if (escaped) {
        escaped = false;
      } else if (char === "\\") {
        escaped = true;
      } else if (char === quote) {
        quote = "";
      }
      continue;
    }
    if (char === '"' || char === "'") {
      quote = char;
      output += " ";
    } else {
      output += char;
    }
  }

  return output;
}

function parseDescriptor(argumentSource) {
  if (!argumentSource.startsWith("{") || !argumentSource.endsWith("}")) {
    throw new Error("Root.create(...) 参数不是静态对象字面量");
  }

  try {
    return JSON.parse(argumentSource);
  } catch {
    // 兼容旧版 pbjs 生成的未加引号对象键；只允许静态对象语法。
    const syntaxOnly = stripQuotedText(argumentSource);
    if (/[();`=]/.test(syntaxOnly)) {
      throw new Error("旧版协议描述包含非静态表达式，已拒绝执行");
    }
    const descriptor = vm.runInNewContext(
      `(${argumentSource})`,
      Object.create(null),
      { timeout: 1000 }
    );
    return JSON.parse(JSON.stringify(descriptor));
  }
}

function findProtocolNamespace(node, path = []) {
  if (!isRecord(node)) return null;
  const entries = isRecord(node.nested) ? node.nested : node;

  if (isRecord(entries.ServerMessage?.fields) && isRecord(entries.ClientMessage?.fields)) {
    return { entries, path };
  }

  for (const [name, child] of Object.entries(entries)) {
    if (!isRecord(child) || isRecord(child.fields)) continue;
    const found = findProtocolNamespace(child, [...path, name]);
    if (found) return found;
  }

  return null;
}

function getEnumValues(definition) {
  if (!isRecord(definition)) return null;
  const candidate = isRecord(definition.values) ? definition.values : definition;
  const entries = Object.entries(candidate);
  if (entries.length === 0 || entries.some(([, value]) => !Number.isInteger(value))) {
    return null;
  }
  return Object.fromEntries(entries.sort((left, right) => left[1] - right[1]));
}

function normalizeTypeName(type) {
  return String(type || "")
    .replace(/^\./, "")
    .split(".")
    .pop();
}

function getOneofByField(definition) {
  const result = new Map();
  if (!isRecord(definition.oneofs)) return result;

  for (const [groupName, groupDefinition] of Object.entries(definition.oneofs)) {
    const fieldNames = Array.isArray(groupDefinition)
      ? groupDefinition
      : groupDefinition?.oneof;
    if (!Array.isArray(fieldNames)) continue;
    for (const fieldName of fieldNames) result.set(fieldName, groupName);
  }

  return result;
}

function buildSchema({ messageName, definition, messages, enumNames }) {
  const schema = {};
  const usedIds = new Set();
  const oneofByField = getOneofByField(definition);

  for (const [fieldKey, fieldDefinition] of Object.entries(definition.fields)) {
    const id = Number(fieldDefinition.id);
    if (!Number.isInteger(id) || id <= 0) {
      throw new Error(`${messageName}.${fieldKey} 的字段编号无效`);
    }
    if (usedIds.has(id)) {
      throw new Error(`${messageName} 存在重复字段编号 ${id}`);
    }
    usedIds.add(id);

    const rule = fieldDefinition.rule;
    if (rule && !["optional", "required", "repeated", "map"].includes(rule)) {
      throw new Error(`${messageName}.${fieldKey} 使用了不支持的规则 ${rule}`);
    }

    const fieldName = fieldDefinition.protoName || fieldKey;
    const typeName = normalizeTypeName(fieldDefinition.type);
    const keyTypeName = normalizeTypeName(
      fieldDefinition.keyType ?? fieldDefinition.keytype
    );
    const isMap = rule === "map" || Boolean(keyTypeName);

    if (isMap) {
      if (!supportedMapKeyKinds.has(keyTypeName)) {
        throw new Error(
          `${messageName}.${fieldKey} 使用了不支持的 map key 类型 ${keyTypeName}`
        );
      }
      const valueIsMessage = messages.has(typeName);
      const valueKind = enumNames.has(typeName) ? "int32" : typeName;
      if (!valueIsMessage && !supportedScalarKinds.has(valueKind)) {
        throw new Error(
          `${messageName}.${fieldKey} 使用了不支持的 map value 类型 ${typeName}`
        );
      }
      schema[id] = valueIsMessage
        ? {
            name: fieldName,
            kind: "map",
            keyKind: keyTypeName,
            valueKind: "message",
            valueType: typeName,
          }
        : {
            name: fieldName,
            kind: "map",
            keyKind: keyTypeName,
            valueKind,
          };
      continue;
    }

    const isMessage = messages.has(typeName);
    const kind = enumNames.has(typeName) ? "int32" : typeName;
    if (!isMessage && !supportedScalarKinds.has(kind)) {
      throw new Error(`${messageName}.${fieldKey} 使用了不支持的类型 ${typeName}`);
    }

    const repeated = rule === "repeated";
    if (repeated && !isMessage && !["string", "bytes"].includes(kind)) {
      throw new Error(
        `${messageName}.${fieldKey} 是 packed scalar repeated，当前通用解码器尚不支持`
      );
    }

    const field = isMessage
      ? { name: fieldName, kind: "message", type: typeName }
      : { name: fieldName, kind };
    if (repeated) field.repeated = true;

    const oneof =
      fieldDefinition.oneof ||
      oneofByField.get(fieldKey) ||
      compatibilityOneofs[messageName]?.[fieldName];
    if (oneof) field.oneof = oneof;
    schema[id] = field;
  }

  return schema;
}

function formatPropertyKey(key) {
  return /^(?:[A-Za-z_$][\w$]*|\d+)$/.test(key) ? key : JSON.stringify(key);
}

function formatJsValue(value, depth = 0) {
  if (!isRecord(value)) return JSON.stringify(value);

  const entries = Object.entries(value);
  if (entries.length === 0) return "{}";

  const indent = "  ".repeat(depth);
  const primitiveEntries = entries.every(([, child]) => !isRecord(child));
  if (primitiveEntries && value.kind !== "map") {
    const singleLine = `{ ${entries
      .map(([key, child]) => `${formatPropertyKey(key)}: ${JSON.stringify(child)}`)
      .join(", ")} }`;
    if (indent.length + singleLine.length <= 100) return singleLine;
  }

  const childIndent = "  ".repeat(depth + 1);
  const lines = entries.map(
    ([key, child]) =>
      `${childIndent}${formatPropertyKey(key)}: ${formatJsValue(child, depth + 1)},`
  );
  return `{\n${lines.join("\n")}\n${indent}}`;
}

function createGeneratedSource(source) {
  const descriptor = parseDescriptor(extractRootCreateArgument(source));
  const namespace = findProtocolNamespace(descriptor);
  if (!namespace) {
    throw new Error("协议描述中未找到 ServerMessage 和 ClientMessage");
  }

  const messages = new Map();
  const enums = new Map();
  for (const [name, definition] of Object.entries(namespace.entries)) {
    if (isRecord(definition?.fields)) {
      messages.set(name, definition);
      continue;
    }
    const values = getEnumValues(definition);
    if (values) enums.set(name, values);
  }

  const serverMessageType = enums.get("ServerMessageType");
  const clientMessageType = enums.get("ClientMessageType");
  if (!serverMessageType || !clientMessageType) {
    throw new Error("协议描述缺少 ServerMessageType 或 ClientMessageType");
  }

  const enumNames = new Set(enums.keys());
  const schemaByName = new Map();
  for (const [messageName, definition] of messages) {
    schemaByName.set(
      messageName,
      buildSchema({ messageName, definition, messages, enumNames })
    );
  }

  const reachable = new Set();
  function collectReachable(messageName) {
    if (reachable.has(messageName)) return;
    const schema = schemaByName.get(messageName);
    if (!schema) throw new Error(`找不到消息定义 ${messageName}`);
    reachable.add(messageName);
    for (const field of Object.values(schema)) {
      if (field.kind === "message") collectReachable(field.type);
      if (field.kind === "map" && field.valueKind === "message") {
        collectReachable(field.valueType);
      }
    }
  }
  collectReachable("ServerMessage");

  const schemas = {};
  for (const messageName of messages.keys()) {
    if (reachable.has(messageName)) schemas[messageName] = schemaByName.get(messageName);
  }

  const sourceHash = createHash("sha256").update(source).digest("hex").slice(0, 16);
  const namespaceName = namespace.path.join(".") || "root";
  const fieldCount = Object.values(schemas).reduce(
    (total, schema) => total + Object.keys(schema).length,
    0
  );
  const generatedBlock = [
    generatedStartMarker,
    "// 此区块由 scripts/generate-match-schema.mjs 自动生成，请勿手动修改。",
    `// 来源：src/utils/match.min.js（sha256: ${sourceHash}）`,
    `// 协议命名空间：${namespaceName}；消息数：${Object.keys(schemas).length}；字段数：${fieldCount}`,
    "",
    `export const ServerMessageType = ${formatJsValue(serverMessageType)};`,
    "",
    `export const ClientMessageType = ${formatJsValue(clientMessageType)};`,
    "",
    `const SCHEMAS = ${formatJsValue(schemas)};`,
    generatedEndMarker,
  ].join("\n");

  return {
    generatedBlock,
    messageCount: Object.keys(schemas).length,
    fieldCount,
  };
}

function main() {
  const args = process.argv.slice(2);
  const unknownArgs = args.filter((arg) => arg !== "--check");
  if (unknownArgs.length > 0) {
    throw new Error(`未知参数：${unknownArgs.join(", ")}`);
  }

  // 统一换行符，避免 Windows 的 CRLF 让未变更的协议产生不同哈希。
  const source = readFileSync(sourcePath, "utf8").replace(/\r\n?/g, "\n");
  const { generatedBlock, messageCount, fieldCount } = createGeneratedSource(source);
  const runtimeSource = readFileSync(runtimePath, "utf8");
  const startIndex = runtimeSource.indexOf(generatedStartMarker);
  const endMarkerIndex = runtimeSource.indexOf(generatedEndMarker);
  if (startIndex < 0 || endMarkerIndex < startIndex) {
    throw new Error("matchProtocol.js 缺少协议生成区块标记");
  }
  const endIndex = endMarkerIndex + generatedEndMarker.length;
  const currentBlock = runtimeSource.slice(startIndex, endIndex);

  if (args.includes("--check")) {
    if (currentBlock !== generatedBlock) {
      throw new Error(
        "matchProtocol.js 的生成区块与 match.min.js 不同步，请运行 npm run proto:generate"
      );
    }
    console.log(`[match-schema] 同步校验通过：${messageCount} 个消息，${fieldCount} 个字段`);
    return;
  }

  if (currentBlock === generatedBlock) {
    console.log(`[match-schema] 无需更新：${messageCount} 个消息，${fieldCount} 个字段`);
    return;
  }

  const nextRuntimeSource =
    runtimeSource.slice(0, startIndex) + generatedBlock + runtimeSource.slice(endIndex);
  writeFileSync(runtimePath, nextRuntimeSource, "utf8");
  console.log(`[match-schema] 已生成：${messageCount} 个消息，${fieldCount} 个字段`);
}

try {
  main();
} catch (error) {
  console.error(`[match-schema] ${error.message}`);
  process.exitCode = 1;
}
