<script setup lang="ts">
import {
  computed,
  ref,
  watch,
  nextTick,
  onMounted,
  onBeforeUnmount,
} from "vue";
import Plotly from "plotly.js-dist-min";

/* ===== Types ===== */
type MetricKV = { key: string; value: number };
type RunDetailType = {
  info: any;
  data: { metrics: MetricKV[]; params?: any[]; tags?: any[] };
};

/* ===== Props / Emits ===== */
const props = withDefaults(
  defineProps<{
    modelValue: boolean;
    items: Array<{ value: string; label: string }>;
    selectedRunIds: string[];
    selectedMetricKeys: string[];
    ensureRunDetail: (runId: string) => Promise<RunDetailType | null>;
    runDetailCache: Map<string, RunDetailType>;
    /** runId → 실행 시 입력한 이름(Execution name, 예: auto26run) */
    runIdToExecutionName?: Record<string, string>;
    compareChartMode?: "byMetric" | "byRun";
    normalizeValues?: boolean;
    baselineRunId?: string | null;
  }>(),
  {
    modelValue: false,
    items: () => [],
    selectedRunIds: () => [],
    selectedMetricKeys: () => [],
    runIdToExecutionName: () => ({}),
    compareChartMode: "byMetric",
    normalizeValues: false,
    baselineRunId: null,
  },
);

const emit = defineEmits<{
  (e: "update:modelValue", v: boolean): void;
  (e: "update:selectedRunIds", v: string[]): void;
  (e: "update:selectedMetricKeys", v: string[]): void;
}>();

/* ===== local refs ===== */
const dialogOpen = computed({
  get: () => props.modelValue,
  set: (v: boolean) => emit("update:modelValue", v),
});
const selectedRunIdsProxy = computed({
  get: () => props.selectedRunIds,
  set: (v: string[]) => emit("update:selectedRunIds", v),
});
const selectedMetricKeysProxy = computed({
  get: () => props.selectedMetricKeys,
  set: (v: string[]) => emit("update:selectedMetricKeys", v),
});

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

/* ===== helpers ===== */
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 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);
  const 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)));
};
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;
};

/* ===== derived ===== */
const compareRuns = computed<RunDetailType[]>(
  () =>
    selectedRunIdsProxy.value
      .map((id) => props.runDetailCache.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<string[]>(() =>
  selectedMetricKeysProxy.value.length
    ? selectedMetricKeysProxy.value
    : commonMetricKeys.value,
);

/* ===== responsive widths for horizontal scroll =====
   - 그래프: 막대 개수에 비례해 내부 너비를 크게 잡아 가로 스크롤 허용
   - 테이블: (메트릭 수 + Run 컬럼 1개) * 160px 정도로 최소 너비 설정
*/
const chartInnerWidth = computed(() => {
  // byMetric: X축 = metric 개수, byRun: X축 = run 개수
  const xCount =
    props.compareChartMode === "byMetric"
      ? activeMetricKeys.value.length
      : compareRuns.value.length;
  // 막대 간격 여유를 위해 140px씩, 최소 900px 확보
  return Math.max(900, xCount * 140 + 240);
});
const tableInnerWidth = computed(() => {
  const cols = 2 + activeMetricKeys.value.length; // Execution name + Run + metric 수
  return Math.max(900, cols * 160);
});

/* ===== chart ===== */
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 = props.normalizeValues ? normalizeArray(yRaw) : yRaw;
    const text = yRaw.map((v) => fmtNumber(v));
    return {
      type: "bar",
      name: runLabels[i],
      x: metricKeys,
      y,
      text: props.normalizeValues ? 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 = props.normalizeValues ? normalizeArray(yRaw) : yRaw;
    const text = yRaw.map((v) => fmtNumber(v));
    return {
      type: "bar",
      name: key,
      x: rowLabels,
      y,
      text: props.normalizeValues ? undefined : text,
      textposition: "auto",
      customdata: text,
      hovertemplate: `${key}: %{customdata}<extra>%{x}</extra>`,
    };
  });
}
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 =
    props.compareChartMode === "byMetric"
      ? createTracesByMetric(metricKeys, runsData)
      : createTracesByRun(metricKeys, runsData);

  // byMetric 모드에서 분산 큰 순으로 정렬(가독성)
  if (props.compareChartMode === "byMetric") {
    const varianceOrder = metricKeys
      .map((k, idx) => {
        const vals = traces
          .map((t: any) => t.y[idx])
          .filter((v: any) => v != null) as number[];
        const min = Math.min(...vals),
          max = Math.max(...vals);
        return { k, spread: max - min };
      })
      .sort((a, b) => b.spread - a.spread)
      .map((v) => v.k);

    traces.forEach((t: any) => {
      t.x = varianceOrder;
      t.y = varianceOrder.map((mk: string) => t.y[metricKeys.indexOf(mk)]);
      if (t.text)
        t.text = varianceOrder.map(
          (mk: string) => t.text[metricKeys.indexOf(mk)],
        );
      if (t.customdata)
        t.customdata = varianceOrder.map(
          (mk: string) => t.customdata[metricKeys.indexOf(mk)],
        );
    });
  }

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

/* ===== load & watch ===== */
async function loadCompareData() {
  if (!dialogOpen.value) return;
  loading.value = true;
  try {
    await Promise.all(selectedRunIdsProxy.value.map(props.ensureRunDetail));
    if (selectedMetricKeysProxy.value.length === 0) {
      selectedMetricKeysProxy.value = commonMetricKeys.value.slice(0, 6);
    }
    await nextTick();
    drawCompareChart();
  } finally {
    loading.value = false;
  }
}
watch(
  () => dialogOpen.value,
  (open) => {
    if (open) loadCompareData();
  },
);
watch([selectedRunIdsProxy, selectedMetricKeysProxy], async () => {
  if (!dialogOpen.value) return;
  await loadCompareData();
});

