type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
type JsonObject = { [key: string]: JsonValue };

type ValidationRequest = {
  requestId: number;
  activeType: string;
  rootKey: string;
  parsedPayload: JsonObject | null;
  records: JsonObject[];
};

type ValidationResponse = {
  requestId: number;
  issues: string[];
};

function isPlainObject(value: JsonValue | undefined): value is JsonObject {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function getDialogueNodes(record: JsonObject): JsonObject[] {
  const raw = record.dialogueNodes;
  if (!Array.isArray(raw)) {
    return [];
  }
  return raw.filter((entry) => isPlainObject(entry));
}

function computeValidationIssues(input: Omit<ValidationRequest, "requestId">): string[] {
  const { activeType, rootKey, parsedPayload, records } = input;
  const issues: string[] = [];

  if (!parsedPayload || !rootKey) {
    return issues;
  }

  const rawList = parsedPayload[rootKey];
  if (!Array.isArray(rawList)) {
    issues.push(`Expected ${rootKey} to be an array.`);
    return issues;
  }

  const seen = new Set<string | number>();

  records.forEach((record, index) => {
    if (activeType === "quests") {
      const questId = Number(record.questId);
      if (!Number.isInteger(questId) || questId < 1) {
        issues.push(`Record ${index + 1}: questId must be an integer >= 1.`);
      } else if (seen.has(questId)) {
        issues.push(`Duplicate questId found: ${questId}.`);
      } else {
        seen.add(questId);
      }
    } else {
      const id = String(record.id || "").trim();
      if (!id) {
        issues.push(`Record ${index + 1}: id is required.`);
      } else if (seen.has(id)) {
        issues.push(`Duplicate id found: ${id}.`);
      } else {
        seen.add(id);
      }
    }

    const name = String(record.name || "").trim();
    if (activeType !== "npcs" && !name) {
      issues.push(`Record ${index + 1}: name is required.`);
    }

    if (activeType === "npcs") {
      const templateId = String(record.templateId || "").trim();
      const mapId = String(record.mapId || "").trim();
      if (templateId) {
        issues.push(`NPC instance ${record.id || index + 1}: templateId is no longer stored on disk.`);
      }
      if (mapId) {
        issues.push(`NPC instance ${record.id || index + 1}: mapId is no longer stored on disk.`);
      }
    }

    if (activeType === "dialogues") {
      const npcNodes = getDialogueNodes(record);
      const seenNodeIds = new Set<string>();
      npcNodes.forEach((node, nodeIndex) => {
        const nodeId = String(node.id || "").trim();
        if (!nodeId) {
          issues.push(`Dialogue ${record.id || index + 1} node ${nodeIndex + 1}: id is required.`);
          return;
        }
        if (seenNodeIds.has(nodeId)) {
          issues.push(`Dialogue ${record.id || index + 1}: duplicate node id ${nodeId}.`);
          return;
        }
        seenNodeIds.add(nodeId);
      });
    }
  });

  return issues;
}

self.onmessage = (event: MessageEvent<ValidationRequest>) => {
  const { requestId, activeType, rootKey, parsedPayload, records } = event.data;
  const issues = computeValidationIssues({ activeType, rootKey, parsedPayload, records });
  const response: ValidationResponse = { requestId, issues };
  self.postMessage(response);
};
