import {
  createLinuxCncIniSdk,
  createLinuxCncInterpSdk,
} from "../../sdk/src/index.js";
import {
  getOpfsRoot,
  loadTextFile,
  saveTextFile,
} from "../../opfs/file-service.js";
import {
  gcodeFilenameFromProgramPath,
  loadGcodeProgram,
  loadMachineTextFiles,
  machineFilePaths,
  saveGcodeProgram,
  saveMachineTextFiles,
} from "../../opfs/machine-file-store.js";
import {
  loadMachineSessionFromOpfs,
} from "../../opfs/linuxcnc-machine-session-bridge.js";
import {
  readMachineSessionReadiness,
} from "../../opfs/machine-session-readiness.js";
import {
  loadMachineSessionSnapshot,
  saveMachineSessionSnapshot,
} from "../../opfs/snapshot-store.js";
import {
  createMachineFileLoadSummary,
  createMachineFileSaveSummary,
  machineFileLoadLogLines,
  machineFileSaveLogLines,
} from "./machine-file-summary.js";
import {
  summarizeRunResult,
} from "./run-summary.js";
import {
  createRunSessionReadinessState,
  runGcodeSessionWorkflow,
} from "./run-workflow.js";
import {
  loadMachineSessionIntoWasmWorkflow,
  readMachineSessionReadinessWorkflow,
  restoreMachineSessionSnapshotWorkflow,
  saveMachineSessionSnapshotWorkflow,
} from "./session-workflow.js";
import {
  createMachineSessionControlView,
  createMachineSessionStateSnapshot,
  createMachineSessionStateBundle,
  createMachineSessionStateReport,
  createMachineSessionStateReportExport,
  createMachineSessionWorkflowStatus,
} from "./panel-state-summary.js";
import {
  renderMachineSessionControlView,
} from "./control-view-renderer.js";

const SAMPLE_PATH =
  "../../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-dual-rotary/xyzab-tdr.ini";
const FIVEAXIS_TRT_SOURCE_PATH =
  "../../../vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting";
const FIVEAXIS_TRT_WASM_DIR = "/work/ui-fiveaxis/table-rotary-tilting";
const FIVEAXIS_REMAP_FILES = [
  "428remap.ngc",
  "429remap.ngc",
  "430remap.ngc",
  "centering.ngc",
  "helix_ac.ngc",
  "helix_bc.ngc",
  "xyzac_switchkins_sub.ngc",
  "xyzbc_switchkins_sub.ngc",
];
const MACHINE_ID = "xyzab-tdr";
const MACHINE_PATHS = machineFilePaths(MACHINE_ID);
const OPFS_FILE = MACHINE_PATHS.ini;
const WASM_FILE = "/work/xyzab-tdr.ini";
const SESSION_WASM_FILES = {
  iniWasmPath: "/work/session-machine.ini",
  parameterWasmPath: "/work/session-linuxcnc.var",
  toolTableWasmPath: "/work/session-tool.tbl",
};
const GCODE_FILENAME = "ui-session.ngc";
const GCODE_WASM_FILE = "/work/ui-session.ngc";
const UI_SESSION = {
  snapshotId: "ui-machine-session",
  snapshotFilename: "ui-machine-session.json",
  defaultGcodeFilename: GCODE_FILENAME,
  gcodeWasmPath: GCODE_WASM_FILE,
};
const DEFAULT_TOOL_TABLE = "T2 P7 Z3.125 D1.5 I12 J34 Q4 ;ui session tool\n";
const DEFAULT_PARAMETERS = [
  "5161 0.0",
  "5162 0.0",
  "5220 1",
  "5221 0.0",
  "5399 0.0",
  "",
].join("\n");
const DEFAULT_GCODE = [
  "G0 X1.0 Y2.0 (Comment)",
  "G1 X3.0 Y4.0 F120.0",
  "",
].join("\n");

