export type NodeHash = string; // first byte of the hash

export const RouteType = {
  TRANSPORT_FLOOD:  0x00,
  FLOOD:            0x01,
  DIRECT:           0x02,
  TRANSPORT_DIRECT: 0x03,
} as const;

export type RouteType = typeof RouteType[keyof typeof RouteType] | number;

export const PayloadType = {
  REQUEST:    0x00,  // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
  RESPONSE:   0x01,  // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
  TEXT:       0x02,  // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
  ACK:        0x03,  // a simple ack
  ADVERT:     0x04,  // a node advertising its Identity
  GROUP_TEXT: 0x05,  // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
  GROUP_DATA: 0x06,  // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
  ANON_REQ:   0x07,  // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
  PATH:       0x08,  // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
  TRACE:      0x09,  // trace a path, collecting SNI for each hop
  MULTIPART:  0x0A,  // packet is one of a set of packets
  CONTROL:    0x0B,  // a control/discovery packet
  RAW_CUSTOM: 0x0F,  // custom packet as raw bytes, for applications with custom encryption, payloads, etc
} as const;

export type PayloadTypeValue = typeof PayloadType[keyof typeof PayloadType];
export type PayloadType = PayloadTypeValue | number;

export interface Packet {
  version:         number;
  transportCodes?: Uint16Array;
  routeType:       RouteType;
  payloadType:     PayloadType;
  path:            Uint8Array;
  payload:         Uint8Array;
}

export abstract class BasePacket {
  protected version:         number     = 1;
  protected transportCodes?: Uint16Array;
  protected routeType:       number     = 0;
  protected payloadType:     number     = 0;
  protected pathLength:      number     = 0;
  protected path:            Uint8Array = new Uint8Array();
  protected payload:         Uint8Array = new Uint8Array();

  abstract getPathHashSize(): number;
  abstract getPathHashCount(): number;
  abstract hash(): Uint8Array;
  abstract decode(): Payload;
}

export type Payload =
  | RequestPayload
  | ResponsePayload
  | TextPayload
  | AckPayload
  | AdvertPayload
  | GroupTextPayload
  | GroupDataPayload
  | AnonReqPayload
  | PathPayload
  | TracePayload
  | MultipartPayload
  | ControlPayload
  | RawCustomPayload;

export abstract class BasePayload<T extends Payload> {
  protected type: PayloadType;
  protected raw:  Uint8Array;

  constructor(type: PayloadType, raw: Uint8Array) {
    this.type = type;
    this.raw = raw;
  }

  abstract decode(): T;
}

export interface EncryptedPayload {
  cipherMAC:  Uint8Array;
  cipherText: Uint8Array;
}

export const RequestType = {
  GET_STATS: 0x01,
  KEEPALIVE: 0x02,           // deprecated
  GET_TELEMETRY: 0x03,
  GET_MIN_MAX_AVG: 0x04,     // sensor nodes
  GET_ACCESS_LIST: 0x05,
  GET_NEIGHBORS: 0x06,
  GET_OWNER_INFO: 0x07
} as const;

export type RequestType = typeof RequestType[keyof typeof RequestType];

export interface RequestPayload extends EncryptedPayload {
  readonly payloadType: typeof PayloadType.REQUEST;
  dstHash: NodeHash;
  srcHash: NodeHash;
}

export interface DecryptedRequest {
  timestamp: Date;
  requestType: RequestType;
  requestData: Uint8Array;
}

export interface StatsRequest extends DecryptedRequest {
  requestType: typeof RequestType.GET_STATS;
}

export interface TelemetryRequest extends DecryptedRequest {
  requestType: typeof RequestType.GET_TELEMETRY;
}

export interface MinMaxAvgRequest extends DecryptedRequest {
  requestType: typeof RequestType.GET_MIN_MAX_AVG;
}

export interface AccessListRequest extends DecryptedRequest {
  requestType: typeof RequestType.GET_ACCESS_LIST;
}

export interface NeighborsRequest extends DecryptedRequest {
  requestType: typeof RequestType.GET_NEIGHBORS;
}

export interface OwnerInfoRequest extends DecryptedRequest {
  requestType: typeof RequestType.GET_OWNER_INFO;
}

export interface ResponsePayload extends EncryptedPayload {
  readonly payloadType: typeof PayloadType.RESPONSE;
  dstHash: NodeHash;
  srcHash: NodeHash;
}

