import fs from "fs";
import geo from "geojson";
import geolib from "geolib";
import magvar from "magvar";

/* Constants */
const π = Math.PI;
const AC_SPEED = 250;
const AC_VS = 3000;
// ADJ to location, (NG), E is neg.
/* LFPV */
//const MAGVAR = -1;
//const PROCEDURE_ID = 10394;
//const PROCEDURE_ID = 10395;
/* LFRK */
//const MAGVAR = 0;
//const PROCEDURE_ID = 10475;
//const PROCEDURE_ID = 10480;
//const PROCEDURE_ID = 10482;
//const PROCEDURE_ID = 10485;
const MAGVAR = -5;
const PROCEDURE_ID = 10653;

/* Prototypes */
Number.prototype.toRadians = function () {
  return (this * Math.PI) / 180;
};
Number.prototype.toDegrees = function () {
  return (this * 180) / Math.PI;
};
Number.prototype.flipCourse = function () {
  let inv = this + 180;
  inv = inv >= 360 ? inv - 360 : inv;
  return inv;
};
Number.prototype.normaliseDegrees = function () {
  return this >= 360 ? this - 360 : this < 0 ? this + 360 : this;
};
Number.prototype.toTrue = function (fix) {
  // IRL Magvar
  //return (this - magvar.magvar(fix.lat, fix.lon)).normaliseDegrees();
  // NG Magvar
  return (this - MAGVAR).normaliseDegrees();
};

/* Functions */
/**
 *
 * @param {[number, number]} line line segment
 */
const updateLastCourse = (line) => {
  lastCourse = geolib.getGreatCircleBearing(
    {
      latitude: line.at(-2)[1],
      longitude: line.at(-2)[0],
    },
    {
      latitude: line.at(-1)[1],
      longitude: line.at(-1)[0],
    }
  );
};
/**
 *
 * @param {number} nmi Nautical Miles
 * @returns Metres
 */
const nmiToMetre = (nmi) => nmi * 1852.0;
/**
 *
 * @param {{lat: number, lon: number}} p1 Point 1
 * @param {number} brng1 Bearing from Point 1
 * @param {{lat: number, lon: number}} p2 Point 2
 * @param {number} brng2 bearing from Point 2
 * @returns {{lat: number, lon: number}} Intersection point
 */
