import { createHash } from "crypto";
import {
  existsSync,
  mkdirSync,
  readFileSync,
  renameSync,
  statSync,
  writeFileSync,
} from "fs";
import { basename, dirname, join } from "path";
import { homedir } from "os";
import {
  getRouteCMatrixActorByKey,
  getRouteCMatrixActorByUserId,
  getRouteCMatrixActorRegistry,
} from "./matrix-room-binding.mjs";
import { readConfig } from "./config.mjs";

const CONFIG_DIR =
  process.env.OYSTERUN_CONFIG_DIR || join(homedir(), ".oysterun");
const STORAGE_SCHEMA_VERSION = "routec.host_owned_matrix_storage.v1";
const OYSTERUN_BRANCH_COPY_NAMESPACE = "org.oysterun.branch_copy.v1";
const ROUTEC_CHECKPOINT_TOKEN_PREFIX = "routec_s";
const DEFAULT_SYNC_TIMELINE_LIMIT = 30;
const DEFAULT_MESSAGES_LIMIT = 30;
const DEFAULT_SEARCH_LIMIT = 20;
const MAX_MATRIX_TIMELINE_LIMIT = 100;
const OYSTERUN_SEMANTIC_NAMESPACE = "org.oysterun.semantic.v1";
const MATRIX_ROOM_CREATE_EVENT_TYPE = "m.room.create";
const MATRIX_ROOM_MESSAGE_EVENT_TYPE = "m.room.message";
const MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE = "m.room.pinned_events";
const MATRIX_ROOM_POWER_LEVELS_STATE_TYPE = "m.room.power_levels";
const MATRIX_STATE_KEY_SEPARATOR = "\u001f";
const ROUTEC_BODY_KEYWORD_SEARCH_CATEGORIES = Object.freeze([
  "message.user",
  "message.assistant",
  "tool.call",
  "tool.result",
  "status",
  "control.request",
  "control.outcome",
]);
const ROUTEC_BODY_KEYWORD_SEARCH_CATEGORY_SET = new Set(
  ROUTEC_BODY_KEYWORD_SEARCH_CATEGORIES
);
const ROUTEC_BODY_KEYWORD_SEARCH_CATEGORY_COMPATIBILITY = new Map([
  ["tool.output", "tool.result"],
  ["tool.failure", "tool.result"],
  ["session_lifecycle", "status"],
  ["runtime.error", "status"],
  ["outbox.delivery", "status"],
  ["ambiguous.stalled", "status"],
  ["control.cancel.request", "control.request"],
  ["control.cancel.outcome", "control.outcome"],
]);

let cachedStorePath = null;
let cachedStoreSize = null;
let cachedStoreMtimeMs = null;
let cachedStore = null;

function isRouteCMatrixStorageCacheEnabled() {
  return readConfig().routec_matrix_storage_cache_enabled !== false;
}

function clearStoreCache() {
  cachedStorePath = null;
  cachedStoreSize = null;
  cachedStoreMtimeMs = null;
  cachedStore = null;
}

class RouteCMatrixStorageClientError extends Error {
  constructor(status, errcode, error) {
    super(error);
    this.name = "RouteCMatrixStorageClientError";
    this.status = status;
    this.body = matrixJsonError(errcode, error);
  }
}

function sha256(value) {
  return createHash("sha256").update(value).digest("hex");
}

function sanitizeStorageSegment(value) {
  return String(value).replace(/[^A-Za-z0-9_.:-]/g, "_");
}

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

function stackRootFromConfigDir() {
  return basename(CONFIG_DIR) === "host" ? dirname(CONFIG_DIR) : CONFIG_DIR;
}

export function getRouteCMatrixStoragePath() {
  const configured = process.env.OYSTERUN_ROUTEC_MATRIX_STORAGE_PATH;
  if (typeof configured === "string" && configured.trim()) {
    return configured.trim();
  }
  return join(stackRootFromConfigDir(), "matrix", "homeserver.db");
}

export function getRouteCMatrixStorageProof({ ensure = false } = {}) {
  const storagePath = getRouteCMatrixStoragePath();
  if (ensure) {
    ensureStore();
  }
  return {
    storage_adapter: "host_owned_routec_matrix_storage",
    storage_schema_version: STORAGE_SCHEMA_VERSION,
    storage_path: storagePath,
    storage_path_source: process.env.OYSTERUN_ROUTEC_MATRIX_STORAGE_PATH
      ? "OYSTERUN_ROUTEC_MATRIX_STORAGE_PATH"
      : "OYSTERUN_CONFIG_DIR_derived_stack_matrix_path",
    stack_owned_matrix_storage: true,
    raw_synapse_base_url_required: false,
    raw_synapse_token_required: false,
    synapse_proxy_attempted: false,
    browser_direct_synapse_dependency: false,
    raw_synapse_token_exposed: false,
    foundation_pass_claimed: false,
  };
}

function initialStore() {
  return {
    schema_version: STORAGE_SCHEMA_VERSION,
    created_at: new Date().toISOString(),
    updated_at: new Date().toISOString(),
    next_stream_seq: 1,
    rooms: {},
    events_by_id: {},
    txn_to_event_id: {},
  };
}

function validateStore(store) {
  if (!store || typeof store !== "object" || Array.isArray(store)) {
    throw new Error("Route C Matrix storage store must be an object");
  }
  if (store.schema_version !== STORAGE_SCHEMA_VERSION) {
    throw new Error(
      `Route C Matrix storage schema mismatch: ${
        store.schema_version || "missing"
      }`
    );
  }
  if (
    !store.rooms ||
    typeof store.rooms !== "object" ||
    Array.isArray(store.rooms)
  ) {
    throw new Error("Route C Matrix storage missing rooms object");
  }
  if (
    !store.events_by_id ||
    typeof store.events_by_id !== "object" ||
    Array.isArray(store.events_by_id)
  ) {
    throw new Error("Route C Matrix storage missing events_by_id object");
  }
  if (
    !store.txn_to_event_id ||
    typeof store.txn_to_event_id !== "object" ||
    Array.isArray(store.txn_to_event_id)
  ) {
    throw new Error("Route C Matrix storage missing txn_to_event_id object");
  }
  if (!Number.isInteger(store.next_stream_seq) || store.next_stream_seq < 1) {
    throw new Error(
      "Route C Matrix storage next_stream_seq must be a positive integer"
    );
  }
}

function readStore() {
  const storagePath = getRouteCMatrixStoragePath();
  if (!existsSync(storagePath)) {
    return initialStore();
  }
  const stats = statSync(storagePath);
  const cacheEnabled = isRouteCMatrixStorageCacheEnabled();
  if (
    cacheEnabled &&
    cachedStore &&
    cachedStorePath === storagePath &&
    cachedStoreSize === stats.size &&
    cachedStoreMtimeMs === stats.mtimeMs
  ) {
    return cachedStore;
  }
  const parsed = JSON.parse(readFileSync(storagePath, "utf8"));
  validateStore(parsed);
  if (cacheEnabled) {
    cachedStorePath = storagePath;
    cachedStoreSize = stats.size;
    cachedStoreMtimeMs = stats.mtimeMs;
    cachedStore = parsed;
  } else {
    clearStoreCache();
  }
  return parsed;
}

function writeStore(store) {
  validateStore(store);
  store.updated_at = new Date().toISOString();
  const storagePath = getRouteCMatrixStoragePath();
  mkdirSync(dirname(storagePath), { recursive: true });
  const tempPath = `${storagePath}.tmp-${process.pid}-${Date.now()}`;
  writeFileSync(tempPath, JSON.stringify(store, null, 2) + "\n");
  renameSync(tempPath, storagePath);
  if (isRouteCMatrixStorageCacheEnabled()) {
    const stats = statSync(storagePath);
    cachedStorePath = storagePath;
    cachedStoreSize = stats.size;
    cachedStoreMtimeMs = stats.mtimeMs;
    cachedStore = store;
  } else {
    clearStoreCache();
  }
}

function ensureStore() {
  writeStore(readStore());
}