export interface DecryptedResponse {
  tag: number;                // 4 bytes, LE (TODO in spec)
  content: Uint8Array;
}

export const TextMessageType = {
  PLAIN_TEXT:        0x00,
  CLI_COMMAND:       0x01,
  SIGNED_PLAIN_TEXT: 0x02
} as const;

export type TextMessageType = typeof TextMessageType[keyof typeof TextMessageType];

export interface TextPayload extends EncryptedPayload {
  readonly payloadType: typeof PayloadType.TEXT;
  dstHash: NodeHash;
  srcHash: NodeHash;
}

export interface DecryptedTextMessage {
  timestamp: Date;              // 4 bytes, LE
  txtTypeAndAttempt: number;    // Upper 6 bits: txt_type, lower 2 bits: attempt (0-3)
  message: Uint8Array | string; // Message content
}

export interface SignedTextMessage extends DecryptedTextMessage {
  senderPubkeyPrefix: Uint8Array;  // First 4 bytes of sender pubkey (when txt_type = 0x02)
}

export interface AckPayload {
  readonly payloadType: typeof PayloadType.ACK;
  checksum: number;            // 4 bytes, LE - CRC of message timestamp, text, and sender pubkey
}

export const AdvertisementFlags = {
  IS_CHAT_NODE: 0x01,
  IS_REPEATER: 0x02,
  IS_ROOM_SERVER: 0x03,
  IS_SENSOR: 0x04,
  HAS_LOCATION: 0x10,
  HAS_FEATURE_1: 0x20,
  HAS_FEATURE_2: 0x40,
  HAS_NAME: 0x80
} as const;

export type AdvertisementFlags = typeof AdvertisementFlags[keyof typeof AdvertisementFlags];

export interface AdvertisementAppData {
  flags: number;
  latitude?: number;      // decimal latitude * 1000000, integer
  longitude?: number;     // decimal longitude * 1000000, integer
  feature1?: number;      // reserved
  feature2?: number;      // reserved
  name?: string;          // node name
}

export interface AdvertPayload {
  readonly payloadType: typeof PayloadType.ADVERT;
  publicKey: Uint8Array;      // 32 bytes Ed25519 public key
  timestamp?: Date;           // Unix timestamp (4 bytes, LE), undefined if timestamp is 0
  signature: Uint8Array;      // 64 bytes Ed25519 signature
  appdata:   AdvertisementAppData;
}

export interface EncryptedGroupPayload extends EncryptedPayload {
  channelHash: NodeHash;
}

export interface GroupTextPayload extends EncryptedGroupPayload {
  readonly payloadType: typeof PayloadType.GROUP_TEXT;
}

export interface GroupDataPayload extends EncryptedGroupPayload {
  readonly payloadType: typeof PayloadType.GROUP_DATA;
}

export interface DecryptedGroupMessage {
  timestamp: Date;             // 4 bytes, LE
  flags: number;               // Usually 0x00 for plain text
  message: string;             // Format: "<sender name>: <message body>"
  group?:  string;
}

export interface AnonReqPayload extends EncryptedPayload {
  readonly payloadType: typeof PayloadType.ANON_REQ;
  dstHash:   NodeHash;         // first byte of destination node public key
  publicKey: Uint8Array;       // 32 bytes - sender's ephemeral Ed25519 public key
}

export const AnonRequestSubType = {
  ROOM_LOGIN: 0x00,
  REPEATER_LOGIN: 0x01,
  REGIONS_REQUEST: 0x01,
  OWNER_INFO_REQUEST: 0x02,
  CLOCK_STATUS_REQUEST: 0x03
} as const;

export type AnonRequestSubType = typeof AnonRequestSubType[keyof typeof AnonRequestSubType];

export interface RoomServerLogin {
  timestamp: number;           // 4 bytes, LE
  syncTimestamp: number;       // 4 bytes, LE - "sync messages SINCE x"
  password: string;
}

export interface RepeaterSensorLogin {
  timestamp: number;           // 4 bytes, LE
  password: string;
}

export interface RepeaterRegionsRequest {
  timestamp: Date;           // 4 bytes, LE
  reqType: typeof AnonRequestSubType.REPEATER_LOGIN;
  replyPathLen: number;        // 1 byte
  replyPath: NodeHash[];       // Variable length
}