const computeIntersection = (p1, brng1, p2, brng2) => {
  if (isNaN(brng1)) throw new TypeError(`invalid brng1 ${brng1}`);
  if (isNaN(brng2)) throw new TypeError(`invalid brng2 ${brng2}`);

  // see www.edwilliams.org/avform.htm#Intersection

  const φ1 = p1.lat.toRadians(),
    λ1 = p1.lon.toRadians();
  const φ2 = p2.lat.toRadians(),
    λ2 = p2.lon.toRadians();
  const θ13 = Number(brng1).toRadians(),
    θ23 = Number(brng2).toRadians();
  const Δφ = φ2 - φ1,
    Δλ = λ2 - λ1;

  // angular distance p1-p2
  const δ12 =
    2 *
    Math.asin(
      Math.sqrt(
        Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
          Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
      )
    );
  if (Math.abs(δ12) < Number.EPSILON) return p1; // coincident points

  // initial/final bearings between points
  const cosθa =
    (Math.sin(φ2) - Math.sin(φ1) * Math.cos(δ12)) /
    (Math.sin(δ12) * Math.cos(φ1));
  const cosθb =
    (Math.sin(φ1) - Math.sin(φ2) * Math.cos(δ12)) /
    (Math.sin(δ12) * Math.cos(φ2));
  const θa = Math.acos(Math.min(Math.max(cosθa, -1), 1)); // protect against rounding errors
  const θb = Math.acos(Math.min(Math.max(cosθb, -1), 1)); // protect against rounding errors

  const θ12 = Math.sin(λ2 - λ1) > 0 ? θa : 2 * π - θa;
  const θ21 = Math.sin(λ2 - λ1) > 0 ? 2 * π - θb : θb;

  const α1 = θ13 - θ12; // angle 2-1-3
  const α2 = θ21 - θ23; // angle 1-2-3

  if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return null; // infinite intersections
  if (Math.sin(α1) * Math.sin(α2) < 0) return null; // ambiguous intersection (antipodal/360°)

  const cosα3 =
    -Math.cos(α1) * Math.cos(α2) + Math.sin(α1) * Math.sin(α2) * Math.cos(δ12);

  const δ13 = Math.atan2(
    Math.sin(δ12) * Math.sin(α1) * Math.sin(α2),
    Math.cos(α2) + Math.cos(α1) * cosα3
  );

  const φ3 = Math.asin(
    Math.min(
      Math.max(
        Math.sin(φ1) * Math.cos(δ13) +
          Math.cos(φ1) * Math.sin(δ13) * Math.cos(θ13),
        -1
      ),
      1
    )
  );

  const Δλ13 = Math.atan2(
    Math.sin(θ13) * Math.sin(δ13) * Math.cos(φ1),
    Math.cos(δ13) - Math.sin(φ1) * Math.sin(φ3)
  );
  const λ3 = λ1 + Δλ13;

  const lat = φ3.toDegrees();
  const lon = λ3.toDegrees();

  return { lat, lon, name: "INTC", alt: p1.alt };
};
/**
 *
 * @param {number} inbdCrs Course into arc endpoint
 * @param {*} outbCrs Course into arc origin point
 * @param {{lat: number, lon: number}} arcStart Arc origin point
 * @param {{lat: number, lon: number}} legEnd Arc endpoint
 * @param {"R"|"L"|"E"|null} turnDir Turn direction
 * @returns {[[number, number]]} Line segments
 */
const generateTangentialArc = (inbdCrs, outbCrs, arcStart, legEnd, turnDir) => {
  const line = [];

  if (inbdCrs !== outbCrs) {
    // Course to the end of the arc
    let crsToArcEnd;
    if (!turnDir || turnDir === "E") {
      let prov = outbCrs - inbdCrs;
      prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
      turnDir = prov > 0 ? "L" : "R";
    }
    if (turnDir === "R") {
      const delta = (360 - outbCrs + inbdCrs).normaliseDegrees();
      crsToArcEnd = (outbCrs + delta / 2).normaliseDegrees();
    } else {
      const delta = (outbCrs + 360 - inbdCrs).normaliseDegrees();
      crsToArcEnd = (outbCrs - delta / 2).normaliseDegrees();
    }

    // Arc end
    const arcEnd = computeIntersection(
      arcStart,
      crsToArcEnd,
      legEnd,
      inbdCrs.flipCourse()
    );

    if (!arcEnd) {
      // Early end due to no intercept
      return null;
    }

    let startPerpCrs;
    let endPerpCrs;
    if (turnDir === "R") {
      startPerpCrs = (outbCrs + 90).normaliseDegrees();
      endPerpCrs = (inbdCrs + 90).normaliseDegrees();
    } else {
      startPerpCrs = (outbCrs - 90).normaliseDegrees();
      endPerpCrs = (inbdCrs - 90).normaliseDegrees();
    }

    // Generate arc
    const arcCenter = computeIntersection(
      arcStart,
      startPerpCrs,
      arcEnd,
      endPerpCrs
    );
    const arcRad = geolib.getDistance(
      {
        latitude: arcCenter.lat,
        longitude: arcCenter.lon,
      },
      {
        latitude: arcStart.lat,
        longitude: arcStart.lon,
      }
    );

    startPerpCrs = startPerpCrs.flipCourse();
    endPerpCrs = endPerpCrs.flipCourse();
    // Start turn immediately
    if (turnDir === "R") {
      startPerpCrs += startPerpCrs < 1 ? startPerpCrs : 1;
    } else {
      startPerpCrs -= startPerpCrs < 1 ? startPerpCrs : 1;
    }

    while (startPerpCrs !== endPerpCrs) {
      if (turnDir === "R") {
        const delta = (endPerpCrs - startPerpCrs).normaliseDegrees();
        startPerpCrs += delta < 1 ? delta : 1;
        startPerpCrs = startPerpCrs.normaliseDegrees();
      } else {
        const delta = (startPerpCrs - endPerpCrs).normaliseDegrees();
        startPerpCrs -= delta < 1 ? delta : 1;
        startPerpCrs = startPerpCrs.normaliseDegrees();
      }
      if (startPerpCrs === endPerpCrs) break;

      const arcFix = geolib.computeDestinationPoint(
        arcCenter,
        arcRad,
        startPerpCrs
      );

      line.push([arcFix.longitude, arcFix.latitude]);
    }
  }

  return line;
};
/**
 *
 * @param {number} inbdCrs Course into arc endpoint
 * @param {number} outbCrs Course into arc origin point
 * @param {{lat: number, lon: number}} arcStart Arc origin point
 * @param {"R"|"L"|"E"|null} turnDir Turn direction
 * @returns {[[number, number]]} Line segments
 */