function mutateStore(mutator) {
  const store = readStore();
  const result = mutator(store);
  writeStore(store);
  return result;
}

function requireBinding(binding) {
  if (!binding || typeof binding !== "object") {
    throw new Error("Route C Matrix storage requires binding object");
  }
  for (const key of [
    "host_session_id",
    "host_agent_id",
    "matrix_room_id",
    "matrix_user_id",
  ]) {
    if (typeof binding[key] !== "string" || !binding[key].trim()) {
      throw new Error(`Route C Matrix storage binding missing ${key}`);
    }
  }
}

function routeCCheckpointToken(seq) {
  if (!Number.isSafeInteger(seq) || seq < 1) {
    throw new Error(
      `Route C Matrix storage checkpoint sequence must be a positive safe integer: ${seq}`
    );
  }
  return `${ROUTEC_CHECKPOINT_TOKEN_PREFIX}${seq}`;
}

function invalidMatrixParam(error) {
  return new RouteCMatrixStorageClientError(400, "M_INVALID_PARAM", error);
}

function parseRouteCCheckpointToken(token, fieldName) {
  if (typeof token !== "string" || !token.trim()) {
    throw invalidMatrixParam(
      `Route C Matrix storage ${fieldName} checkpoint token is required.`
    );
  }
  const trimmed = token.trim();
  const match = trimmed.match(/^routec_s([1-9]\d*)$/);
  if (!match) {
    throw invalidMatrixParam(
      `Route C Matrix storage ${fieldName} checkpoint token must match routec_s<N>.`
    );
  }
  const seq = Number(match[1]);
  if (!Number.isSafeInteger(seq) || seq < 1) {
    throw invalidMatrixParam(
      `Route C Matrix storage ${fieldName} checkpoint sequence must be a positive safe integer.`
    );
  }
  return seq;
}

function parseOptionalRouteCCheckpointToken(token, fieldName) {
  if (token === null || token === undefined) return null;
  return parseRouteCCheckpointToken(token, fieldName);
}

function optionalSearchParam(url, name) {
  if (!url?.searchParams?.has(name)) return null;
  return url.searchParams.get(name);
}

function assertCheckpointWithinStore(seq, store, fieldName) {
  if (seq === null) return;
  if (seq > store.next_stream_seq) {
    throw invalidMatrixParam(
      `Route C Matrix storage ${fieldName} checkpoint token is beyond the current stream checkpoint.`
    );
  }
}

function normalizeTimelineLimit(rawLimit, defaultLimit) {
  if (rawLimit === null || rawLimit === undefined || rawLimit === "")
    return defaultLimit;
  const numericLimit =
    typeof rawLimit === "number" ? rawLimit : Number(rawLimit);
  if (!Number.isInteger(numericLimit) || numericLimit <= 0) return defaultLimit;
  return Math.min(numericLimit, MAX_MATRIX_TIMELINE_LIMIT);
}

function normalizeNonEmptyString(value) {
  if (typeof value !== "string") return "";
  return value.trim();
}

function cloneJsonObject(value, fieldName) {
  if (!isObject(value)) {
    throw new Error(`${fieldName} must be a JSON object`);
  }
  const encoded = JSON.stringify(value);
  if (encoded === undefined) {
    throw new Error(`${fieldName} is not JSON serializable`);
  }
  const parsed = JSON.parse(encoded);
  if (!isObject(parsed)) {
    throw new Error(`${fieldName} did not round-trip as a JSON object`);
  }
  return parsed;
}

function normalizeStringArrayOrNull(value, fieldName) {
  if (value === null || value === undefined) return null;
  if (!Array.isArray(value)) {
    throw invalidMatrixParam(
      `Route C Matrix storage search ${fieldName} must be an array when supplied.`
    );
  }
  return value.map((entry) => {
    if (typeof entry !== "string" || !entry.trim()) {
      throw invalidMatrixParam(
        `Route C Matrix storage search ${fieldName} must contain non-empty strings.`
      );
    }
    return entry.trim();
  });
}

function syncTimelineLimitFromUrl(url) {
  const rawFilter = url?.searchParams?.get("filter");
  if (typeof rawFilter !== "string" || !rawFilter.trim()) {
    return DEFAULT_SYNC_TIMELINE_LIMIT;
  }
  const trimmedFilter = rawFilter.trim();
  if (!trimmedFilter.startsWith("{")) {
    return DEFAULT_SYNC_TIMELINE_LIMIT;
  }
  let filter;
  try {
    filter = JSON.parse(trimmedFilter);
  } catch (err) {
    throw invalidMatrixParam(
      "Route C Matrix storage sync filter must be valid JSON when supplied inline."
    );
  }
  return normalizeTimelineLimit(
    filter?.room?.timeline?.limit,
    DEFAULT_SYNC_TIMELINE_LIMIT
  );
}

function normalizedMessagesDirection(rawDirection) {
  if (
    rawDirection === null ||
    rawDirection === undefined ||
    rawDirection === ""
  )
    return "b";
  const direction = String(rawDirection).trim();
  if (direction !== "b" && direction !== "f") {
    throw invalidMatrixParam(
      "Route C Matrix storage messages dir must be b or f."
    );
  }
  return direction;
}

function eventStreamSeq(event) {
  if (
    !Number.isSafeInteger(event?.routec_stream_seq) ||
    event.routec_stream_seq < 1
  ) {
    throw new Error(
      `Route C Matrix storage event missing valid routec_stream_seq: ${
        event?.event_id || "unknown"
      }`
    );
  }
  return event.routec_stream_seq;
}

function roomMembershipEventForActor(binding, room, actor) {
  const eventId = `$routec_member_${sha256(
    `${binding.matrix_room_id}:${actor.matrix_user_id}`
  ).slice(0, 32)}`;
  return {
    type: "m.room.member",
    room_id: binding.matrix_room_id,
    sender: actor.matrix_user_id,
    state_key: actor.matrix_user_id,
    content: {
      membership: "join",
      displayname: actor.display_name,
    },
    event_id: eventId,
    origin_server_ts: room.created_at_ms,
    unsigned: {
      routec_host_owned_matrix_storage: true,
      routec_matrix_actor_key: actor.actor_key,
      routec_matrix_actor_kind: actor.actor_kind,
      routec_matrix_actor_registry_version:
        getRouteCMatrixActorRegistry(binding).registry_version,
      committed_transcript_truth: "matrix_room_timeline",
    },
  };
}

function roomMembershipEvents(binding, room) {
  return getRouteCMatrixActorRegistry(binding).actors.map((actor) =>
    roomMembershipEventForActor(binding, room, actor)
  );
}

function roomCreateEventForBinding(binding, room) {
  const eventId = `$routec_room_create_${sha256(binding.matrix_room_id).slice(
    0,
    32
  )}`;
  return {
    type: MATRIX_ROOM_CREATE_EVENT_TYPE,
    room_id: binding.matrix_room_id,
    sender: binding.matrix_user_id,
    state_key: "",
    content: {
      room_version: "11",
    },
    event_id: eventId,
    origin_server_ts: room.created_at_ms,
    unsigned: {
      routec_host_owned_matrix_storage: true,
      routec_room_create_source: "host_owned_routec_matrix_storage",
      host_session_id: binding.host_session_id,
      committed_transcript_truth: "matrix_room_state",
    },
  };
}

function roomNameEventForBinding(binding, room) {
  const name =
    normalizeNonEmptyString(binding.host_session_name) ||
    binding.host_session_id;
  const eventId = `$routec_room_name_${sha256(
    `${binding.matrix_room_id}:${name}`
  ).slice(0, 32)}`;
  return {
    type: "m.room.name",
    room_id: binding.matrix_room_id,
    sender: binding.matrix_user_id,
    state_key: "",
    content: {
      name,
    },
    event_id: eventId,
    origin_server_ts: room.created_at_ms,
    unsigned: {
      routec_host_owned_matrix_storage: true,
      routec_room_name_source: "host_session_name",
      host_session_id: binding.host_session_id,
      committed_transcript_truth: "matrix_room_timeline",
    },
  };
}

