const state = {
  adminKey: localStorage.getItem("oralTrainerAdminKey") || "",
  videos: [],
};

const elements = {
  adminKey: document.querySelector("#admin-key"),
  saveKey: document.querySelector("#save-key"),
  refresh: document.querySelector("#refresh"),
  serviceState: document.querySelector("#service-state"),
  uploadForm: document.querySelector("#upload-form"),
  uploadState: document.querySelector("#upload-state"),
  uploadProgress: document.querySelector("#upload-progress"),
  videoFile: document.querySelector("#video-file"),
  videoTitle: document.querySelector("#video-title"),
  videoRows: document.querySelector("#video-rows"),
  videoCount: document.querySelector("#video-count"),
  emptyState: document.querySelector("#empty-state"),
  dialog: document.querySelector("#sentence-dialog"),
  sentenceTitle: document.querySelector("#sentence-title"),
  sentenceMeta: document.querySelector("#sentence-meta"),
  sentenceRows: document.querySelector("#sentence-rows"),
  closeDialog: document.querySelector("#close-dialog"),
};

elements.adminKey.value = state.adminKey;
elements.saveKey.addEventListener("click", () => {
  state.adminKey = elements.adminKey.value;
  localStorage.setItem("oralTrainerAdminKey", state.adminKey);
  elements.serviceState.textContent = "密钥已保存";
});
elements.refresh.addEventListener("click", loadVideos);
elements.closeDialog.addEventListener("click", () => elements.dialog.close());
elements.uploadForm.addEventListener("submit", uploadVideo);
elements.videoFile.addEventListener("change", () => {
  if (!elements.videoFile.files[0]) return;
  elements.videoTitle.value = titleFromFilename(elements.videoFile.files[0].name);
});

async function api(path, options = {}) {
  const headers = new Headers(options.headers || {});
  if (state.adminKey) headers.set("X-Admin-Key", state.adminKey);
  const response = await fetch(path, { ...options, headers });
  if (!response.ok) {
    let detail = `HTTP ${response.status}`;
    try {
      const payload = await response.json();
      detail = typeof payload.detail === "string" ? payload.detail : JSON.stringify(payload.detail);
    } catch (_) {}
    throw new Error(detail);
  }
  return response.status === 204 ? null : response.json();
}

async function checkHealth() {
  try {
    const health = await api("/healthz");
    elements.serviceState.textContent = health.moss_configured ? "服务正常 / MOSS 已连接" : "服务正常 / MOSS 未配置";
  } catch (error) {
    elements.serviceState.textContent = `服务异常: ${error.message}`;
  }
}

async function loadVideos() {
  try {
    const payload = await api("/api/v1/videos");
    state.videos = payload.videos;
    renderVideos();
  } catch (error) {
    elements.serviceState.textContent = `加载失败: ${error.message}`;
  }
}

function renderVideos() {
  elements.videoRows.replaceChildren();
  elements.videoCount.textContent = `${state.videos.length} 个视频`;
  elements.emptyState.hidden = state.videos.length !== 0;
  for (const video of state.videos) {
    const row = document.createElement("tr");
    row.append(
      cellWithError(video.title, video.error_message),
      statusCell(video.status),
      textCell(formatDuration(video.duration_ms)),
      textCell(String(video.sentence_count)),
      textCell(formatBytes(video.size_bytes)),
      textCell(formatDate(video.updated_at)),
      actionCell(video),
    );
    elements.videoRows.append(row);
  }
}

function textCell(value) {
  const cell = document.createElement("td");
  cell.textContent = value;
  return cell;
}

function cellWithError(title, error) {
  const cell = textCell(title);
  if (error) {
    const message = document.createElement("div");
    message.className = "error-message";
    message.textContent = error;
    cell.append(message);
  }
  return cell;
}

function statusCell(status) {
  const cell = document.createElement("td");
  const label = document.createElement("span");
  label.className = `status status-${status}`;
  label.textContent = { uploaded: "已上传", processing: "处理中", ready: "可用", failed: "失败" }[status] || status;
  cell.append(label);
  return cell;
}