const editor = document.getElementById("ini-editor");
const logNode = document.getElementById("log");
const eventNode = document.getElementById("canon-events");
const eventFilterNode = document.getElementById("event-filter");
const eventCountNode = document.getElementById("event-count");
const wasmBadge = document.getElementById("wasm-badge");
const interpBadge = document.getElementById("interp-badge");
const opfsBadge = document.getElementById("opfs-badge");
const runProgressNode = document.getElementById("run-progress");
const runProgressLabelNode = document.getElementById("run-progress-label");
const runMotionNode = document.getElementById("run-motion");
const runLineNode = document.getElementById("run-line");
const runStatementNode = document.getElementById("run-statement");
const controlViewNode = document.getElementById("machine-session-control-view");
const axisNodes = {
  x: document.getElementById("axis-x"),
  y: document.getElementById("axis-y"),
  z: document.getElementById("axis-z"),
  a: document.getElementById("axis-a"),
  b: document.getElementById("axis-b"),
  c: document.getElementById("axis-c"),
  u: document.getElementById("axis-u"),
  v: document.getElementById("axis-v"),
  w: document.getElementById("axis-w"),
};

const fields = {
  machine: document.getElementById("field-machine"),
  display: document.getElementById("field-display"),
  kinematics: document.getElementById("field-kinematics"),
  joints: document.getElementById("field-joints"),
  coordinates: document.getElementById("field-coordinates"),
  parameterFile: document.getElementById("field-parameter-file"),
  toolTable: document.getElementById("field-tool-table"),
  sessionIni: document.getElementById("field-session-ini"),
  sessionParameters: document.getElementById("field-session-parameters"),
  sessionToolTable: document.getElementById("field-session-tool-table"),
  sessionGcode: document.getElementById("field-session-gcode"),
  sessionSnapshot: document.getElementById("field-session-snapshot"),
  runStatus: document.getElementById("field-run-status"),
  runSource: document.getElementById("field-run-source"),
  runSnapshot: document.getElementById("field-run-snapshot"),
  runWorkflow: document.getElementById("field-run-workflow"),
  runOpfsPath: document.getElementById("field-run-opfs-path"),
  runWasmPath: document.getElementById("field-run-wasm-path"),
  fiveaxisRemap: document.getElementById("field-fiveaxis-remap"),
};

let iniSdk = null;
let interpSdk = null;
let loadedSession = null;
let restoredSessionSnapshot = null;
let canonicalEventText = "";
let runPlaybackTimer = null;
let panelMachineSessionState = createMachineSessionStateSnapshot();

function getMachineSessionStateReport() {
  return createMachineSessionStateReport(window.linuxCncIniPanelMachineSessionState);
}

function getMachineSessionStateReportExport() {
  return createMachineSessionStateReportExport(window.linuxCncIniPanelMachineSessionState);
}

function getMachineSessionWorkflowStatus() {
  return createMachineSessionWorkflowStatus(window.linuxCncIniPanelMachineSessionState);
}

function getMachineSessionStateBundle() {
  return createMachineSessionStateBundle(window.linuxCncIniPanelMachineSessionState);
}

function getMachineSessionControlView() {
  return createMachineSessionControlView(getMachineSessionStateBundle());
}

function renderCurrentMachineSessionControlView(container = controlViewNode) {
  return renderMachineSessionControlView(container, getMachineSessionControlView());
}

function updatePanelMachineSessionState(patch = {}) {
  panelMachineSessionState = createMachineSessionStateSnapshot({
    ...panelMachineSessionState,
    ...patch,
    fields: {
      ...panelMachineSessionState.fields,
      ...(patch.fields ?? {}),
    },
  });
  window.linuxCncIniPanelMachineSessionState = panelMachineSessionState;
  renderCurrentMachineSessionControlView();
  return panelMachineSessionState;
}

window.linuxCncIniPanelApi = {
  getMachineSessionControlView,
  getMachineSessionStateBundle,
  getMachineSessionStateReport,
  getMachineSessionStateReportExport,
  getMachineSessionWorkflowStatus,
  renderMachineSessionControlView: renderCurrentMachineSessionControlView,
};
window.getMachineSessionControlView = getMachineSessionControlView;
window.getMachineSessionStateBundle = getMachineSessionStateBundle;
window.getMachineSessionStateReport = getMachineSessionStateReport;
window.getMachineSessionStateReportExport = getMachineSessionStateReportExport;
window.getMachineSessionWorkflowStatus = getMachineSessionWorkflowStatus;
window.renderMachineSessionControlView = renderCurrentMachineSessionControlView;
window.linuxCncIniPanelMachineSessionState = panelMachineSessionState;
renderCurrentMachineSessionControlView();

function setBadge(node, text, className = "badge") {
  node.className = className;
  node.textContent = text;
}

function setLog(message, isError = false) {
  logNode.textContent = message;
  logNode.className = `log${isError ? " danger" : ""}`;
}