function roomPowerLevelsEventForBinding(binding, room) {
  const eventId = `$routec_room_power_levels_${sha256(
    binding.matrix_room_id
  ).slice(0, 32)}`;
  return {
    type: MATRIX_ROOM_POWER_LEVELS_STATE_TYPE,
    room_id: binding.matrix_room_id,
    sender: binding.matrix_user_id,
    state_key: "",
    content: {
      users_default: 0,
      state_default: 50,
      events_default: 0,
      invite: 0,
      redact: 50,
      kick: 50,
      ban: 50,
      events: {
        [MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE]: 0,
      },
    },
    event_id: eventId,
    origin_server_ts: room.created_at_ms,
    unsigned: {
      routec_host_owned_matrix_storage: true,
      routec_room_power_levels_source: "host_owned_routec_matrix_storage",
      routec_pin_messages_permission_source: "matrix_power_levels_state",
      host_session_id: binding.host_session_id,
      committed_transcript_truth: "matrix_room_state",
    },
  };
}

function ensureRoomStateEventsStore(room) {
  if (!room.state_events) {
    room.state_events = {};
  }
  if (
    typeof room.state_events !== "object" ||
    Array.isArray(room.state_events)
  ) {
    throw new Error(
      "Route C Matrix storage room state_events must be an object"
    );
  }
  return room.state_events;
}

function matrixStateStoreKey(eventType, stateKey = "") {
  return `${eventType}${MATRIX_STATE_KEY_SEPARATOR}${stateKey}`;
}

function persistedRoomStateEvents(room) {
  const stateEvents = ensureRoomStateEventsStore(room);
  return Object.values(stateEvents)
    .sort((left, right) => {
      const leftKey = `${left.type || ""}${MATRIX_STATE_KEY_SEPARATOR}${
        left.state_key || ""
      }`;
      const rightKey = `${right.type || ""}${MATRIX_STATE_KEY_SEPARATOR}${
        right.state_key || ""
      }`;
      return leftKey.localeCompare(rightKey);
    })
    .map(eventForClient);
}

function roomStateEvents(binding, room) {
  return [
    roomCreateEventForBinding(binding, room),
    roomPowerLevelsEventForBinding(binding, room),
    roomNameEventForBinding(binding, room),
    ...roomMembershipEvents(binding, room),
    ...persistedRoomStateEvents(room),
  ];
}

function ensureRoomInStore(store, binding) {
  requireBinding(binding);
  const existing = store.rooms[binding.matrix_room_id];
  if (existing) {
    if (existing.host_session_id !== binding.host_session_id) {
      throw new Error(
        "Route C Matrix storage room is already bound to a different Host session"
      );
    }
    if (!Array.isArray(existing.event_ids)) {
      throw new Error("Route C Matrix storage room event_ids must be an array");
    }
    ensureRoomStateEventsStore(existing);
    return existing;
  }
  const now = Date.now();
  const room = {
    room_id: binding.matrix_room_id,
    host_session_id: binding.host_session_id,
    host_agent_id: binding.host_agent_id,
    matrix_user_id: binding.matrix_user_id,
    routec_matrix_actor_registry: getRouteCMatrixActorRegistry(binding),
    created_at: new Date(now).toISOString(),
    created_at_ms: now,
    state_events: {},
    event_ids: [],
  };
  store.rooms[binding.matrix_room_id] = room;
  return room;
}

export function ensureRouteCMatrixRoomStorage({ binding }) {
  return mutateStore((store) => {
    const room = ensureRoomInStore(store, binding);
    return {
      ...getRouteCMatrixStorageProof(),
      room_id: room.room_id,
      host_session_id: room.host_session_id,
      matrix_user_id: room.matrix_user_id,
      routec_matrix_actor_registry: getRouteCMatrixActorRegistry(binding),
      room_ready: true,
    };
  });
}

function requireBranchCopySourceRoom(store, binding) {
  requireBinding(binding);
  const room = store.rooms[binding.matrix_room_id];
  if (!room) {
    throw new Error("Route C branch/copy source Matrix room storage not found");
  }
  if (room.host_session_id !== binding.host_session_id) {
    throw new Error(
      "Route C branch/copy source Matrix room is bound to a different Host session"
    );
  }
  if (!Array.isArray(room.event_ids)) {
    throw new Error(
      "Route C branch/copy source Matrix room event_ids must be an array"
    );
  }
  return room;
}

export function getRouteCMatrixRoomTimelineReplaySourceProof({
  sourceBinding,
}) {
  const store = readStore();
  const room = requireBranchCopySourceRoom(store, sourceBinding);
  const events = room.event_ids.map((eventId) => {
    const event = store.events_by_id[eventId];
    if (!event) {
      throw new Error(
        `Route C branch/copy source Matrix room references missing event: ${eventId}`
      );
    }
    return event;
  });
  boundRoomEventsInStreamOrder(store, sourceBinding);
  return {
    status: "routec_branch_copy_matrix_source_ready",
    source_host_session_id: sourceBinding.host_session_id,
    source_matrix_room_id: sourceBinding.matrix_room_id,
    source_event_count: events.length,
    committed_transcript_truth: "matrix_room_timeline",
    db_transcript_copy_product_truth: false,
    product_local_transcript_replay_shortcut_used: false,
    direct_matrix_harness_write_used: false,
    routec_host_owned_matrix_storage: true,
  };
}

export function readRouteCMatrixRoomTimelinePreview({
  binding,
  limit = DEFAULT_MESSAGES_LIMIT,
}) {
  const store = readStore();
  const room = requireBranchCopySourceRoom(store, binding);
  const normalizedLimit = normalizeTimelineLimit(limit, DEFAULT_MESSAGES_LIMIT);
  const events = room.event_ids.map((eventId) => {
    const event = store.events_by_id[eventId];
    if (!event) {
      throw new Error(
        `Route C Matrix preview source room references missing event: ${eventId}`
      );
    }
    return event;
  });
  let previousSeq = 0;
  for (const event of events) {
    const seq = eventStreamSeq(event);
    if (seq <= previousSeq) {
      throw new Error(
        "Route C Matrix preview source room events are not in strictly increasing stream order"
      );
    }
    previousSeq = seq;
  }
  const chunk = events.slice(-normalizedLimit);
  return {
    chunk: chunk.map((event) => {
      const clientEvent = eventForClient(event);
      clientEvent.unsigned = {
        ...(clientEvent.unsigned || {}),
        routec_stream_seq: eventStreamSeq(event),
        routec_host_owned_matrix_storage: true,
        host_owned_matrix_json_timeline_read: true,
      };
      return clientEvent;
    }),
    routec_messages_checkpoint_proof: {
      checkpoint_token_format: "routec_s<N>",
      start: routeCCheckpointToken(
        chunk.length > 0 ? eventStreamSeq(chunk[0]) : store.next_stream_seq
      ),
      end: routeCCheckpointToken(store.next_stream_seq),
      current_next_batch: routeCCheckpointToken(store.next_stream_seq),
      current_next_batch_seq: store.next_stream_seq,
      limit: normalizedLimit,
      returned_event_count: chunk.length,
      total_bound_room_event_count: events.length,
      matrix_room_id_hash: sha256(binding.matrix_room_id),
      host_session_id: binding.host_session_id,
      committed_transcript_truth: "matrix_room_timeline",
      durable_matrix_source: "host_owned_routec_matrix_json_timeline",
      host_owned_matrix_json_timeline_read: true,
      storage_adapter_call_graph_used: true,
      storage_adapter_call_graph_role: "host_owned_matrix_json_timeline_read",
      preview_read_only: true,
      mutation_performed: false,
      host_db_transcript_product_truth: false,
    },
  };
}

function eventIdForCommit({ roomId, txnId, eventType, content }) {
  const contentHash = sha256(JSON.stringify(content || {}));
  return `$routec_${sha256(
    `${roomId}:${eventType}:${txnId}:${contentHash}`
  ).slice(0, 40)}`;
}

