<script setup lang="ts">
/* ========= Imports ========= */
import { MlflowService } from "@/components/service/mlflow/MlflowService";
import { ExternalAuthControllerService } from "@/components/service/management/ExternalAuthControllerService";
import {
  computed,
  watch,
  ref,
  nextTick,
  onMounted,
  onBeforeUnmount,
} from "vue";
import Plotly from "plotly.js-dist-min";
import CompareRunsDialog from "@/components/atoms/organisms/CompareRunDialog.vue";
import DeploymentDialog from "@/components/atoms/organisms/DeploymentDialog.vue";
import { StorageService } from "@/components/service/management/StorageService";
import IconDownloadBtn from "@/components/atoms/button/IconDownloadBtn.vue";
import IconDeployBtn from "@/components/atoms/button/IconDeployBtn.vue";

/* ========= Constants & Types ========= */
const AUTH_KEY = "external-auth";

type MlflowArtifactList = {
  root_uri: string;
  files: { path: string; is_dir: boolean; file_size?: number }[];
};
type ArtifactRow = {
  path: string;
  is_dir?: boolean;
  file_size?: number;
  depth?: number;
};
type MetricKV = { key: string; value: number };
type RunDetailType = {
  info: any;
  data: { metrics: MetricKV[]; params?: any[]; tags?: any[] };
};
type ExternalAuth = { id: string; name: string; token: string };
type PackageOption = { label: string; value: string; raw: any };
type ArtifactGroup = { dir: string; files: ArtifactRow[] };
const FILE_ICON = "mdi-file-document-outline";
/* ========= Props/Emits ========= */
const props = defineProps<{ experimentInfo: any }>();
const emit = defineEmits<{ (e: "close"): void }>();

/* ========= Auth State ========= */
const loginDialog = ref(false);
const loginLoading = ref(false);
const loginForm = ref({ id: "", password: "" });
const loginError = ref("");
const isAuthenticated = ref(false);
const externalAuth = ref<ExternalAuth | null>(null);
const externalToken = computed(() => externalAuth.value?.token ?? "");

/* ========= Deployment State ========= */
const isEditVisible = ref(false);
const lastArtifactUri = ref<string>("");
const pendingArtifactPath = ref<string | null>(null);
const packageOptions = ref<PackageOption[]>([]);
const packagesLoading = ref(false);
const packagesError = ref("");
const shouldOpenDeploymentAfterLogin = ref(false);
const dlgHideArtifactPath = ref(false);
const dlgHideUploadFile = ref(false);
/* ========= MLflow State ========= */
const runs = ref<any[]>([]);
const loadingRuns = ref(false);
const selectedRunId = ref<string>("");
const loadingRunDetail = ref(false);
const runDetail = ref<RunDetailType | null>(null);
const currentExperimentId = ref<string>("");
const runDetailCache = ref<Map<string, RunDetailType>>(new Map());

/* ========= UI Tabs / Compare ========= */
const mainTab = ref<"details" | "viz" | "artifacts">("details");
const vizTab = ref<"metrics" | "scatter" | "box" | "contour">("metrics");
const deployOpenMode = ref<"artifact" | "upload">("artifact");
const compareDialog = ref(false);
const compareLoading = ref(false);
const compareChartMode = ref<"byMetric" | "byRun">("byMetric");
const compareSelectedRunIds = ref<string[]>([]);
const compareSelectedMetricKeys = ref<string[]>([]);
const normalizeValues = ref(false);
const baselineRunId = ref<string | null>(null);

const elMetrics = ref<HTMLDivElement | null>(null);
const elCompare = ref<HTMLDivElement | null>(null);

/* ========= Artifacts: Two-step(flat) ========= */
const artifactGroups = computed<ArtifactGroup[]>(() => {
  const rootFiles = (lvl1.value || []).filter((x) => !x.is_dir);
  const dirs = (lvl1.value || []).filter((x) => x.is_dir).map((x) => x.path);

  const groups: ArtifactGroup[] = [];
  for (const d of dirs) {
    const children = (lvl2.value || [])
      .filter((f) => f.path.startsWith(`${d}/`))
      .map((f) => {
        const rel = f.path.slice(d.length + 1);
        const depth = Math.max(rel.split("/").length - 1, 0);
        return { ...f, depth };
      })

      .sort((a, b) => {
        if (!!b.is_dir - !!a.is_dir) return !!b.is_dir - !!a.is_dir;
        return a.path.localeCompare(b.path);
      });

    groups.push({ dir: d, files: children });
  }

  if (rootFiles.length > 0) {
    groups.unshift({
      dir: "(root)",
      files: rootFiles.map((f) => ({ ...f, depth: 0 })),
    });
  }
  return groups;
});

const lvl1 = ref<ArtifactRow[]>([]);
const lvl2 = ref<ArtifactRow[]>([]);
const twoStepLoading = ref(false);
const twoStepError = ref("");

