import "./style.css";
import { getAppElements } from "./ui/layout";
import { indexFiles, type IndexedFile } from "./log/fileIndex";
import { probeGroup, unionRange, type ProbedFile } from "./log/timeProbe";
import {
  formatTimestamp,
  fromDatetimeLocalValue,
  toDatetimeLocalValue,
} from "./log/timestamp";
import type { BuildRequestMessage, WorkerResponseMessage } from "./log/parse.worker";
import { renderCartesianScene } from "./render/cartesianScene";
import { renderFrenetScene } from "./render/frenetScene";
import { renderCartesianInfoPanel, renderFrenetInfoPanel } from "./render/infoPanel";
import { renderAChart, renderVChart } from "./render/vtAtChart";
import { renderKappaChart } from "./render/kappaChart";
import { LOG_KEYS, type Dataset, type LogKey } from "./types";

const el = getAppElements();

let currentIndexed: Map<LogKey, IndexedFile[]> | null = null;
let worker: Worker | null = null;
let dataset: Dataset | null = null;
let currentFrameIndex = 0;

let isPlaying = false;
let playRafId: number | null = null;
let playSpeed = 1;
let playStartWallMs = 0;
let playStartLogMs = 0;

function describeCounts(indexed: Map<LogKey, IndexedFile[]>): string {
  return LOG_KEYS.map((k) => `${k}:${indexed.get(k)?.length ?? 0}`).join(" ");
}

el.folderInput.addEventListener("change", async () => {
  const files = el.folderInput.files;
  if (!files || files.length === 0) return;

  currentIndexed = indexFiles(files);
  const counts = describeCounts(currentIndexed);
  el.folderStatus.textContent = `已选择 ${files.length} 个文件（${counts}），正在探测时间范围…`;
  el.loadButton.disabled = true;
  el.startInput.disabled = true;
  el.endInput.disabled = true;

  let probed: ProbedFile[] = await probeGroup(currentIndexed.get("frame") ?? []);
  if (probed.length === 0) {
    const all: ProbedFile[] = [];
    for (const key of LOG_KEYS) {
      all.push(...(await probeGroup(currentIndexed.get(key) ?? [])));
    }
    probed = all;
  }

  const overall = unionRange(probed.map((p) => p.range));
  if (!overall) {
    el.folderStatus.textContent =
      `已选择 ${files.length} 个文件（${counts}），但没能从中识别出任何时间戳，` +
      "请确认选中的是 planning 日志所在的文件夹。";
    return;
  }

  el.startInput.value = toDatetimeLocalValue(overall.start);
  el.endInput.value = toDatetimeLocalValue(overall.end);
  el.startInput.disabled = false;
  el.endInput.disabled = false;
  el.loadButton.disabled = false;
  el.folderStatus.textContent =
    `已选择 ${files.length} 个文件（${counts}）。探测到时间范围 ` +
    `${formatTimestamp(overall.start)} ~ ${formatTimestamp(overall.end)}，可以按需调整后开始解析。`;
});

el.loadButton.addEventListener("click", () => {
  if (!currentIndexed) return;
  const startMs = fromDatetimeLocalValue(el.startInput.value);
  const endMs = fromDatetimeLocalValue(el.endInput.value);
  if (startMs == null || endMs == null || startMs >= endMs) {
    el.progressText.textContent = "请检查起止时间（结束时间必须晚于起始时间）";
    return;
  }

  el.loadButton.disabled = true;
  el.progressText.textContent = "解析中…";

  worker?.terminate();
  worker = new Worker(new URL("./log/parse.worker.ts", import.meta.url), {
    type: "module",
  });
  worker.onmessage = (event: MessageEvent<WorkerResponseMessage>) => {
    const data = event.data;
    if (data.type === "progress") {
      el.progressText.textContent = `解析中… ${data.stage}${data.detail ? `: ${data.detail}` : ""}`;
    } else if (data.type === "done") {
      dataset = data.dataset;
      el.loadButton.disabled = false;
      el.progressText.textContent = "";
      onDatasetReady();
    } else if (data.type === "error") {
      el.loadButton.disabled = false;
      el.progressText.textContent = `解析失败：${data.message}`;
    }
  };

  const message: BuildRequestMessage = {
    type: "build",
    indexedEntries: Array.from(currentIndexed.entries()),
    startMs,
    endMs,
  };
  worker.postMessage(message);
});