function actionCell(video) {
  const cell = document.createElement("td");
  const actions = document.createElement("div");
  actions.className = "actions";
  const play = document.createElement("a");
  play.href = video.stream_url;
  play.target = "_blank";
  play.rel = "noopener";
  play.textContent = "播放";
  actions.append(play);
  actions.append(button("句子", () => openSentences(video)));
  actions.append(button("重新处理", () => reprocess(video.video_hash)));
  const remove = button("删除", () => deleteVideo(video));
  remove.className = "danger";
  actions.append(remove);
  cell.append(actions);
  return cell;
}

function button(label, handler) {
  const result = document.createElement("button");
  result.type = "button";
  result.textContent = label;
  result.addEventListener("click", handler);
  return result;
}

function uploadVideo(event) {
  event.preventDefault();
  const file = document.querySelector("#video-file").files[0];
  if (!file) return;
  const title = document.querySelector("#video-title").value;
  const language = document.querySelector("#video-language").value;
  const query = new URLSearchParams({ filename: file.name });
  if (title) query.set("title", title);
  if (language) query.set("language", language);
  elements.uploadProgress.hidden = false;
  elements.uploadProgress.value = 0;
  elements.uploadState.textContent = "上传中";
  const request = new XMLHttpRequest();
  request.open("PUT", `/api/v1/admin/videos/raw?${query.toString()}`);
  if (state.adminKey) request.setRequestHeader("X-Admin-Key", state.adminKey);
  request.setRequestHeader("Content-Type", file.type || "application/octet-stream");
  request.upload.addEventListener("progress", (progress) => {
    if (progress.lengthComputable) elements.uploadProgress.value = progress.loaded / progress.total * 100;
  });
  request.addEventListener("load", async () => {
    if (request.status >= 200 && request.status < 300) {
      elements.uploadState.textContent = "上传完成，正在处理";
      elements.uploadForm.reset();
      await loadVideos();
    } else {
      elements.uploadState.textContent = `上传失败: ${readXhrError(request)}`;
    }
    elements.uploadProgress.hidden = true;
  });
  request.addEventListener("error", () => {
    elements.uploadState.textContent = "上传连接中断";
    elements.uploadProgress.hidden = true;
  });
  request.send(file);
}

function titleFromFilename(filename) {
  const name = filename.replace(/\\/g, "/").split("/").pop() || "";
  const dotIndex = name.lastIndexOf(".");
  const stem = dotIndex > 0 ? name.slice(0, dotIndex) : name;
  return stem.slice(0, 200);
}

function readXhrError(request) {
  try { return JSON.parse(request.responseText).detail || `HTTP ${request.status}`; }
  catch (_) { return `HTTP ${request.status}`; }
}

async function openSentences(video) {
  try {
    const detail = await api(`/api/v1/videos/${video.video_hash}`);
    elements.sentenceTitle.textContent = detail.video.title;
    const sentences = detail.boundaries?.sentences || [];
    elements.sentenceMeta.textContent = `${sentences.length} 句 / ${detail.video.video_hash}`;
    elements.sentenceRows.replaceChildren();
    for (const sentence of sentences) {
      elements.sentenceRows.append(sentenceRow(video, sentence));
    }
    if (!elements.dialog.open) elements.dialog.showModal();
  } catch (error) {
    elements.serviceState.textContent = `句子加载失败: ${error.message}`;
  }
}

function sentenceRow(video, sentence) {
  const row = document.createElement("tr");
  const textArea = document.createElement("textarea");
  textArea.value = sentence.text || "";
  const language = document.createElement("select");
  for (const [value, label] of [["", "自动"], ["en", "英语"], ["zh", "中文"]]) {
    const option = document.createElement("option");
    option.value = value;
    option.textContent = label;
    option.selected = value === (sentence.language || "");
    language.append(option);
  }
  const textContainer = document.createElement("td");
  textContainer.append(textArea);
  const languageContainer = document.createElement("td");
  languageContainer.append(language);
  const saveContainer = document.createElement("td");
  const save = button("保存", async () => {
    save.disabled = true;
    try {
      const body = JSON.stringify({ text: textArea.value, language: language.value || null });
      if (/\r?\n/.test(textArea.value)) {
        await api(`/api/v1/admin/videos/${video.video_hash}/sentences/${sentence.index}/split`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body,
        });
        save.textContent = "已拆分";
        await openSentences(video);
      } else {
        await api(`/api/v1/admin/videos/${video.video_hash}/sentences/${sentence.index}`, {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body,
        });
        save.textContent = "已保存";
      }
    } catch (error) {
      elements.serviceState.textContent = `句子保存失败: ${error.message}`;
      save.textContent = error.message;
    } finally {
      window.setTimeout(() => { save.disabled = false; save.textContent = "保存"; }, 1600);
    }
  });
  saveContainer.append(save);
  const timeContainer = document.createElement("td");
  const startLabel = document.createElement("span");
  startLabel.textContent = formatDuration(sentence.start_ms);
  timeContainer.append(startLabel, " — ", boundaryControl(video, sentence));
  row.append(
    textCell(String(sentence.index + 1)),
    timeContainer,
    textContainer,
    languageContainer,
    saveContainer,
  );
  return row;
}