const generatePerformanceArc = (inbdCrs, outbCrs, arcStart, turnDir) => {
  const line = [[arcStart.lon, arcStart.lat]];

  if (inbdCrs !== outbCrs) {
    // Turn Dir
    if (!turnDir || turnDir === "E") {
      let prov = outbCrs - inbdCrs;
      prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
      turnDir = prov > 0 ? "L" : "R";
    }

    // Generate arc
    while (outbCrs !== inbdCrs) {
      if (turnDir === "R") {
        const delta = (inbdCrs - outbCrs).normaliseDegrees();
        outbCrs += delta < 1 ? delta : 1;
        outbCrs = outbCrs.normaliseDegrees();
      } else {
        const delta = (outbCrs - inbdCrs).normaliseDegrees();
        outbCrs -= delta < 1 ? delta : 1;
        outbCrs = outbCrs.normaliseDegrees();
      }
      if (outbCrs === inbdCrs) break;

      const arcFix = geolib.computeDestinationPoint(
        {
          latitude: line.at(-1)[1],
          longitude: line.at(-1)[0],
        },
        nmiToMetre(240 / 3600),
        outbCrs
      );

      line.push([arcFix.longitude, arcFix.latitude]);
    }
  } else {
    line.push([arcStart.lon, arcStart.lat]);
  }

  return line.slice(1);
};
/**
 *
 * @param {number} inbdCrs Course into arc endpoint
 * @param {number} outbCrs Course into arc origin point
 * @param {{lat: number, lon: number}} arcStart Arc origin point
 * @param {{lat: number, lon: number}} center Arc center point
 * @param {"R"|"L"|"E"|null} turnDir
 * @returns {[[number, number]]} Line segments
 */
