"use client";

import { useCallback, useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { useLocale, useTranslations } from "next-intl";
import { useAuth } from "@/components/AuthProvider";
import {
  getTest,
  API_URL,
  listFixGuides,
  generateFixGuide,
  approveFixGuide,
  rejectFixGuide,
  type TestItem,
  type FixGuideItem,
} from "@/lib/api";

interface StepResult {
  step: number;
  action?: string;
  description?: string;
  target?: string;
  value?: string;
  status: string;
  error?: string;
  elapsed_ms?: number;
  screenshot_before?: string;
  screenshot_after?: string;
  screenshot_error?: string;
}

interface ScenarioResult {
  scenario_id: string;
  scenario_name: string;
  passed: boolean;
  steps: StepResult[];
}

interface ConsoleLogEntry {
  level: string; // "error" | "warning" | "info"
  text: string;
  url?: string;
  step?: string;
  step_num?: string;
  timestamp?: string;
}

interface ResultJSON {
  passed: boolean;
  scenarios?: ScenarioResult[];
  error?: string;
  duration_ms?: number;
  screenshots_dir?: string;
  initial_screenshot?: string;
  console_logs?: ConsoleLogEntry[];
}

const CONSOLE_PATTERNS: { pattern: RegExp; key: string; impact: "high" | "medium" | "low" }[] = [
  { pattern: /status of 400/, key: "http400", impact: "medium" },
  { pattern: /status of 401/, key: "http401", impact: "high" },
  { pattern: /status of 403/, key: "http403", impact: "high" },
  { pattern: /status of 404/, key: "http404", impact: "medium" },
  { pattern: /status of 5\d\d/, key: "http5xx", impact: "high" },
  { pattern: /CORS|cross-origin/i, key: "cors", impact: "medium" },
  { pattern: /net::ERR_/i, key: "network", impact: "high" },
  { pattern: /WebGL|GPU/i, key: "webgl", impact: "low" },
  { pattern: /Mixed Content/i, key: "mixed", impact: "medium" },
  { pattern: /Uncaught.*TypeError/i, key: "typeError", impact: "high" },
  { pattern: /Uncaught.*ReferenceError/i, key: "refError", impact: "high" },
  { pattern: /deprecated/i, key: "deprecated", impact: "low" },
];

function matchConsolePattern(text: string) {
  for (const p of CONSOLE_PATTERNS) {
    if (p.pattern.test(text)) return p;
  }
  return null;
}

const IMPACT_STYLES: Record<string, string> = {
  high: "bg-red-100 text-red-700",
  medium: "bg-yellow-100 text-yellow-700",
  low: "bg-gray-100 text-gray-500",
};

const STATUS_BADGE: Record<string, string> = {
  passed: "bg-green-100 text-green-700",
  done: "bg-green-100 text-green-700",
  partial: "bg-amber-100 text-amber-700",
  failed: "bg-red-100 text-red-700",
  error: "bg-red-100 text-red-700",
  queued: "bg-yellow-100 text-yellow-700",
  running: "bg-blue-100 text-blue-700",
};

function screenshotUrl(path: string | undefined | null): string | null {
  if (!path) return null;
  // Base64 data URLs are used directly (Render ephemeral FS)
  if (path.startsWith("data:image/")) return path;
  // Legacy: file path format → static file URL
  const relative = path.replace(/^(cloud\/)?screenshots\//, "");
  return `${API_URL}/screenshots/${relative}`;
}

/* ------------------------------------------------------------------ */
/* Screenshot Modal                                                     */
/* ------------------------------------------------------------------ */

function ScreenshotModal({
  src,
  alt,
  onClose,
}: {
  src: string;
  alt: string;
  onClose: () => void;
}) {
  useEffect(() => {
    const handleKey = (e: KeyboardEvent) => {
      if (e.key === "Escape") onClose();
    };
    document.addEventListener("keydown", handleKey);
    return () => document.removeEventListener("keydown", handleKey);
  }, [onClose]);

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4"
      onClick={onClose}
    >
      <div
        className="relative max-h-[90vh] max-w-[90vw]"
        onClick={(e) => e.stopPropagation()}
      >
        <button
          onClick={onClose}
          className="absolute -right-3 -top-3 flex h-8 w-8 items-center justify-center rounded-full bg-white text-gray-600 shadow-lg hover:bg-gray-100"
        >
          &#10005;
        </button>
        <img
          src={src}
          alt={alt}
          className="max-h-[85vh] max-w-[85vw] rounded-lg object-contain shadow-2xl"
        />
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* Console Log Icon                                                     */
/* ------------------------------------------------------------------ */

function ConsoleIcon({ level }: { level: string }) {
  if (level === "error") {
    return (
      <span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-red-100 text-[10px] text-red-600">
        &#10007;
      </span>
    );
  }
  if (level === "warning") {
    return (
      <span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-orange-100 text-[10px] text-orange-600">
        &#9888;
      </span>
    );
  }
  return (
    <span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-gray-100 text-[10px] text-gray-500">
      i
    </span>
  );
}

/* ------------------------------------------------------------------ */
/* Console Errors — grouped, deduplicated                               */
/* ------------------------------------------------------------------ */

/** Group key for a console log entry */
function consoleGroupKey(log: ConsoleLogEntry): string {
  const p = matchConsolePattern(log.text);
  if (p) return p.key;
  // Dedup by first 120 chars (ignore dynamic suffixes like URLs)
  return log.text.slice(0, 120);
}

interface GroupedLog {
  key: string;
  pattern: ReturnType<typeof matchConsolePattern>;
  logs: ConsoleLogEntry[];
  level: string; // highest severity in group
}

function groupConsoleLogs(logs: ConsoleLogEntry[]): GroupedLog[] {
  const map = new Map<string, GroupedLog>();
  for (const log of logs) {
    const k = consoleGroupKey(log);
    const existing = map.get(k);
    if (existing) {
      existing.logs.push(log);
      if (log.level === "error") existing.level = "error";
    } else {
      map.set(k, {
        key: k,
        pattern: matchConsolePattern(log.text),
        logs: [log],
        level: log.level,
      });
    }
  }
  return Array.from(map.values());
}

/** Category labels for known pattern keys */
const CATEGORY_MAP: Record<string, string> = {
  cors: "cors",
  network: "network",
  http400: "http",
  http401: "http",
  http403: "http",
  http404: "http",
  http5xx: "http",
  mixed: "network",
};

interface CategoryGroup {
  category: string;
  groups: GroupedLog[];
  totalCount: number;
}

function categorizeGroups(groups: GroupedLog[]): { categories: CategoryGroup[]; ungrouped: GroupedLog[] } {
  const catMap = new Map<string, CategoryGroup>();
  const ungrouped: GroupedLog[] = [];

  for (const g of groups) {
    const cat = g.pattern ? CATEGORY_MAP[g.pattern.key] : undefined;
    if (cat) {
      const existing = catMap.get(cat);
      if (existing) {
        existing.groups.push(g);
        existing.totalCount += g.logs.length;
      } else {
        catMap.set(cat, { category: cat, groups: [g], totalCount: g.logs.length });
      }
    } else {
      ungrouped.push(g);
    }
  }
  return { categories: Array.from(catMap.values()), ungrouped };
}

function ConsoleErrorsSection({
  logs,
  errorCount,
  warnCount,
  showConsole,
  setShowConsole,
  showWarnings,
  setShowWarnings,
  t,
}: {
  logs: ConsoleLogEntry[];
  errorCount: number;
  warnCount: number;
  showConsole: boolean;
  setShowConsole: (v: boolean) => void;
  showWarnings: boolean;
  setShowWarnings: (v: boolean) => void;
  t: ReturnType<typeof useTranslations>;
}) {
  const [expandedCategories, setExpandedCategories] = useState<Set<string>>(new Set());
  const [expandedRawLogs, setExpandedRawLogs] = useState<Set<string>>(new Set());

  const filteredLogs = logs.filter((l) => l.level === "error" || showWarnings);
  const grouped = groupConsoleLogs(filteredLogs);
  const { categories, ungrouped } = categorizeGroups(grouped);

  const toggleCategory = (cat: string) => {
    const next = new Set(expandedCategories);
    if (next.has(cat)) next.delete(cat); else next.add(cat);
    setExpandedCategories(next);
  };

  const toggleRawLog = (key: string) => {
    const next = new Set(expandedRawLogs);
    if (next.has(key)) next.delete(key); else next.add(key);
    setExpandedRawLogs(next);
  };

  return (
    <div className="mb-6 rounded-lg border border-gray-200">
      {/* Header — collapsed by default */}
      <button
        onClick={() => setShowConsole(!showConsole)}
        className="flex w-full items-center justify-between px-4 py-3 text-left hover:bg-gray-50"
      >
        <div className="flex items-center gap-2">
          <span className="text-sm font-medium text-gray-900">{t("consoleErrors")}</span>
          <span className="rounded-full bg-gray-100 px-1.5 py-0.5 text-[10px] font-medium text-gray-500">
            {errorCount + warnCount}
          </span>
        </div>
        <svg
          className={`h-4 w-4 text-gray-500 transition-transform ${showConsole ? "rotate-180" : ""}`}
          fill="none" viewBox="0 0 24 24" stroke="currentColor"
        >
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
        </svg>
      </button>

      {showConsole && (
        <div className="border-t border-gray-100">
          {/* Disclaimer */}
          <div className="flex items-start gap-2 bg-blue-50 px-4 py-2.5">
            <span className="mt-0.5 text-blue-400">&#9432;</span>
            <p className="text-xs text-blue-700">{t("consoleDisclaimer")}</p>
          </div>

          {/* Summary bar */}
          <div className="flex items-center justify-between bg-gray-50 px-4 py-2">
            <span className="text-xs text-gray-600">
              {errorCount > 0 && warnCount > 0
                ? t("consoleSummary", { errors: errorCount, warnings: warnCount })
                : errorCount > 0
                ? t("consoleSummaryErrorOnly", { errors: errorCount })
                : t("consoleSummaryWarningOnly", { warnings: warnCount })}
            </span>
            {warnCount > 0 && (
              <button
                onClick={() => setShowWarnings(!showWarnings)}
                className="rounded px-2 py-0.5 text-[10px] font-medium text-gray-500 hover:bg-gray-200 transition-colors"
              >
                {showWarnings ? t("consoleHideWarnings") : t("consoleShowWarnings")}
              </button>
            )}
          </div>

          {/* Categorized groups (CORS, Network, HTTP) */}
          {categories.map((cat) => (
            <div key={cat.category} className="border-t border-gray-100">
              <button
                onClick={() => toggleCategory(cat.category)}
                className="flex w-full items-center gap-2 px-4 py-2 text-left hover:bg-gray-50"
              >
                <span className="text-[10px] text-gray-400">
                  {expandedCategories.has(cat.category) ? "▾" : "▸"}
                </span>
                <ConsoleIcon level={cat.groups[0].level} />
                <span className="text-xs font-medium text-gray-800">
                  {t(`consoleCategory_${cat.category}`)}
                </span>
                <span className="rounded-full bg-gray-100 px-1.5 py-0.5 text-[10px] font-medium text-gray-500">
                  {cat.totalCount}
                </span>
                {cat.groups[0].pattern && (
                  <span className={`ml-auto rounded-full px-1.5 py-0.5 text-[9px] font-medium ${IMPACT_STYLES[cat.groups[0].pattern.impact]}`}>
                    {t(`consoleImpact${cat.groups[0].pattern.impact.charAt(0).toUpperCase()}${cat.groups[0].pattern.impact.slice(1)}`)}
                  </span>
                )}
              </button>
              {expandedCategories.has(cat.category) && (
                <div className="bg-gray-50/50 px-4 pb-2">
                  {/* Explanation for first pattern */}
                  {cat.groups[0].pattern && (
                    <p className="mb-2 ml-6 text-xs text-gray-600">
                      {t(`explain_${cat.groups[0].pattern.key}_desc`)}
                    </p>
                  )}
                  {cat.groups.map((g) => (
                    <div key={g.key} className="ml-6 mb-1">
                      <button
                        onClick={() => toggleRawLog(g.key)}
                        className="text-[10px] text-gray-400 hover:text-gray-600"
                      >
                        {expandedRawLogs.has(g.key) ? "▾" : "▸"}{" "}
                        {g.pattern ? t(`explain_${g.pattern.key}_title`) : g.key.slice(0, 60)}
                        {g.logs.length > 1 && (
                          <span className="ml-1 text-gray-500">×{g.logs.length}</span>
                        )}
                      </button>
                      {expandedRawLogs.has(g.key) && (
                        <pre className="mt-1 rounded bg-gray-100 px-2 py-1 text-[10px] font-mono text-gray-600 whitespace-pre-wrap break-all">
                          {g.logs[0].text}
                        </pre>
                      )}
                    </div>
                  ))}
                </div>
              )}
            </div>
          ))}

          {/* Ungrouped entries (deduplicated) */}
          {ungrouped.length > 0 && (
            <div className="max-h-64 overflow-y-auto divide-y divide-gray-100">
              {ungrouped.map((g) => (
                <div
                  key={g.key}
                  className={`flex items-start gap-2 px-4 py-1.5 text-xs ${
                    g.level === "error" ? "bg-red-50/50" : g.level === "warning" ? "bg-orange-50/50" : "bg-white"
                  }`}
                >
                  <ConsoleIcon level={g.level} />
                  <div className="min-w-0 flex-1">
                    <span className={`font-mono break-all ${
                      g.level === "error" ? "text-red-700" : g.level === "warning" ? "text-orange-700" : "text-gray-600"
                    }`}>
                      {g.logs[0].text.slice(0, 200)}
                    </span>
                    {g.logs.length > 1 && (
                      <span className="ml-1 rounded bg-gray-200 px-1 py-0.5 text-[10px] text-gray-600">
                        ×{g.logs.length}
                      </span>
                    )}
                    {g.logs[0].step && g.logs[0].step_num && (
                      <div className="mt-0.5 text-[10px] text-gray-400">
                        {t("consoleLocation", { step_num: g.logs[0].step_num, step: g.logs[0].step })}
                      </div>
                    )}
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* Main Page                                                            */
/* ------------------------------------------------------------------ */

export default function TestDetailPage() {
  const params = useParams();
  const router = useRouter();
  const { user, loading: authLoading } = useAuth();
  const locale = useLocale();
  const t = useTranslations("testDetail");
  const tc = useTranslations("common");
  const [test, setTest] = useState<TestItem | null>(null);
  const [result, setResult] = useState<ResultJSON | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState("");
  const [showScenarios, setShowScenarios] = useState(false);
  const [modalImage, setModalImage] = useState<{ src: string; alt: string } | null>(null);
  const [showConsole, setShowConsole] = useState(false);
  const [showWarnings, setShowWarnings] = useState(false);


  // Fix Guide state
  const [fixGuides, setFixGuides] = useState<FixGuideItem[]>([]);
  const [generatingFix, setGeneratingFix] = useState<string | null>(null);
  const [fixGuideError, setFixGuideError] = useState<{ scenarioId: string; message: string } | null>(null);
  const [approvingFix, setApprovingFix] = useState<number | null>(null);

  const testId = Number(params.id);

  useEffect(() => {
    if (!authLoading && !user) {
      router.push("/login");
      return;
    }
    if (user && testId) fetchTest();
  }, [user, authLoading, testId]);

  const fetchTest = async () => {
    try {
      const data = await getTest(testId);
      setTest(data);
      if (data.result_json) {
        setResult(JSON.parse(data.result_json));
      }
      // Load fix guides
      try {
        const guides = await listFixGuides(testId);
        setFixGuides(guides);
      } catch {
        // ignore — fix guides might not exist
      }
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to load test");
    } finally {
      setLoading(false);
    }
  };

  const handleGenerateFixGuide = async (scenarioId: string) => {
    setGeneratingFix(scenarioId);
    setFixGuideError(null);
    try {
      const guide = await generateFixGuide(testId, scenarioId, locale);
      setFixGuides((prev) => [guide, ...prev.filter((g) => g.scenario_id !== scenarioId)]);
    } catch (err) {
      const msg = err instanceof Error ? err.message : "Unknown error";
      setFixGuideError({ scenarioId, message: msg });
    } finally {
      setGeneratingFix(null);
    }
  };

  const handleApproveFix = async (guideId: number) => {
    setApprovingFix(guideId);
    try {
      const updated = await approveFixGuide(testId, guideId);
      setFixGuides((prev) => prev.map((g) => (g.id === guideId ? updated : g)));
    } catch (err) {
      const msg = err instanceof Error ? err.message : "Approve failed";
      setFixGuideError({ scenarioId: "", message: msg });
    } finally {
      setApprovingFix(null);
    }
  };

  const handleRejectFix = async (guideId: number) => {
    try {
      const updated = await rejectFixGuide(testId, guideId);
      setFixGuides((prev) => prev.map((g) => (g.id === guideId ? updated : g)));
    } catch (err) {
      const msg = err instanceof Error ? err.message : "Reject failed";
      setFixGuideError({ scenarioId: "", message: msg });
    }
  };

  const openModal = useCallback((src: string, alt: string) => {
    setModalImage({ src, alt });
  }, []);

  const closeModal = useCallback(() => {
    setModalImage(null);
  }, []);

  if (authLoading || loading) {
    return (
      <div className="flex min-h-[60vh] items-center justify-center">
        <p className="text-gray-500">{tc("loading")}</p>
      </div>
    );
  }

  if (error) {
    return (
      <div className="mx-auto max-w-2xl px-4 py-8">
        <p className="text-red-600">{error}</p>
      </div>
    );
  }

  if (!test) return null;

  const consoleLogs = result?.console_logs ?? [];
  const errorCount = consoleLogs.filter((l) => l.level === "error").length;
  const warnCount = consoleLogs.filter((l) => l.level === "warning").length;

  // Compute scenario-level pass/fail counts
  const scenarios = result?.scenarios ?? [];
  const scenariosPassedCount = scenarios.filter((s) => s.passed).length;
  const scenariosFailedCount = scenarios.filter((s) => !s.passed).length;
  const scenariosTotalCount = scenarios.length;

  // Step-level counts
  const allSteps = scenarios.flatMap((s) => s.steps);
  const stepsPassedCount = allSteps.filter((s) => s.status === "passed").length;
  const stepsFailedCount = allSteps.filter((s) => s.status === "error" || s.status === "failed").length;
  const stepsTotalCount = allSteps.length || test.steps_total;

  // Partial pass: some scenarios passed, some failed
  const isPartialPass = test.status === "failed" && scenariosTotalCount > 1 && scenariosPassedCount > 0 && scenariosFailedCount > 0;
  const isAllFailed = test.status === "failed" && scenariosPassedCount === 0;
  const passedPct = stepsTotalCount > 0 ? (stepsPassedCount / stepsTotalCount) * 100 : 0;
  const failedPct = stepsTotalCount > 0 ? (stepsFailedCount / stepsTotalCount) * 100 : 0;

  return (
    <div className="mx-auto max-w-3xl px-4 py-8">
      {/* Screenshot modal */}
      {modalImage && (
        <ScreenshotModal src={modalImage.src} alt={modalImage.alt} onClose={closeModal} />
      )}

      {/* Header */}
      <button
        onClick={() => router.push("/tests")}
        className="mb-4 text-sm text-gray-500 hover:text-gray-700"
      >
        &larr; {t("backToHistory")}
      </button>

      <div className="mb-6 flex items-start justify-between">
        <div>
          <h1 className="text-xl font-bold text-gray-900">
            {t("testTitle", { id: test.id })}
          </h1>
          <p className="mt-1 text-sm text-gray-600">{test.target_url}</p>
          <p className="mt-0.5 text-xs text-gray-400">
            {new Date(test.created_at).toLocaleString()}
            {result?.duration_ms &&
              ` · ${(result.duration_ms / 1000).toFixed(1)}s`}
          </p>
        </div>
        <span
          className={`rounded-full px-3 py-1 text-sm font-medium ${
            isPartialPass
              ? STATUS_BADGE.partial
              : STATUS_BADGE[test.status] || "bg-gray-100 text-gray-600"
          }`}
        >
          {isPartialPass
            ? t("scenarioSummary", { passed: scenariosPassedCount, failed: scenariosFailedCount, total: scenariosTotalCount })
            : test.status}
        </span>
      </div>

      {/* Error message */}
      {test.error_message && (
        <div className="mb-4 rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700">
          {test.error_message}
        </div>
      )}

      {/* Scenario summary (when multiple scenarios) */}
      {scenariosTotalCount > 1 && (test.status === "done" || test.status === "failed") && (
        <div className="mb-4 rounded-lg border border-gray-200 p-4">
          <div className="mb-2 text-sm font-medium text-gray-900">
            {t("scenarioSummary", { passed: scenariosPassedCount, failed: scenariosFailedCount, total: scenariosTotalCount })}
          </div>
          <div className="flex gap-2">
            {scenarios.map((sc, i) => (
              <span
                key={i}
                className={`rounded-full px-2 py-0.5 text-xs font-medium ${
                  sc.passed ? "bg-green-100 text-green-700" : "bg-red-100 text-red-700"
                }`}
                title={sc.scenario_name || sc.scenario_id}
              >
                {sc.scenario_name || sc.scenario_id}
              </span>
            ))}
          </div>
        </div>
      )}

      {/* Progress summary */}
      {test.steps_total > 0 && (
        <div className="mb-6 rounded-lg border border-gray-200 p-4">
          <div className="mb-2 flex items-center justify-between text-sm">
            <span className="text-gray-600">{t("progress")}</span>
            <span className="text-gray-900">
              {isPartialPass
                ? t("stepsDetail", { passed: stepsPassedCount, failed: stepsFailedCount, total: stepsTotalCount })
                : t("stepsCount", { completed: test.steps_completed, total: test.steps_total })}
            </span>
          </div>
          <div className="h-2 rounded-full bg-gray-100">
            {isPartialPass ? (
              <div className="flex h-2 overflow-hidden rounded-full">
                <div
                  className="h-full bg-green-500 transition-all duration-300"
                  style={{ width: `${passedPct}%` }}
                />
                <div
                  className="h-full bg-red-400 transition-all duration-300"
                  style={{ width: `${failedPct}%` }}
                />
              </div>
            ) : (
              <div
                className={`h-2 rounded-full ${
                  test.status === "done" ? "bg-green-500" : test.status === "failed" ? "bg-red-400" : "bg-blue-500"
                }`}
                style={{
                  width: `${
                    test.steps_total > 0
                      ? (test.steps_completed / test.steps_total) * 100
                      : 0
                  }%`,
                }}
              />
            )}
          </div>
        </div>
      )}

      {/* Scenario YAML viewer (collapsible) */}
      {test.scenario_yaml && (
        <div className="mb-6 rounded-lg border border-gray-200">
          <button
            onClick={() => setShowScenarios(!showScenarios)}
            className="flex w-full items-center justify-between px-4 py-3 text-left hover:bg-gray-50"
          >
            <span className="text-sm font-medium text-gray-900">
              {t("viewScenarios")}
            </span>
            <svg
              className={`h-4 w-4 text-gray-500 transition-transform ${showScenarios ? "rotate-180" : ""}`}
              fill="none"
              viewBox="0 0 24 24"
              stroke="currentColor"
            >
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
            </svg>
          </button>
          {showScenarios && (
            <ScenarioViewer yaml={test.scenario_yaml} t={t} />
          )}
        </div>
      )}

      {/* Scenarios */}
      {result?.scenarios?.map((scenario, si) => (
        <div key={si} className="mb-6 rounded-lg border border-gray-200">
          <div className="flex items-center justify-between border-b border-gray-100 px-4 py-3">
            <h2 className="text-sm font-medium text-gray-900">
              {scenario.scenario_name || scenario.scenario_id}
            </h2>
            <span
              className={`rounded-full px-2 py-0.5 text-xs font-medium ${
                scenario.passed
                  ? "bg-green-100 text-green-700"
                  : "bg-red-100 text-red-700"
              }`}
            >
              {scenario.passed ? t("passed") : t("failed")}
            </span>
          </div>

          <div className="divide-y divide-gray-50">
            {scenario.steps.map((step, i) => (
              <div key={i} className="px-4 py-3">
                <div className="flex items-center gap-2">
                  <span
                    className={`flex h-5 w-5 items-center justify-center rounded-full text-xs ${
                      step.status === "passed"
                        ? "bg-green-100 text-green-600"
                        : step.status === "error" || step.status === "failed"
                        ? "bg-red-100 text-red-600"
                        : "bg-gray-100 text-gray-500"
                    }`}
                  >
                    {step.step}
                  </span>
                  <span className="text-sm text-gray-700">
                    {step.action || t("stepLabel", { step: step.step })}
                  </span>
                  {(step.target || step.value || step.description) && (
                    <span className="truncate text-xs text-gray-400" title={step.description || undefined}>
                      {step.target
                        ? `"${step.target}"`
                        : step.value
                          ? step.value
                          : step.description}
                    </span>
                  )}
                  <span
                    className={`ml-auto text-xs ${
                      step.status === "passed"
                        ? "text-green-600"
                        : "text-red-600"
                    }`}
                  >
                    {step.status}
                  </span>
                  {step.elapsed_ms !== undefined && (
                    <span className="text-xs text-gray-400">
                      {(step.elapsed_ms / 1000).toFixed(1)}s
                    </span>
                  )}
                </div>

                {/* Error detail — red box */}
                {step.error && (
                  <div className="mt-2 ml-7 rounded-lg border border-red-200 bg-red-50 px-3 py-2">
                    <span className="text-[10px] font-semibold text-red-500">{t("failureReason")}</span>
                    <p className="mt-0.5 text-xs text-red-700 font-mono whitespace-pre-wrap break-all">
                      {step.error}
                    </p>
                  </div>
                )}

                {/* Screenshots — click to enlarge */}
                {(step.screenshot_before ||
                  step.screenshot_after ||
                  step.screenshot_error) && (
                  <div className="mt-2 ml-7 flex gap-2">
                    {step.screenshot_before && (
                      <div className="text-center">
                        <img
                          src={screenshotUrl(step.screenshot_before) || ""}
                          alt="Before"
                          className="h-24 cursor-pointer rounded border border-gray-200 object-cover transition-transform hover:scale-105"
                          loading="lazy"
                          onClick={() =>
                            openModal(
                              screenshotUrl(step.screenshot_before) || "",
                              `Step ${step.step} — Before`
                            )
                          }
                        />
                        <span className="text-[10px] text-gray-400">
                          {t("before")}
                        </span>
                      </div>
                    )}
                    {step.screenshot_after && (
                      <div className="text-center">
                        <img
                          src={screenshotUrl(step.screenshot_after) || ""}
                          alt="After"
                          className="h-24 cursor-pointer rounded border border-gray-200 object-cover transition-transform hover:scale-105"
                          loading="lazy"
                          onClick={() =>
                            openModal(
                              screenshotUrl(step.screenshot_after) || "",
                              `Step ${step.step} — After`
                            )
                          }
                        />
                        <span className="text-[10px] text-gray-400">
                          {t("after")}
                        </span>
                      </div>
                    )}
                    {step.screenshot_error && (
                      <div className="text-center">
                        <img
                          src={screenshotUrl(step.screenshot_error) || ""}
                          alt="Error"
                          className="h-24 cursor-pointer rounded border border-red-200 object-cover transition-transform hover:scale-105"
                          loading="lazy"
                          onClick={() =>
                            openModal(
                              screenshotUrl(step.screenshot_error) || "",
                              `Step ${step.step} — Error`
                            )
                          }
                        />
                        <span className="text-[10px] text-red-400">{t("error")}</span>
                      </div>
                    )}
                  </div>
                )}
              </div>
            ))}
          </div>

          {/* Fix Guide for failed scenario */}
          {!scenario.passed && (
            <FixGuideSection
              scenarioId={scenario.scenario_id}
              guide={fixGuides.find((g) => g.scenario_id === scenario.scenario_id)}
              generatingFix={generatingFix}
              fixError={fixGuideError?.scenarioId === scenario.scenario_id ? fixGuideError.message : null}
              approvingFix={approvingFix}
              onGenerate={handleGenerateFixGuide}
              onApprove={handleApproveFix}
              onReject={handleRejectFix}
              t={t}
            />
          )}
        </div>
      ))}

      {/* Console Errors — grouped & deduplicated */}
      {consoleLogs.length > 0 && (
        <ConsoleErrorsSection
          logs={consoleLogs}
          errorCount={errorCount}
          warnCount={warnCount}
          showConsole={showConsole}
          setShowConsole={setShowConsole}
          showWarnings={showWarnings}
          setShowWarnings={setShowWarnings}
          t={t}
        />
      )}

      {/* Raw result fallback */}
      {result && !result.scenarios && result.error && (
        <div className="rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-700">
          {result.error}
        </div>
      )}
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* Scenario YAML Viewer                                                */
/* ------------------------------------------------------------------ */

interface ParsedStep {
  step?: number;
  action?: string;
  description?: string;
  target?: { text?: string };
  value?: string;
  assert_type?: string;
  expected?: Array<{ type?: string; value?: string }>;
  humanize?: boolean;
}

interface ParsedScenario {
  id?: string;
  name?: string;
  description?: string;
  tags?: string[];
  steps?: ParsedStep[];
  expected_result?: Array<{ type?: string; value?: string }>;
}

const ACTION_COLORS: Record<string, string> = {
  navigate: "bg-blue-100 text-blue-700",
  find_and_click: "bg-purple-100 text-purple-700",
  find_and_type: "bg-amber-100 text-amber-700",
  type_text: "bg-amber-100 text-amber-700",
  press_key: "bg-gray-100 text-gray-700",
  assert: "bg-green-100 text-green-700",
  wait: "bg-gray-100 text-gray-500",
  screenshot: "bg-cyan-100 text-cyan-700",
};

function ScenarioViewer({
  yaml: yamlStr,
  t,
}: {
  yaml: string;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  t: any;
}) {
  let scenarios: ParsedScenario[] = [];
  try {
    // scenario_yaml can be a list or {scenarios: [...]}
    const parsed = JSON.parse(
      JSON.stringify(
        // simple YAML-like parse: split documents
        yamlStr
      )
    );
    // Actually we need to parse YAML — but we don't have js-yaml in frontend.
    // scenario_yaml is stored as YAML string. Let's parse it manually for common patterns.
    // Better approach: parse in a simple way since the YAML is machine-generated.
    scenarios = parseSimpleYaml(yamlStr);
  } catch {
    // fallback: show raw
  }

  if (scenarios.length === 0) {
    return (
      <div className="border-t border-gray-100 px-4 py-3">
        <pre className="max-h-80 overflow-auto rounded bg-gray-50 p-3 text-xs text-gray-700 whitespace-pre-wrap">
          {yamlStr}
        </pre>
      </div>
    );
  }

  return (
    <div className="border-t border-gray-100 divide-y divide-gray-50">
      {scenarios.map((sc, si) => (
        <div key={si} className="px-4 py-3">
          <div className="mb-2 flex items-center gap-2">
            <span className="rounded bg-gray-200 px-1.5 py-0.5 text-[10px] font-mono text-gray-600">
              {sc.id || `SC-${si + 1}`}
            </span>
            <span className="text-sm font-medium text-gray-900">
              {sc.name || t("scenarioLabel")}
            </span>
          </div>
          {sc.description && (
            <p className="mb-2 text-xs text-gray-500">{sc.description}</p>
          )}
          {sc.tags && sc.tags.length > 0 && (
            <div className="mb-2 flex gap-1">
              {sc.tags.map((tag, ti) => (
                <span key={ti} className="rounded-full bg-blue-50 px-2 py-0.5 text-[10px] text-blue-600">
                  {tag}
                </span>
              ))}
            </div>
          )}
          <div className="space-y-1">
            {sc.steps?.map((step, i) => (
              <div key={i} className="flex items-start gap-2 text-xs">
                <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-gray-100 text-[10px] text-gray-500">
                  {step.step ?? i + 1}
                </span>
                <span className={`shrink-0 rounded px-1.5 py-0.5 text-[10px] font-medium ${ACTION_COLORS[step.action || ""] || "bg-gray-100 text-gray-600"}`}>
                  {step.action || "?"}
                </span>
                <span className="text-gray-700">
                  {step.description || ""}
                </span>
                {step.target?.text && (
                  <span className="shrink-0 rounded bg-gray-50 px-1 text-[10px] text-gray-500 font-mono">
                    target: &quot;{step.target.text}&quot;
                  </span>
                )}
                {step.value && step.action !== "navigate" && (
                  <span className="shrink-0 rounded bg-gray-50 px-1 text-[10px] text-gray-500 font-mono">
                    value: &quot;{step.value}&quot;
                  </span>
                )}
              </div>
            ))}
          </div>
          {sc.expected_result && sc.expected_result.length > 0 && (
            <div className="mt-2 rounded bg-green-50 px-2 py-1">
              <span className="text-[10px] font-medium text-green-700">{t("expectedResult")}:</span>
              {sc.expected_result.map((er, ei) => (
                <span key={ei} className="ml-1 text-[10px] text-green-600">
                  {er.type}: &quot;{er.value}&quot;
                </span>
              ))}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

/**
 * Simple YAML parser for machine-generated scenario YAML.
 * Handles the specific format used by AAT scenario generation.
 */
/* ------------------------------------------------------------------ */
/* Diff Safety Analysis                                                 */
/* ------------------------------------------------------------------ */

interface FixGuideFileChange {
  path: string;
  action: string;
  original?: string;
  suggested?: string;
  explanation?: string;
}

interface DiffStats {
  totalAdded: number;
  totalRemoved: number;
  filesChanged: number;
  warnings: string[];
}

function analyzeDiff(files: FixGuideFileChange[]): DiffStats {
  let totalAdded = 0;
  let totalRemoved = 0;
  const warnings: string[] = [];

  for (const file of files) {
    const origLines = (file.original || "").split("\n").filter(l => l.trim()).length;
    const sugLines = (file.suggested || "").split("\n").filter(l => l.trim()).length;
    totalRemoved += origLines;
    totalAdded += sugLines;

    // 1. Major deletion: >50% of file removed
    if (file.action === "modify" && origLines > 10) {
      const deleteRatio = origLines > 0 ? (origLines - sugLines) / origLines : 0;
      if (deleteRatio > 0.5) {
        warnings.push("warnMajorDeletion");
      }
    }

    // 2. All imports removed
    if (file.action === "modify" && file.original && file.suggested) {
      const origImports = (file.original.match(/^(import |from .+ import )/gm) || []).length;
      const sugImports = (file.suggested.match(/^(import |from .+ import )/gm) || []).length;
      if (origImports >= 3 && sugImports === 0) {
        warnings.push("warnImportsRemoved");
      }
    }

    // 3. Large block replaced with stub
    if (file.action === "modify" && origLines > 20 && sugLines < origLines * 0.2) {
      warnings.push("warnReplacedWithStub");
    }
  }

  // 4. Deletions exceed additions by 10x
  if (totalRemoved > 0 && totalAdded > 0 && totalRemoved > totalAdded * 10) {
    warnings.push("warnExcessiveDeletion");
  }

  return {
    totalAdded,
    totalRemoved,
    filesChanged: files.length,
    warnings: [...new Set(warnings)],
  };
}

/* ------------------------------------------------------------------ */
/* Fix Guide Section                                                    */
/* ------------------------------------------------------------------ */

const ACTION_BADGE: Record<string, string> = {
  modify: "bg-amber-100 text-amber-700",
  create: "bg-green-100 text-green-700",
  delete: "bg-red-100 text-red-700",
};

const STATUS_LABEL: Record<string, string> = {
  ready: "fixStatusReady",
  approved: "fixStatusApproved",
  rejected: "fixStatusRejected",
  pr_created: "fixStatusPrCreated",
};

function FixGuideSection({
  scenarioId,
  guide,
  generatingFix,
  fixError,
  approvingFix,
  onGenerate,
  onApprove,
  onReject,
  t,
}: {
  scenarioId: string;
  guide: import("@/lib/api").FixGuideItem | undefined;
  generatingFix: string | null;
  fixError: string | null;
  approvingFix: number | null;
  onGenerate: (sid: string) => void;
  onApprove: (gid: number) => void;
  onReject: (gid: number) => void;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  t: any;
}) {
  const [expandedFiles, setExpandedFiles] = useState<Set<number>>(new Set());

  const toggleFile = (idx: number) => {
    setExpandedFiles((prev) => {
      const next = new Set(prev);
      if (next.has(idx)) next.delete(idx);
      else next.add(idx);
      return next;
    });
  };

  // Translate backend error codes to user-friendly messages
  const getErrorHint = (msg: string): string => {
    if (msg.includes("no_ai_key_configured")) return t("fixErrorNoAiKey");
    if (msg.includes("502") || msg.includes("generation failed")) return t("fixErrorGenFailed");
    if (msg.includes("503")) return t("fixErrorService");
    if (msg.includes("timeout") || msg.includes("Timeout")) return t("fixErrorTimeout");
    return t("fixErrorGeneric");
  };

  // No guide yet — show generate button (or error + retry)
  if (!guide) {
    return (
      <div className="border-t border-gray-100 px-4 py-3">
        {fixError && (
          <div className="mb-3 rounded border border-red-200 bg-red-50 px-3 py-2">
            <p className="text-sm font-medium text-red-700">{t("fixGuideFailed")}</p>
            <p className="mt-1 text-xs text-red-600">{getErrorHint(fixError)}</p>
          </div>
        )}
        <button
          onClick={() => onGenerate(scenarioId)}
          disabled={generatingFix === scenarioId}
          className="rounded bg-indigo-600 px-4 py-2 text-sm font-medium text-white hover:bg-indigo-700 disabled:opacity-50"
        >
          {generatingFix === scenarioId
            ? t("generatingFix")
            : fixError
            ? t("retryFixGuide")
            : t("generateFixGuide")}
        </button>
      </div>
    );
  }

  // Guide exists — analyze diff for safety warnings
  const stats = analyzeDiff(guide.files as FixGuideFileChange[]);

  return (
    <div className="border-t border-gray-100 px-4 py-4">
      <div className="mb-3 flex items-center gap-2">
        <h3 className="text-sm font-semibold text-gray-900">
          {t("fixGuideSummary")}
        </h3>
        <span
          className={`rounded-full px-2 py-0.5 text-[10px] font-medium ${
            guide.status === "pr_created"
              ? "bg-green-100 text-green-700"
              : guide.status === "rejected"
              ? "bg-red-100 text-red-700"
              : guide.status === "ready"
              ? "bg-blue-100 text-blue-700"
              : "bg-gray-100 text-gray-600"
          }`}
        >
          {t(STATUS_LABEL[guide.status] || guide.status)}
        </span>
      </div>

      {/* Summary */}
      {guide.summary && (
        <p className="mb-3 text-sm text-gray-700">{guide.summary}</p>
      )}

      {/* Safety warning banner */}
      {stats.warnings.length > 0 && (
        <div className="mb-3 rounded border border-amber-300 bg-amber-50 px-3 py-2">
          <p className="text-xs font-medium text-amber-800">&#9888;&#65039; {t("fixSafetyTitle")}</p>
          <ul className="mt-1 list-disc pl-4 text-xs text-amber-700">
            {stats.warnings.map(w => <li key={w}>{t(w)}</li>)}
          </ul>
        </div>
      )}

      {/* File changes */}
      {guide.files.length > 0 && (
        <div className="mb-3 space-y-2">
          {guide.files.map((file, idx) => {
            const origLines = (file.original || "").split("\n").filter((l: string) => l.trim()).length;
            const sugLines = (file.suggested || "").split("\n").filter((l: string) => l.trim()).length;
            const isHighDeletion = file.action === "modify" && origLines > 10 && origLines > 0 && (origLines - sugLines) / origLines > 0.5;

            return (
            <div key={idx} className="rounded border border-gray-200">
              <button
                onClick={() => toggleFile(idx)}
                className="flex w-full items-center gap-2 px-3 py-2 text-left hover:bg-gray-50"
              >
                <span
                  className={`rounded px-1.5 py-0.5 text-[10px] font-medium ${
                    ACTION_BADGE[file.action] || "bg-gray-100 text-gray-600"
                  }`}
                >
                  {t(
                    file.action === "modify"
                      ? "fileModify"
                      : file.action === "create"
                      ? "fileCreate"
                      : "fileDelete"
                  )}
                </span>
                <span className="flex-1 font-mono text-xs text-gray-700">
                  {file.path}
                </span>
                <span className="text-[10px] text-gray-400">
                  +{sugLines} / -{origLines}
                </span>
                {isHighDeletion && (
                  <span className="rounded bg-amber-100 px-1.5 py-0.5 text-[10px] font-medium text-amber-700">
                    &#9888;&#65039;
                  </span>
                )}
                <svg
                  className={`h-4 w-4 text-gray-400 transition-transform ${
                    expandedFiles.has(idx) ? "rotate-180" : ""
                  }`}
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke="currentColor"
                >
                  <path
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth={2}
                    d="M19 9l-7 7-7-7"
                  />
                </svg>
              </button>
              {expandedFiles.has(idx) && (
                <div className="border-t border-gray-100 px-3 py-2">
                  {file.explanation && (
                    <p className="mb-2 text-xs text-gray-600 italic">
                      {file.explanation}
                    </p>
                  )}
                  {file.original && (
                    <div className="mb-2">
                      <span className="text-[10px] font-medium text-red-600">
                        {t("originalCode")}
                      </span>
                      <pre className="mt-1 max-h-40 overflow-auto rounded bg-red-50 px-2 py-1 text-[11px] font-mono text-red-800 whitespace-pre-wrap">
                        {file.original}
                      </pre>
                    </div>
                  )}
                  {file.suggested && (
                    <div>
                      <span className="text-[10px] font-medium text-green-600">
                        {t("suggestedCode")}
                      </span>
                      <pre className="mt-1 max-h-40 overflow-auto rounded bg-green-50 px-2 py-1 text-[11px] font-mono text-green-800 whitespace-pre-wrap">
                        {file.suggested}
                      </pre>
                    </div>
                  )}
                </div>
              )}
            </div>
            );
          })}
        </div>
      )}

      {/* PR link */}
      {guide.pr_url && (
        <div className="mb-3 rounded border border-green-200 bg-green-50 px-3 py-2">
          <span className="text-sm text-green-700">
            {t("prCreated", { number: guide.pr_number })}
          </span>
          <a
            href={guide.pr_url}
            target="_blank"
            rel="noopener noreferrer"
            className="ml-2 text-sm font-medium text-green-700 underline"
          >
            {t("viewPR")}
          </a>
        </div>
      )}

      {/* Approve / Reject buttons */}
      {guide.status === "ready" && (
        <div className="flex items-center gap-2">
          <span className={`text-xs ${stats.warnings.length > 0 ? "text-amber-600 font-medium" : "text-gray-500"}`}>
            {t("diffSummary", { files: stats.filesChanged, added: stats.totalAdded, removed: stats.totalRemoved })}
          </span>
          <button
            onClick={() => onApprove(guide.id)}
            disabled={approvingFix === guide.id}
            className="rounded bg-green-600 px-4 py-2 text-sm font-medium text-white hover:bg-green-700 disabled:opacity-50"
          >
            {approvingFix === guide.id
              ? t("approvingFix")
              : t("approveAndCreatePR")}
          </button>
          <button
            onClick={() => onReject(guide.id)}
            className="rounded border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50"
          >
            {t("rejectFix")}
          </button>
        </div>
      )}

      {/* No GitHub connected hint */}
      {guide.status === "approved" && !guide.pr_url && (
        <p className="text-xs text-amber-600">{t("noGithubConnected")}</p>
      )}
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* Simple YAML Parser                                                  */
/* ------------------------------------------------------------------ */

function parseSimpleYaml(yamlStr: string): ParsedScenario[] {
  try {
    // The YAML is a list of scenario objects. Try JSON first (some formats).
    const asJson = JSON.parse(yamlStr);
    if (Array.isArray(asJson)) return asJson;
    if (asJson?.scenarios) return asJson.scenarios;
  } catch {
    // Not JSON, parse as YAML manually using line-by-line
  }

  // Simple line-based YAML parser for known structure
  const scenarios: ParsedScenario[] = [];
  let current: ParsedScenario | null = null;
  let currentStep: ParsedStep | null = null;
  let inSteps = false;
  let inExpected = false;
  let inTags = false;

  for (const raw of yamlStr.split("\n")) {
    const line = raw.trimEnd();
    const trimmed = line.trimStart();
    const indent = line.length - trimmed.length;

    // New scenario (top-level list item)
    if (trimmed.startsWith("- id:")) {
      if (current) scenarios.push(current);
      current = { id: trimmed.replace("- id:", "").trim().replace(/['"]/g, ""), steps: [] };
      inSteps = false;
      inExpected = false;
      inTags = false;
      currentStep = null;
      continue;
    }

    if (!current) continue;

    // Scenario-level fields
    if (indent <= 2 && trimmed.startsWith("name:")) {
      current.name = trimmed.replace("name:", "").trim().replace(/['"]/g, "");
      inSteps = false; inExpected = false; inTags = false;
    } else if (indent <= 2 && trimmed.startsWith("description:")) {
      current.description = trimmed.replace("description:", "").trim().replace(/['"]/g, "");
      inSteps = false; inExpected = false; inTags = false;
    } else if (indent <= 2 && trimmed.startsWith("tags:")) {
      inTags = true; inSteps = false; inExpected = false;
      current.tags = [];
    } else if (indent <= 2 && trimmed.startsWith("steps:")) {
      inSteps = true; inTags = false; inExpected = false;
    } else if (indent <= 2 && trimmed.startsWith("expected_result:")) {
      inExpected = true; inSteps = false; inTags = false;
      current.expected_result = [];
    } else if (inTags && trimmed.startsWith("- ")) {
      current.tags = current.tags || [];
      current.tags.push(trimmed.replace("- ", "").replace(/['"]/g, ""));
    } else if (inExpected && trimmed.startsWith("- type:")) {
      // inline expected_result item
      current.expected_result = current.expected_result || [];
      const obj: { type?: string; value?: string } = {};
      obj.type = trimmed.replace("- type:", "").trim().replace(/['"]/g, "");
      current.expected_result.push(obj);
    } else if (inExpected && trimmed.startsWith("value:")) {
      const arr = current.expected_result || [];
      if (arr.length > 0) {
        arr[arr.length - 1].value = trimmed.replace("value:", "").trim().replace(/['"]/g, "");
      }
    } else if (inSteps && trimmed.startsWith("- step:")) {
      if (currentStep) current.steps!.push(currentStep);
      currentStep = { step: parseInt(trimmed.replace("- step:", "").trim()) };
    } else if (inSteps && currentStep) {
      if (trimmed.startsWith("action:")) {
        currentStep.action = trimmed.replace("action:", "").trim().replace(/['"]/g, "");
      } else if (trimmed.startsWith("description:")) {
        currentStep.description = trimmed.replace("description:", "").trim().replace(/['"]/g, "");
      } else if (trimmed.startsWith("value:")) {
        currentStep.value = trimmed.replace("value:", "").trim().replace(/['"]/g, "");
      } else if (trimmed.startsWith("humanize:")) {
        currentStep.humanize = trimmed.includes("true");
      } else if (trimmed.startsWith("target:")) {
        // target might be inline or multi-line
        currentStep.target = {};
      } else if (trimmed.startsWith("text:") && currentStep.target) {
        currentStep.target.text = trimmed.replace("text:", "").trim().replace(/['"]/g, "");
      } else if (trimmed.startsWith("assert_type:")) {
        currentStep.assert_type = trimmed.replace("assert_type:", "").trim().replace(/['"]/g, "");
      }
    }
  }

  // Push last items
  if (currentStep && current) current.steps!.push(currentStep);
  if (current) scenarios.push(current);

  return scenarios;
}
