import { FieldType, type Segment } from "@hamradio/packet";

import { base91ToNumber, knotsToKmh } from ".";
import { DO_NOT_ARCHIVE_MARKER, DataType, type IAddress, MicEPayload, type Payload } from "./frame.types";
import { attachExtras, decodeCommentExtras } from "./payload.extras";

export const decodeMicEPayload = (
  destination: IAddress,
  raw: string,
  withStructure: boolean = false
): {
  payload: Payload | null;
  segment?: Segment[];
} => {
  try {
    // Mic-E encodes position in both destination address and information field
    const dest = destination.call;

    if (dest.length < 6) return { payload: null };
    if (raw.length < 9) return { payload: null }; // Need at least data type + 8 bytes

    const segments: Segment[] = withStructure ? [] : [];

    // Decode latitude from destination address (6 characters)
    const latResult = decodeMicELatitude(dest);
    if (!latResult) return { payload: null };

    const { latitude, messageType, longitudeOffset, isWest, isStandard } = latResult;

    if (withStructure) {
      segments.push({
        name: "mic-E destination",
        data: new TextEncoder().encode(dest).buffer,
        isString: true,
        fields: [
          {
            type: FieldType.STRING,
            name: "destination",
            length: dest.length
          }
        ]
      });
    }

    // Parse information field (skip data type identifier at position 0)
    let offset = 1;

    // Longitude: 3 bytes (degrees, minutes, hundredths)
    const lonDegRaw = raw.charCodeAt(offset) - 28;
    const lonMinRaw = raw.charCodeAt(offset + 1) - 28;
    const lonHunRaw = raw.charCodeAt(offset + 2) - 28;
    offset += 3;

    // Apply longitude offset and hemisphere
    let lonDeg = lonDegRaw;
    if (longitudeOffset) {
      lonDeg += 100;
    }
    if (lonDeg >= 180 && lonDeg <= 189) {
      lonDeg -= 80;
    } else if (lonDeg >= 190 && lonDeg <= 199) {
      lonDeg -= 190;
    }

    let longitude = lonDeg + lonMinRaw / 60.0 + lonHunRaw / 6000.0;
    if (isWest) {
      longitude = -longitude;
    }

    // Speed and course: 3 bytes
    const sp = raw.charCodeAt(offset) - 28;
    const dc = raw.charCodeAt(offset + 1) - 28;
    const se = raw.charCodeAt(offset + 2) - 28;
    offset += 3;

    let speed = sp * 10 + Math.floor(dc / 10); // Speed in knots
    let course = (dc % 10) * 100 + se; // Course in degrees

    if (course >= 400) course -= 400;
    if (speed >= 800) speed -= 800;

    // Convert speed from knots to km/h
    const speedKmh = knotsToKmh(speed);

    // Symbol code and table
    if (raw.length < offset + 2) return { payload: null };
    const symbolCode = raw.charAt(offset);
    const symbolTable = raw.charAt(offset + 1);
    offset += 2;

    // Parse remaining data (altitude, comment, telemetry)
    const remaining = raw.substring(offset);
    const doNotArchive = remaining.includes(DO_NOT_ARCHIVE_MARKER);
    let altitude: number | undefined = undefined;
    let comment = remaining;

    // Check for altitude in old format
    if (comment.length >= 4 && comment.charAt(3) === "}") {
      try {
        const altBase91 = comment.substring(0, 3);
        altitude = base91ToNumber(altBase91) - 10000; // Relative to 10km below mean sea level
        comment = comment.substring(4); // Remove altitude token from comment
      } catch {
        // Ignore altitude parsing errors
      }
    }

    // Parse RNG/PHG tokens from comment (defer attaching to result until created)
    const remainder = comment; // Use the remaining comment text for parsing extras
    const extras = decodeCommentExtras(remainder, withStructure);
    comment = extras.comment;

    let payloadType: DataType.MicE | DataType.MicEOld;
    switch (raw.charAt(0)) {
      case "`":
        payloadType = DataType.MicE;
        break;
      case "'":
        payloadType = DataType.MicEOld;
        break;
      default:
        return { payload: null };
    }

    const result: MicEPayload = {
      type: payloadType,
      doNotArchive,
      position: {
        latitude,
        longitude,
        symbol: {
          table: symbolTable,
          code: symbolCode
        }
      },
      messageType,
      isStandard
    };

    if (speed > 0) {
      result.position.speed = speedKmh;
    }

    if (course > 0 && course < 360) {
      result.position.course = course;
    }

    if (altitude !== undefined) {
      result.position.altitude = altitude;
    }

    if (comment) {
      result.position.comment = comment;
    }

    // Attach parsed extras if present
    attachExtras(result, extras);

    if (withStructure) {
      // Information field section (bytes after data type up to comment)
      const infoData = raw.substring(1, offset);
      segments.push({
        name: "mic-E info",
        data: new TextEncoder().encode(infoData).buffer,
        isString: true,
        fields: [
          { type: FieldType.CHAR, name: "longitude deg", length: 1 },
          { type: FieldType.CHAR, name: "longitude min", length: 1 },
          { type: FieldType.CHAR, name: "longitude hundredths", length: 1 },
          { type: FieldType.CHAR, name: "speed byte", length: 1 },
          { type: FieldType.CHAR, name: "course byte 1", length: 1 },
          { type: FieldType.CHAR, name: "course byte 2", length: 1 },
          { type: FieldType.CHAR, name: "symbol code", length: 1 },
          { type: FieldType.CHAR, name: "symbol table", length: 1 }
        ]
      });

      if (comment && comment.length > 0) {
        segments.push({
          name: "comment",
          data: new TextEncoder().encode(remainder).buffer,
          isString: true,
          fields: extras.fields || []
        });
      } else if (extras.fields) {
        segments.push({
          name: "comment",
          data: new TextEncoder().encode(remainder).buffer,
          isString: true,
          fields: extras.fields
        });
      }

      return { payload: result, segment: segments };
    }

    return { payload: result };
  } catch {
    return { payload: null };
  }
};