function setCanonicalEvents(text) {
  canonicalEventText = text || "";
  renderCanonicalEvents();
}

function renderCanonicalEvents() {
  const lines = canonicalEventText.split("\n").filter(Boolean);
  const filter = eventFilterNode.value.trim().toLowerCase();
  const visibleLines = filter
    ? lines.filter((line) => line.toLowerCase().includes(filter))
    : lines;

  eventNode.textContent = visibleLines.join("\n");
  eventCountNode.textContent = `${visibleLines.length} / ${lines.length}`;
}

function setField(name, value) {
  fields[name].textContent = value ?? "-";
}

function nextFrame() {
  return new Promise((resolve) => requestAnimationFrame(resolve));
}

function formatAxisValue(value) {
  return Number.isFinite(value) ? value.toFixed(3) : "0.000";
}

function clearRunPlayback() {
  if (runPlaybackTimer) {
    clearInterval(runPlaybackTimer);
    runPlaybackTimer = null;
  }
}

function setRunMonitor(progress, label, axes = {}, motionText = "idle", lineText = "line -", statement = "-") {
  runProgressNode.value = Math.max(0, Math.min(100, progress));
  runProgressLabelNode.textContent = label;
  runMotionNode.textContent = motionText;
  runLineNode.textContent = lineText;
  runStatementNode.textContent = statement;
  for (const [axis, node] of Object.entries(axisNodes)) {
    node.textContent = formatAxisValue(axes[axis] ?? 0);
  }
}

function readCanonicalNumber(line, name) {
  const match = line.match(new RegExp(`(?:^| )${name}=([-+0-9.eE]+)`));
  return match ? Number(match[1]) : null;
}

function programLineMap(programText) {
  const lines = new Map();
  programText.split(/\r?\n/).forEach((line, index) => {
    lines.set(index + 1, line.trim() || "(blank)");
  });
  return lines;
}

function parseRunMotion(resultText, programText) {
  const axes = { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0, u: 0, v: 0, w: 0 };
  const snapshots = [];
  const sourceLines = programLineMap(programText);
  const motionByLine = new Map();

  for (const line of resultText.split("\n")) {
    const motion = line.match(/^canon_event=(STRAIGHT_TRAVERSE|STRAIGHT_FEED|ARC_FEED)\b/);
    if (!motion) {
      continue;
    }
    const sourceLine = readCanonicalNumber(line, "line");
    if (Number.isFinite(sourceLine)) {
      motionByLine.set(sourceLine, motion[1]);
    }
  }

  for (const line of resultText.split("\n")) {
    if (!line.startsWith("run_step phase=execute ")) {
      continue;
    }

    for (const axis of Object.keys(axes)) {
      const value = readCanonicalNumber(line, axis);
      if (value !== null && Number.isFinite(value)) {
        axes[axis] = value;
      }
    }

    const sourceLine = readCanonicalNumber(line, "line");
    const statementIndex = line.indexOf(" statement_uri=");
    const statementValue =
      statementIndex >= 0 ? line.slice(statementIndex + " statement_uri=".length).trim() : "";
    let statement = "";
    try {
      statement = statementValue ? decodeURIComponent(statementValue) : "";
    } catch {
      statement = statementValue;
    }
    snapshots.push({
      type: motionByLine.get(sourceLine) ?? "EXECUTE",
      line: sourceLine,
      statement: statement || (Number.isFinite(sourceLine) ? (sourceLines.get(sourceLine) ?? "-") : "-"),
      axes: { ...axes },
    });
  }

  if (snapshots.length > 0) {
    return snapshots;
  }

  for (const line of resultText.split("\n")) {
    const motion = line.match(/^canon_event=(STRAIGHT_TRAVERSE|STRAIGHT_FEED|ARC_FEED)\b/);
    if (!motion) {
      continue;
    }

    for (const axis of Object.keys(axes)) {
      const value = readCanonicalNumber(line, axis);
      if (value !== null && Number.isFinite(value)) {
        axes[axis] = value;
      }
    }

    const sourceLine = readCanonicalNumber(line, "line");
    snapshots.push({
      type: motion[1],
      line: sourceLine,
      statement: Number.isFinite(sourceLine) ? (sourceLines.get(sourceLine) ?? "-") : "-",
      axes: { ...axes },
    });
  }

  return snapshots;
}