function resolveCommitSender({
  binding,
  senderMatrixUserId,
  senderActorKey,
  tokenRecord,
}) {
  const requestedSender = senderMatrixUserId || binding.matrix_user_id;
  if (tokenRecord && requestedSender !== tokenRecord.matrix_user_id) {
    throw invalidMatrixParam(
      "Route C Matrix facade token cannot send as a non-human actor."
    );
  }
  const actor = getRouteCMatrixActorByUserId(binding, requestedSender);
  if (!actor) {
    throw invalidMatrixParam(
      "Route C Matrix storage sender is not a bound room actor."
    );
  }
  if (senderActorKey && actor.actor_key !== senderActorKey) {
    throw invalidMatrixParam(
      "Route C Matrix storage sender actor key does not match sender user id."
    );
  }
  return actor;
}

function commitRoomEventInStore(
  store,
  {
    binding,
    roomId,
    txnId,
    eventType,
    content,
    senderMatrixUserId = null,
    senderActorKey = null,
    tokenRecord = null,
    branchCopyLineage = null,
  }
) {
  requireBinding(binding);
  if (roomId !== binding.matrix_room_id) {
    throw new Error(
      "Route C Matrix storage send room does not match bound room"
    );
  }
  if (!txnId || !eventType) {
    throw new Error("Route C Matrix storage send requires txnId and eventType");
  }
  const room = ensureRoomInStore(store, binding);
  const senderActor = resolveCommitSender({
    binding,
    senderMatrixUserId,
    senderActorKey,
    tokenRecord,
  });
  const txnKey = `${roomId}\u001f${eventType}\u001f${txnId}`;
  const existingEventId = store.txn_to_event_id[txnKey];
  if (existingEventId) {
    const existingEvent = store.events_by_id[existingEventId];
    if (!existingEvent) {
      throw new Error(
        "Route C Matrix storage txn index points to missing event"
      );
    }
    if (existingEvent.sender !== senderActor.matrix_user_id) {
      throw invalidMatrixParam(
        "Route C Matrix storage duplicate txn sender mismatch."
      );
    }
    return {
      event: existingEvent,
      duplicate_txn: true,
    };
  }
  const eventId = eventIdForCommit({ roomId, txnId, eventType, content });
  const lineage = branchCopyLineage
    ? cloneJsonObject(branchCopyLineage, "Route C branch/copy lineage")
    : null;
  const now = Date.now();
  const event = {
    type: eventType,
    room_id: roomId,
    sender: senderActor.matrix_user_id,
    content: content || {},
    event_id: eventId,
    origin_server_ts: now,
    unsigned: {
      transaction_id: txnId,
      routec_host_owned_matrix_storage: true,
      host_session_id: binding.host_session_id,
      routec_matrix_actor_key: senderActor.actor_key,
      routec_matrix_actor_kind: senderActor.actor_kind,
      routec_matrix_actor_display_name: senderActor.display_name,
      routec_matrix_actor_sender_source: senderActor.sender_source,
      committed_transcript_truth: "matrix_room_timeline",
      ...(lineage ? { routec_branch_copy_lineage: lineage } : {}),
    },
    routec_stream_seq: store.next_stream_seq,
  };
  store.next_stream_seq += 1;
  store.events_by_id[eventId] = event;
  store.txn_to_event_id[txnKey] = eventId;
  room.event_ids.push(eventId);
  return {
    event,
    duplicate_txn: false,
  };
}

function commitRoomEvent(args) {
  return mutateStore((store) => commitRoomEventInStore(store, args));
}

function stateEventIdForCommit({ roomId, eventType, stateKey, content }) {
  const contentHash = sha256(JSON.stringify(content || {}));
  return `$routec_state_${sha256(
    `${roomId}:${eventType}:${stateKey}:${contentHash}`
  ).slice(0, 40)}`;
}

function normalizePinnedEventsContent(store, room, binding, content) {
  if (!isObject(content)) {
    throw invalidMatrixParam(
      "Route C Matrix pinned events state content must be a JSON object."
    );
  }
  const contentKeys = Object.keys(content);
  if (contentKeys.length !== 1 || contentKeys[0] !== "pinned") {
    throw invalidMatrixParam(
      "Route C Matrix pinned events state content must contain only pinned."
    );
  }
  if (!Array.isArray(content.pinned)) {
    throw invalidMatrixParam(
      "Route C Matrix pinned events content.pinned must be a string array."
    );
  }

  const seen = new Set();
  const pinned = content.pinned.map((eventId) => {
    if (
      typeof eventId !== "string" ||
      !eventId.trim() ||
      eventId.trim() !== eventId
    ) {
      throw invalidMatrixParam(
        "Route C Matrix pinned event IDs must be non-empty strings."
      );
    }
    if (seen.has(eventId)) {
      throw invalidMatrixParam(
        "Route C Matrix pinned event IDs must not contain duplicates."
      );
    }
    seen.add(eventId);
    const event = store.events_by_id[eventId];
    if (!event || !room.event_ids.includes(eventId)) {
      throw invalidMatrixParam(
        "Route C Matrix pinned event must exist in the bound room timeline."
      );
    }
    if (event.room_id !== binding.matrix_room_id) {
      throw invalidMatrixParam(
        "Route C Matrix pinned event must belong to the bound room."
      );
    }
    if (event.type !== MATRIX_ROOM_MESSAGE_EVENT_TYPE) {
      throw invalidMatrixParam(
        "Route C Matrix pinned event type is not allowed."
      );
    }
    if (
      event.unsigned?.redacted_because ||
      event.unsigned?.routec_redacted === true ||
      event.redacts
    ) {
      throw invalidMatrixParam(
        "Route C Matrix redacted events cannot be pinned."
      );
    }
    return eventId;
  });

  return { pinned };
}

function pinnedEventsStateProof({ binding, pinned, eventId }) {
  return {
    state_event_type: MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE,
    state_key: "",
    event_id: eventId,
    pinned_event_ids: pinned,
    pinned_event_count: pinned.length,
    bound_matrix_room_id: binding.matrix_room_id,
    host_session_id: binding.host_session_id,
    routec_host_owned_matrix_storage: true,
    committed_transcript_truth: "matrix_room_state",
    host_db_only_pin_truth: false,
    local_react_only_pin_truth: false,
    direct_matrix_bypass_write: false,
    unsafe_event_type_pinning: false,
  };
}

function writePinnedEventsState({ binding, roomId, content }) {
  return mutateStore((store) => {
    const room = ensureRoomInStore(store, binding);
    if (roomId !== binding.matrix_room_id) {
      throw invalidMatrixParam(
        "Route C Matrix pinned events state room does not match binding."
      );
    }
    const normalizedContent = normalizePinnedEventsContent(
      store,
      room,
      binding,
      content
    );
    const stateEvents = ensureRoomStateEventsStore(room);
    const eventId = stateEventIdForCommit({
      roomId,
      eventType: MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE,
      stateKey: "",
      content: normalizedContent,
    });
    const senderActor = getRouteCMatrixActorByUserId(
      binding,
      binding.matrix_user_id
    );
    if (!senderActor) {
      throw invalidMatrixParam(
        "Route C Matrix pinned events sender is not a bound room actor."
      );
    }
    const event = {
      type: MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE,
      room_id: roomId,
      sender: senderActor.matrix_user_id,
      state_key: "",
      content: normalizedContent,
      event_id: eventId,
      origin_server_ts: Date.now(),
      unsigned: {
        routec_host_owned_matrix_storage: true,
        routec_matrix_actor_key: senderActor.actor_key,
        routec_matrix_actor_kind: senderActor.actor_kind,
        routec_matrix_actor_display_name: senderActor.display_name,
        routec_matrix_actor_sender_source: senderActor.sender_source,
        host_session_id: binding.host_session_id,
        committed_transcript_truth: "matrix_room_state",
        host_db_only_pin_truth: false,
        local_react_only_pin_truth: false,
        direct_matrix_bypass_write: false,
        unsafe_event_type_pinning: false,
      },
      routec_state_seq: store.next_stream_seq,
    };
    stateEvents[matrixStateStoreKey(MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE, "")] =
      event;
    return {
      event_id: eventId,
      committed_transcript_truth: "matrix_room_state",
      routec_pinned_events_state_proof: pinnedEventsStateProof({
        binding,
        pinned: normalizedContent.pinned,
        eventId,
      }),
    };
  });
}