const generateRFArc = (inbdCrs, outbCrs, arcStart, center, turnDir) => {
  const line = [];

  if (inbdCrs !== outbCrs) {
    // Turn Dir
    if (!turnDir || turnDir === "E") {
      let prov = outbCrs - inbdCrs;
      prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
      turnDir = prov > 0 ? "L" : "R";
    }

    let startPerpCrs;
    let endPerpCrs;
    if (turnDir === "R") {
      startPerpCrs = (outbCrs + 90).normaliseDegrees();
      endPerpCrs = (inbdCrs + 90).normaliseDegrees();
    } else {
      startPerpCrs = (outbCrs - 90).normaliseDegrees();
      endPerpCrs = (inbdCrs - 90).normaliseDegrees();
    }

    const arcRad = geolib.getDistance(
      {
        latitude: center.lat,
        longitude: center.lon,
      },
      {
        latitude: arcStart.lat,
        longitude: arcStart.lon,
      }
    );

    startPerpCrs = startPerpCrs.flipCourse();
    endPerpCrs = endPerpCrs.flipCourse();
    // Start turn immediately
    if (turnDir === "R") {
      startPerpCrs += startPerpCrs < 1 ? startPerpCrs : 1;
    } else {
      startPerpCrs -= startPerpCrs < 1 ? startPerpCrs : 1;
    }

    while (startPerpCrs !== endPerpCrs) {
      if (turnDir === "R") {
        const delta = (endPerpCrs - startPerpCrs).normaliseDegrees();
        startPerpCrs += delta < 1 ? delta : 1;
        startPerpCrs = startPerpCrs.normaliseDegrees();
      } else {
        const delta = (startPerpCrs - endPerpCrs).normaliseDegrees();
        startPerpCrs -= delta < 1 ? delta : 1;
        startPerpCrs = startPerpCrs.normaliseDegrees();
      }
      if (startPerpCrs === endPerpCrs) break;

      const arcFix = geolib.computeDestinationPoint(
        center,
        arcRad,
        startPerpCrs
      );

      line.push([arcFix.longitude, arcFix.latitude]);
    }
  }

  return line;
};
/**
 *
 * @param {number} inbdCrs Course into arc endpoint
 * @param {number} outbCrs Course into arc origin point
 * @param {{lat: number, lon: number}} arcStart Arc origin point
 * @param {{lat: number, lon: number}} center Arc center point
 * @param {number} radius Arc radius in nmi
 * @param {"R"|"L"|"E"|null} turnDir
 * @returns {[[number, number]]} Line segments
 */
const generateAFArc = (inbdCrs, outbCrs, arcStart, center, radius, turnDir) => {
  const line = [[arcStart.lon, arcStart.lat]];

  if (inbdCrs !== outbCrs) {
    // Turn Dir
    if (!turnDir || turnDir === "E") {
      let prov = outbCrs - inbdCrs;
      prov = prov > 180 ? prov - 360 : prov <= -180 ? prov + 360 : prov;
      turnDir = prov > 0 ? "L" : "R";
    }

    while (outbCrs !== inbdCrs) {
      if (turnDir === "R") {
        const delta = (inbdCrs - outbCrs).normaliseDegrees();
        outbCrs += delta < 1 ? delta : 1;
        outbCrs = outbCrs.normaliseDegrees();
      } else {
        const delta = (outbCrs - inbdCrs).normaliseDegrees();
        outbCrs -= delta < 1 ? delta : 1;
        outbCrs = outbCrs.normaliseDegrees();
      }
      if (outbCrs === inbdCrs) break;

      const arcFix = geolib.computeDestinationPoint(
        center,
        nmiToMetre(radius),
        outbCrs
      );

      line.push([arcFix.longitude, arcFix.latitude]);
    }
  }

  return line;
};
const getCourseAndFixForInterceptions = (leg, origin) => {
  switch (leg.TrackCode) {
    case "CF": {
      const fix = { lat: leg.WptLat, lon: leg.WptLon };
      return [leg.Course.flipCourse().toTrue(fix), fix];
    }
    case "FM": {
      const fix = { lat: leg.WptLat, lon: leg.WptLon };
      return [leg.Course.toTrue(fix), fix];
    }
    case "TF": {
      return [
        geolib.getGreatCircleBearing(
          { latitude: origin.lat, longitude: origin.lon },
          { latitude: leg.WptLat, longitude: leg.WptLon }
        ),
        { lat: leg.WptLat, lon: leg.WptLon },
      ];
    }
    case "AF": {
      const fix = { lat: leg.WptLat, lon: leg.WptLon };
      return [leg.Course.flipCourse().toTrue(fix), fix];
    }
  }
};
const handleTurnAtFix = (inbdCrs, inbdCrs2, start, end, turnDir) => {
  // Begin line drawing at previous end
  const line = [[start.lon, start.lat]];

  // Draw arcs only if origin was flyover
  if (start.isFlyOver) {
    const arc1 = generateTangentialArc(
      inbdCrs,
      lastCourse,
      start,
      end,
      turnDir
    );

    const arc2 = generatePerformanceArc(inbdCrs2, lastCourse, start, turnDir);

    let arc;
    if (arc1) {
      const endCrs = geolib.getGreatCircleBearing(
        {
          latitude: arc1.at(-1)[1],
          longitude: arc1.at(-1)[0],
        },
        {
          latitude: end.lat,
          longitude: end.lon,
        }
      );

      if (endCrs <= inbdCrs + 1 && endCrs >= inbdCrs - 1) arc = arc1;
      else arc = arc2;
    } else {
      arc = arc2;
    }

    // Push line
    line.push(...arc);
  } else {
    //FIXME: Non Flyover
    //line.push([end.lon, end.lat]);
  }

  return line;
};
const parseAltitude = (alt) => {
  return Number.parseInt(alt.substring(0, 5));
};