async function fetchArtifactsTwoStep(runId: string) {
  lvl1.value = [];
  lvl2.value = [];
  twoStepError.value = "";
  if (!runId) return;

  twoStepLoading.value = true;
  try {
    // 1) 루트(=runId) 기준 1단계
    const res1 = await MlflowService.artifact(runId);
    const body1 = unwrapAxiosLike(res1) as MlflowArtifactList;
    const firstFiles = (body1?.files ?? []).map((f) => ({
      path: f.path,
      is_dir: !!f.is_dir,
      file_size: f.file_size,
    })) as ArtifactRow[];
    lvl1.value = firstFiles;

    // 2) 1단계의 "디렉터리"에 대해 하위(=2단계) 조회
    const dirPaths = firstFiles.filter((x) => x.is_dir).map((x) => x.path);
    let merged: ArtifactRow[] = [];
    if (dirPaths.length > 0) {
      const results = await Promise.all(
        dirPaths.map(async (p) => {
          const r = await MlflowService.artifact(runId, p);
          return unwrapAxiosLike(r) as MlflowArtifactList;
        }),
      );
      for (const r of results) {
        const files2 = (r?.files ?? []).map((f) => ({
          path: f.path, // ex) "sklearn-model/MLmodel" 또는 "sklearn-model/sub"
          is_dir: !!f.is_dir,
          file_size: f.file_size,
        })) as ArtifactRow[];
        merged.push(...files2);
      }

      // 3) 2단계 결과 중 "하위 폴더"가 있으면 그 하위(=3단계)도 한 번 더 조회 (lazy 없이 자동 1번만)
      const subDirs = merged.filter((x) => x.is_dir);
      if (subDirs.length > 0) {
        const results3 = await Promise.all(
          subDirs.map(async (sd) => {
            const r3 = await MlflowService.artifact(runId, sd.path);
            return unwrapAxiosLike(r3) as MlflowArtifactList;
          }),
        );
        for (const r3 of results3) {
          const files3 = (r3?.files ?? []).map((f) => ({
            path: f.path, // ex) "sklearn-model/sub/deeper.txt"
            is_dir: !!f.is_dir,
            file_size: f.file_size,
          })) as ArtifactRow[];
          merged.push(...files3);
        }
      }
    }
    lvl2.value = merged;
  } catch (e: any) {
    twoStepError.value =
      e?.response?.data?.message || e?.message || "artifact 조회 실패";
    console.error("[Artifacts][TwoStep] ERROR", e);
  } finally {
    twoStepLoading.value = false;
  }
}

/* ========= Helpers ========= */
const safeParse = <T = any,>(v: any): T | null => {
  try {
    return typeof v === "string" ? (JSON.parse(v) as T) : (v as T);
  } catch {
    return null;
  }
};
const unwrapAxiosLike = (res: any) => res?.data ?? res;

const normalizeRun = (res: any): RunDetailType | null => {
  let obj = unwrapAxiosLike(res);
  if (!obj?.run && (res?.response || res?.responseText)) {
    const parsed = safeParse(res.response) ?? safeParse(res.responseText);
    if (parsed?.run) obj = parsed;
  }
  const v = obj?.run ?? obj;
  return v?.info && v?.data ? v : null;
};

const pickExpName = (v: any) =>
  v?.expName || v?.experiment || v?.name || v?.raw?.displayName || "";

const getTag = (run: any, key: string) => {
  const tags = run?.data?.tags;
  if (Array.isArray(tags)) return tags.find((t: any) => t?.key === key)?.value;
  if (tags && typeof tags === "object") return tags[key];
};

const pickParentRunId = (v: any) =>
  String(
    [
      v?.run_id,
      v?.runId,
      v?.raw?.run_id,
      v?.raw?.runId,
      v?.raw?.kfp_run_id,
      v?.raw?.kubeflow_run_id,
      v?.raw?.id,
    ].filter(Boolean)[0] ?? "",
  );

const fmtNumber = (v: number | null, digits = 3) => {
  if (v == null || !Number.isFinite(v)) return "";
  const abs = Math.abs(v);
  if (abs >= 1000) return v.toLocaleString();
  if (abs >= 1) return v.toFixed(digits);
  if (abs >= 0.01) return v.toFixed(digits + 1);
  return v.toExponential(2);
};

const labelOfRun = (r: RunDetailType) => r.info.run_name || r.info.run_id;
const valueOf = (run: RunDetailType, key: string): number | null => {
  const m = run.data.metrics.find((x) => x.key === key)?.value;
  return Number.isFinite(m as number) ? Number(m) : null;
};

const fileExt = (name: string) => {
  const n = (name || "").toLowerCase().trim();
  const idx = n.lastIndexOf(".");
  return idx >= 0 ? n.slice(idx + 1) : "";
};

const bytes = (n?: number) =>
  typeof n === "number" && isFinite(n)
    ? n < 1024
      ? `${n} B`
      : n < 1024 ** 2
        ? `${(n / 1024).toFixed(1)} KB`
        : n < 1024 ** 3
          ? `${(n / 1024 ** 2).toFixed(1)} MB`
          : `${(n / 1024 ** 3).toFixed(2)} GB`
    : "—";

/* ========= Derived (computed) ========= */
const runItems = computed(() =>
  (runs.value || []).map((r: any) => ({
    value: r?.info?.run_id || r?.info?.run_uuid || "",
    label: r?.info?.run_name || r?.info?.run_id,
  })),
);

const selectedMetrics = computed<MetricKV[]>(() =>
  (runDetail.value?.data?.metrics ?? []).map((m) => ({
    key: m.key,
    value: m.value,
  })),
);

/* ========= URI 조합 & 다운로드/배포 ========= */
function buildArtifactUri(fullPath: string) {
  const expId =
    currentExperimentId.value ||
    runDetail.value?.info?.experiment_id ||
    runs.value[0]?.info?.experiment_id ||
    "";
  const runId = runDetail.value?.info?.run_id || selectedRunId.value || "";
  return `${expId}/${runId}/artifacts/${fullPath}`;
}

async function onClickArtifact(fullPath: string) {
  const objectName = buildArtifactUri(fullPath);
  try {
    artifactsLoading.value = true;
    await StorageService.download(objectName);
  } finally {
    artifactsLoading.value = false;
  }
}

/* ========= Fetchers ========= */
const findFromList = (runId: string) =>
  runs.value.find(
    (r: any) =>
      r?.info?.run_id === runId ||
      r?.info?.run_uuid === runId ||
      r?.info?.run_name === runId,
  ) ?? null;

async function ensureRunDetail(runId: string): Promise<RunDetailType | null> {
  if (!runId) return null;
  const cached = runDetailCache.value.get(runId);
  if (cached) return cached;
  const res = await MlflowService.getExperimentRun(runId);
  const v = normalizeRun(res) || findFromList(runId);
  if (!v) return null;
  const shaped: RunDetailType = {
    info: v.info ?? {},
    data: {
      metrics: (v.data?.metrics ?? []).map((m: any) => ({
        key: m.key,
        value: Number(m.value),
      })),
      params: v.data?.params,
      tags: v.data?.tags,
    },
  };
  runDetailCache.value.set(runId, shaped);
  return shaped;
}