function readPinnedEventsState({ binding, roomId }) {
  return mutateStore((store) => {
    const room = ensureRoomInStore(store, binding);
    if (roomId !== binding.matrix_room_id) {
      return {
        status: 403,
        body: matrixJsonError(
          "M_FORBIDDEN",
          "Route C Matrix pinned events state room does not match binding."
        ),
      };
    }
    const stateEvents = ensureRoomStateEventsStore(room);
    const event =
      stateEvents[
        matrixStateStoreKey(MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE, "")
      ];
    if (!event) {
      return {
        status: 404,
        body: matrixJsonError(
          "M_NOT_FOUND",
          "Route C Matrix pinned events state not found."
        ),
      };
    }
    return {
      status: 200,
      body: cloneJsonObject(
        event.content,
        "Route C Matrix pinned events state content"
      ),
    };
  });
}

function resolveBranchCopySourceActorKey({ sourceBinding, sourceEvent }) {
  const unsignedActorKey = normalizeNonEmptyString(
    sourceEvent?.unsigned?.routec_matrix_actor_key
  );
  if (unsignedActorKey) return unsignedActorKey;
  const sourceActor = getRouteCMatrixActorByUserId(
    sourceBinding,
    sourceEvent?.sender
  );
  if (!sourceActor) {
    throw new Error(
      `Route C branch/copy source Matrix sender is not a bound actor: ${
        sourceEvent?.sender || "missing"
      }`
    );
  }
  return sourceActor.actor_key;
}

function resolveBranchCopyTargetActor({ targetBinding, sourceActorKey }) {
  const targetActor = getRouteCMatrixActorByKey(targetBinding, sourceActorKey);
  if (!targetActor) {
    throw new Error(
      `Route C branch/copy target Matrix room is missing actor key: ${sourceActorKey}`
    );
  }
  return targetActor;
}

function buildBranchCopyLineage({
  sourceBinding,
  targetBinding,
  sourceEvent,
  sourceIndex,
}) {
  return {
    branch_copy_contract: "routec.p4_1.matrix_timeline_replay.v1",
    parent_session_id: sourceBinding.host_session_id,
    child_session_id: targetBinding.host_session_id,
    source_host_session_id: sourceBinding.host_session_id,
    target_host_session_id: targetBinding.host_session_id,
    source_matrix_room_id: sourceBinding.matrix_room_id,
    target_matrix_room_id: targetBinding.matrix_room_id,
    source_event_id: sourceEvent.event_id,
    source_event_type: sourceEvent.type,
    source_event_index: sourceIndex,
    source_origin_server_ts: sourceEvent.origin_server_ts ?? null,
    source_routec_stream_seq: sourceEvent.routec_stream_seq ?? null,
    committed_transcript_truth: "matrix_room_timeline",
    db_transcript_copy_product_truth: false,
    product_local_transcript_replay_shortcut_used: false,
    direct_matrix_harness_write_used: false,
  };
}

function rewriteBranchCopyContent({
  content,
  sourceBinding,
  targetBinding,
  targetActor,
  lineage,
}) {
  const cloned = cloneJsonObject(
    content,
    "Route C branch/copy Matrix event content"
  );
  const semantic = cloned[OYSTERUN_SEMANTIC_NAMESPACE];
  if (isObject(semantic)) {
    semantic.branch_copy_lineage = cloneJsonObject(
      lineage,
      "Route C branch/copy semantic lineage"
    );
    semantic.branch_copy_source_semantic_id = semantic.semantic_id || null;
    semantic.branch_copy_source_host_session_id = sourceBinding.host_session_id;
    semantic.branch_copy_source_matrix_room_id = sourceBinding.matrix_room_id;
    semantic.branch_copy_source_event_id = lineage.source_event_id;
    semantic.host_session_id = targetBinding.host_session_id;
    semantic.matrix_room_id = targetBinding.matrix_room_id;
    if (
      !normalizeNonEmptyString(semantic.target_session_id) ||
      semantic.target_session_id === sourceBinding.host_session_id
    ) {
      semantic.target_session_id = targetBinding.host_session_id;
    }
    semantic.matrix_event_sender = targetActor.matrix_user_id;
    semantic.matrix_event_sender_actor_key = targetActor.actor_key;
    semantic.matrix_event_sender_actor_kind = targetActor.actor_kind;
    semantic.matrix_event_sender_display_name = targetActor.display_name;
    semantic.matrix_event_sender_source = targetActor.sender_source;
    semantic.committed_transcript_truth = "matrix_room_timeline";
  }
  cloned[OYSTERUN_BRANCH_COPY_NAMESPACE] = cloneJsonObject(
    lineage,
    "Route C branch/copy content lineage"
  );
  return cloned;
}

export function copyRouteCMatrixRoomTimeline({
  sourceBinding,
  targetBinding,
  parentSessionId,
  childSessionId,
}) {
  requireBinding(sourceBinding);
  requireBinding(targetBinding);
  if (sourceBinding.host_session_id !== parentSessionId) {
    throw new Error(
      "Route C branch/copy source binding does not match parent_session_id"
    );
  }
  if (targetBinding.host_session_id !== childSessionId) {
    throw new Error(
      "Route C branch/copy target binding does not match child session id"
    );
  }
  if (sourceBinding.matrix_room_id === targetBinding.matrix_room_id) {
    throw new Error("Route C branch/copy requires a new Matrix room id");
  }
  return mutateStore((store) => {
    const sourceRoom = requireBranchCopySourceRoom(store, sourceBinding);
    const sourceEventIdsBefore = [...sourceRoom.event_ids];
    const sourceEvents = boundRoomEventsInStreamOrder(store, sourceBinding);
    const targetRoom = ensureRoomInStore(store, targetBinding);
    const copiedEvents = [];
    sourceEvents.forEach((sourceEvent, index) => {
      if (!sourceEvent?.event_id || !sourceEvent?.type) {
        throw new Error(
          "Route C branch/copy source Matrix event requires event_id and type"
        );
      }
      const sourceActorKey = resolveBranchCopySourceActorKey({
        sourceBinding,
        sourceEvent,
      });
      const targetActor = resolveBranchCopyTargetActor({
        targetBinding,
        sourceActorKey,
      });
      const lineage = buildBranchCopyLineage({
        sourceBinding,
        targetBinding,
        sourceEvent,
        sourceIndex: index,
      });
      const content = rewriteBranchCopyContent({
        content: sourceEvent.content,
        sourceBinding,
        targetBinding,
        targetActor,
        lineage,
      });
      const txnId = `routec_branch_copy_${sha256(
        JSON.stringify({
          parent_session_id: parentSessionId,
          child_session_id: childSessionId,
          source_event_id: sourceEvent.event_id,
          source_index: index,
        })
      ).slice(0, 32)}`;
      const committed = commitRoomEventInStore(store, {
        binding: targetBinding,
        roomId: targetBinding.matrix_room_id,
        txnId,
        eventType: sourceEvent.type,
        content,
        senderMatrixUserId: targetActor.matrix_user_id,
        senderActorKey: targetActor.actor_key,
        branchCopyLineage: lineage,
      });
      copiedEvents.push({
        source_event_id: sourceEvent.event_id,
        target_event_id: committed.event.event_id,
        source_event_type: sourceEvent.type,
        source_event_index: index,
        source_actor_key: sourceActorKey,
        target_actor_key: targetActor.actor_key,
        duplicate_txn: committed.duplicate_txn,
      });
    });
    const sourceRoomUnchanged =
      sourceRoom.event_ids.length === sourceEventIdsBefore.length &&
      sourceRoom.event_ids.every(
        (eventId, index) => eventId === sourceEventIdsBefore[index]
      );
    return {
      status: "routec_branch_copy_matrix_timeline_replayed",
      parent_session_id: parentSessionId,
      child_session_id: childSessionId,
      source_matrix_room_id: sourceBinding.matrix_room_id,
      target_matrix_room_id: targetBinding.matrix_room_id,
      source_event_count: sourceEvents.length,
      copied_event_count: copiedEvents.length,
      child_room_event_count: targetRoom.event_ids.length,
      source_room_unchanged: sourceRoomUnchanged,
      copied_events: copiedEvents,
      committed_transcript_truth: "matrix_room_timeline",
      db_transcript_copy_product_truth: false,
      product_local_transcript_replay_shortcut_used: false,
      direct_matrix_harness_write_used: false,
      routec_host_owned_matrix_storage: true,
    };
  });
}

