import { DIFF_INDICATORS, DIFF_LAYOUTS, DIFF_THEMES } from "./types.js";
import type { DiffViewerPayload } from "./types.js";

const OVERFLOW_VALUES = ["scroll", "wrap"] as const;

export function parseViewerPayloadJson(raw: string): DiffViewerPayload {
  let parsed: unknown;
  try {
    parsed = JSON.parse(raw);
  } catch {
    throw new Error("Diff payload is not valid JSON.");
  }

  if (!isDiffViewerPayload(parsed)) {
    throw new Error("Diff payload has invalid shape.");
  }

  return parsed;
}

function isDiffViewerPayload(value: unknown): value is DiffViewerPayload {
  if (!isRecord(value)) {
    return false;
  }

  if (typeof value.prerenderedHTML !== "string") {
    return false;
  }

  if (!Array.isArray(value.langs) || !value.langs.every((lang) => typeof lang === "string")) {
    return false;
  }

  if (!isViewerOptions(value.options)) {
    return false;
  }

  const hasFileDiff = isRecord(value.fileDiff);
  const hasBeforeAfterFiles = isRecord(value.oldFile) && isRecord(value.newFile);
  if (!hasFileDiff && !hasBeforeAfterFiles) {
    return false;
  }

  return true;
}

function isViewerOptions(value: unknown): boolean {
  if (!isRecord(value)) {
    return false;
  }

  if (!isRecord(value.theme)) {
    return false;
  }
  if (value.theme.light !== "pierre-light" || value.theme.dark !== "pierre-dark") {
    return false;
  }

  if (!includesValue(DIFF_LAYOUTS, value.diffStyle)) {
    return false;
  }
  if (!includesValue(DIFF_INDICATORS, value.diffIndicators)) {
    return false;
  }
  if (!includesValue(DIFF_THEMES, value.themeType)) {
    return false;
  }
  if (!includesValue(OVERFLOW_VALUES, value.overflow)) {
    return false;
  }

  if (typeof value.disableLineNumbers !== "boolean") {
    return false;
  }
  if (typeof value.expandUnchanged !== "boolean") {
    return false;
  }
  if (typeof value.backgroundEnabled !== "boolean") {
    return false;
  }
  if (typeof value.unsafeCSS !== "string") {
    return false;
  }

  return true;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null;
}

function includesValue<T extends readonly string[]>(values: T, value: unknown): value is T[number] {
  return typeof value === "string" && values.includes(value as T[number]);
}
