import * as Base64 from "base64-js";
import { JSONValue } from "@convex-dev/common";
import { Long } from "../long.js";

/**
 * Shared schema
 */

export function u64ToLong(encoded: EncodedU64): U64 {
  const integerBytes = Base64.toByteArray(encoded);
  return Long.fromBytesLE(Array.from(integerBytes));
}

export function parseServerMessage(
  encoded: EncodedServerMessage
): ServerMessage {
  switch (encoded.type) {
    case "FatalError": {
      return { ...encoded };
    }
    case "MutationResponse": {
      if (encoded.success) {
        return { ...encoded, ts: u64ToLong(encoded.ts) };
      } else {
        return { ...encoded };
      }
    }
    case "Transition": {
      return {
        ...encoded,
        startVersion: {
          ...encoded.startVersion,
          ts: u64ToLong(encoded.startVersion.ts),
        },
        endVersion: {
          ...encoded.endVersion,
          ts: u64ToLong(encoded.endVersion.ts),
        },
      };
    }
    default: {
      const _exhaustivenessCheck: never = encoded;
    }
  }
  return undefined as never;
}

type U64 = Long;
type EncodedU64 = string;

/**
 * Unique nonnegative integer identifying a single query.
 */
export type QueryId = number; // nonnegative int

export type QuerySetVersion = number; // nonnegative int

export type MutationId = number; // nonnegative int

export type IdentityVersion = number; // nonnegative int

/**
 * Client message schema
 */

type Connect = {
  type: "Connect";
  sessionId: string;
  connectionCount: number;
};

export type AddQuery = {
  type: "Add";
  queryId: QueryId;
  udfPath: string;
  args: JSONValue[];
};

export type RemoveQuery = {
  type: "Remove";
  queryId: QueryId;
};

export type QuerySetModification = {
  type: "ModifyQuerySet";
  baseVersion: QuerySetVersion;
  newVersion: QuerySetVersion;
  modifications: (AddQuery | RemoveQuery)[];
};

export type Mutation = {
  type: "Mutation";
  mutationId: MutationId;
  udfPath: string;
  args: JSONValue[];
};

export type Authenticate =
  | {
      type: "Authenticate";
      tokenType: "Admin";
      value: string;
      baseVersion: IdentityVersion;
    }
  | {
      type: "Authenticate";
      tokenType: "User";
      value: string;
      baseVersion: IdentityVersion;
    }
  | {
      type: "Authenticate";
      tokenType: "None";
      baseVersion: IdentityVersion;
    };
export type ClientMessage =
  | Connect
  | Authenticate
  | QuerySetModification
  | Mutation;

/**
 * Server message schema
 */
type TS = U64;
type EncodedTS = EncodedU64;
type LogLines = string[];

export type StateVersion = {
  querySet: QueryId;
  ts: TS;
  identity: IdentityVersion;
};
type EncodedStateVersion = Omit<StateVersion, "ts"> & { ts: EncodedTS };

type StateModification =
  | {
      type: "QueryUpdated";
      queryId: QueryId;
      value: JSONValue;
      logLines: LogLines;
    }
  | {
      type: "QueryFailed";
      queryId: QueryId;
      errorMessage: string;
      logLines: LogLines;
    }
  | {
      type: "QueryRemoved";
      queryId: QueryId;
    };

export type Transition = {
  type: "Transition";
  startVersion: StateVersion;
  endVersion: StateVersion;
  modifications: StateModification[];
};

type MutationSuccess = {
  type: "MutationResponse";
  mutationId: MutationId;
  success: true;
  result: JSONValue;
  ts: TS;
  logLines: LogLines;
};
type MutationFailed = {
  type: "MutationResponse";
  mutationId: MutationId;
  success: false;
  result: string;
  logLines: LogLines;
};
export type MutationResponse = MutationSuccess | MutationFailed;
type FatalError = {
  type: "FatalError";
  error: string;
};

export type ServerMessage = Transition | MutationResponse | FatalError;

type EncodedTransition = Omit<Transition, "startVersion" | "endVersion"> & {
  startVersion: EncodedStateVersion;
  endVersion: EncodedStateVersion;
};
type EncodedMutationSuccess = Omit<MutationSuccess, "ts"> & { ts: EncodedTS };
type EncodedMutationResponse = MutationFailed | EncodedMutationSuccess;

type EncodedServerMessage =
  | EncodedTransition
  | EncodedMutationResponse
  | FatalError;
