export const TESTKIT_BROWSER_PROTOCOL_VERSION = 1;
export const TESTKIT_COVERAGE_GRAPH_VERSION = 1;

const TARGET_KINDS = new Set(["testId", "role", "text", "css", "xpath", "component"]);
const CONFIDENCE_LEVELS = new Set(["low", "medium", "high"]);
const FAILURE_STATES = new Set(["failing", "healthy", "unavailable"]);
const COVERAGE_STATES = new Set(["covered", "missing", "unavailable"]);
const NODE_KINDS = new Set([
  "page_view",
  "ui_surface",
  "ui_action",
  "client_request",
  "api_route",
  "server_action",
  "server_capability",
  "data_capability",
  "test_file",
]);
const EDGE_KINDS = new Set([
  "contains",
  "renders",
  "triggers",
  "requests",
  "handles",
  "delegates_to",
  "covers",
]);
const EVIDENCE_SOURCES = new Set(["convention", "static", "runtime"]);

export function normalizeBrowserTarget(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const kind = normalizeOptionalString(value.kind);
  const targetValue = normalizeOptionalString(value.value);
  if (!kind || !TARGET_KINDS.has(kind) || !targetValue) return null;
  const label = normalizeOptionalString(value.label);
  const confidence = normalizeOptionalString(value.confidence);
  return {
    kind,
    value: targetValue,
    ...(label ? { label } : {}),
    ...(confidence && CONFIDENCE_LEVELS.has(confidence) ? { confidence } : {}),
  };
}

export function normalizeBrowserMetadata(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const label = normalizeOptionalString(value.label);
  const origins = normalizeStringArray(value.origins);
  const routes = normalizeStringArray(value.routes);
  const targets = Array.isArray(value.targets) ? value.targets.map(normalizeBrowserTarget).filter(Boolean) : [];
  if (!label && origins.length === 0 && routes.length === 0 && targets.length === 0) {
    return null;
  }
  return {
    ...(label ? { label } : {}),
    ...(origins.length > 0 ? { origins } : {}),
    ...(routes.length > 0 ? { routes } : {}),
    ...(targets.length > 0 ? { targets } : {}),
  };
}

export function normalizeBrowserMetadataDocument(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const schemaVersion =
    Number.isInteger(value.schemaVersion) && value.schemaVersion > 0
      ? value.schemaVersion
      : TESTKIT_BROWSER_PROTOCOL_VERSION;
  const browser = normalizeBrowserMetadata(value.browser);
  if (!browser) return null;
  return {
    schemaVersion,
    browser,
  };
}

export function normalizeCoverageGraphNode(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const id = normalizeOptionalString(value.id);
  const kind = normalizeOptionalString(value.kind);
  const service = normalizeOptionalString(value.service);
  const label = normalizeOptionalString(value.label);
  if (!id || !kind || !NODE_KINDS.has(kind) || !service || !label) return null;
  const filePath = normalizeOptionalString(value.filePath);
  const route = normalizeOptionalString(value.route);
  const method = normalizeOptionalString(value.method);
  const path = normalizeOptionalString(value.path);
  const target = normalizeBrowserTarget(value.target);
  const metadata = normalizeMetadataRecord(value.metadata);
  return {
    id,
    kind,
    service,
    label,
    ...(filePath ? { filePath } : {}),
    ...(route ? { route } : {}),
    ...(method ? { method } : {}),
    ...(path ? { path } : {}),
    ...(target ? { target } : {}),
    ...(metadata ? { metadata } : {}),
  };
}

export function normalizeCoverageGraphEdge(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const id = normalizeOptionalString(value.id);
  const kind = normalizeOptionalString(value.kind);
  const from = normalizeOptionalString(value.from);
  const to = normalizeOptionalString(value.to);
  if (!id || !kind || !EDGE_KINDS.has(kind) || !from || !to) return null;
  const confidence = normalizeOptionalString(value.confidence);
  const metadata = normalizeMetadataRecord(value.metadata);
  return {
    id,
    kind,
    from,
    to,
    ...(confidence && CONFIDENCE_LEVELS.has(confidence) ? { confidence } : {}),
    ...(metadata ? { metadata } : {}),
  };
}