async function fetchRuns(expName?: string) {
  const parentRunId = pickParentRunId(props.experimentInfo);
  if (!expName) {
    runs.value = [];
    selectedRunId.value = "";
    currentExperimentId.value = "";
    return;
  }
  loadingRuns.value = true;
  try {
    const exp = unwrapAxiosLike(
      await MlflowService.getExperimentByName(expName),
    );
    const expId = String(
      exp?.experiment_id ?? exp?.experimentId ?? exp?.id ?? "",
    );
    currentExperimentId.value = expId;

    if (!expId) {
      runs.value = [];
      selectedRunId.value = "";
      return;
    }

    const body = unwrapAxiosLike(await MlflowService.getRuns(expId));
    const list =
      body?.runs ?? body?.data?.runs ?? (Array.isArray(body) ? body : []);
    const matched = (Array.isArray(list) ? list : []).filter(
      (r: any) => getTag(r, "experiment_id") === parentRunId,
    );
    const final = matched.length > 0 ? matched : list;
    const sorted = [...final].sort(
      (a, b) => (b?.info?.start_time ?? 0) - (a?.info?.start_time ?? 0),
    );
    runs.value = sorted;
    selectedRunId.value =
      sorted[0]?.info?.run_id || sorted[0]?.info?.run_uuid || "";
  } finally {
    loadingRuns.value = false;
  }
}

async function fetchRunDetail(runId: string) {
  loadingRunDetail.value = true;
  try {
    if (!runId) {
      runDetail.value = null;
      await nextTick();
      drawSelectedRunChart();
      return;
    }
    const res = await MlflowService.getExperimentRun(runId);
    runDetail.value = normalizeRun(res) || findFromList(runId);
    if (runDetail.value) runDetailCache.value.set(runId, runDetail.value);
    await nextTick();
    drawSelectedRunChart();
  } finally {
    loadingRunDetail.value = false;
  }
}

/* ========= Charts ========= */
function drawSelectedRunChart() {
  if (!elMetrics.value) return;
  const mm = selectedMetrics.value.filter(
    (m) => typeof m.value === "number" && isFinite(m.value),
  );
  const x = mm.map((m) => m.key);
  const y = mm.map((m) => m.value);

  if (x.length === 0) {
    Plotly.purge(elMetrics.value);
    elMetrics.value.innerHTML =
      '<div style="padding:16px;color:#999;text-align:center;">No Data</div>';
    return;
  }

  Plotly.react(
    elMetrics.value,
    [
      {
        type: "bar",
        x,
        y,
        text: y.map((v) => fmtNumber(v, 4)),
        textposition: "auto",
        hovertemplate: "%{x}: %{text}<extra></extra>",
      },
    ],
    {
      margin: { t: 40, r: 20, b: 60, l: 50 },
      height: 400,
      showlegend: false,
      paper_bgcolor: "rgba(0,0,0,0)",
      plot_bgcolor: "rgba(0,0,0,0)",
      font: { color: "#E0E0E0" },
      xaxis: {
        automargin: true,
        gridcolor: "rgba(255,255,255,0.08)",
        zerolinecolor: "rgba(255,255,255,0.18)",
        linecolor: "rgba(255,255,255,0.18)",
      },
      yaxis: {
        rangemode: "tozero",
        gridcolor: "rgba(255,255,255,0.08)",
        zerolinecolor: "rgba(255,255,255,0.18)",
        linecolor: "rgba(255,255,255,0.18)",
      },
    },
    { displayModeBar: false, responsive: true },
  );
}

const commonLayout: Partial<any> = {
  autosize: true,
  margin: { t: 28, r: 12, b: 56, l: 56 },
  paper_bgcolor: "rgba(0,0,0,0)",
  plot_bgcolor: "rgba(0,0,0,0)",
  font: { color: "#E0E0E0" },
  xaxis: { automargin: true, gridcolor: "rgba(255,255,255,0.08)" },
  yaxis: {
    rangemode: "tozero",
    gridcolor: "rgba(255,255,255,0.08)",
    automargin: true,
  },
  legend: {
    orientation: "h",
    x: 0,
    y: 1.12,
    yanchor: "bottom",
    xanchor: "left",
  },
};

function createTracesByMetric(metricKeys: string[], runsData: RunDetailType[]) {
  const runLabels = runsData.map(labelOfRun);
  return runsData.map((run, i) => {
    const yRaw = metricKeys.map((k) => valueOf(run, k));
    const y = normalizeValues.value ? normalizeArray(yRaw) : yRaw;
    const text = yRaw.map((v) => fmtNumber(v));
    return {
      type: "bar",
      name: runLabels[i],
      x: metricKeys,
      y,
      text: normalizeValues.value ? undefined : text,
      textposition: "auto",
      customdata: text,
      hovertemplate: `${runLabels[i]}<br>%{x}: %{customdata}<extra></extra>`,
    };
  });
}
function createTracesByRun(metricKeys: string[], runsData: RunDetailType[]) {
  const rowLabels = runsData.map(labelOfRun);
  return metricKeys.map((key) => {
    const yRaw = rowLabels.map((_, i) => valueOf(runsData[i], key));
    const y = normalizeValues.value ? normalizeArray(yRaw) : yRaw;
    const text = yRaw.map((v) => fmtNumber(v));
    return {
      type: "bar",
      name: key,
      x: rowLabels,
      y,
      text: normalizeValues.value ? undefined : text,
      textposition: "auto",
      customdata: text,
      hovertemplate: `${key}: %{customdata}<extra>%{x}</extra>`,
    };
  });
}
const normalizeArray = (vals: (number | null)[]) => {
  const xs = vals.filter((v): v is number => Number.isFinite(v as number));
  if (xs.length === 0) return vals;
  const min = Math.min(...xs),
    max = Math.max(...xs);
  if (max === min) return vals.map((v) => (v == null ? v : 1));
  return vals.map((v) => (v == null ? v : (v - min) / (max - min)));
};
function drawCompareChart() {
  if (!elCompare.value) return;
  const metricKeys = activeMetricKeys.value;
  const runsData = compareRuns.value;
  if (!metricKeys.length || !runsData.length) {
    elCompare.value.innerHTML =
      '<div style="padding:16px;color:#999;text-align:center;">No Data</div>';
    return;
  }
  const traces =
    compareChartMode.value === "byMetric"
      ? createTracesByMetric(metricKeys, runsData)
      : createTracesByRun(metricKeys, runsData);

  Plotly.react(
    elCompare.value,
    traces,
    { ...commonLayout, barmode: "group" },
    { displayModeBar: false, responsive: true },
  );
}