function showRunSnapshot(snapshot, index, total) {
  const progress = total > 0 ? Math.round(((index + 1) / total) * 100) : 0;
  const lineText = Number.isFinite(snapshot.line) ? `line ${snapshot.line}` : "line -";
  setRunMonitor(
    progress,
    `${progress}%`,
    snapshot.axes,
    `${snapshot.type} ${lineText} (${index + 1}/${total})`,
    lineText,
    snapshot.statement,
  );
}

function playRunMotion(resultText, programText, snapshots = parseRunMotion(resultText, programText)) {
  clearRunPlayback();
  if (snapshots.length === 0) {
    setRunMonitor(100, "100%", {}, "complete; no axis motion");
    return;
  }

  let index = 0;
  showRunSnapshot(snapshots[index], index, snapshots.length);
  runPlaybackTimer = setInterval(() => {
    index += 1;
    if (index >= snapshots.length) {
      clearRunPlayback();
      showRunSnapshot(snapshots[snapshots.length - 1], snapshots.length - 1, snapshots.length);
      return;
    }
    showRunSnapshot(snapshots[index], index, snapshots.length);
  }, 220);
}

function requireIniSdk() {
  if (!iniSdk) {
    throw new Error("INI WASM module is not ready yet.");
  }
  return iniSdk;
}

function requireInterpSdk() {
  if (!interpSdk) {
    throw new Error("Interpreter WASM module is not ready yet.");
  }
  return interpSdk;
}

function syncEditorToWasmFs() {
  requireIniSdk().writeTextFile(WASM_FILE, editor.value);
}

function sessionSnapshotLabel(snapshot = UI_SESSION) {
  const sessionId = typeof snapshot === "string" ? snapshot : snapshot.snapshotId ?? snapshot.sessionId;
  return `${sessionId}/${UI_SESSION.snapshotFilename}`;
}

function defaultGcodeOpfsPath() {
  return `linuxcnc/gcode/${UI_SESSION.defaultGcodeFilename}`;
}

function sessionSnapshotMetadata() {
  return {
    source: "ini-panel",
    workflow: "save-session-snapshot",
    snapshotLabel: sessionSnapshotLabel(UI_SESSION),
    defaultGcodeOpfsPath: defaultGcodeOpfsPath(),
  };
}

function clearLastRunSummary() {
  window.linuxCncIniPanelLastRunSummary = null;
  setField("runSource", "-");
  setField("runSnapshot", "-");
  setField("runWorkflow", "-");
  setField("runOpfsPath", "-");
  setField("runWasmPath", "-");
}

function setLastRunSummary(summary) {
  window.linuxCncIniPanelLastRunSummary = summary;
  setField("runSource", summary.programSource);
  setField("runSnapshot", summary.snapshotLabel ?? "-");
  setField("runWorkflow", summary.workflow ?? "-");
  setField("runOpfsPath", summary.programOpfsPath);
  setField("runWasmPath", summary.programWasmPath);
  updatePanelMachineSessionState({
    run: summary,
    fields: {
      runStatus: summary.runStatus,
      runSource: summary.programSource,
      runSnapshot: summary.snapshotLabel ?? "-",
      runWorkflow: summary.workflow ?? "-",
      runOpfsPath: summary.programOpfsPath,
      runWasmPath: summary.programWasmPath,
    },
  });
  return summary;
}

async function restoreSessionSnapshotToEditor() {
  const snapshot = await loadMachineSessionSnapshot(
    UI_SESSION.snapshotId,
    MACHINE_ID,
    { filename: UI_SESSION.snapshotFilename },
  );
  const files = await loadMachineTextFiles(MACHINE_ID);
  editor.value = files.ini;
  setField("sessionIni", snapshot.payload.files.ini);
  setField("sessionParameters", snapshot.payload.files.parameters);
  setField("sessionToolTable", snapshot.payload.files.toolTable);
  setField("sessionGcode", snapshot.payload.files.gcode);
  setField("sessionSnapshot", sessionSnapshotLabel(snapshot.sessionId));
  restoredSessionSnapshot = snapshot;
  return snapshot;
}

async function loadMachineSessionIntoWasm() {
  loadedSession = await loadMachineSessionFromOpfs(
    requireInterpSdk(),
    MACHINE_ID,
    {
      ...SESSION_WASM_FILES,
      iniSdk: requireIniSdk(),
    },
  );
  setField("sessionIni", loadedSession.ini.wasmPath);
  setField("sessionParameters", loadedSession.parameters.wasmPath);
  setField("sessionToolTable", loadedSession.toolTable.wasmPath);
  return loadedSession;
}

