import type { MotionDiagnostic } from "../kdl/types.js";
import { pathOperationToGrl, type JsonPatchOperation, type OlpPathPoint, type OlpProjectModel, type OlpTarget } from "../olp/index.js";
import { applyPatch } from "../olp/index.js";

export type ImportBrand = "abb" | "kuka" | "fanuc";

export interface BrandImportResult {
  brand: ImportBrand;
  patch: JsonPatchOperation[];
  model: OlpProjectModel;
  grl: string;
  diagnostics: MotionDiagnostic[];
  report: {
    status: "pass" | "warn" | "fail";
    targets: number;
    motions: number;
    approximations: string[];
  };
}

interface ParsedMotion {
  motion: "movej" | "movel" | "movec";
  targetName: string;
  viaName?: string;
  sourceLine: number;
}

export function importBrandProgram(
  brand: ImportBrand,
  text: string,
  baseModel: OlpProjectModel,
  options: { operationId?: string; pathId?: string; robotId?: string } = {}
): BrandImportResult {
  const diagnostics: MotionDiagnostic[] = [];
  const targets = parseTargets(brand, text, diagnostics);
  const motions = parseMotions(brand, text, diagnostics);
  const pathId = options.pathId ?? `${brand}_import_path`;
  const operationId = options.operationId ?? `${brand}_import_op`;
  const targetPatch = targets.map((target): JsonPatchOperation => ({ op: "add", path: "/targets/-", value: target }));
  const pathPoints = motions.map((motion, index): OlpPathPoint => ({
    id: `p${String(index).padStart(2, "0")}`,
    name: `p${String(index).padStart(2, "0")}`,
    motion: motion.motion,
    targetId: targetId(motion.targetName),
    ...(motion.viaName ? { viaTargetId: targetId(motion.viaName) } : {})
  }));
  const patch: JsonPatchOperation[] = [
    ...targetPatch,
    {
      op: "add",
      path: "/paths/-",
      value: {
        id: pathId,
        name: pathId,
        source: { type: "brand_import", brand },
        points: pathPoints
      }
    },
    {
      op: "add",
      path: "/operations/-",
      value: {
        id: operationId,
        name: operationId,
        kind: "imported",
        pathId,
        ...(options.robotId ? { robotId: options.robotId } : {})
      }
    }
  ];
  const model = applyPatch(baseModel, patch);
  const grlResult = pathOperationToGrl(model, operationId, {
    moduleName: `${brand}_Imported`,
    style: "expanded"
  });
  model.programs.push({
    id: `${operationId}_source`,
    name: `${brand}_source`,
    language: brand === "abb" ? "abb_rapid" : brand === "kuka" ? "kuka_krl" : "fanuc_ls",
    entryOperationIds: [operationId],
    source: {
      kind: "imported",
      text
    }
  });
  model.programs.push(grlResult.program);
  if (motions.length === 0) {
    diagnostics.push({
      severity: "error",
      code: "BRAND_IMPORT_NO_MOTIONS",
      message: `No motions were found in ${brand} program`
    });
  }

  return {
    brand,
    patch,
    model,
    grl: grlResult.text,
    diagnostics,
    report: {
      status: diagnostics.some((diagnostic) => diagnostic.severity === "error")
        ? "fail"
        : diagnostics.some((diagnostic) => diagnostic.severity === "warning")
          ? "warn"
          : "pass",
      targets: targets.length,
      motions: motions.length,
      approximations: diagnostics
        .filter((diagnostic) => diagnostic.code.includes("APPROX"))
        .map((diagnostic) => diagnostic.message)
    }
  };
}