const onResize = () => {
  if (elCompare.value) Plotly.Plots.resize(elCompare.value);
};
onMounted(() => window.addEventListener("resize", onResize));
onBeforeUnmount(() => window.removeEventListener("resize", onResize));
</script>

<template>
  <!-- 가로도 넉넉히, 세로는 90vh 제한 -->
  <v-dialog v-model="dialogOpen" max-width="80vw">
    <v-card class="rounded-lg overflow-hidden">
      <v-card-title
        class="text-white font-weight-bold text-h6"
        style="background-color: #1976d2"
      >
        Compare Runs
      </v-card-title>

      <!-- 본문 전체에 세로 스크롤 -->
      <div class="scroll-y">
        <!-- 상단 선택 섹션 -->
        <v-card-text class="pa-6">
          <v-row dense class="mb-2">
            <v-col cols="12">
              <div class="text-subtitle-2 font-weight-medium mb-2">
                Select Runs
              </div>
              <v-autocomplete
                v-model="selectedRunIdsProxy"
                :items="items"
                item-title="label"
                item-value="value"
                chips
                multiple
                closable-chips
                variant="outlined"
                density="comfortable"
                hide-details
                :disabled="loading"
                placeholder="Select runs to compare"
              />
            </v-col>
          </v-row>

          <v-expand-transition>
            <v-alert
              v-if="!loading && selectedRunIdsProxy.length < 2"
              type="info"
              variant="tonal"
              density="compact"
              class="mt-2"
            >
              2개 이상의 Run을 선택해 주세요.
            </v-alert>
          </v-expand-transition>

          <v-row dense class="mt-4">
            <v-col cols="12">
              <div class="text-subtitle-2 font-weight-medium mb-2">Metrics</div>
              <v-autocomplete
                v-model="selectedMetricKeysProxy"
                :items="activeMetricKeys.length ? activeMetricKeys : []"
                :menu-props="{ maxHeight: 360 }"
                chips
                multiple
                closable-chips
                variant="outlined"
                density="comfortable"
                hide-details
                :disabled="loading || compareRuns.length === 0"
                placeholder="Select metrics (empty = all common)"
              />
              <div class="d-flex align-center mt-2 ga-2">
                <v-chip size="small" color="primary" variant="tonal">
                  Mode:
                  {{
                    props.compareChartMode === "byMetric"
                      ? "By Metric"
                      : "By Run"
                  }}
                </v-chip>
                <v-chip size="small" color="secondary" variant="tonal">
                  {{ props.normalizeValues ? "Normalized" : "Raw values" }}
                </v-chip>
                <v-chip
                  v-if="props.baselineRunId"
                  size="small"
                  color="info"
                  variant="tonal"
                >
                  Baseline: {{ props.baselineRunId }}
                </v-chip>
              </div>
            </v-col>
          </v-row>
        </v-card-text>

        <!-- 차트 (가로 스크롤) -->
        <v-card-text class="pt-0 px-6 pb-2">
          <v-card variant="tonal" class="mb-4">
            <v-card-title class="py-2 px-4">Metrics (grouped bar)</v-card-title>
            <v-divider />
            <v-card-text class="px-4">
              <div v-if="loading" class="my-2">
                <v-progress-linear indeterminate />
              </div>
              <div class="scroll-x">
                <div
                  ref="elCompare"
                  :style="{ width: chartInnerWidth + 'px', height: '420px' }"
                />
              </div>
            </v-card-text>
          </v-card>

          <!-- 테이블 (가로 스크롤) -->
          <v-card variant="tonal">
            <v-card-title class="py-2 px-4">Comparison Table</v-card-title>
            <v-divider />
            <v-card-text class="px-0">
              <div class="scroll-x">
                <div :style="{ minWidth: tableInnerWidth + 'px' }">
                  <v-table density="comfortable">
                    <thead>
                      <tr>
                        <th style="width: 22%">Execution name</th>
                        <th style="width: 22%">Run</th>
                        <th v-for="k in activeMetricKeys" :key="k">{{ k }}</th>
                      </tr>
                    </thead>
                    <tbody>
                      <tr
                        v-for="(r, idx) in compareRuns"
                        :key="selectedRunIdsProxy[idx] ?? r.info.run_id"
                      >
                        <td class="text-no-wrap">
                          {{
                            props.runIdToExecutionName?.[
                              selectedRunIdsProxy[idx]
                            ] ?? "—"
                          }}
                        </td>
                        <td class="text-no-wrap">
                          {{ r.info.run_name || r.info.run_id || "—" }}
                        </td>
                        <td v-for="k in activeMetricKeys" :key="k">
                          {{
                            r.data.metrics.find((m) => m.key === k)?.value ??
                            "—"
                          }}
                        </td>
                      </tr>
                    </tbody>
                  </v-table>
                </div>
              </div>
            </v-card-text>
          </v-card>
        </v-card-text>
      </div>

      <!-- 액션 -->
      <v-card-actions class="justify-end">
        <v-btn variant="text" class="text-white" @click="dialogOpen = false"
          >CLOSE</v-btn
        >
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<style scoped>
.scroll-y {
  max-height: 90vh; /* 다이얼로그 본문 세로 스크롤 */
  overflow-y: auto;
}
.scroll-x {
  overflow-x: auto; /* 가로 스크롤 */
}
</style>