function eventForClient(event) {
  const {
    routec_stream_seq: _routecStreamSeq,
    routec_state_seq: _routecStateSeq,
    ...clientEvent
  } = event;
  return clientEvent;
}

function normalizeRouteCSearchCategory(value) {
  const normalized = normalizeNonEmptyString(value);
  if (!normalized) return null;
  if (ROUTEC_BODY_KEYWORD_SEARCH_CATEGORY_SET.has(normalized))
    return normalized;
  return (
    ROUTEC_BODY_KEYWORD_SEARCH_CATEGORY_COMPATIBILITY.get(normalized) || null
  );
}

function routeCSearchCategoryForEvent(event, binding) {
  const content = isObject(event?.content) ? event.content : {};
  const semantic = isObject(content[OYSTERUN_SEMANTIC_NAMESPACE])
    ? content[OYSTERUN_SEMANTIC_NAMESPACE]
    : {};
  const candidates = [
    semantic.semantic_category,
    semantic.semantic_type,
    content.semantic_category,
    content.semantic_type,
    content.category,
    content.type,
    event?.type,
  ];
  for (const candidate of candidates) {
    const category = normalizeRouteCSearchCategory(candidate);
    if (category) return category;
  }
  if (
    event?.type === "m.room.message" &&
    event?.sender === binding.matrix_user_id &&
    content.msgtype === "m.text" &&
    typeof content.body === "string"
  ) {
    return "message.user";
  }
  return null;
}

function addSearchableBody(values, value) {
  if (typeof value === "string" && value.trim()) {
    values.push(value);
  }
}

function routeCSearchableBodiesForEvent(event) {
  const content = isObject(event?.content) ? event.content : {};
  const semantic = isObject(content[OYSTERUN_SEMANTIC_NAMESPACE])
    ? content[OYSTERUN_SEMANTIC_NAMESPACE]
    : {};
  const bodies = [];
  addSearchableBody(bodies, content.body);
  addSearchableBody(bodies, semantic.body);
  addSearchableBody(bodies, semantic.text);
  addSearchableBody(bodies, semantic.summary);
  return [...new Set(bodies)];
}

function extractRoomEventsSearchRequest({ body, requestedNextBatchToken }) {
  if (!isObject(body)) {
    throw invalidMatrixParam(
      "Route C Matrix storage search body must be a JSON object."
    );
  }
  if (!isObject(body.search_categories)) {
    throw invalidMatrixParam(
      "Route C Matrix storage search_categories object is required."
    );
  }
  if (!isObject(body.search_categories.room_events)) {
    throw invalidMatrixParam(
      "Route C Matrix storage room_events search category is required."
    );
  }
  const roomEvents = body.search_categories.room_events;
  const searchTerm = normalizeNonEmptyString(roomEvents.search_term);
  if (!searchTerm) {
    throw invalidMatrixParam(
      "Route C Matrix storage room_events search_term is required."
    );
  }
  if (
    roomEvents.filter !== null &&
    roomEvents.filter !== undefined &&
    !isObject(roomEvents.filter)
  ) {
    throw invalidMatrixParam(
      "Route C Matrix storage room_events filter must be an object when supplied."
    );
  }
  const filter = isObject(roomEvents.filter) ? roomEvents.filter : {};
  const roomFilter = normalizeStringArrayOrNull(filter.rooms, "filter.rooms");
  const senderFilter = normalizeStringArrayOrNull(
    filter.senders,
    "filter.senders"
  );
  const bodyNextBatchToken =
    normalizeNonEmptyString(roomEvents.next_batch) || null;
  const effectiveNextBatchToken = requestedNextBatchToken ?? bodyNextBatchToken;
  const nextBatchSeq = parseOptionalRouteCCheckpointToken(
    effectiveNextBatchToken,
    "next_batch"
  );
  return {
    searchTerm,
    limit: normalizeTimelineLimit(filter.limit, DEFAULT_SEARCH_LIMIT),
    orderBy: normalizeNonEmptyString(roomEvents.order_by) || "recent",
    requestedNextBatchToken: effectiveNextBatchToken,
    nextBatchSeq,
    roomFilter,
    senderFilter,
  };
}

function searchEventMatches({
  event,
  binding,
  searchTermLower,
  roomFilterSet,
  senderFilterSet,
}) {
  if (roomFilterSet && !roomFilterSet.has(event.room_id)) return null;
  if (senderFilterSet && !senderFilterSet.has(event.sender)) return null;
  const category = routeCSearchCategoryForEvent(event, binding);
  if (!category) return null;
  const bodies = routeCSearchableBodiesForEvent(event);
  const matched = bodies.some((body) =>
    body.toLowerCase().includes(searchTermLower)
  );
  if (!matched) return null;
  return { event, category };
}

function buildSearchResult(match) {
  const seq = eventStreamSeq(match.event);
  return {
    rank: 1,
    result: eventForClient(match.event),
    context: {
      start: routeCCheckpointToken(seq),
      end: routeCCheckpointToken(seq + 1),
      events_before: [],
      events_after: [],
      profile_info: {},
    },
  };
}

function searchRoomEvents({ binding, body, requestedNextBatchToken }) {
  return mutateStore((store) => {
    ensureRoomInStore(store, binding);
    const request = extractRoomEventsSearchRequest({
      body,
      requestedNextBatchToken,
    });
    assertCheckpointWithinStore(request.nextBatchSeq, store, "next_batch");
    const events = boundRoomEventsInStreamOrder(store, binding);
    const roomFilterSet = request.roomFilter
      ? new Set(request.roomFilter)
      : null;
    const senderFilterSet = request.senderFilter
      ? new Set(request.senderFilter)
      : null;
    const matches = events
      .slice()
      .reverse()
      .filter(
        (event) =>
          request.nextBatchSeq === null ||
          eventStreamSeq(event) < request.nextBatchSeq
      )
      .map((event) =>
        searchEventMatches({
          event,
          binding,
          searchTermLower: request.searchTerm.toLowerCase(),
          roomFilterSet,
          senderFilterSet,
        })
      )
      .filter(Boolean);
    const selected = matches.slice(0, request.limit);
    const nextBatch =
      matches.length > selected.length
        ? routeCCheckpointToken(
            eventStreamSeq(selected[selected.length - 1].event)
          )
        : null;
    const matchedCategories = [
      ...new Set(matches.map((match) => match.category)),
    ].sort();
    const proof = {
      checkpoint_token_format: "routec_s<N>",
      requested_next_batch: request.requestedNextBatchToken,
      next_batch: nextBatch,
      order_by: request.orderBy,
      limit: request.limit,
      returned_event_count: selected.length,
      total_match_count: matches.length,
      total_bound_room_event_count: events.length,
      bound_matrix_room_id: binding.matrix_room_id,
      requested_room_filter: request.roomFilter,
      requested_room_filter_includes_bound_room: request.roomFilter
        ? request.roomFilter.includes(binding.matrix_room_id)
        : null,
      requested_sender_filter: request.senderFilter,
      searched_body_fields: [
        "content.body",
        "org.oysterun.semantic.v1.body",
        "org.oysterun.semantic.v1.text",
        "org.oysterun.semantic.v1.summary",
      ],
      searched_categories: ROUTEC_BODY_KEYWORD_SEARCH_CATEGORIES,
      matched_categories: matchedCategories,
      deterministic_event_ids_preserved: true,
      host_session_room_binding_preserved: true,
      product_local_transcript_replay_shortcut_used: false,
      foundation_pass_claimed: false,
    };
    return {
      search_categories: {
        room_events: {
          count: matches.length,
          highlights: [request.searchTerm],
          results: selected.map(buildSearchResult),
          state: {},
          groups: {},
          next_batch: nextBatch,
          routec_body_keyword_search_proof: proof,
        },
      },
      routec_body_keyword_search_proof: proof,
      routec_host_owned_matrix_storage: true,
      synapse_proxy_attempted: false,
      browser_direct_synapse_dependency: false,
      raw_synapse_token_exposed: false,
      foundation_pass_claimed: false,
    };
  });
}

