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

import { DO_NOT_ARCHIVE_MARKER, DataType, type IPosition, ObjectPayload, type Payload } from "./frame.types";
import { attachExtras, decodeCommentExtras } from "./payload.extras";
import { isCompressedPosition, parseCompressedPosition, parseUncompressedPosition } from "./payload.position";
import Timestamp from "./timestamp";

export const decodeObjectPayload = (
  raw: string,
  withStructure: boolean = false
): {
  payload: Payload | null;
  segment?: Segment[];
} => {
  try {
    // Object format: ;AAAAAAAAAcDDHHMMzDDMM.hhN/DDDMM.hhW$comment
    //                ^ data type
    //                 9-char name
    //                          alive (*) / killed (_)
    if (raw.length < 18) return { payload: null }; // 1 + 9 + 1 + 7 minimum

    let offset = 1; // Skip data type identifier ';'
    const segment: Segment[] = withStructure ? [] : [];

    const rawName = raw.substring(offset, offset + 9);
    const name = rawName.trimEnd();
    if (withStructure) {
      segment.push({
        name: "object",
        data: new TextEncoder().encode(rawName).buffer,
        isString: true,
        fields: [{ type: FieldType.STRING, name: "name", length: 9 }]
      });
    }
    offset += 9;

    const stateChar = raw.charAt(offset);
    if (stateChar !== "*" && stateChar !== "_") {
      return { payload: null };
    }
    const alive = stateChar === "*";
    if (withStructure) {
      let state: string = "invalid";
      if (stateChar === "*") {
        state = "alive";
      } else if (stateChar === "_") {
        state = "killed";
      }
      segment[segment.length - 1].data = new TextEncoder().encode(raw.substring(offset - 9, offset + 1)).buffer;
      segment[segment.length - 1].fields.push({
        type: FieldType.CHAR,
        name: "state",
        length: 1,
        value: state
      });
    }
    offset += 1;

    const timeStr = raw.substring(offset, offset + 7);
    const { timestamp, segment: timestampSection } = Timestamp.fromString(timeStr, withStructure);
    if (!timestamp) {
      return { payload: null };
    }
    if (timestampSection) {
      segment.push(timestampSection);
    }
    offset += 7;

    const isCompressed = isCompressedPosition(raw.substring(offset));

    let position: IPosition | null = null;
    let consumed = 0;

    if (isCompressed) {
      const { position: compressed, segment: compressedSection } = parseCompressedPosition(
        raw.substring(offset),
        withStructure
      );
      if (!compressed) return { payload: null };

      position = {
        latitude: compressed.latitude,
        longitude: compressed.longitude,
        symbol: compressed.symbol,
        altitude: compressed.altitude
      };
      consumed = 13;

      if (compressedSection) {
        segment.push(compressedSection);
      }
    } else {
      const { position: uncompressed, segment: uncompressedSection } = parseUncompressedPosition(
        raw.substring(offset),
        withStructure
      );
      if (!uncompressed) return { payload: null };

      position = {
        latitude: uncompressed.latitude,
        longitude: uncompressed.longitude,
        symbol: uncompressed.symbol,
        ambiguity: uncompressed.ambiguity
      };
      consumed = 19;

      if (uncompressedSection) {
        segment.push(uncompressedSection);
      }
    }

    offset += consumed;
    const remainder = raw.substring(offset);
    const doNotArchive = remainder.includes(DO_NOT_ARCHIVE_MARKER);
    let comment = remainder;

    // Parse RNG/PHG tokens
    const extras = decodeCommentExtras(comment, withStructure);
    comment = extras.comment;

    if (comment) {
      position.comment = comment;

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

    const payload: ObjectPayload = {
      type: DataType.Object,
      doNotArchive,
      name,
      timestamp,
      alive,
      position
    };
    attachExtras(payload, extras);

    if (withStructure) {
      return { payload, segment };
    }

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

export default decodeObjectPayload;