function parseTargets(brand: ImportBrand, text: string, diagnostics: MotionDiagnostic[]): OlpTarget[] {
  if (brand === "abb") {
    return [...text.matchAll(/CONST\s+robtarget\s+(\w+)\s*:=\s*\[\[([^\]]+)\]/gi)]
      .map((match) => makePoseTarget(match[1]!, match[2]!));
  }
  if (brand === "kuka") {
    return [...text.matchAll(/DECL\s+E6POS\s+(\w+)\s*=\s*\{([^}]+)\}/gi)]
      .map((match) => makePoseTarget(match[1]!, kukaPoseValues(match[2]!)));
  }
  const fanucTargets = [...text.matchAll(/P\[(\d+)\]\s*\{([^}]+)\}/gi)]
    .map((match) => makePoseTarget(`P${match[1]!}`, fanucPoseValues(match[2]!)));
  if (fanucTargets.length === 0) {
    diagnostics.push({
      severity: "warning",
      code: "BRAND_IMPORT_APPROX_FANUC_POSITIONS",
      message: "FANUC LS position block was not found; referenced positions use zero pose placeholders"
    });
  }
  return fanucTargets;
}

function parseMotions(brand: ImportBrand, text: string, diagnostics: MotionDiagnostic[]): ParsedMotion[] {
  const lines = text.split(/\r?\n/);
  return lines.flatMap((line, index): ParsedMotion[] => {
    const sourceLine = index + 1;
    if (brand === "abb") {
      const move = line.match(/\b(MoveJ|MoveL|MoveC)\s+([^,;\s]+)(?:\s*,\s*([^,;\s]+))?/i);
      if (!move) return [];
      const kind = move[1]!.toLowerCase();
      if (kind === "movec") {
        return [{ motion: "movec", viaName: move[2]!, targetName: move[3] ?? move[2]!, sourceLine }];
      }
      return [{ motion: kind === "movej" ? "movej" : "movel", targetName: move[2]!, sourceLine }];
    }
    if (brand === "kuka") {
      const move = line.match(/\b(PTP|LIN|CIRC)\s+(\w+)(?:\s*,\s*(\w+))?/i);
      if (!move) return [];
      if (move[1]!.toUpperCase() === "CIRC") {
        return [{ motion: "movec", viaName: move[2]!, targetName: move[3] ?? move[2]!, sourceLine }];
      }
      return [{ motion: move[1]!.toUpperCase() === "PTP" ? "movej" : "movel", targetName: move[2]!, sourceLine }];
    }
    const move = line.match(/\b([JLC])\s+P\[(\d+)\](?:\s+P\[(\d+)\])?/i);
    if (!move) return [];
    if (move[1]!.toUpperCase() === "C") {
      return [{ motion: "movec", viaName: `P${move[2]!}`, targetName: `P${move[3] ?? move[2]}`, sourceLine }];
    }
    return [{ motion: move[1]!.toUpperCase() === "J" ? "movej" : "movel", targetName: `P${move[2]!}`, sourceLine }];
  }).map((motion) => {
    if (motion.motion === "movec" && !motion.viaName) {
      diagnostics.push({
        severity: "warning",
        code: "BRAND_IMPORT_APPROX_MOVEC",
        message: `MoveC at line ${motion.sourceLine} has no explicit via target`
      });
    }
    return motion;
  });
}

function makePoseTarget(name: string, values: string | number[]): OlpTarget {
  const pose = Array.isArray(values)
    ? values
    : values.split(",").map((value) => Number(value.trim())).filter((value) => Number.isFinite(value));
  const padded = [...pose, 0, 0, 0, 0, 0, 0].slice(0, 6) as [number, number, number, number, number, number];
  return {
    id: targetId(name),
    name: sanitizeName(name),
    kind: "pose",
    pose: padded
  };
}

function targetId(name: string): string {
  return `import_${sanitizeName(name)}`;
}

function sanitizeName(name: string): string {
  const sanitized = name.replace(/[^A-Za-z0-9_]/g, "_");
  return /^[A-Za-z_]/.test(sanitized) ? sanitized : `P${sanitized}`;
}

function kukaPoseValues(raw: string): number[] {
  return ["X", "Y", "Z", "A", "B", "C"].map((key) => field(raw, key));
}

function fanucPoseValues(raw: string): number[] {
  return ["X", "Y", "Z", "W", "P", "R"].map((key) => field(raw, key));
}

function field(raw: string, key: string): number {
  const match = raw.match(new RegExp(`${key}\\s*[= ]\\s*(-?\\d+(?:\\.\\d+)?)`, "i"));
  return match ? Number(match[1]) : 0;
}
