// #region ValidationRunDetailModelTests [C:2] [TYPE Module]
// @BRIEF L1 unit tests for ValidationRunDetailModel — no DOM render.
// @RELATION BINDS_TO -> [ValidationRunDetailModel]
import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("$lib/api.js", () => ({
  api: {
    fetchApi: vi.fn(),
    getStorageFileBlob: vi.fn(),
    getTaskLogs: vi.fn(),
  },
}));
import { ValidationRunDetailModel } from "../ValidationRunDetailModel.svelte.ts";
import { api } from "$lib/api.js";

// #region ValidationRunDetailModel.InvariantTests [C:2] [TYPE Function]
// @BRIEF L1 invariants: initial state, toggle actions, derived state.
describe("ValidationRunDetailModel — L1 invariants", () => {
  let model: ValidationRunDetailModel;
  beforeEach(() => { vi.clearAllMocks(); model = new ValidationRunDetailModel(); });

  // #region ValidationRunDetailModel.InitialState [C:2] [TYPE Test]
  it("starts in populated state", () => {
    expect(model.uxState).toBe("populated");
    expect(model.run).toBeNull();
    expect(model.dashboards).toEqual([]);
  });
  // #endregion

  // #region ValidationRunDetailModel.ToggleDashboard [C:2] [TYPE Test]
  it("toggleDashboard toggles expandedDashboards set", () => {
    expect(model.expandedDashboards.has("d1")).toBe(false);
    model.toggleDashboard("d1");
    expect(model.expandedDashboards.has("d1")).toBe(true);
    model.toggleDashboard("d1");
    expect(model.expandedDashboards.has("d1")).toBe(false);
  });

  it("toggleDashboard selects first screenshot tab when expanding", () => {
    model.run = { dashboards: [{ id: "d1", screenshots: [{ path: "s1.png" }] }] } as any;
    model.toggleDashboard("d1");
    expect(model.selectedScreenshotTab["d1"]).toBe("0");
  });

  it("toggleDashboard loads screenshot for first tab", () => {
    const loadSpy = vi.spyOn(model, "loadScreenshot").mockResolvedValue();
    model.run = { dashboards: [{ id: "d1", screenshots: [{ path: "s1.png" }] }] } as any;
    model.toggleDashboard("d1");
    expect(loadSpy).toHaveBeenCalledWith("s1.png", "d1_0");
    loadSpy.mockRestore();
  });

  it("toggleDashboard does not reload screenshot if already cached", () => {
    model.screenshotUrls["d1_0"] = "blob:existing";
    const loadSpy = vi.spyOn(model, "loadScreenshot").mockResolvedValue();
    model.run = { dashboards: [{ id: "d1", screenshots: [{ path: "s1.png" }] }] } as any;
    model.toggleDashboard("d1");
    expect(loadSpy).not.toHaveBeenCalled();
    loadSpy.mockRestore();
  });

  it("isDashboardExpanded checks set membership", () => {
    model.expandedDashboards = new Set(["d1"]) as any;
    expect(model.isDashboardExpanded("d1")).toBe(true);
    expect(model.isDashboardExpanded("d2")).toBe(false);
  });
  // #endregion

  // #region ValidationRunDetailModel.ToggleLogsSent [C:2] [TYPE Test]
  it("toggleLogsSent toggles logsSentExpanded set", () => {
    model.toggleLogsSent("key-1");
    expect(model.logsSentExpanded.has("key-1")).toBe(true);
    model.toggleLogsSent("key-1");
    expect(model.logsSentExpanded.has("key-1")).toBe(false);
  });
  // #endregion

  // #region ValidationRunDetailModel.ToggleTaskLogs [C:2] [TYPE Test]
  it("toggleTaskLogs expands and collapses", () => {
    model.toggleTaskLogs("tasklogs_d1");
    expect(model.taskLogsExpanded.has("tasklogs_d1")).toBe(true);
    model.toggleTaskLogs("tasklogs_d1");
    expect(model.taskLogsExpanded.has("tasklogs_d1")).toBe(false);
  });

  it("toggleTaskLogs loads logs when expanding new dashboard", () => {
    vi.mocked(api.getTaskLogs).mockResolvedValue([{ level: "info", message: "log1" }]);
    model.run = { task_id: "t-1", dashboards: [{ id: "d1" }] } as any;
    model.toggleTaskLogs("tasklogs_d1");
    expect(api.getTaskLogs).toHaveBeenCalled();
  });

  it("toggleTaskLogs does not reload if already loaded", () => {
    vi.mocked(api.getTaskLogs).mockResolvedValue([]);
    model.run = { task_id: "t-1", dashboards: [{ id: "d1", _taskLogsLoaded: true }] } as any;
    model.toggleTaskLogs("tasklogs_d1");
    expect(api.getTaskLogs).not.toHaveBeenCalled();
  });
  // #endregion

  // #region ValidationRunDetailModel.LoadScreenshot [C:2] [TYPE Test]
  it("loadScreenshot returns early when already cached", async () => {
    model.screenshotUrls["cache-key"] = "blob:existing";
    await model.loadScreenshot("path.png", "cache-key");
    expect(api.getStorageFileBlob).not.toHaveBeenCalled();
  });

  it("loadScreenshot fetches blob and creates URL", async () => {
    const blob = new Blob(["test"]);
    vi.mocked(api.getStorageFileBlob).mockResolvedValue(blob);
    const createSpy = vi.spyOn(URL, "createObjectURL").mockReturnValue("blob:url");
    await model.loadScreenshot("path.png", "cache-key");
    expect(model.screenshotUrls["cache-key"]).toBe("blob:url");
    createSpy.mockRestore();
  });

  it("loadScreenshot silently fails on error", async () => {
    vi.mocked(api.getStorageFileBlob).mockRejectedValue(new Error("Not found"));
    await model.loadScreenshot("bad.png", "cache-key");
    expect(model.screenshotUrls["cache-key"]).toBeUndefined();
  });
  // #endregion

  // #region ValidationRunDetailModel.OpenScreenshot [C:2] [TYPE Test]
  it("openScreenshot opens window", () => {
    const openSpy = vi.spyOn(window, "open").mockReturnValue(null);
    model.openScreenshot("blob:url");
    expect(openSpy).toHaveBeenCalledWith("blob:url", "_blank");
    openSpy.mockRestore();
  });
  // #endregion

  // #region ValidationRunDetailModel.ToggleDashboardScreenshotString [C:2] [TYPE Test]
  it("toggleDashboard handles screenshot path as string", () => {
    const loadSpy = vi.spyOn(model, "loadScreenshot").mockResolvedValue();
    model.run = { dashboards: [{ id: "d1", screenshots: ["raw_screenshot_path"] }] } as any;
    model.toggleDashboard("d1");
    expect(loadSpy).toHaveBeenCalledWith("raw_screenshot_path", "d1_0");
    loadSpy.mockRestore();
  });
  // #endregion

  // #region ValidationRunDetailModel.ToggleTaskLogsError [C:2] [TYPE Test]
  it("toggleTaskLogs handles API error gracefully", () => {
    vi.mocked(api.getTaskLogs).mockRejectedValue(new Error("Logs error"));
    model.run = { task_id: "t-1", dashboards: [{ id: "d1" }] } as any;
    model.toggleTaskLogs("tasklogs_d1");
    expect(model.taskLogsExpanded.has("tasklogs_d1")).toBe(true);
  });
  // #endregion
});
// #endregion