function readCurrentMachineSessionReadiness() {
  return readMachineSessionReadiness(MACHINE_ID, {
    sessionId: UI_SESSION.snapshotId,
    snapshotFilename: UI_SESSION.snapshotFilename,
    gcodeFilename: UI_SESSION.defaultGcodeFilename,
  });
}

async function loadSample() {
  setLog(`Fetching ${SAMPLE_PATH}`);
  const response = await fetch(SAMPLE_PATH);
  if (!response.ok) {
    throw new Error(`Cannot fetch sample INI (${response.status})`);
  }
  editor.value = await response.text();
  setLog("Loaded LinuxCNC sample INI into the standalone editor.");
}

async function fetchTextFile(path) {
  const response = await fetch(path);
  if (!response.ok) {
    throw new Error(`Cannot fetch ${path} (${response.status})`);
  }
  return response.text();
}

async function writeFetchedTextFile(interp, sourcePath, wasmPath) {
  interp.writeTextFile(wasmPath, await fetchTextFile(sourcePath));
}

async function writeFiveAxisTrtMachineFiles(interp) {
  await writeFetchedTextFile(
    interp,
    `${FIVEAXIS_TRT_SOURCE_PATH}/xyzac-trt.ini`,
    `${FIVEAXIS_TRT_WASM_DIR}/xyzac-trt.ini`,
  );
  await writeFetchedTextFile(
    interp,
    `${FIVEAXIS_TRT_SOURCE_PATH}/xyzac-trt.tbl`,
    `${FIVEAXIS_TRT_WASM_DIR}/xyzac-trt.tbl`,
  );
  for (const filename of FIVEAXIS_REMAP_FILES) {
    await writeFetchedTextFile(
      interp,
      `${FIVEAXIS_TRT_SOURCE_PATH}/remap_subs/${filename}`,
      `${FIVEAXIS_TRT_WASM_DIR}/remap_subs/${filename}`,
    );
  }
  await writeFetchedTextFile(
    interp,
    `${FIVEAXIS_TRT_SOURCE_PATH}/demos/impeller-7bl-xyzac.ngc`,
    `${FIVEAXIS_TRT_WASM_DIR}/demos/impeller-7bl-xyzac.ngc`,
  );
}

async function boot() {
  try {
    iniSdk = await createLinuxCncIniSdk();
    setBadge(wasmBadge, "INI WASM: ready");
    updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, ini: "INI WASM: ready" } });
  } catch (error) {
    setBadge(wasmBadge, "INI WASM: failed", "badge danger");
    setLog(`INI WASM init failed: ${error.message}`, true);
    updatePanelMachineSessionState({
      badges: { ...panelMachineSessionState.badges, ini: "INI WASM: failed" },
      lastAction: "boot",
      error: error.message,
    });
    throw error;
  }

  try {
    interpSdk = await createLinuxCncInterpSdk();
    setBadge(interpBadge, "Interpreter WASM: ready");
    updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, interp: "Interpreter WASM: ready" } });
  } catch (error) {
    setBadge(interpBadge, "Interpreter WASM: failed", "badge danger");
    setLog(`Interpreter WASM init failed: ${error.message}`, true);
    updatePanelMachineSessionState({
      badges: { ...panelMachineSessionState.badges, interp: "Interpreter WASM: failed" },
      lastAction: "boot",
      error: error.message,
    });
    throw error;
  }

  try {
    await getOpfsRoot();
    setBadge(opfsBadge, "OPFS: ready");
    updatePanelMachineSessionState({ badges: { ...panelMachineSessionState.badges, opfs: "OPFS: ready" } });
  } catch (error) {
    setBadge(opfsBadge, "OPFS: unavailable", "badge danger");
    setLog(`OPFS check failed: ${error.message}`, true);
    updatePanelMachineSessionState({
      badges: { ...panelMachineSessionState.badges, opfs: "OPFS: unavailable" },
      lastAction: "boot",
      error: error.message,
    });
  }
}

document.getElementById("load-sample").addEventListener("click", async () => {
  try {
    await loadSample();
  } catch (error) {
    setLog(error.message, true);
  }
});

document.getElementById("save-opfs").addEventListener("click", async () => {
  try {
    await saveTextFile(OPFS_FILE, editor.value);
    setLog(`Saved current INI text to OPFS at ${OPFS_FILE}`);
  } catch (error) {
    setLog(`OPFS save failed: ${error.message}`, true);
  }
});