function boundRoomEvents(store, binding) {
  const room = ensureRoomInStore(store, binding);
  return room.event_ids.map((eventId) => {
    const event = store.events_by_id[eventId];
    if (!event) {
      throw new Error(
        `Route C Matrix storage room index references missing event: ${eventId}`
      );
    }
    return event;
  });
}

function boundRoomEventsInStreamOrder(store, binding) {
  const events = boundRoomEvents(store, binding);
  let previousSeq = 0;
  for (const event of events) {
    const seq = eventStreamSeq(event);
    if (seq <= previousSeq) {
      throw new Error(
        "Route C Matrix storage room events are not in strictly increasing stream order"
      );
    }
    previousSeq = seq;
  }
  return events;
}

function emptyMessagesEndSeq({ direction, startSeq, toSeq }) {
  if (toSeq === null) return startSeq;
  if (direction === "b" && toSeq < startSeq) return toSeq;
  if (direction === "f" && toSeq > startSeq) return toSeq;
  return startSeq;
}

function selectMessagesChunk({ events, startSeq, toSeq, direction, limit }) {
  if (direction === "b") {
    const candidates = events
      .filter((event) => {
        const seq = eventStreamSeq(event);
        return seq < startSeq && (toSeq === null || seq >= toSeq);
      })
      .reverse();
    const chunk = candidates.slice(0, limit);
    const endSeq =
      chunk.length > 0
        ? eventStreamSeq(chunk[chunk.length - 1])
        : emptyMessagesEndSeq({ direction, startSeq, toSeq });
    return { chunk, endSeq };
  }

  const candidates = events.filter((event) => {
    const seq = eventStreamSeq(event);
    return seq >= startSeq && (toSeq === null || seq < toSeq);
  });
  const chunk = candidates.slice(0, limit);
  const endSeq =
    chunk.length > 0
      ? eventStreamSeq(chunk[chunk.length - 1]) + 1
      : emptyMessagesEndSeq({ direction, startSeq, toSeq });
  return { chunk, endSeq };
}

function buildTimelineForSync({ events, sinceSeq, limit }) {
  if (sinceSeq !== null) {
    const incrementalEvents = events.filter(
      (event) => eventStreamSeq(event) >= sinceSeq
    );
    return {
      events: incrementalEvents,
      limited: false,
      prevBatchSeq: sinceSeq,
      syncMode: "incremental_since_checkpoint",
    };
  }

  const tail = events.slice(-limit);
  return {
    events: tail,
    limited: events.length > tail.length,
    prevBatchSeq: tail.length > 0 ? eventStreamSeq(tail[0]) : 1,
    syncMode: "bounded_initial_tail",
  };
}

function buildSyncBody({
  binding,
  sinceSeq,
  requestedSinceToken,
  timelineLimit,
}) {
  return mutateStore((store) => {
    const room = ensureRoomInStore(store, binding);
    assertCheckpointWithinStore(sinceSeq, store, "since");
    const events = boundRoomEventsInStreamOrder(store, binding);
    const timeline = buildTimelineForSync({
      events,
      sinceSeq,
      limit: timelineLimit,
    });
    const memberships = roomMembershipEvents(binding, room);
    const stateEvents = roomStateEvents(binding, room);
    const nextBatch = routeCCheckpointToken(store.next_stream_seq);
    const prevBatch = routeCCheckpointToken(timeline.prevBatchSeq);
    return {
      next_batch: nextBatch,
      rooms: {
        join: {
          [binding.matrix_room_id]: {
            timeline: {
              events: timeline.events.map(eventForClient),
              limited: timeline.limited,
              prev_batch: prevBatch,
            },
            state: {
              events: stateEvents,
            },
            ephemeral: {
              events: [],
            },
            account_data: {
              events: [],
            },
            unread_notifications: {
              notification_count: 0,
              highlight_count: 0,
            },
            summary: {
              "m.joined_member_count": memberships.length,
              "m.invited_member_count": 0,
            },
          },
        },
        invite: {},
        leave: {},
        knock: {},
      },
      presence: {
        events: [],
      },
      account_data: {
        events: [],
      },
      to_device: {
        events: [],
      },
      device_lists: {
        changed: [],
        left: [],
      },
      device_one_time_keys_count: {},
      routec_host_owned_matrix_storage: true,
      routec_sync_checkpoint_proof: {
        checkpoint_token_format: "routec_s<N>",
        requested_since: requestedSinceToken,
        since_seq: sinceSeq,
        next_batch: nextBatch,
        next_batch_seq: store.next_stream_seq,
        prev_batch: prevBatch,
        prev_batch_seq: timeline.prevBatchSeq,
        sync_mode: timeline.syncMode,
        timeline_limit: timelineLimit,
        returned_event_count: timeline.events.length,
        total_bound_room_event_count: events.length,
        bound_matrix_room_id: binding.matrix_room_id,
        routec_matrix_actor_registry_version:
          getRouteCMatrixActorRegistry(binding).registry_version,
        routec_matrix_actor_count: memberships.length,
        deterministic_event_ids_preserved: true,
        host_session_room_binding_preserved: true,
        product_local_transcript_replay_shortcut_used: false,
        foundation_pass_claimed: false,
      },
      synapse_proxy_attempted: false,
      browser_direct_synapse_dependency: false,
      raw_synapse_token_exposed: false,
      foundation_pass_claimed: false,
    };
  });
}

function readEvent({ binding, eventId }) {
  return mutateStore((store) => {
    ensureRoomInStore(store, binding);
    const event = store.events_by_id[eventId];
    if (!event || event.room_id !== binding.matrix_room_id) {
      return null;
    }
    return eventForClient(event);
  });
}

export function readRouteCMatrixRoomMessages({
  binding,
  limit,
  direction,
  fromSeq,
  requestedFromToken,
  toSeq,
  requestedToToken,
}) {
  const store = readStore();
  requireBranchCopySourceRoom(store, binding);
  assertCheckpointWithinStore(fromSeq, store, "from");
  assertCheckpointWithinStore(toSeq, store, "to");
  const normalizedLimit = normalizeTimelineLimit(limit, DEFAULT_MESSAGES_LIMIT);
  const events = boundRoomEventsInStreamOrder(store, binding);
  const startSeq = fromSeq ?? (direction === "f" ? 1 : store.next_stream_seq);
  const selected = selectMessagesChunk({
    events,
    startSeq,
    toSeq,
    direction,
    limit: normalizedLimit,
  });
  const latestBoundRoomStreamSeq = events.reduce(
    (latest, event) => Math.max(latest, eventStreamSeq(event)),
    0
  );
  const earliestBoundRoomStreamSeq =
    events.length > 0 ? eventStreamSeq(events[0]) : 0;
  const start = routeCCheckpointToken(startSeq);
  const end = routeCCheckpointToken(selected.endSeq);
  return {
    start,
    end,
    chunk: selected.chunk.map((event) => {
      const clientEvent = eventForClient(event);
      clientEvent.unsigned = {
        ...(clientEvent.unsigned || {}),
        routec_stream_seq: eventStreamSeq(event),
        routec_host_owned_matrix_storage: true,
        host_owned_matrix_json_timeline_read: true,
      };
      return clientEvent;
    }),
    state: [],
    routec_messages_checkpoint_proof: {
      checkpoint_token_format: "routec_s<N>",
      requested_from: requestedFromToken,
      requested_to: requestedToToken,
      requested_dir: direction,
      start,
      end,
      start_seq: startSeq,
      end_seq: selected.endSeq,
      current_next_batch: routeCCheckpointToken(store.next_stream_seq),
      current_next_batch_seq: store.next_stream_seq,
      limit: normalizedLimit,
      returned_event_count: selected.chunk.length,
      total_bound_room_event_count: events.length,
      earliest_bound_room_stream_seq: earliestBoundRoomStreamSeq,
      latest_bound_room_stream_seq: latestBoundRoomStreamSeq,
      bound_matrix_room_id: binding.matrix_room_id,
      deterministic_event_ids_preserved: true,
      host_session_room_binding_preserved: true,
      host_owned_matrix_json_timeline_read: true,
      host_db_transcript_product_truth: false,
      product_local_transcript_replay_shortcut_used: false,
      foundation_pass_claimed: false,
    },
    routec_host_owned_matrix_storage: true,
    synapse_proxy_attempted: false,
    browser_direct_synapse_dependency: false,
    raw_synapse_token_exposed: false,
    foundation_pass_claimed: false,
  };
}