// #region ValidationRunDetailModel.DerivedState [C:2] [TYPE Function]
// @BRIEF Derived state: dashboards, pass/warn/fail/unknown counts, totalIssues.
describe("ValidationRunDetailModel — Derived State", () => {
  let model: ValidationRunDetailModel;
  beforeEach(() => { vi.clearAllMocks(); model = new ValidationRunDetailModel(); });

  it("dashboards uses dashboards field", () => {
    model.run = { dashboards: [{ id: "d1" }, { id: "d2" }] } as any;
    expect(model.dashboards).toHaveLength(2);
  });

  it("dashboards falls back to results field", () => {
    model.run = { results: [{ id: "d1" }] } as any;
    expect(model.dashboards).toHaveLength(1);
  });

  it("passCount counts PASS status", () => {
    model.run = { dashboards: [{ status: "PASS" }, { status: "PASS" }, { status: "FAIL" }] } as any;
    expect(model.passCount).toBe(2);
  });

  it("warnCount counts WARN status", () => {
    model.run = { dashboards: [{ status: "WARN" }, { status: "PASS" }] } as any;
    expect(model.warnCount).toBe(1);
  });

  it("failCount counts FAIL status", () => {
    model.run = { dashboards: [{ status: "FAIL" }, { status: "WARN" }] } as any;
    expect(model.failCount).toBe(1);
  });

  it("unknownCount counts missing/UNKNOWN status", () => {
    model.run = { dashboards: [{ status: "UNKNOWN" }, {}] } as any;
    expect(model.unknownCount).toBe(2);
  });

  it("totalIssues sums issues across dashboards", () => {
    model.run = { dashboards: [{ issues: [{ severity: "high" }, { severity: "low" }] }, { issues: [{ severity: "critical" }] }] } as any;
    expect(model.totalIssues).toBe(3);
  });

  it("totalIssues returns 0 when no issues", () => {
    model.run = { dashboards: [{}, {}] } as any;
    expect(model.totalIssues).toBe(0);
  });
});
// #endregion

