import { FieldType, type Segment } from "@hamradio/packet";
import { DTM, GGA, INmeaSentence, Decoder as NmeaDecoder, RMC } from "extended-nmea";

import { DataType, type IPosition, type Payload, type RawGPSPayload } from "./frame.types";

export const decodeRawGPSPayload = (
  raw: string,
  withStructure: boolean = false
): {
  payload: Payload | null;
  segment?: Segment[];
} => {
  try {
    if (raw.length < 2) return { payload: null };

    // Raw GPS payloads start with '$' followed by an NMEA sentence
    const sentence = raw.substring(1).trim();

    // Attempt to parse with extended-nmea Decoder to extract position (best-effort)
    let parsed: INmeaSentence | null = null;
    try {
      const full = sentence.startsWith("$") ? sentence : `$${sentence}`;
      parsed = NmeaDecoder.decode(full);
    } catch {
      // ignore parse errors - accept any sentence as raw-gps per APRS
    }

    const payload: RawGPSPayload = {
      type: DataType.RawGPS,
      sentence
    };

    // If parse produced latitude/longitude, attach structured position.
    // Otherwise fallback to a minimal NMEA parser for common sentences (RMC, GGA).
    if (
      parsed &&
      (parsed instanceof RMC || parsed instanceof GGA || parsed instanceof DTM) &&
      parsed.latitude &&
      parsed.longitude
    ) {
      // extended-nmea latitude/longitude are GeoCoordinate objects with
      // fields { degrees, decimal, quadrant }
      const latObj = parsed.latitude;
      const lonObj = parsed.longitude;
      const lat = latObj.degrees + (Number(latObj.decimal) || 0) / 60.0;
      const lon = lonObj.degrees + (Number(lonObj.decimal) || 0) / 60.0;
      const latitude = latObj.quadrant === "S" ? -lat : lat;
      const longitude = lonObj.quadrant === "W" ? -lon : lon;

      const pos: IPosition = {
        latitude,
        longitude
      };

      // altitude
      if ("altMean" in parsed && parsed.altMean !== undefined) {
        pos.altitude = Number(parsed.altMean);
      }
      if ("altitude" in parsed && parsed.altitude !== undefined) {
        pos.altitude = Number(parsed.altitude);
      }

      // speed/course (RMC fields)
      if ("speedOverGround" in parsed && parsed.speedOverGround !== undefined) {
        pos.speed = Number(parsed.speedOverGround);
      }
      if ("courseOverGround" in parsed && parsed.courseOverGround !== undefined) {
        pos.course = Number(parsed.courseOverGround);
      }

      payload.position = pos;
    } else {
      try {
        const full = sentence.startsWith("$") ? sentence : `$${sentence}`;
        const withoutChecksum = full.split("*")[0];
        const parts = withoutChecksum.split(",");
        const header = parts[0].slice(1).toUpperCase();

        const parseCoord = (coord: string, hemi: string) => {
          if (!coord || coord === "") return undefined;
          const degDigits = hemi === "N" || hemi === "S" ? 2 : 3;
          if (coord.length <= degDigits) return undefined;
          const degPart = coord.slice(0, degDigits);
          const minPart = coord.slice(degDigits);
          const degrees = parseFloat(degPart);
          const mins = parseFloat(minPart);
          if (Number.isNaN(degrees) || Number.isNaN(mins)) return undefined;
          let dec = degrees + mins / 60.0;
          if (hemi === "S" || hemi === "W") dec = -dec;
          return dec;
        };

        if (header.endsWith("RMC")) {
          const lat = parseCoord(parts[3], parts[4]);
          const lon = parseCoord(parts[5], parts[6]);
          if (lat !== undefined && lon !== undefined) {
            const pos: IPosition = { latitude: lat, longitude: lon };
            if (parts[7]) pos.speed = Number(parts[7]);
            if (parts[8]) pos.course = Number(parts[8]);
            payload.position = pos;
          }
        } else if (header.endsWith("GGA")) {
          const lat = parseCoord(parts[2], parts[3]);
          const lon = parseCoord(parts[4], parts[5]);
          if (lat !== undefined && lon !== undefined) {
            const pos: IPosition = { latitude: lat, longitude: lon };
            if (parts[9]) pos.altitude = Number(parts[9]);
            payload.position = pos;
          }
        }
      } catch {
        // ignore fallback parse errors
      }
    }

    if (withStructure) {
      const segments: Segment[] = [
        {
          name: "raw-gps",
          data: new TextEncoder().encode(sentence).buffer,
          isString: true,
          fields: [
            {
              type: FieldType.STRING,
              name: "sentence",
              length: sentence.length
            }
          ]
        }
      ];

      if (payload.position) {
        segments.push({
          name: "raw-gps-position",
          data: new TextEncoder().encode(JSON.stringify(payload.position)).buffer,
          isString: true,
          fields: [
            {
              type: FieldType.STRING,
              name: "latitude",
              length: String(payload.position.latitude).length
            },
            {
              type: FieldType.STRING,
              name: "longitude",
              length: String(payload.position.longitude).length
            }
          ]
        });
      }

      return { payload, segment: segments };
    }

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

export default decodeRawGPSPayload;
