import type { SemanticProgramIr } from "../grl/ir/index.js";
import type { MotionDiagnostic } from "../kdl/types.js";
import type { IoRuntimeSnapshot } from "../runtime/ioRuntime.js";
import type { MotionQueueSnapshot } from "../runtime/motionQueue.js";
import type { IrExecutionRuntimeSnapshot } from "../controller/runtime.js";
import type { RuntimeSourceLocation } from "../controller/sourceMap.js";

export type WorkbenchObjectKind =
  | "station"
  | "robot"
  | "tool"
  | "frame"
  | "target"
  | "path"
  | "operation"
  | "program"
  | "report";

export interface WorkbenchObjectNode {
  id: string;
  label: string;
  kind: WorkbenchObjectKind;
  children?: WorkbenchObjectNode[];
  source?: RuntimeSourceLocation;
  data?: Record<string, unknown>;
}

export interface EditorModel {
  activeFile?: string;
  cursor?: {
    line: number;
    column: number;
  };
  currentSource?: RuntimeSourceLocation;
  diagnostics: MotionDiagnostic[];
}

export interface TeachPendantModel {
  state: string;
  procedure?: string;
  pc: number;
  joints?: number[];
  tcp?: unknown;
  alarms: IrExecutionRuntimeSnapshot["alarmQueue"];
}

export interface IoPanelModel {
  image: IoRuntimeSnapshot["image"];
  events: IoRuntimeSnapshot["events"];
  waits: Array<{
    condition: string;
    source?: RuntimeSourceLocation;
  }>;
}

export interface ReportEntryModel {
  id: string;
  label: string;
  kind: "reachability" | "cycle_time" | "io_wait" | "post" | "import";
  status: "ready" | "pending" | "error";
  diagnostics: MotionDiagnostic[];
}

export interface WorkbenchFacadeSnapshot {
  objectTree: WorkbenchObjectNode[];
  editor: EditorModel;
  teachPendant: TeachPendantModel;
  ioPanel: IoPanelModel;
  reports: ReportEntryModel[];
}

export interface WorkbenchFacadeInput {
  program: SemanticProgramIr;
  runtime: IrExecutionRuntimeSnapshot;
  io: IoRuntimeSnapshot;
  motion?: MotionQueueSnapshot;
  controllerState: string;
}

export class WorkbenchFacade {
  snapshot(input: WorkbenchFacadeInput): WorkbenchFacadeSnapshot {
    const currentPoint = currentMotionPoint(input.motion);
    const editorSource = input.runtime.currentSource;
    const editorCursor = cursorFromSource(editorSource);
    return {
      objectTree: buildObjectTree(input.program),
      editor: {
        ...(editorSource?.sourceMap?.file ? { activeFile: editorSource.sourceMap.file } : {}),
        ...(editorCursor ? { cursor: editorCursor } : {}),
        ...(editorSource ? { currentSource: editorSource } : {}),
        diagnostics: input.program.diagnostics
      },
      teachPendant: {
        state: input.controllerState,
        ...(input.runtime.procedure ? { procedure: input.runtime.procedure } : {}),
        pc: input.runtime.pc,
        ...(currentPoint ? { joints: currentPoint.joints, tcp: currentPoint.tcp } : {}),
        alarms: input.runtime.alarmQueue
      },
      ioPanel: {
        image: input.io.image,
        events: input.io.events,
        waits: input.runtime.trace
          .filter((event) => event.kind === "wait")
          .map((event) => ({
            condition: String(event.data?.condition ?? event.data?.status ?? ""),
            ...(event.source ? { source: event.source } : {})
          }))
      },
      reports: buildReportEntries(input.program)
    };
  }
}

function buildObjectTree(program: SemanticProgramIr): WorkbenchObjectNode[] {
  return [
    {
      id: "station",
      label: "Station",
      kind: "station",
      children: [
        {
          id: "programs",
          label: "Programs",
          kind: "program",
          children: program.procedures.map((procedure) => ({
            id: `program:${procedure.name}`,
            label: procedure.name,
            kind: "program",
            ...(procedure.sourceMap
              ? { source: { kind: "procedure", id: procedure.name, sourceMap: procedure.sourceMap } }
              : {})
          }))
        },
        {
          id: "paths",
          label: "Paths",
          kind: "path",
          children: program.paths.map((path) => ({
            id: `path:${path.pathId}`,
            label: path.pathId,
            kind: "path",
            children: path.motions.map((motion) => ({
              id: `path:${path.pathId}:${motion.pointId ?? motion.id ?? motion.kind}`,
              label: motion.pointId ?? motion.id ?? motion.kind,
              kind: "target",
              source: {
                kind: "path_point",
                id: motion.pointId ?? motion.id ?? motion.kind,
                ...(motion.sourceMap ? { sourceMap: motion.sourceMap } : {}),
                pathId: path.pathId,
                ...(motion.pointId ? { pointId: motion.pointId } : {})
              }
            }))
          }))
        },
        {
          id: "operations",
          label: "Operations",
          kind: "operation",
          children: program.operations.map((operation) => ({
            id: `operation:${operation.operationId}`,
            label: operation.operationId,
            kind: "operation",
            data: {
              kind: operation.kind,
              pathId: operation.pathId
            }
          }))
        },
        {
          id: "reports",
          label: "Reports",
          kind: "report"
        }
      ]
    }
  ];
}

function buildReportEntries(program: SemanticProgramIr): ReportEntryModel[] {
  return [
    report("reachability", "Reachability", program.diagnostics),
    report("cycle_time", "Cycle Time", []),
    report("io_wait", "IO and Wait", []),
    report("post", "Post Process", []),
    report("import", "Import", [])
  ];
}

function report(kind: ReportEntryModel["kind"], label: string, diagnostics: MotionDiagnostic[]): ReportEntryModel {
  return {
    id: `report:${kind}`,
    label,
    kind,
    status: diagnostics.some((diagnostic) => diagnostic.severity === "error") ? "error" : "ready",
    diagnostics
  };
}

function cursorFromSource(source?: RuntimeSourceLocation): EditorModel["cursor"] {
  if (!source?.sourceMap?.line) {
    return undefined;
  }
  return {
    line: source.sourceMap.line,
    column: source.sourceMap.column ?? 1
  };
}

function currentMotionPoint(motion?: MotionQueueSnapshot) {
  const item = motion?.items[motion.activeIndex];
  return item?.planned?.points.at(-1);
}