const decodeMicELatitude = (
  dest: string
): {
  latitude: number;
  messageType: string;
  longitudeOffset: boolean;
  isWest: boolean;
  isStandard: boolean;
} | null => {
  if (dest.length < 6) return null;

  // Each destination character encodes a latitude digit and message bits
  const digits: number[] = [];
  const messageBits: number[] = [];

  for (let i = 0; i < 6; i++) {
    const code = dest.charCodeAt(i);
    let digit: number;
    let msgBit: number;

    if (code >= 48 && code <= 57) {
      // '0'-'9'
      digit = code - 48;
      msgBit = 0;
    } else if (code >= 65 && code <= 74) {
      // 'A'-'J' (A=0, B=1, ... J=9)
      digit = code - 65;
      msgBit = 1;
    } else if (code === 75) {
      // 'K' means space (used for ambiguity)
      digit = 0;
      msgBit = 1;
    } else if (code === 76) {
      // 'L' means space
      digit = 0;
      msgBit = 0;
    } else if (code >= 80 && code <= 89) {
      // 'P'-'Y' custom message types (P=0, Q=1, R=2, ... Y=9)
      digit = code - 80;
      msgBit = 1;
    } else if (code === 90) {
      // 'Z' means space
      digit = 0;
      msgBit = 1;
    } else {
      return null; // Invalid character
    }

    digits.push(digit);
    messageBits.push(msgBit);
  }

  // Decode latitude: format is DDMM.HH (degrees, minutes, hundredths)
  const latDeg = digits[0] * 10 + digits[1];
  const latMin = digits[2] * 10 + digits[3];
  const latHun = digits[4] * 10 + digits[5];

  let latitude = latDeg + latMin / 60.0 + latHun / 6000.0;

  // Message bits determine hemisphere and other flags
  // Bit 3 (messageBits[3]): 0 = North, 1 = South
  // Bit 4 (messageBits[4]): 0 = West, 1 = East
  // Bit 5 (messageBits[5]): 0 = longitude offset +0, 1 = longitude offset +100
  const isNorth = messageBits[3] === 0;
  const isWest = messageBits[4] === 0;
  const longitudeOffset = messageBits[5] === 1;

  if (!isNorth) {
    latitude = -latitude;
  }

  // Decode message type from bits 0, 1, 2
  const msgValue = messageBits[0] * 4 + messageBits[1] * 2 + messageBits[2];
  const messageTypes = [
    "M0: Off Duty",
    "M1: En Route",
    "M2: In Service",
    "M3: Returning",
    "M4: Committed",
    "M5: Special",
    "M6: Priority",
    "M7: Emergency"
  ];
  const messageType = messageTypes[msgValue] || "Unknown";

  // Standard vs custom message indicator
  const isStandard = messageBits[0] === 1;

  return {
    latitude,
    messageType,
    longitudeOffset,
    isWest,
    isStandard
  };
};

export default decodeMicEPayload;