/* Data */
const waypoints = JSON.parse(
  fs.readFileSync("browser/public/navdata/Waypoints.json")
);
const terminal = JSON.parse(
  fs.readFileSync("browser/public/navdata/Terminals.json")
).filter(({ ID }) => ID === PROCEDURE_ID)[0];
const runway = JSON.parse(
  fs.readFileSync("browser/public/navdata/Runways.json")
).filter(({ ID }) => ID === terminal.RwyID)[0];
const procedure = JSON.parse(
  fs.readFileSync(`browser/public/navdata/TermID_${PROCEDURE_ID}.json`)
);

/* Output */
const points = [
  { lat: runway.Latitude, lon: runway.Longitude, alt: runway.Elevation },
];
const lines = [];
let lastCourse = runway.TrueHeading;

/* Main */
for (let index = 0; index < procedure.length; index++) {
  const leg = procedure[index];
  const waypoint = waypoints.filter(({ ID }) => ID === leg.WptID)[0];

  switch (leg.TrackCode) {
    case "AF": {
      // Push in ending waypoint
      points.push({
        lat: leg.WptLat,
        lon: leg.WptLon,
        name: waypoint?.Ident ?? undefined,
        "marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
        isFlyOver: leg.IsFlyOver !== 0,
        alt: points.at(-1).alt,
      });

      const arcEndCrs = geolib.getGreatCircleBearing(
        {
          latitude: leg.NavLat,
          longitude: leg.NavLon,
        },
        {
          latitude: leg.WptLat,
          longitude: leg.WptLon,
        }
      );

      const line = generateAFArc(
        arcEndCrs,
        leg.Course.toTrue({ lat: leg.NavLat, lon: leg.NavLon }),
        points.at(-2),
        { lat: leg.NavLat, lon: leg.NavLon },
        leg.NavDist,
        leg.TurnDir
      );
      lines.push({ line });
      updateLastCourse(line);

      break;
    }
    case "CA":
    case "CD":
      break;
    case "CF": {
      // Push in ending waypoint
      points.push({
        lat: leg.WptLat,
        lon: leg.WptLon,
        name: waypoint?.Ident ?? undefined,
        "marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
        isFlyOver: leg.IsFlyOver !== 0,
        alt: points.at(-1).alt,
      });

      const line = handleTurnAtFix(
        leg.Course.toTrue(points.at(-1)),
        leg.Course.toTrue(points.at(-1)),
        points.at(-2),
        points.at(-1),
        leg.TurnDir,
        index
      );

      line.push([leg.WptLon, leg.WptLat]);
      lines.push({ line });
      updateLastCourse(lines.at(-1).line);

      break;
    }
    case "CI": {
      // Course into the destination fix and said fix
      const [inbdCrs, fix] = getCourseAndFixForInterceptions(
        procedure[index + 1],
        points.at(-1)
      );

      // Compute INTC
      const intc = computeIntersection(
        points.at(-1),
        leg.Course.toTrue(fix),
        fix,
        inbdCrs
      );

      const line = handleTurnAtFix(
        inbdCrs,
        leg.Course.toTrue(fix),
        points.at(-1),
        intc,
        leg.TurnDir,
        index
      );
      lines.push({ line });
      updateLastCourse(line);

      const intc2 = computeIntersection(
        { lat: line.at(-1)[1], lon: line.at(-1)[0] },
        leg.Course.toTrue(fix),
        fix,
        inbdCrs
      );
      if (intc2) {
        points.push(intc2);
        lines.push({ line: [line.at(-1), [intc2.lon, intc2.lat]] });
        updateLastCourse(line);
      } else {
        points.push(intc);
        lines.push({
          line: [line.at(-1), [intc.lon, intc.lat]],
        });
      }

      break;
    }
    case "CR": {
      // Course into the destination fix
      const inbdCrs = leg.Course.toTrue(points.at(-1));

      // Compute INTC
      const intc = computeIntersection(
        points.at(-1),
        inbdCrs,
        { lat: leg.NavLat, lon: leg.NavLon },
        leg.NavBear.toTrue({ lat: leg.NavLat, lon: leg.NavLon })
      );
      points.push(intc);

      const line = handleTurnAtFix(
        inbdCrs,
        leg.Course.toTrue(points.at(-2)),
        points.at(-2),
        points.at(-1),
        leg.TurnDir,
        index
      );
      line.push([intc.lon, intc.lat]);
      lines.push({ line });
      updateLastCourse(line);

      break;
    }
    case "DF":
    case "FA":
    case "FC":
    case "FD":
      break;
    case "FM": {
      const end = geolib.computeDestinationPoint(
        {
          latitude: points.at(-1).lat,
          longitude: points.at(-1).lon,
        },
        nmiToMetre(10),
        leg.Course.toTrue(points.at(-1))
      );

      const line = handleTurnAtFix(
        leg.Course.toTrue(points.at(-1)),
        leg.Course.toTrue(points.at(-1)),
        points.at(-1),
        { lat: end.latitude, lon: end.longitude },
        leg.TurnDir,
        index
      );
      lines.push({ line });
      line.push([end.longitude, end.latitude]);
      updateLastCourse(line);

      break;
    }
    case "HA":
    case "HF":
    case "HM":
    case "IF":
    case "PI":
      break;
    case "RF": {
      // Push in ending waypoint
      points.push({
        lat: leg.WptLat,
        lon: leg.WptLon,
        name: waypoint?.Ident ?? undefined,
        "marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
        isFlyOver: leg.IsFlyOver !== 0,
        alt: points.at(-1).alt,
      });

      const [inbdCrs] = getCourseAndFixForInterceptions(
        procedure[index + 1],
        points.at(-1)
      );

      const line = generateRFArc(
        inbdCrs,
        lastCourse,
        points.at(-1),
        { lat: leg.CenterLat, lon: leg.CenterLon },
        leg.TurnDir
      );
      lines.push({ line });
      updateLastCourse(line);

      break;
    }
    case "TF": {
      // Push in ending waypoint
      points.push({
        lat: leg.WptLat,
        lon: leg.WptLon,
        name: waypoint?.Ident ?? undefined,
        "marker-color": leg.IsFlyOver !== 0 ? "#ff0000" : undefined,
        isFlyOver: leg.IsFlyOver !== 0,
        alt: points.at(-1).alt,
      });

      const inbdCrs = geolib.getGreatCircleBearing(
        { latitude: points.at(-2).lat, longitude: points.at(-2).lon },
        { latitude: points.at(-1).lat, longitude: points.at(-1).lon }
      );

      const line = handleTurnAtFix(
        inbdCrs,
        inbdCrs,
        points.at(-2),
        points.at(-1),
        leg.TurnDir,
        index
      );
      line.push([leg.WptLon, leg.WptLat]);
      lines.push({ line });
      updateLastCourse(line);

      break;
    }
    case "VA": {
      // NOTE: No wind adjustments to be made, no clue how *that* would draw

      const end = geolib.computeDestinationPoint(
        {
          latitude: points.at(-1).lat,
          longitude: points.at(-1).lon,
        },
        nmiToMetre(
          ((parseAltitude(leg.Alt) - points.at(-1).alt) / AC_VS) *
            (AC_SPEED / 60)
        ),
        leg.Course.toTrue(points.at(-1))
      );
      points.push({
        lat: end.latitude,
        lon: end.longitude,
        name: leg.Alt,
        "marker-color": "#ff0000",
        isFlyOver: true,
        alt: parseAltitude(leg.Alt),
      });

      const line = handleTurnAtFix(
        leg.Course.toTrue(points.at(-2)),
        leg.Course.toTrue(points.at(-2)),
        points.at(-2),
        { lat: end.latitude, lon: end.longitude },
        leg.TurnDir,
        index
      );
      line.push([end.longitude, end.latitude]);
      lines.push({ line });
      updateLastCourse(line);

      break;
    }
    case "VD": {
      // NOTE: No wind adjustments to be made, no clue how *that* would draw

      const end = geolib.computeDestinationPoint(
        {
          latitude: points.at(-1).lat,
          longitude: points.at(-1).lon,
        },
        //NOTE: Does not account for slant
        nmiToMetre(leg.Distance),
        leg.Course.toTrue(points.at(-1))
      );
      points.push({
        lat: end.latitude,
        lon: end.longitude,
        name: leg.Alt,
        "marker-color": "#ff0000",
        isFlyOver: true,
        alt: points.at(-1).alt,
      });

      const line = handleTurnAtFix(
        leg.Course.toTrue(points.at(-2)),
        leg.Course.toTrue(points.at(-2)),
        points.at(-2),
        { lat: end.latitude, lon: end.longitude },
        leg.TurnDir,
        index
      );
      line.push([end.longitude, end.latitude]);
      lines.push({ line });
      updateLastCourse(line);

      break;
    }
    case "VI": {
      // NOTE: No wind adjustments to be made, no clue how *that* would draw

      // Course into the destination fix and said fix
      const [inbdCrs, fix] = getCourseAndFixForInterceptions(
        procedure[index + 1],
        points.at(-1)
      );

      // Compute INTC
      const intc = computeIntersection(
        points.at(-1),
        leg.Course.toTrue(fix),
        fix,
        inbdCrs
      );

      const line = handleTurnAtFix(
        inbdCrs,
        leg.Course.toTrue(fix),
        points.at(-1),
        intc,
        leg.TurnDir,
        index
      );
      lines.push({ line });
      updateLastCourse(line);

      const intc2 = computeIntersection(
        { lat: line.at(-1)[1], lon: line.at(-1)[0] },
        leg.Course.toTrue(fix),
        fix,
        inbdCrs
      );
      if (intc2) {
        points.push(intc2);
        lines.push({ line: [line.at(-1), [intc2.lon, intc2.lat]] });
        updateLastCourse(line);
      }

      break;
    }
    case "VM": {
      const end = geolib.computeDestinationPoint(
        {
          latitude: points.at(-1).lat,
          longitude: points.at(-1).lon,
        },
        nmiToMetre(10),
        leg.Course.toTrue(points.at(-1))
      );

      const line = handleTurnAtFix(
        leg.Course.toTrue(points.at(-1)),
        leg.Course.toTrue(points.at(-1)),
        points.at(-1),
        { lat: end.latitude, lon: end.longitude },
        leg.TurnDir,
        index
      );
      line.push([end.longitude, end.latitude]);
      lines.push({ line });
      updateLastCourse(line);

      break;
    }
    case "VR":
    case "AF":
    default:
      break;
  }
}

/* geoJSON */
const output = geo.parse([...points, ...lines], {
  Point: ["lat", "lon"],
  LineString: "line",
});
console.log(JSON.stringify(output, null, 2));