document.getElementById("save-machine-files").addEventListener("click", async () => {
  try {
    const paths = await saveMachineTextFiles(MACHINE_ID, {
      ini: editor.value,
      toolTable: DEFAULT_TOOL_TABLE,
      parameters: DEFAULT_PARAMETERS,
    });
    const gcodePath = await saveGcodeProgram(UI_SESSION.defaultGcodeFilename, DEFAULT_GCODE);
    const summary = createMachineFileSaveSummary(paths, gcodePath);
    updatePanelMachineSessionState({
      machineFiles: summary,
      lastAction: "save-machine-files",
      error: null,
    });
    setLog(
      [
        "Saved machine text files to OPFS.",
        ...machineFileSaveLogLines(summary),
      ].join("\n"),
    );
  } catch (error) {
    setLog(`Machine file save failed: ${error.message}`, true);
  }
});

document.getElementById("save-session-snapshot").addEventListener("click", async () => {
  try {
    const workflow = await saveMachineSessionSnapshotWorkflow({
      machineId: MACHINE_ID,
      snapshotId: UI_SESSION.snapshotId,
      snapshotFilename: UI_SESSION.snapshotFilename,
      snapshotLabel: sessionSnapshotLabel(UI_SESSION),
      iniText: editor.value,
      toolTableText: DEFAULT_TOOL_TABLE,
      parameterText: DEFAULT_PARAMETERS,
      gcodeFilename: UI_SESSION.defaultGcodeFilename,
      gcodeText: DEFAULT_GCODE,
      metadata: sessionSnapshotMetadata(),
      saveMachineTextFiles,
      saveGcodeProgram,
      saveMachineSessionSnapshot,
    });
    for (const [name, value] of Object.entries(workflow.fields)) {
      setField(name, value);
    }
    restoredSessionSnapshot = workflow.snapshot;
    updatePanelMachineSessionState({
      sessionSnapshot: workflow.summary,
      sessionLoad: null,
      selectedProgram: null,
      fields: workflow.fields,
      lastAction: "save-session-snapshot",
      error: null,
    });
    setLog(
      [
        "Saved machine session snapshot to OPFS.",
        ...workflow.logLines,
      ].join("\n"),
    );
  } catch (error) {
    updatePanelMachineSessionState({
      lastAction: "save-session-snapshot",
      error: error.message,
    });
    setLog(`Machine session snapshot save failed: ${error.message}`, true);
  }
});

document.getElementById("restore-session-snapshot").addEventListener("click", async () => {
  try {
    const workflow = await restoreMachineSessionSnapshotWorkflow({
      machineId: MACHINE_ID,
      snapshotId: UI_SESSION.snapshotId,
      snapshotFilename: UI_SESSION.snapshotFilename,
      snapshotLabel: sessionSnapshotLabel(UI_SESSION),
      loadMachineSessionSnapshot,
      loadMachineTextFiles,
      readReadiness: readCurrentMachineSessionReadiness,
    });
    editor.value = workflow.editorIniText;
    setField("sessionIni", workflow.fields.sessionIni);
    setField("sessionParameters", workflow.fields.sessionParameters);
    setField("sessionToolTable", workflow.fields.sessionToolTable);
    setField("sessionGcode", workflow.fields.sessionGcode);
    setField("sessionSnapshot", workflow.fields.sessionSnapshot);
    restoredSessionSnapshot = workflow.snapshot;
    updatePanelMachineSessionState({
      sessionSnapshot: workflow.summary,
      sessionReadiness: workflow.readiness,
      sessionLoad: null,
      selectedProgram: null,
      fields: workflow.fields,
      lastAction: "restore-session-snapshot",
      error: null,
    });
    setLog(
      [
        "Restored machine session snapshot from OPFS.",
        ...workflow.logLines,
      ].join("\n"),
    );
  } catch (error) {
    updatePanelMachineSessionState({
      lastAction: "restore-session-snapshot",
      error: error.message,
    });
    setLog(`Machine session snapshot restore failed: ${error.message}`, true);
  }
});