export function normalizeCoverageEvidence(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const id = normalizeOptionalString(value.id);
  const source = normalizeOptionalString(value.source);
  const service = normalizeOptionalString(value.service);
  const suiteName = normalizeOptionalString(value.suiteName);
  const selectionType = normalizeOptionalString(value.selectionType);
  const framework = normalizeOptionalString(value.framework);
  const testFilePath = normalizeOptionalString(value.testFilePath);
  const coveredNodeIds = normalizeStringArray(value.coveredNodeIds);
  if (
    !id ||
    !source ||
    !EVIDENCE_SOURCES.has(source) ||
    !service ||
    !suiteName ||
    !selectionType ||
    !framework ||
    !testFilePath ||
    coveredNodeIds.length === 0
  ) {
    return null;
  }
  const confidence = normalizeOptionalString(value.confidence);
  const details = normalizeEvidenceDetails(value.details);
  return {
    id,
    source,
    service,
    suiteName,
    selectionType,
    framework,
    testFilePath,
    coveredNodeIds,
    ...(confidence && CONFIDENCE_LEVELS.has(confidence) ? { confidence } : {}),
    ...(details ? { details } : {}),
  };
}

export function normalizeCoverageGraph(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const schemaVersion =
    Number.isInteger(value.schemaVersion) && value.schemaVersion > 0
      ? value.schemaVersion
      : TESTKIT_COVERAGE_GRAPH_VERSION;
  const nodes = Array.isArray(value.nodes) ? value.nodes.map(normalizeCoverageGraphNode).filter(Boolean) : [];
  const edges = Array.isArray(value.edges) ? value.edges.map(normalizeCoverageGraphEdge).filter(Boolean) : [];
  const evidence = Array.isArray(value.evidence)
    ? value.evidence.map(normalizeCoverageEvidence).filter(Boolean)
    : [];
  if (nodes.length === 0) return null;
  return {
    schemaVersion,
    nodes,
    edges,
    evidence,
  };
}

export function isPageOverlayResponse(value) {
  return Boolean(normalizePageOverlayResponse(value));
}

export function normalizePageOverlayResponse(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  if (value.protocolVersion !== TESTKIT_BROWSER_PROTOCOL_VERSION) return null;
  if (!value.page || typeof value.page !== "object") return null;
  if (!value.match || typeof value.match !== "object") return null;
  if (!value.summary || typeof value.summary !== "object") return null;
  if (!Array.isArray(value.coverage) || !Array.isArray(value.failures)) return null;
  if (!FAILURE_STATES.has(value.summary.failureState)) return null;
  if (!COVERAGE_STATES.has(value.summary.coverageState)) return null;
  return value;
}

export function createBridgeErrorResponse(code, message) {
  return {
    protocolVersion: TESTKIT_BROWSER_PROTOCOL_VERSION,
    error: {
      code,
      message,
    },
  };
}

function normalizeEvidenceDetails(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const requestPaths = normalizeStringArray(value.requestPaths);
  const route = normalizeOptionalString(value.route);
  if (requestPaths.length === 0 && !route) return null;
  return {
    ...(requestPaths.length > 0 ? { requestPaths } : {}),
    ...(route ? { route } : {}),
  };
}

function normalizeMetadataRecord(value) {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  const normalized = {};
  for (const [key, entry] of Object.entries(value)) {
    const normalizedKey = normalizeOptionalString(key);
    if (!normalizedKey) continue;
    if (
      entry === null ||
      typeof entry === "string" ||
      typeof entry === "number" ||
      typeof entry === "boolean"
    ) {
      normalized[normalizedKey] = entry;
    }
  }
  return Object.keys(normalized).length > 0 ? normalized : null;
}

function normalizeStringArray(value) {
  if (!Array.isArray(value)) return [];
  return value.map(normalizeOptionalString).filter(Boolean);
}

function normalizeOptionalString(value) {
  if (typeof value !== "string") return null;
  const normalized = value.trim();
  return normalized.length > 0 ? normalized : null;
}