function onDatasetReady(): void {
  if (!dataset) return;

  el.setupPanel.classList.add("hidden");
  el.viewerPanel.classList.remove("hidden");

  const maxIndex = Math.max(0, dataset.frames.length - 1);
  el.frameSlider.max = String(maxIndex);
  el.frameJumpInput.max = String(maxIndex);

  const avail = dataset.meta.sourceAvailability;
  el.availabilityText.textContent = LOG_KEYS.map((key) => {
    const info = avail[key];
    if (!info?.present) return `${key}✗`;
    return `${key}✓(${info.recordCount})`;
  }).join("  ");

  renderFrame(0);
}

function renderFrame(index: number): void {
  if (!dataset || dataset.frames.length === 0) return;
  const clamped = Math.min(Math.max(0, index), dataset.frames.length - 1);
  currentFrameIndex = clamped;
  const frame = dataset.frames[clamped];

  el.frameLabel.textContent =
    `Frame ${clamped + 1} / ${dataset.frames.length}  ${formatTimestamp(frame.timestampMs)}`;
  el.cartesianInfo.textContent = renderCartesianInfoPanel(frame, dataset.meta);
  el.frenetInfo.textContent = renderFrenetInfoPanel(frame, dataset.meta);

  renderCartesianScene(el.cartesianCanvas, frame, dataset.meta);
  renderFrenetScene(el.frenetCanvas, frame);
  renderVChart(el.vChartCanvas, frame);
  renderAChart(el.aChartCanvas, frame);
  renderKappaChart(el.kappaChartCanvas, frame);

  el.frameSlider.value = String(clamped);
  el.frameJumpInput.value = String(clamped);
}

function stopPlayback(): void {
  isPlaying = false;
  el.playButton.textContent = "播放";
  if (playRafId != null) {
    cancelAnimationFrame(playRafId);
    playRafId = null;
  }
}

function startPlayback(): void {
  if (!dataset || dataset.frames.length < 2) return;
  isPlaying = true;
  el.playButton.textContent = "暂停";
  playStartWallMs = performance.now();
  playStartLogMs = dataset.frames[currentFrameIndex].timestampMs;

  const step = () => {
    if (!isPlaying || !dataset) return;
    const elapsedWall = performance.now() - playStartWallMs;
    const targetLogMs = playStartLogMs + elapsedWall * playSpeed;

    let idx = currentFrameIndex;
    while (idx < dataset.frames.length - 1 && dataset.frames[idx + 1].timestampMs <= targetLogMs) {
      idx++;
    }
    if (idx !== currentFrameIndex) renderFrame(idx);

    if (idx >= dataset.frames.length - 1) {
      stopPlayback();
      return;
    }
    playRafId = requestAnimationFrame(step);
  };
  playRafId = requestAnimationFrame(step);
}

el.playButton.addEventListener("click", () => {
  if (isPlaying) stopPlayback();
  else startPlayback();
});

el.frameSlider.addEventListener("input", () => {
  stopPlayback();
  renderFrame(Number(el.frameSlider.value));
});

el.frameJumpInput.addEventListener("change", () => {
  stopPlayback();
  renderFrame(Number(el.frameJumpInput.value));
});

el.speedSelect.addEventListener("change", () => {
  playSpeed = Number(el.speedSelect.value);
  if (isPlaying && dataset) {
    playStartWallMs = performance.now();
    playStartLogMs = dataset.frames[currentFrameIndex].timestampMs;
  }
});

el.reselectButton.addEventListener("click", () => {
  stopPlayback();
  el.viewerPanel.classList.add("hidden");
  el.setupPanel.classList.remove("hidden");
});

let resizeTimer: number | undefined;
window.addEventListener("resize", () => {
  window.clearTimeout(resizeTimer);
  resizeTimer = window.setTimeout(() => {
    if (dataset) renderFrame(currentFrameIndex);
  }, 120);
});