document.getElementById("load-session").addEventListener("click", async () => {
  try {
    const readinessWorkflow = await readMachineSessionReadinessWorkflow({
      readReadiness: readCurrentMachineSessionReadiness,
    });
    const workflow = await loadMachineSessionIntoWasmWorkflow({
      loadSession: () => loadMachineSessionIntoWasm(),
    });
    loadedSession = workflow.loadedSession;
    updatePanelMachineSessionState({
      sessionReadiness: readinessWorkflow.readiness,
      sessionLoad: workflow.loadSummary,
      fields: {
        ...readinessWorkflow.fields,
        ...workflow.fields,
      },
      lastAction: "load-session",
      error: null,
    });
    setLog([
      ...readinessWorkflow.logLines,
      ...workflow.logLines,
    ].join("\n"));
  } catch (error) {
    updatePanelMachineSessionState({
      lastAction: "load-session",
      error: error.message,
    });
    setLog(`Machine session load failed: ${error.message}`, true);
  }
});

document.getElementById("restore-load-session").addEventListener("click", async () => {
  try {
    const workflow = await restoreMachineSessionSnapshotWorkflow({
      machineId: MACHINE_ID,
      snapshotId: UI_SESSION.snapshotId,
      snapshotFilename: UI_SESSION.snapshotFilename,
      snapshotLabel: sessionSnapshotLabel(UI_SESSION),
      loadMachineSessionSnapshot,
      loadMachineTextFiles,
      readReadiness: readCurrentMachineSessionReadiness,
      loadSession: () => loadMachineSessionIntoWasm(),
    });
    editor.value = workflow.editorIniText;
    setField("sessionIni", workflow.fields.sessionIni);
    setField("sessionParameters", workflow.fields.sessionParameters);
    setField("sessionToolTable", workflow.fields.sessionToolTable);
    setField("sessionGcode", workflow.fields.sessionGcode);
    setField("sessionSnapshot", workflow.fields.sessionSnapshot);
    restoredSessionSnapshot = workflow.snapshot;
    if (workflow.loadedSession) {
      loadedSession = workflow.loadedSession;
    }
    updatePanelMachineSessionState({
      sessionSnapshot: workflow.summary,
      sessionReadiness: workflow.readiness,
      sessionLoad: workflow.loadSummary,
      selectedProgram: null,
      fields: workflow.fields,
      lastAction: "restore-load-session",
      error: null,
    });
    setLog(
      [
        "Restored and loaded machine session snapshot.",
        ...workflow.logLines,
      ].join("\n"),
    );
  } catch (error) {
    updatePanelMachineSessionState({
      lastAction: "restore-load-session",
      error: error.message,
    });
    setLog(`Machine session snapshot restore/load failed: ${error.message}`, true);
  }
});

document.getElementById("run-gcode").addEventListener("click", async () => {
  try {
    const readiness = createRunSessionReadinessState({
      loadedSession,
      snapshot: restoredSessionSnapshot,
      defaultFilename: UI_SESSION.defaultGcodeFilename,
      defaultOpfsPath: defaultGcodeOpfsPath(),
      wasmPath: UI_SESSION.gcodeWasmPath,
      gcodeFilenameFromProgramPath,
    });
    updatePanelMachineSessionState({
      runReadiness: readiness,
      selectedProgram: readiness.program,
      fields: {
        sessionGcode: readiness.program.opfsPath,
        runSource: readiness.program.source,
        runOpfsPath: readiness.program.opfsPath,
        runWasmPath: readiness.program.wasmPath,
      },
      lastAction: "run-gcode-readiness",
      error: readiness.runButton.reason,
    });
    if (!readiness.canRun) {
      throw new Error(readiness.runButton.reason ?? "Load a machine session before running G-code.");
    }
    const interp = requireInterpSdk();
    clearRunPlayback();
    setField("runStatus", "running");
    clearLastRunSummary();
    setRunMonitor(5, "running", {}, "running");
    await nextFrame();
    const workflow = await runGcodeSessionWorkflow({
      interp,
      loadedSession,
      snapshot: restoredSessionSnapshot,
      defaultFilename: UI_SESSION.defaultGcodeFilename,
      defaultOpfsPath: defaultGcodeOpfsPath(),
      wasmPath: UI_SESSION.gcodeWasmPath,
      gcodeFilenameFromProgramPath,
      loadGcodeProgram,
      snapshotLabel: restoredSessionSnapshot ? sessionSnapshotLabel(restoredSessionSnapshot.sessionId) : null,
      summarizeRun: (resultText, programText) => {
        const motionSnapshots = parseRunMotion(resultText, programText);
        return {
          ...summarizeRunResult(resultText, motionSnapshots),
          motionSnapshots,
        };
      },
    });
    setField("sessionGcode", workflow.fields.sessionGcode);
    setField("runStatus", workflow.fields.runStatus);
    setLastRunSummary(workflow.summary);
    updatePanelMachineSessionState({
      runReadiness: workflow.readiness,
      selectedProgram: {
        source: workflow.program.source,
        opfsPath: workflow.program.opfsPath,
        wasmPath: workflow.program.wasmPath,
      },
      run: workflow.summary,
      fields: workflow.fields,
      lastAction: "run-gcode",
      error: null,
    });
    setCanonicalEvents(workflow.resultText);
    playRunMotion(workflow.resultText, workflow.program.text, workflow.motionSnapshots);
    setLog(
      [
        "Ran G-code through LinuxCNC interpreter WASM.",
        ...workflow.logLines,
      ].join("\n"),
    );
  } catch (error) {
    setField("runStatus", "failed");
    setCanonicalEvents("");
    clearRunPlayback();
    clearLastRunSummary();
    setRunMonitor(0, "failed", {}, "failed");
    updatePanelMachineSessionState({
      fields: {
        runStatus: "failed",
      },
      lastAction: "run-gcode",
      error: error.message,
    });
    setLog(`G-code run failed: ${error.message}`, true);
  }
});