function boundaryControl(video, sentence) {
  const container = document.createElement("div");
  container.className = "boundary-control";
  const label = document.createElement("span");
  label.className = "boundary-time";
  label.textContent = formatDuration(sentence.end_ms);
  const status = document.createElement("span");
  status.className = "boundary-status";

  async function adjust(deltaMs, changedButton) {
    const buttons = [minus, plus];
    buttons.forEach((item) => { item.disabled = true; });
    status.textContent = "";
    try {
      await api(
        `/api/v1/admin/videos/${video.video_hash}/sentences/${sentence.index}/boundary`,
        {
          method: "PUT",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ delta_ms: deltaMs }),
        },
      );
      await openSentences(video);
    } catch (error) {
      elements.serviceState.textContent = `分割点调整失败: ${error.message}`;
      status.textContent = "失败";
      buttons.forEach((item) => { item.disabled = false; });
      window.setTimeout(() => { status.textContent = ""; }, 1800);
    }
    if (changedButton) {
      window.setTimeout(() => {
        changedButton.textContent = deltaMs < 0 ? "−0.1" : "+0.1";
      }, 900);
    }
  }

  const minus = button("−0.1", (event) => adjust(event.shiftKey ? -10 : -100, minus));
  const plus = button("+0.1", (event) => adjust(event.shiftKey ? 10 : 100, plus));
  minus.title = "结束点提前 0.1 秒；按住 Shift 微调 0.01 秒";
  plus.title = "结束点延后 0.1 秒；按住 Shift 微调 0.01 秒";
  container.append(minus, label, plus, status);
  return container;
}

async function reprocess(videoHash) {
  try {
    await api(`/api/v1/admin/videos/${videoHash}/process`, { method: "POST" });
    await loadVideos();
  } catch (error) {
    elements.serviceState.textContent = `处理启动失败: ${error.message}`;
  }
}

async function deleteVideo(video) {
  if (!window.confirm(`删除“${video.title}”？`)) return;
  try {
    await api(`/api/v1/admin/videos/${video.video_hash}`, { method: "DELETE" });
    await loadVideos();
  } catch (error) {
    elements.serviceState.textContent = `删除失败: ${error.message}`;
  }
}

function formatDuration(ms) {
  if (!ms) return "-";
  const totalTenths = Math.floor(ms / 100);
  const hours = Math.floor(totalTenths / 36000);
  const minutes = Math.floor(totalTenths % 36000 / 600);
  const seconds = Math.floor(totalTenths % 600 / 10);
  const tenths = totalTenths % 10;
  const base = `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${tenths}`;
  return hours ? `${hours}:${base}` : base;
}

function formatBytes(bytes) {
  if (bytes < 1024 ** 2) return `${(bytes / 1024).toFixed(1)} KB`;
  if (bytes < 1024 ** 3) return `${(bytes / 1024 ** 2).toFixed(1)} MB`;
  return `${(bytes / 1024 ** 3).toFixed(2)} GB`;
}

function formatDate(value) {
  return new Intl.DateTimeFormat("zh-CN", { dateStyle: "short", timeStyle: "short" }).format(new Date(value));
}

checkHealth();
loadVideos();
window.setInterval(() => {
  if (!elements.dialog.open) loadVideos();
}, 5000);