function matrixJsonError(errcode, error) {
  return { errcode, error };
}

function extractRoomSendPathParts(path) {
  const match = path.match(
    /^\/_matrix\/client\/v3\/rooms\/([^/]+)\/send\/([^/]+)\/([^/]+)$/
  );
  if (!match) return null;
  return {
    roomId: decodeURIComponent(match[1]),
    eventType: decodeURIComponent(match[2]),
    txnId: decodeURIComponent(match[3]),
  };
}

function extractPinnedEventsStatePathParts(path) {
  const match = path.match(
    /^\/_matrix\/client\/v3\/rooms\/([^/]+)\/state\/m\.room\.pinned_events\/?$/
  );
  if (!match) return null;
  return {
    roomId: decodeURIComponent(match[1]),
    eventType: MATRIX_ROOM_PINNED_EVENTS_STATE_TYPE,
    stateKey: "",
  };
}

function extractEventPathParts(path) {
  const match = path.match(
    /^\/_matrix\/client\/v3\/rooms\/([^/]+)\/event\/([^/]+)$/
  );
  if (!match) return null;
  return {
    roomId: decodeURIComponent(match[1]),
    eventId: decodeURIComponent(match[2]),
  };
}

function extractMessagesPathParts(path) {
  const match = path.match(/^\/_matrix\/client\/v3\/rooms\/([^/]+)\/messages$/);
  if (!match) return null;
  return {
    roomId: decodeURIComponent(match[1]),
  };
}

function response(body, status = 200) {
  return {
    status,
    body: {
      ...body,
      routec_host_owned_matrix_storage: true,
      synapse_proxy_attempted: false,
      browser_direct_synapse_dependency: false,
      raw_synapse_token_exposed: false,
      foundation_pass_claimed: false,
    },
    proxy_ready: true,
    retry_after_header: null,
  };
}

export async function handleRouteCMatrixStorageRequest({
  req,
  path,
  url,
  body,
  binding,
  tokenRecord,
  senderMatrixUserId = null,
  senderActorKey = null,
}) {
  const effectiveBinding = binding || {
    host_session_id: tokenRecord?.host_session_id,
    host_agent_id: tokenRecord?.host_agent_id,
    matrix_room_id: tokenRecord?.matrix_room_id,
    matrix_user_id: tokenRecord?.matrix_user_id,
    routec_matrix_actor_registry: tokenRecord?.routec_matrix_actor_registry,
  };
  requireBinding(effectiveBinding);

  try {
    if (req.method === "PUT") {
      const pinnedEventsStateParts = extractPinnedEventsStatePathParts(path);
      if (pinnedEventsStateParts) {
        const written = writePinnedEventsState({
          binding: effectiveBinding,
          roomId: pinnedEventsStateParts.roomId,
          content: body,
        });
        return response(written);
      }

      const sendParts = extractRoomSendPathParts(path);
      if (sendParts) {
        const committed = commitRoomEvent({
          binding: effectiveBinding,
          roomId: sendParts.roomId,
          txnId: sendParts.txnId,
          eventType: sendParts.eventType,
          content: body,
          senderMatrixUserId,
          senderActorKey,
          tokenRecord,
        });
        return response({
          event_id: committed.event.event_id,
          duplicate_txn: committed.duplicate_txn,
          matrix_event_sender: committed.event.sender,
          routec_matrix_actor_key:
            committed.event.unsigned.routec_matrix_actor_key,
          routec_matrix_actor_kind:
            committed.event.unsigned.routec_matrix_actor_kind,
          semantic_role_is_sender: false,
          committed_transcript_truth: "matrix_room_timeline",
          storage_path_hash: sha256(getRouteCMatrixStoragePath()),
        });
      }
    }

    if (req.method === "GET" && path === "/_matrix/client/v3/sync") {
      const requestedSinceToken = optionalSearchParam(url, "since");
      const sinceSeq = parseOptionalRouteCCheckpointToken(
        requestedSinceToken,
        "since"
      );
      const timelineLimit = syncTimelineLimitFromUrl(url);
      return response(
        buildSyncBody({
          binding: effectiveBinding,
          sinceSeq,
          requestedSinceToken,
          timelineLimit,
        })
      );
    }

    if (req.method === "POST" && path === "/_matrix/client/v3/search") {
      const requestedNextBatchToken = optionalSearchParam(url, "next_batch");
      return response(
        searchRoomEvents({
          binding: effectiveBinding,
          body,
          requestedNextBatchToken,
        })
      );
    }

    if (req.method === "GET") {
      const pinnedEventsStateParts = extractPinnedEventsStatePathParts(path);
      if (pinnedEventsStateParts) {
        const read = readPinnedEventsState({
          binding: effectiveBinding,
          roomId: pinnedEventsStateParts.roomId,
        });
        return response(read.body, read.status);
      }

      const eventParts = extractEventPathParts(path);
      if (eventParts) {
        if (eventParts.roomId !== effectiveBinding.matrix_room_id) {
          return response(
            matrixJsonError(
              "M_FORBIDDEN",
              "Route C Matrix storage event room does not match binding."
            ),
            403
          );
        }
        const event = readEvent({
          binding: effectiveBinding,
          eventId: eventParts.eventId,
        });
        if (!event) {
          return response(
            matrixJsonError(
              "M_NOT_FOUND",
              "Route C Matrix storage event not found."
            ),
            404
          );
        }
        return response(event);
      }
      const messagesParts = extractMessagesPathParts(path);
      if (messagesParts) {
        if (messagesParts.roomId !== effectiveBinding.matrix_room_id) {
          return response(
            matrixJsonError(
              "M_FORBIDDEN",
              "Route C Matrix storage messages room does not match binding."
            ),
            403
          );
        }
        const requestedFromToken = optionalSearchParam(url, "from");
        const requestedToToken = optionalSearchParam(url, "to");
        const fromSeq = parseOptionalRouteCCheckpointToken(
          requestedFromToken,
          "from"
        );
        const toSeq = parseOptionalRouteCCheckpointToken(
          requestedToToken,
          "to"
        );
        const direction = normalizedMessagesDirection(
          url?.searchParams?.get("dir")
        );
        const limit = url?.searchParams?.get("limit");
        return response(
          readRouteCMatrixRoomMessages({
            binding: effectiveBinding,
            limit,
            direction,
            fromSeq,
            requestedFromToken,
            toSeq,
            requestedToToken,
          })
        );
      }
    }

    return response(
      matrixJsonError(
        "M_UNRECOGNIZED",
        `Unsupported Route C Host-owned Matrix storage endpoint: ${req.method} ${path}`
      ),
      501
    );
  } catch (err) {
    if (err instanceof RouteCMatrixStorageClientError) {
      return response(err.body, err.status);
    }
    throw err;
  }
}