document.getElementById("run-fiveaxis-remap").addEventListener("click", async () => {
  try {
    const interp = requireInterpSdk();
    await writeFiveAxisTrtMachineFiles(interp);
    const iniPath = `${FIVEAXIS_TRT_WASM_DIR}/xyzac-trt.ini`;
    const programPath = `${FIVEAXIS_TRT_WASM_DIR}/demos/impeller-7bl-xyzac.ngc`;
    const result = interp.runFiveAxisRemapFile(programPath, iniPath);
    setField("fiveaxisRemap", "ok");
    setCanonicalEvents(result.trim());
    setLog(
      [
        "Ran LinuxCNC 5-axis switchkins remap demo through interpreter WASM.",
        `Program: ${programPath}`,
        `INI: ${iniPath}`,
        result.trim(),
      ].join("\n"),
    );
  } catch (error) {
    setField("fiveaxisRemap", "failed");
    setCanonicalEvents("");
    setLog(`5-axis remap run failed: ${error.message}`, true);
  }
});

document.getElementById("load-opfs").addEventListener("click", async () => {
  try {
    editor.value = await loadTextFile(OPFS_FILE);
    setLog(`Loaded INI text from OPFS path ${OPFS_FILE}`);
  } catch (error) {
    setLog(`OPFS load failed: ${error.message}`, true);
  }
});

document.getElementById("sync-wasm").addEventListener("click", () => {
  try {
    syncEditorToWasmFs();
    setLog(`Synced editor contents to WASM filesystem at ${WASM_FILE}`);
  } catch (error) {
    setLog(`WASM sync failed: ${error.message}`, true);
  }
});

document.getElementById("query").addEventListener("click", async () => {
  try {
    syncEditorToWasmFs();
    const values = requireIniSdk().getFields(WASM_FILE, {
      machine: { section: "EMC", tag: "MACHINE" },
      display: { section: "DISPLAY", tag: "DISPLAY" },
      kinematics: { section: "KINS", tag: "KINEMATICS" },
      joints: { section: "KINS", tag: "JOINTS" },
      coordinates: { section: "TRAJ", tag: "COORDINATES" },
      parameterFile: { section: "RS274NGC", tag: "PARAMETER_FILE" },
      toolTable: { section: "EMCIO", tag: "TOOL_TABLE" },
    });

    for (const [name, value] of Object.entries(values)) {
      setField(name, value);
    }

    setLog("Queried LinuxCNC INI fields through the standalone WASM parser.");
  } catch (error) {
    setLog(`Query failed: ${error.message}`, true);
  }
});

document.getElementById("load-machine-files").addEventListener("click", async () => {
  try {
    const files = await loadMachineTextFiles(MACHINE_ID);
    editor.value = files.ini;
    const summary = createMachineFileLoadSummary(files);
    setLog(
      [
        "Loaded machine text files from OPFS.",
        ...machineFileLoadLogLines(summary),
      ].join("\n"),
    );
  } catch (error) {
    setLog(`Machine file load failed: ${error.message}`, true);
  }
});

eventFilterNode.addEventListener("input", () => {
  renderCanonicalEvents();
});

boot().catch(() => {});