/* ========= Compare Derived/Actions ========= */
const compareRuns = computed<RunDetailType[]>(
  () =>
    compareSelectedRunIds.value
      .map((id) => runDetailCache.value.get(id))
      .filter(Boolean) as RunDetailType[],
);
const metricsKeySetByRun = computed(() =>
  compareRuns.value.map((r) => new Set(r.data.metrics.map((m) => m.key))),
);
const commonMetricKeys = computed<string[]>(() => {
  if (metricsKeySetByRun.value.length === 0) return [];
  const [first, ...rest] = metricsKeySetByRun.value;
  let inter = new Set(first);
  rest.forEach((s) => (inter = new Set([...inter].filter((k) => s.has(k)))));
  return Array.from(inter).sort();
});
const activeMetricKeys = computed(() =>
  compareSelectedMetricKeys.value.length
    ? compareSelectedMetricKeys.value
    : commonMetricKeys.value,
);

function openCompareDialog() {
  compareSelectedRunIds.value = Array.from(
    new Set([selectedRunId.value].filter(Boolean)),
  );
  compareSelectedMetricKeys.value = [];
  compareDialog.value = true;
}
async function loadCompareData() {
  if (!compareDialog.value) return;
  compareLoading.value = true;
  try {
    await Promise.all(compareSelectedRunIds.value.map(ensureRunDetail));
    if (compareSelectedMetricKeys.value.length === 0) {
      compareSelectedMetricKeys.value = commonMetricKeys.value.slice(0, 6);
    }
    await nextTick();
    drawCompareChart();
  } finally {
    compareLoading.value = false;
  }
}

/* ========= Auth ========= */
function restoreAuthFromStorage() {
  const raw = localStorage.getItem(AUTH_KEY);
  if (!raw) return;
  try {
    const parsed = JSON.parse(raw) as ExternalAuth;
    if (parsed?.token) {
      isAuthenticated.value = true;
      externalAuth.value = parsed;
    }
  } catch {
    /* no-op */
  }
}
function openLoginManually() {
  shouldOpenDeploymentAfterLogin.value = false;
  pendingArtifactPath.value = null;
  loginDialog.value = true;
}

const handleLogout = () => {
  localStorage.removeItem(AUTH_KEY);
  isAuthenticated.value = false;
  externalAuth.value = null;
  packageOptions.value = [];
};
const handleLogin = async () => {
  try {
    loginLoading.value = true;
    loginError.value = "";
    const { id, password } = loginForm.value;
    if (!id || !password) {
      loginError.value = "ID와 비밀번호를 입력하세요.";
      return;
    }

    const res = await ExternalAuthControllerService.signIn(id, password);
    const raw = res?.data ?? res;
    if (!raw) {
      loginError.value = "로그인에 실패했습니다. (서버 응답이 비어있습니다)";
      return;
    }
    const payload = Object.prototype.hasOwnProperty.call(raw, "data")
      ? raw.data
      : raw;
    if (!payload?.token) {
      loginError.value = "로그인에 실패했습니다. 아이디/비밀번호를 확인하세요.";
      return;
    }

    const toSave: ExternalAuth = {
      id: payload.id ?? id,
      name: payload.name ?? id,
      token: payload.token,
    };
    localStorage.setItem(AUTH_KEY, JSON.stringify(toSave));
    externalAuth.value = toSave;
    isAuthenticated.value = true;

    loginDialog.value = false;
    loginForm.value = { id: "", password: "" };

    if (shouldOpenDeploymentAfterLogin.value) {
      shouldOpenDeploymentAfterLogin.value = false;
      await nextTick();
      openDeploymentModal();
    }
  } catch (e: any) {
    loginError.value =
      e?.response?.data?.message || e?.message || "로그인에 실패했습니다.";
  } finally {
    loginLoading.value = false;
  }
};

/* ========= Deployment Modal ========= */
const openDeploymentModal = async (
  fullPath?: string,
  mode: "upload" | "artifact" = fullPath ? "artifact" : "upload",
) => {
  if (fullPath) {
    const uri = buildArtifactUri(fullPath);
    lastArtifactUri.value = uri;
    pendingArtifactPath.value = uri;
  } else {
    lastArtifactUri.value = "";
    pendingArtifactPath.value = null;
  }

  if (!isAuthenticated.value) {
    shouldOpenDeploymentAfterLogin.value = true;
    loginDialog.value = true;
    return;
  }

  if (mode === "artifact") {
    dlgHideArtifactPath.value = false;
    dlgHideUploadFile.value = true;
    if (!fullPath && pendingArtifactPath.value)
      lastArtifactUri.value = pendingArtifactPath.value;
  } else {
    dlgHideArtifactPath.value = true;
    dlgHideUploadFile.value = false;
    lastArtifactUri.value = "";
  }

  isEditVisible.value = true;
  packagesError.value = "";
  packageOptions.value = [];
  const auth =
    externalAuth.value ||
    (() => {
      try {
        const raw = localStorage.getItem(AUTH_KEY);
        return raw ? (JSON.parse(raw) as ExternalAuth) : null;
      } catch {
        return null;
      }
    })();
  if (!auth?.id || !auth?.token) {
    packagesError.value = "로그인 정보가 없습니다.";
    return;
  }

  try {
    packagesLoading.value = true;
    const res = await ExternalAuthControllerService.search(auth.id, auth.token);
    const body = res?.data ?? res;
    const list = body?.data?.data ?? body?.data ?? body?.result ?? body ?? [];
    const arr = Array.isArray(list) ? list : [];
    packageOptions.value = arr.map((it: any) => ({
      label: it?.package_name || it?.package_id || "Unknown",
      value: it?.package_id,
      raw: it,
    }));
  } catch (e: any) {
    packagesError.value =
      e?.response?.data?.message || e?.message || "패키지 조회 실패";
  } finally {
    packagesLoading.value = false;
  }
};