// #region ValidationRunDetailModel.StaticUtils [C:2] [TYPE Function]
// @BRIEF Static utility methods: status classes, dots, duration, labels.
describe("ValidationRunDetailModel — Static Utils", () => {
  // #region ValidationRunDetailModel.GetStatusDotClass [C:2] [TYPE Test]
  it("getStatusDotClass returns correct for PASS", () => {
    expect(ValidationRunDetailModel.getStatusDotClass("PASS")).toContain("success");
  });
  it("getStatusDotClass returns correct for FAIL", () => {
    expect(ValidationRunDetailModel.getStatusDotClass("FAIL")).toContain("destructive");
  });
  it("getStatusDotClass returns correct for WARN", () => {
    expect(ValidationRunDetailModel.getStatusDotClass("WARN")).toContain("warning");
  });
  it("getStatusDotClass returns subtle for UNKNOWN", () => {
    expect(ValidationRunDetailModel.getStatusDotClass("UNKNOWN")).toContain("subtle");
  });
  it("getStatusDotClass returns subtle for undefined", () => {
    expect(ValidationRunDetailModel.getStatusDotClass(undefined)).toContain("subtle");
  });
  // #endregion

  // #region ValidationRunDetailModel.GetStatusDot [C:2] [TYPE Test]
  it("getStatusDot returns emoji for all statuses", () => {
    expect(ValidationRunDetailModel.getStatusDot("PASS")).toBe("🟢");
    expect(ValidationRunDetailModel.getStatusDot("WARN")).toBe("🟡");
    expect(ValidationRunDetailModel.getStatusDot("FAIL")).toBe("🔴");
    expect(ValidationRunDetailModel.getStatusDot("UNKNOWN")).toBe("⚪");
    expect(ValidationRunDetailModel.getStatusDot(undefined)).toBe("⚪");
  });
  // #endregion

  // #region ValidationRunDetailModel.GetSeverityClass [C:2] [TYPE Test]
  it("getSeverityClass returns correct for critical", () => {
    expect(ValidationRunDetailModel.getSeverityClass("critical")).toContain("destructive");
  });
  it("getSeverityClass returns correct for high/warning", () => {
    expect(ValidationRunDetailModel.getSeverityClass("high")).toContain("warning");
  });
  it("getSeverityClass returns correct for info", () => {
    expect(ValidationRunDetailModel.getSeverityClass("info")).toContain("muted");
  });
  it("getSeverityClass returns muted for unknown", () => {
    expect(ValidationRunDetailModel.getSeverityClass("unknown")).toContain("muted");
  });
  it("getSeverityClass is case-insensitive", () => {
    expect(ValidationRunDetailModel.getSeverityClass("ERROR")).toContain("destructive");
  });
  // #endregion

  // #region ValidationRunDetailModel.FormatDuration [C:2] [TYPE Test]
  it("formatDuration returns '-' for undefined", () => {
    expect(ValidationRunDetailModel.formatDuration(undefined)).toBe("-");
  });
  it("formatDuration returns ms for < 1000", () => {
    expect(ValidationRunDetailModel.formatDuration(500)).toBe("500ms");
  });
  it("formatDuration returns seconds for < 60000", () => {
    expect(ValidationRunDetailModel.formatDuration(2500)).toBe("2.5s");
  });
  it("formatDuration returns minutes for >= 60000", () => {
    expect(ValidationRunDetailModel.formatDuration(125000)).toContain("m");
  });
  it("formatDuration handles 0", () => {
    expect(ValidationRunDetailModel.formatDuration(0)).toBe("0ms");
  });
  // #endregion

  // #region ValidationRunDetailModel.GetTriggerLabel [C:2] [TYPE Test]
  it("getTriggerLabel returns Manual", () => {
    const tFn = vi.fn(() => "Manual");
    expect(ValidationRunDetailModel.getTriggerLabel("manual", tFn)).toBe("Manual");
  });
  it("getTriggerLabel returns Scheduled", () => {
    const tFn = vi.fn(() => "Scheduled");
    expect(ValidationRunDetailModel.getTriggerLabel("scheduled", tFn)).toBe("Scheduled");
  });
  it("getTriggerLabel returns raw value", () => {
    const tFn = vi.fn();
    expect(ValidationRunDetailModel.getTriggerLabel("api", tFn)).toBe("api");
  });
  // #endregion

  // #region ValidationRunDetailModel.GetPathLabel [C:2] [TYPE Test]
  it("getPathLabel returns Path A", () => {
    const tFn = vi.fn(() => "Path A");
    expect(ValidationRunDetailModel.getPathLabel("A", tFn)).toBe("Path A");
  });
  it("getPathLabel returns Path B", () => {
    const tFn = vi.fn(() => "Path B");
    expect(ValidationRunDetailModel.getPathLabel("B", tFn)).toBe("Path B");
  });
  it("getPathLabel returns dash for undefined", () => {
    const tFn = vi.fn();
    expect(ValidationRunDetailModel.getPathLabel(undefined, tFn)).toBe("-");
  });
  // #endregion

  // #region ValidationRunDetailModel.GetPathBadgeClass [C:2] [TYPE Test]
  it("getPathBadgeClass for A returns info", () => {
    expect(ValidationRunDetailModel.getPathBadgeClass("A")).toContain("info");
  });
  it("getPathBadgeClass for B returns success", () => {
    expect(ValidationRunDetailModel.getPathBadgeClass("B")).toContain("success");
  });
  it("getPathBadgeClass for others returns muted", () => {
    expect(ValidationRunDetailModel.getPathBadgeClass("C")).toContain("muted");
  });
  // #endregion

  // #region ValidationRunDetailModel.GetTabIssueSeverity [C:2] [TYPE Test]
  it("getTabIssueSeverity returns null when no issues", () => {
    expect(ValidationRunDetailModel.getTabIssueSeverity("Tab1", 0, 3, [])).toBeNull();
  });
  it("getTabIssueSeverity matches by tab_index", () => {
    const issues = [{ severity: "FAIL", tab_index: 0 }, { severity: "WARN", tab_index: 1 }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("Tab1", 0, 3, issues);
    expect(result?.count).toBe(1);
    expect(result?.severity).toBe("FAIL");
  });
  it("getTabIssueSeverity matches by label", () => {
    const issues = [{ severity: "WARN", location: "Tab1" }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("Tab1", 1, 3, issues);
    expect(result?.count).toBe(1);
    expect(result?.severity).toBe("WARN");
  });
  it("getTabIssueSeverity assigns unmatched issues to last tab", () => {
    const issues = [{ severity: "FAIL", tab_index: -1 }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("LastTab", 2, 3, issues);
    expect(result?.count).toBe(1);
  });
  it("getTabIssueSeverity returns null when no matching issues and not last tab", () => {
    const issues = [{ severity: "FAIL", location: "OtherTab" }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("Tab1", 0, 3, issues);
    expect(result).toBeNull();
  });
  it("getTabIssueSeverity matches by label case-insensitive", () => {
    const issues = [{ severity: "WARN", location: "tab1" }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("Tab1", 1, 3, issues);
    expect(result?.count).toBe(1);
  });
  it("getTabIssueSeverity matches by label with location containing tabLabel", () => {
    const issues = [{ severity: "INFO", location: "Tab1 section" }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("Tab1", 0, 3, issues);
    expect(result?.count).toBe(1);
  });
  it("getTabIssueSeverity handles screenshot with string path", () => {
    // When screenshots[0] is a string instead of {path: string}
    const issues = [{ severity: "FAIL", tab_index: 0 }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("Tab1", 0, 3, issues);
    expect(result?.count).toBe(1);
  });

  it("getTabIssueSeverity assigns unmatched issues to last tab with WARN worst", () => {
    const issues = [{ severity: "WARN", tab_index: -1, location: "Something" }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("MyTab", 2, 3, issues);
    expect(result?.count).toBe(1);
    expect(result?.severity).toBe("WARN");
  });

  it("getTabIssueSeverity covers worst severity from byIndex", () => {
    const issues = [{ severity: "WARN", tab_index: 0 }];
    const result = ValidationRunDetailModel.getTabIssueSeverity("Tab1", 0, 3, issues);
    expect(result?.severity).toBe("WARN");
  });
  // #endregion

  });
// #endregion