export interface RepeaterOwnerInfoRequest {
  timestamp: Date;           // 4 bytes, LE
  reqType: typeof AnonRequestSubType.OWNER_INFO_REQUEST;
  replyPathLen: number;
  replyPath: NodeHash[];
}

export interface RepeaterClockStatusRequest {
  timestamp: Date;           // 4 bytes, LE
  reqType: typeof AnonRequestSubType.CLOCK_STATUS_REQUEST;
  replyPathLen: number;
  replyPath: NodeHash[];
}

export interface PathPayload extends EncryptedPayload {
  readonly payloadType: typeof PayloadType.PATH;
  dstHash: NodeHash;
  srcHash: NodeHash;
}

export interface DecryptedPath {
  pathLength: number;          // 1 byte
  path: NodeHash[];            // List of node hashes (1 byte each)
  extraType: PayloadType;      // Bundled payload type (e.g., ACK)
  extra: Uint8Array;           // Bundled payload content
}

export interface TracePayload {
  readonly payloadType: typeof PayloadType.TRACE;
  // Format not fully specified in docs - collecting SNI for each hop
  data: Uint8Array;
}

export interface MultipartPayload {
  readonly payloadType: typeof PayloadType.MULTIPART;
  data: Uint8Array;             // One part of a multi-part packet
}

export const ControlSubType = {
  DISCOVER_REQ: 0x8,           // Upper 4 bits of flags
  DISCOVER_RESP: 0x9
} as const;

export type ControlSubType = typeof ControlSubType[keyof typeof ControlSubType];

export const NodeType = {
  TYPE_UNKNOWN: 0x0,
  TYPE_CHAT_NODE: 0x1,
  TYPE_REPEATER: 0x2,
  TYPE_ROOM_SERVER: 0x3,
  TYPE_SENSOR: 0x4
} as const;

export type NodeType = typeof NodeType[keyof typeof NodeType];

export interface ControlPayload {
  readonly payloadType: typeof PayloadType.CONTROL;
  flags: number;               // Upper 4 bits is sub_type
  data: Uint8Array;
}

export interface DiscoverRequest {
  subType: typeof ControlSubType.DISCOVER_REQ;
  prefixOnly: boolean;         // Lowest bit of flags
  typeFilter: number;          // Bit for each ADV_TYPE_*
  tag: number;                 // 4 bytes, LE - randomly generated
  since?: number;              // 4 bytes, LE - epoch timestamp (optional)
}

export interface DiscoverResponse {
  subType: typeof ControlSubType.DISCOVER_RESP;
  nodeType: NodeType;          // Lower 4 bits of flags
  snr: number;                 // 1 byte, signed - SNR*4
  tag: number;                 // 4 bytes, LE - reflected from request
  pubkey: BasePublicKey;       // 8 or 32 bytes - node's ID or prefix
}

export interface RawCustomPayload {
  readonly payloadType: typeof PayloadType.RAW_CUSTOM;
  data: Uint8Array;            // Raw bytes for custom encryption/application
}

export interface Group {
  name:   string;
  secret: BaseGroupSecret;
}

export type PublicKeyValue = string | Uint8Array

export abstract class BasePublicKey {
  abstract toBytes(): Uint8Array;
  abstract toString(): string;
}

export type PrivateKeyValue = string | Uint8Array

export abstract class BasePrivateKey {
  abstract toBytes(): Uint8Array;
  abstract toString(): string;
  abstract toPublicKey(): BasePublicKey;
}

export type SharedSecretValue = string | Uint8Array

export abstract class BaseSharedSecret {
  abstract decrypt(cipherText: Uint8Array, cipherMAC: Uint8Array): Uint8Array
}

export type GroupSecretValue = string | Uint8Array

export abstract class BaseGroupSecret {
  abstract toBytes(): Uint8Array;
  abstract toString(): string;
  abstract toHash(): string;
  abstract decrypt(cipherText: Uint8Array, cipherMAC: Uint8Array): DecryptedGroupMessage;
}

export interface MeshCoreGroup {
  id: number;
  name: string;
  secret: string;
  isPublic: boolean;
}

export interface MeshCoreGroupMessage {
  hash: string;
  timestamp: Date;
  channelHash: string;
  sender: string;
  message: string;
}