const closeCreateModal = () => {
  isEditVisible.value = false;
};
const saveData = (payload: any) => {
  console.log("[DeploymentDialog payload]", payload);
  isEditVisible.value = false;
};

/* ========= Timeline (fallback) ========= */
const rawHistory = computed<any[]>(() => {
  const h =
    props.experimentInfo?.raw?.state_history ??
    props.experimentInfo?.raw?.stateHistory ??
    props.experimentInfo?.state_history ??
    props.experimentInfo?.stateHistory ??
    [];
  if (Array.isArray(h) && h.length > 0) return h;

  const startIso =
    props.experimentInfo?.startTime &&
    !isNaN(new Date(props.experimentInfo.startTime).getTime())
      ? new Date(props.experimentInfo.startTime).toISOString()
      : undefined;

  const latest = [...(runs.value ?? [])].sort(
    (a, b) => (b?.info?.start_time ?? 0) - (a?.info?.start_time ?? 0),
  )[0];
  const endIso =
    latest?.info?.end_time && isFinite(Number(latest.info.end_time))
      ? new Date(Number(latest.info.end_time)).toISOString()
      : undefined;

  const status = String(props.experimentInfo?.status ?? "").toUpperCase();
  const out: Array<{ state: string; update_time?: string }> = [];
  if (startIso) out.push({ state: "PENDING", update_time: startIso });
  if (startIso) out.push({ state: "RUNNING", update_time: startIso });
  if (status === "SUCCEEDED" || status === "FAILED")
    out.push({ state: status, update_time: endIso ?? startIso });
  return out;
});
const history = computed(() =>
  rawHistory.value
    .slice()
    .filter((h) => h && h.state)
    .sort(
      (a: any, b: any) =>
        new Date(a.update_time || 0).getTime() -
        new Date(b.update_time || 0).getTime(),
    ),
);
const fmt = (iso?: string) => (iso ? new Date(iso).toLocaleString() : "—");

/* ========= Lifecycle & Watchers ========= */
async function refreshIfActive() {
  await fetchRuns(pickExpName(props.experimentInfo));
  await fetchRunDetail(selectedRunId.value);
}
watch(
  () => mainTab.value,
  async (t) => {
    if (t === "viz" || t === "artifacts") await refreshIfActive();
    if (t === "artifacts" && selectedRunId.value) {
      await fetchArtifactsTwoStep(selectedRunId.value);
    }
  },
  { immediate: true },
);
watch(
  () => props.experimentInfo,
  async () => {
    if (mainTab.value === "viz" || mainTab.value === "artifacts")
      await refreshIfActive();
  },
);
watch(selectedRunId, async (id) => {
  fetchRunDetail(id);
  if (mainTab.value === "artifacts" && id) {
    await fetchArtifactsTwoStep(id);
  }
});
watch(vizTab, async (t) => {
  if (mainTab.value === "viz" && t === "metrics") {
    await nextTick();
    drawSelectedRunChart();
    if (elMetrics.value) Plotly.Plots.resize(elMetrics.value);
  }
});
watch([compareSelectedRunIds, compareSelectedMetricKeys], async () => {
  if (!compareDialog.value) return;
  await loadCompareData();
});
watch(compareDialog, (open) => {
  if (open) loadCompareData();
});

const onResize = () => {
  if (mainTab.value === "viz" && vizTab.value === "metrics" && elMetrics.value)
    Plotly.Plots.resize(elMetrics.value);
  if (elCompare.value) Plotly.Plots.resize(elCompare.value);
};
onMounted(() => {
  restoreAuthFromStorage();
  window.addEventListener("resize", onResize);
});
onBeforeUnmount(() => window.removeEventListener("resize", onResize));

const artifactsLoading = ref(false);
</script>

