import { computed, onBeforeUnmount, ref } from "vue";
import { cancelDemoRun, demoEventsUrl, demoRunSnapshot, retryDemoStep, startDemoRun } from "../services/api";
import { deepMerge, RUN_STAGES, safeObject, type Row, type RunEvent, type RunSnapshot, type RunStage, type StepSnapshot } from "../types/demo-run";

export function useDemoRun(onTerminal?: () => void | Promise<void>) {
  const activeRunId = ref("");
  const running = ref(false);
  const visible = ref(false);
  const connectionStatus = ref<"idle" | "connected" | "reconnecting" | "disconnected">("idle");
  const events = ref<RunEvent[]>([]);
  const snapshot = ref<RunSnapshot | null>(null);
  const stepSnapshots = ref<Record<string, StepSnapshot>>({});
  const selectedStepKey = ref("");
  const followRun = ref(true);
  let eventSource: EventSource | null = null;

  const currentStepKey = computed(() => {
    const raw = snapshot.value?.current_step || events.value.at(-1)?.step_key || RUN_STAGES[0].key;
    if (!["run.failed", "run.cancelled"].includes(raw)) return raw;
    return [...events.value].reverse().find((event) => RUN_STAGES.some((stage) => stage.key === event.step_key) && !["run.completed"].includes(event.step_key))?.step_key || RUN_STAGES[0].key;
  });
  const displayedStepKey = computed(() => selectedStepKey.value || currentStepKey.value);
  const displayedStep = computed<StepSnapshot>(() => stepSnapshots.value[displayedStepKey.value] ?? pendingSnapshot(displayedStepKey.value));
  const currentEvent = computed(() => events.value.at(-1) ?? null);
  const runProgress = computed(() => Number(snapshot.value?.progress ?? currentEvent.value?.run_progress ?? currentEvent.value?.progress ?? 0));
  const stages = computed<RunStage[]>(() => {
    const fromSnapshot = new Map((snapshot.value?.steps ?? []).map((stage) => [stage.key, stage]));
    return RUN_STAGES.map((stage) => ({ ...stage, ...fromSnapshot.get(stage.key), ...stepStatusFields(stepSnapshots.value[stage.key]) }));
  });

  async function start() {
    closeStream();
    reset();
    running.value = true;
    visible.value = true;
    const created = await startDemoRun();
    activeRunId.value = created.run_id;
    snapshot.value = created;
    selectedStepKey.value = RUN_STAGES[0].key;
    connect(created.run_id);
    return created;
  }

  async function open(runId: string) {
    closeStream();
    reset();
    visible.value = true;
    const value = await demoRunSnapshot(runId);
    hydrate(value);
    if (value.status === "running") connect(runId);
    return value;
  }

  async function cancel() {
    if (!activeRunId.value) return;
    await cancelDemoRun(activeRunId.value);
  }

  async function retry(stepKey = displayedStepKey.value) {
    if (!activeRunId.value) return;
    const attempt = Number(stepSnapshots.value[stepKey]?.attempt ?? 1);
    await retryDemoStep(activeRunId.value, stepKey, attempt);
    running.value = true;
    followRun.value = true;
    selectedStepKey.value = stepKey;
    connect(activeRunId.value);
  }

  async function reload() {
    if (!activeRunId.value) return;
    hydrate(await demoRunSnapshot(activeRunId.value));
  }

  function selectStep(stepKey: string) {
    selectedStepKey.value = stepKey;
    followRun.value = stepKey === currentStepKey.value;
  }

  function followCurrent() {
    followRun.value = true;
    selectedStepKey.value = currentStepKey.value;
  }

  function connect(runId: string) {
    closeStream();
    connectionStatus.value = "reconnecting";
    eventSource = new EventSource(demoEventsUrl(runId));
    eventSource.onopen = () => {
      connectionStatus.value = "connected";
    };
    eventSource.addEventListener("demo-run-event", async (event: Event) => {
      const message = event as MessageEvent;
      const data = safeObject(message.data) as RunEvent;
      upsertEvent(data);
      mergeEventSnapshot(data);
      snapshot.value = {
        ...(snapshot.value ?? ({ run_id: runId } as RunSnapshot)),
        status: terminalStatus(data) || "running",
        current_step: data.step_key,
        progress: Number(data.run_progress ?? data.progress ?? 0)
      };
      if (followRun.value && RUN_STAGES.some((stage) => stage.key === data.step_key)) selectedStepKey.value = data.step_key;
      if (isTerminal(data)) {
        running.value = false;
        closeStream();
        await reload();
        await onTerminal?.();
      }
    });
    eventSource.onerror = async () => {
      if (!running.value) return;
      connectionStatus.value = "reconnecting";
      try {
        await reload();
        if (["completed", "failed", "cancelled"].includes(String(snapshot.value?.status))) {
          running.value = false;
          closeStream();
          await onTerminal?.();
        }
      } catch {
        connectionStatus.value = "disconnected";
      }
    };
  }

  function hydrate(value: RunSnapshot) {
    snapshot.value = value;
    activeRunId.value = value.run_id;
    events.value = [...(value.events ?? [])].sort((left, right) => Number(left.sequence) - Number(right.sequence));
    stepSnapshots.value = { ...(value.step_snapshots ?? {}) };
    const status = String(value.status);
    running.value = status === "running";
    if (followRun.value || !selectedStepKey.value) {
      selectedStepKey.value = RUN_STAGES.some((stage) => stage.key === value.current_step)
        ? value.current_step
        : currentStepKey.value;
    }
  }

  function mergeEventSnapshot(event: RunEvent) {
    const previous = stepSnapshots.value[event.step_key] ?? pendingSnapshot(event.step_key);
    const visual = deepMerge(safeObject(previous.visual_payload), safeObject(event.visual_payload));
    stepSnapshots.value[event.step_key] = {
      ...previous,
      ...event,
      metrics: safeObject(event.metrics),
      visual_payload: visual
    };
  }

  function upsertEvent(event: RunEvent) {
    if (!event.event_id) return;
    const index = events.value.findIndex((item) => item.event_id === event.event_id);
    if (index >= 0) events.value[index] = event;
    else events.value.push(event);
    events.value.sort((left, right) => Number(left.sequence) - Number(right.sequence));
  }

  function reset() {
    activeRunId.value = "";
    events.value = [];
    snapshot.value = null;
    stepSnapshots.value = {};
    selectedStepKey.value = "";
    followRun.value = true;
    connectionStatus.value = "idle";
  }

  function closeStream() {
    if (eventSource) eventSource.close();
    eventSource = null;
    if (connectionStatus.value === "connected" || connectionStatus.value === "reconnecting") connectionStatus.value = "disconnected";
  }

  onBeforeUnmount(closeStream);

  return {
    activeRunId, running, visible, connectionStatus, events, snapshot, stepSnapshots,
    selectedStepKey, followRun, currentStepKey, displayedStepKey, displayedStep, currentEvent,
    runProgress, stages, start, open, cancel, retry, reload, selectStep, followCurrent
  };
}

function pendingSnapshot(stepKey: string): StepSnapshot {
  const stage = RUN_STAGES.find((item) => item.key === stepKey);
  return { step_key: stepKey, step_name: stage?.name, status: "pending", step_progress: 0, metrics: {}, visual_payload: {} };
}

function stepStatusFields(snapshot?: StepSnapshot) {
  if (!snapshot) return { status: "pending" as const, step_progress: 0, attempt: 1 };
  return { status: snapshot.status, step_progress: Number(snapshot.step_progress ?? 0), attempt: Number(snapshot.attempt ?? 1) };
}

function isTerminal(event: RunEvent) {
  return ["run.completed", "run.failed", "run.cancelled"].includes(event.step_key);
}

function terminalStatus(event: RunEvent) {
  if (event.step_key === "run.completed") return "completed";
  if (event.step_key === "run.failed") return "failed";
  if (event.step_key === "run.cancelled") return "cancelled";
  return "";
}