<template>
  <v-container fluid class="h-100 pa-5 d-flex flex-column align-center">
    <v-card
      flat
      class="bg-shades-transparent d-flex flex-column justify-center w-100"
    >
      <v-card flat class="bg-shades-transparent w-100">
        <v-card-item class="text-h5 font-weight-bold pt-0 pa-5 pl-0">
          <div class="d-flex flex-row justify-start align-center">
            <div>Compare Executions</div>
          </div>
        </v-card-item>
      </v-card>

      <!-- Main Tabs -->
      <v-tabs v-model="mainTab" density="comfortable" color="primary">
        <v-tab value="details">Details</v-tab>
        <v-tab value="viz">Metrics</v-tab>
        <v-tab value="artifacts">Artifacts</v-tab>
      </v-tabs>

      <v-window v-model="mainTab">
        <!-- ========= Details ========= -->
        <v-window-item value="details">
          <v-card flat class="bordered-box mb-6 w-100 rounded-lg pa-8">
            <v-card-title class="grey lighten-4 py-2 px-4">
              <span class="font-weight-bold">Execution Information</span>
            </v-card-title>

            <v-card-text class="px-6 pb-6 pt-4">
              <v-row align="center" class="py-2">
                <v-col cols="3" class="text-h6 font-weight-bold">Name</v-col>
                <v-col cols="9">{{ props.experimentInfo.name }}</v-col>
              </v-row>
              <VDivider class="my-2" />

              <v-row align="center" class="py-2">
                <v-col cols="3" class="text-h6 font-weight-bold">Status</v-col>
                <v-col cols="9">
                  <v-icon
                    v-if="props.experimentInfo.status === 'Succeeded'"
                    color="green"
                    >mdi-check-circle</v-icon
                  >
                  <v-icon
                    v-else-if="props.experimentInfo.status === 'Failed'"
                    color="red"
                    >mdi-close-circle</v-icon
                  >
                  {{ props.experimentInfo.status }}
                </v-col>
              </v-row>
              <VDivider class="my-2" />

              <v-row align="center" class="py-2">
                <v-col cols="3" class="text-h6 font-weight-bold"
                  >Duration</v-col
                >
                <v-col cols="9">{{ props.experimentInfo.duration }}</v-col>
              </v-row>
              <VDivider class="my-2" />

              <v-row align="center" class="py-2">
                <v-col cols="3" class="text-h6 font-weight-bold"
                  >Experiment Name</v-col
                >
                <v-col cols="9">{{ props.experimentInfo.expName }}</v-col>
              </v-row>
              <VDivider class="my-2" />

              <v-row align="center" class="py-2">
                <v-col cols="3" class="text-h6 font-weight-bold"
                  >Workflow Name</v-col
                >
                <v-col cols="9">{{ props.experimentInfo.workflow }}</v-col>
              </v-row>
              <VDivider class="my-2" />

              <v-row align="center" class="py-2">
                <v-col cols="3" class="text-h6 font-weight-bold"
                  >Start Time</v-col
                >
                <v-col cols="9">{{ props.experimentInfo.startTime }}</v-col>
              </v-row>
              <VDivider class="my-2" />

              <v-row align="center" class="py-2">
                <v-col cols="3" class="text-h6 font-weight-bold"
                  >Registry Status</v-col
                >
                <v-col cols="9">{{
                  props.experimentInfo.registryStatus
                }}</v-col>
              </v-row>

              <!-- State History -->
              <v-row align="center" class="py-6">
                <v-col cols="3" class="text-h6 font-weight-bold"
                  >State History</v-col
                >
                <v-col cols="9">
                  <v-timeline direction="horizontal">
                    <v-timeline-item
                      v-for="(h, idx) in history"
                      :key="idx"
                      :dot-color="
                        (h.state || '').toUpperCase() === 'FAILED'
                          ? 'error'
                          : (h.state || '').toUpperCase() === 'SUCCEEDED'
                            ? 'success'
                            : 'primary'
                      "
                      :icon="
                        (h.state || '').toUpperCase() === 'FAILED'
                          ? 'mdi-close'
                          : (h.state || '').toUpperCase() === 'SUCCEEDED'
                            ? 'mdi-check'
                            : 'mdi-progress-clock'
                      "
                    >
                      <div class="text-caption text-medium-emphasis">
                        {{ fmt(h.update_time) }}
                      </div>
                      <div class="text-body-2 font-weight-medium">
                        {{ (h.state || "").toUpperCase() }}
                      </div>
                    </v-timeline-item>
                  </v-timeline>
                </v-col>
              </v-row>
            </v-card-text>

            <div class="d-flex justify-end mb-2">
              <v-btn color="primary" @click="emit('close')">Back to List</v-btn>
            </div>
          </v-card>
        </v-window-item>

        <!-- ========= Visualizations ========= -->
        <v-window-item value="viz">
          <v-card class="rounded-lg pa-8 w-100">
            <v-window v-model="vizTab">
              <v-window-item value="metrics">
                <v-card-text>
                  <v-row class="mb-4" align="center">
                    <v-col cols="12" class="pa-0">
                      <v-alert
                        v-if="!loadingRuns && runs.length === 0"
                        type="info"
                        variant="tonal"
                        class="mx-3"
                        density="comfortable"
                      >
                        부모 runId와 일치하는 MLflow run이 없습니다.
                      </v-alert>
                    </v-col>

                    <v-col
                      cols="12"
                      class="d-flex align-center justify-between flex-wrap ga-3"
                    >
                      <div class="text-body-2">
                        Runs loaded:
                        <strong>{{ runs.length.toLocaleString() }}</strong>
                      </div>

                      <div
                        class="d-flex align-center ga-2"
                        style="min-width: 280px; max-width: 620px"
                      >
                        <v-select
                          v-model="selectedRunId"
                          :items="runItems"
                          item-title="label"
                          item-value="value"
                          density="comfortable"
                          hide-details
                          :clearable="false"
                          clear-icon=""
                          style="min-width: 280px; max-width: 440px"
                        />
                        <v-btn
                          color="primary"
                          variant="elevated"
                          density="comfortable"
                          @click="openCompareDialog"
                          :disabled="runs.length < 2"
                          title="여러 Run을 골라 메트릭 비교"
                        >
                          Compare
                        </v-btn>
                      </div>

                      <v-progress-circular
                        v-if="loadingRuns || loadingRunDetail"
                        indeterminate
                        size="16"
                        class="ml-2"
                      />
                    </v-col>
                  </v-row>

                  <!-- Selected Run -->
                  <!-- Selected Run -->
                  <v-card flat class="mb-6">
                    <v-card-title
                      class="py-2 px-0 text-button text-medium-emphasis"
                    >
                      Selected Run
                    </v-card-title>

                    <v-card-text class="px-0">
                      <v-row>
                        <!-- 기본 정보 -->
                        <v-col cols="12" md="6">
                          <v-table density="comfortable">
                            <thead>
                              <tr>
                                <th style="width: 40%">Field</th>
                                <th>Value</th>
                              </tr>
                            </thead>
                            <tbody>
                              <tr>
                                <td>Run ID</td>
                                <td>{{ runDetail?.info?.run_id || "—" }}</td>
                              </tr>
                              <tr>
                                <td>Run Name</td>
                                <td>{{ runDetail?.info?.run_name || "—" }}</td>
                              </tr>
                              <tr>
                                <td>Status</td>
                                <td>{{ runDetail?.info?.status || "—" }}</td>
                              </tr>
                              <tr>
                                <td>Start</td>
                                <td>
                                  {{
                                    runDetail?.info?.start_time
                                      ? new Date(
                                          runDetail.info.start_time,
                                        ).toLocaleString()
                                      : "—"
                                  }}
                                </td>
                              </tr>
                              <tr>
                                <td>End</td>
                                <td>
                                  {{
                                    runDetail?.info?.end_time
                                      ? new Date(
                                          runDetail.info.end_time,
                                        ).toLocaleString()
                                      : "—"
                                  }}
                                </td>
                              </tr>
                            </tbody>
                          </v-table>
                        </v-col>

                        <!-- 파라미터 + 태그 -->
                        <v-col cols="12" md="6">
                          <div
                            class="text-subtitle-2 text-medium-emphasis mb-2"
                          >
                            Parameters
                          </div>
                          <v-table density="compact" class="mb-4">
                            <thead>
                              <tr>
                                <th style="width: 40%">Key</th>
                                <th>Value</th>
                              </tr>
                            </thead>
                            <tbody>
                              <tr v-if="!runDetail?.data?.params?.length">
                                <td colspan="2" class="text-medium-emphasis">
                                  No params
                                </td>
                              </tr>
                              <tr
                                v-for="p in runDetail?.data?.params || []"
                                :key="p.key"
                              >
                                <td>{{ p.key }}</td>
                                <td>{{ p.value }}</td>
                              </tr>
                            </tbody>
                          </v-table>

                          <div
                            class="text-subtitle-2 text-medium-emphasis mb-2"
                          >
                            Tags
                          </div>
                          <v-table density="compact">
                            <thead>
                              <tr>
                                <th style="width: 40%">Key</th>
                                <th>Value</th>
                              </tr>
                            </thead>
                            <tbody>
                              <tr v-if="!runDetail?.data?.tags?.length">
                                <td colspan="2" class="text-medium-emphasis">
                                  No tags
                                </td>
                              </tr>
                              <tr
                                v-for="t in runDetail?.data?.tags || []"
                                :key="t.key"
                              >
                                <td>{{ t.key }}</td>
                                <td
                                  class="text-truncate"
                                  style="max-width: 420px"
                                >
                                  {{ t.value }}
                                </td>
                              </tr>
                            </tbody>
                          </v-table>
                        </v-col>
                      </v-row>
                    </v-card-text>
                  </v-card>

                  <!-- Model Metrics table -->
                  <v-card flat class="mb-6">
                    <v-card-title
                      class="py-2 px-0 text-button text-medium-emphasis"
                      >Model Metrics (selected run)</v-card-title
                    >
                    <v-table density="comfortable">
                      <thead>
                        <tr>
                          <th class="text-left" style="width: 50%">Metric</th>
                          <th class="text-left">Value</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr v-if="!selectedMetrics.length">
                          <td
                            colspan="2"
                            class="text-center py-6 text-medium-emphasis"
                          >
                            No Data
                          </td>
                        </tr>
                        <tr v-for="m in selectedMetrics" :key="m.key">
                          <td>{{ m.key }}</td>
                          <td>{{ m.value }}</td>
                        </tr>
                      </tbody>
                    </v-table>
                  </v-card>

                  <!-- Bar chart -->
                  <v-card flat class="mb-6">
                    <v-card-title
                      class="py-2 px-0 text-button text-medium-emphasis"
                      >Metrics (bar chart)</v-card-title
                    >
                    <div
                      ref="elMetrics"
                      style="width: 100%; height: 400px"
                    ></div>
                  </v-card>
                </v-card-text>
              </v-window-item>

              <v-window-item value="scatter">
                <v-card-text class="px-6 py-10 text-medium-emphasis"
                  >(준비중) X/Y 축 선택 후 산점도 표시</v-card-text
                >
              </v-window-item>
              <v-window-item value="box">
                <v-card-text class="px-6 py-10 text-medium-emphasis"
                  >(준비중) 메트릭 분포 Box Plot</v-card-text
                >
              </v-window-item>
              <v-window-item value="contour">
                <v-card-text class="px-6 py-10 text-medium-emphasis"
                  >(준비중) 2D/3D Contour Plot</v-card-text
                >
              </v-window-item>
            </v-window>

            <div class="d-flex justify-end mb-2">
              <v-btn color="primary" @click="emit('close')">Back to List</v-btn>
            </div>
          </v-card>
        </v-window-item>

        <!-- ========= Artifacts (Two-step, Flat) ========= -->
        <v-window-item value="artifacts">
          <v-card class="rounded-lg pa-8 w-100">
            <v-card-text>
              <v-row class="mb-4" align="center">
                <v-col
                  cols="12"
                  class="d-flex align-center justify-between flex-wrap ga-3"
                >
                  <div class="text-body-2">
                    Runs loaded:
                    <strong>{{ runs.length.toLocaleString() }}</strong>
                  </div>

                  <v-select
                    v-model="selectedRunId"
                    :items="runItems"
                    item-title="label"
                    item-value="value"
                    density="comfortable"
                    hide-details
                    :clearable="false"
                    clear-icon=""
                    style="min-width: 280px; max-width: 440px"
                  />

                  <div class="d-flex align-center ga-2 ml-auto">
                    <v-btn
                      size="small"
                      variant="tonal"
                      :loading="twoStepLoading"
                      @click="fetchArtifactsTwoStep(selectedRunId)"
                    >
                      Refresh
                    </v-btn>

                    <v-chip
                      v-if="isAuthenticated"
                      color="success"
                      variant="tonal"
                      size="small"
                      class="text-body-2"
                    >
                      <v-icon start size="16">mdi-check-decagram</v-icon>
                      {{ externalAuth?.name || externalAuth?.id }}
                    </v-chip>

                    <v-btn
                      size="small"
                      color="primary"
                      variant="text"
                      @click="openDeploymentModal(undefined, 'upload')"
                      title="업로드 파일로 등록(Artifact Path 숨김)"
                    >
                      <v-icon start size="16">mdi-rocket-launch</v-icon> Deploy
                    </v-btn>

                    <v-btn
                      v-if="isAuthenticated"
                      size="small"
                      color="primary"
                      variant="text"
                      @click="handleLogout"
                    >
                      <v-icon start size="16">mdi-logout</v-icon> Logout
                    </v-btn>
                    <v-btn
                      v-else
                      size="small"
                      color="primary"
                      variant="text"
                      @click="openLoginManually"
                    >
                      <v-icon start size="16">mdi-login</v-icon> Login
                    </v-btn>

                    <v-progress-circular
                      v-if="twoStepLoading || loadingRuns || loadingRunDetail"
                      indeterminate
                      size="16"
                      class="ml-2"
                    />
                  </div>
                </v-col>
              </v-row>

              <v-alert
                v-if="twoStepError"
                type="error"
                variant="tonal"
                class="mb-3"
              >
                {{ twoStepError }}
              </v-alert>

              <!-- 바깥 카드도 flat, 타이틀 톤 통일 -->
              <v-card flat class="mb-2">
                <v-card-title
                  class="py-2 px-0 text-button text-medium-emphasis"
                >
                  Artifacts
                </v-card-title>

                <v-card-text class="px-0">
                  <v-table density="comfortable">
                    <thead>
                      <tr>
                        <th style="width: 48px"></th>
                        <th>Path</th>
                        <th style="width: 160px" class="text-right">Size</th>
                        <th style="width: 180px" class="text-right">Actions</th>
                      </tr>
                    </thead>

                    <tbody>
                      <tr v-if="twoStepLoading">
                        <td
                          colspan="4"
                          class="text-center py-6 text-medium-emphasis"
                        >
                          Loading…
                        </td>
                      </tr>

                      <template v-else>
                        <tr v-if="!artifactGroups.length">
                          <td
                            colspan="4"
                            class="text-center py-6 text-medium-emphasis"
                          >
                            No Artifacts
                          </td>
                        </tr>

                        <!-- 디렉터리 헤더 -->
                        <template v-for="grp in artifactGroups" :key="grp.dir">
                          <tr class="group-row">
                            <td class="text-center">
                              <v-icon>mdi-folder</v-icon>
                            </td>
                            <td>
                              <strong>{{ grp.dir }}</strong>
                            </td>
                            <td class="text-right"></td>
                            <td />
                          </tr>

                          <tr
                            v-for="(it, idx) in grp.files"
                            :key="grp.dir + '-' + idx"
                          >
                            <td colspan="2">
                              <div
                                class="path-cell"
                                :style="{
                                  paddingLeft: `${18 * (1 + (it.depth ?? 0))}px`,
                                }"
                              >
                                <v-icon
                                  :icon="it.is_dir ? 'mdi-folder' : FILE_ICON"
                                  size="18"
                                  class="mr-2"
                                />
                                <span>{{ it.path }}</span>
                              </div>
                            </td>

                            <td class="text-right">
                              {{ bytes(it.file_size) }}
                            </td>

                            <td class="text-right">
                              <template v-if="!it.is_dir">
                                <IconDownloadBtn
                                  @onClick="onClickArtifact(it.path)"
                                />
                                <IconDeployBtn
                                  class="ml-2"
                                  :tooltip="
                                    isAuthenticated
                                      ? 'Deploy'
                                      : 'Login required'
                                  "
                                  @onClick="
                                    openDeploymentModal(it.path, 'artifact')
                                  "
                                />
                              </template>
                              <template v-else>
                                <v-btn size="small" variant="text">Open</v-btn>
                              </template>
                            </td>
                          </tr>
                        </template>
                      </template>
                    </tbody>
                  </v-table>
                </v-card-text>
              </v-card>
            </v-card-text>

            <div class="d-flex justify-end mb-2">
              <v-btn color="primary" @click="emit('close')">Back to List</v-btn>
            </div>
          </v-card>
        </v-window-item>
      </v-window>
    </v-card>

    <CompareRunsDialog
      v-model="compareDialog"
      :items="runItems"
      v-model:selectedRunIds="compareSelectedRunIds"
      v-model:selectedMetricKeys="compareSelectedMetricKeys"
      :ensureRunDetail="ensureRunDetail"
      :runDetailCache="runDetailCache"
      :compareChartMode="compareChartMode"
      :normalizeValues="normalizeValues"
      :baselineRunId="baselineRunId"
    />
  </v-container>

  <!-- 배포 다이얼로그 -->
  <v-dialog v-model="isEditVisible" max-width="800" persistent>
    <DeploymentDialog
      :edit-data="null"
      :mode="'create'"
      :packages="packageOptions"
      :packages-loading="packagesLoading"
      :packages-error="packagesError"
      :artifact-path="lastArtifactUri"
      :token="externalToken"
      :hide-artifact-path="dlgHideArtifactPath"
      :hide-upload-file="dlgHideUploadFile"
      @close-modal="closeCreateModal"
      @handle-data="saveData"
      :user-option="[]"
    />
  </v-dialog>

  <!-- 로그인 모달 -->
  <v-dialog v-model="loginDialog" max-width="450" persistent>
    <v-card>
      <v-card-title
        class="text-white font-weight-bold text-h6"
        style="background-color: #1976d2"
      >
        External Login
      </v-card-title>
      <v-card-text class="pa-6">
        <v-form @submit.prevent="handleLogin">
          <div class="mb-5">
            <label class="text-subtitle-2 font-weight-medium mb-1 d-block"
              >ID</label
            >
            <v-text-field
              v-model="loginForm.id"
              variant="outlined"
              :disabled="loginLoading"
              dense
              hide-details="auto"
              autocomplete="username"
              required
              @keyup.enter.prevent="handleLogin"
            />
          </div>
          <div class="mb-5">
            <label class="text-subtitle-2 font-weight-medium mb-1 d-block"
              >Password</label
            >
            <v-text-field
              v-model="loginForm.password"
              variant="outlined"
              :disabled="loginLoading"
              dense
              hide-details="auto"
              type="password"
              autocomplete="current-password"
              required
              @keyup.enter.prevent="handleLogin"
            />
          </div>
          <div v-if="loginError" class="mt-3 text-error">{{ loginError }}</div>
        </v-form>
      </v-card-text>
      <v-card-actions class="justify-end" style="padding: 16px 24px">
        <v-btn color="success" :loading="loginLoading" @click="handleLogin"
          >Login</v-btn
        >
        <v-btn text :disabled="loginLoading" @click="loginDialog = false"
          >Close</v-btn
        >
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<style scoped>
:root {
  --dot-size: 28px;
}
.group-row {
  background: rgba(255, 255, 255, 0.04);
}
.child-path {
  padding-left: 18px;
}
</style>
