// @vitest-environment jsdom
/**
 * Expanded store tests covering uncovered branches:
 * - QR store: gradient settings, corner colors, custom dot types
 * - Split store: edge cases for effective grid, computed dimensions
 * - Pipeline store: reorder edge cases
 * - PDF store: compressPageRange edge cases
 * - Duplicate store: view mode transitions
 * - Base64 store: concurrent adds
 */
import { beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("zustand/middleware", async (importOriginal) => {
  const actual: Record<string, unknown> = await importOriginal();
  return {
    ...actual,
    persist: (config: unknown) => config,
  };
});

const revokeObjectURL = vi.fn();
const createObjectURL = vi.fn((_obj: Blob | MediaSource) => "blob:fake-url");

vi.stubGlobal("URL", {
  ...globalThis.URL,
  createObjectURL,
  revokeObjectURL,
});

vi.stubGlobal("fetch", vi.fn());

vi.stubGlobal("localStorage", {
  getItem: vi.fn(() => null),
  setItem: vi.fn(),
  removeItem: vi.fn(),
  clear: vi.fn(),
  get length() {
    return 0;
  },
  key: vi.fn((_i: number) => null),
});

vi.stubGlobal(
  "matchMedia",
  vi.fn().mockReturnValue({
    matches: false,
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
  }),
);

vi.mock("@/lib/api", () => ({
  apiGet: vi.fn(),
  apiPost: vi.fn(),
  apiPut: vi.fn(),
  apiDelete: vi.fn(),
  formatHeaders: vi.fn(() => new Headers()),
}));

vi.mock("@/lib/collage-templates", () => ({
  COLLAGE_TEMPLATES: [
    {
      id: "2-h-equal",
      imageCount: 2,
      cells: [
        { gridColumn: "1", gridRow: "1" },
        { gridColumn: "2", gridRow: "1" },
      ],
    },
  ],
  getDefaultTemplate: () => ({
    id: "2-h-equal",
    imageCount: 2,
    cells: [
      { gridColumn: "1", gridRow: "1" },
      { gridColumn: "2", gridRow: "1" },
    ],
  }),
}));

vi.mock("@/lib/image-preview", () => ({
  needsServerPreview: vi.fn(() => false),
  fetchDecodedPreview: vi.fn(() => Promise.resolve(null)),
  revokePreviewUrl: vi.fn(),
}));

// ---------------------------------------------------------------------------
// QR Store expanded
// ---------------------------------------------------------------------------
import { encodeQrData, useQrStore } from "@/stores/qr-store";

describe("useQrStore expanded", () => {
  beforeEach(() => {
    useQrStore.getState().reset();
  });

  it("setDotGradientEnabled toggles gradient", () => {
    useQrStore.getState().setDotGradientEnabled(true);
    expect(useQrStore.getState().dotGradientEnabled).toBe(true);
  });

  it("setDotGradientType changes gradient type", () => {
    useQrStore.getState().setDotGradientType("radial");
    expect(useQrStore.getState().dotGradientType).toBe("radial");
  });

  it("setDotGradientColor1 updates first gradient color", () => {
    useQrStore.getState().setDotGradientColor1("#FF0000");
    expect(useQrStore.getState().dotGradientColor1).toBe("#FF0000");
  });

  it("setDotGradientColor2 updates second gradient color", () => {
    useQrStore.getState().setDotGradientColor2("#0000FF");
    expect(useQrStore.getState().dotGradientColor2).toBe("#0000FF");
  });

  it("setDotGradientRotation updates rotation", () => {
    useQrStore.getState().setDotGradientRotation(45);
    expect(useQrStore.getState().dotGradientRotation).toBe(45);
  });

  it("setCornerSquareColor updates corner square color", () => {
    useQrStore.getState().setCornerSquareColor("#00FF00");
    expect(useQrStore.getState().cornerSquareColor).toBe("#00FF00");
  });

  it("setCornerDotColor updates corner dot color", () => {
    useQrStore.getState().setCornerDotColor("#FFFF00");
    expect(useQrStore.getState().cornerDotColor).toBe("#FFFF00");
  });

  it("setUseCustomCornerColors toggles custom corner colors", () => {
    useQrStore.getState().setUseCustomCornerColors(true);
    expect(useQrStore.getState().useCustomCornerColors).toBe(true);
  });

  it("setDotType changes dot type", () => {
    useQrStore.getState().setDotType("dots");
    expect(useQrStore.getState().dotType).toBe("dots");
  });

  it("setCornerSquareType changes corner square type", () => {
    useQrStore.getState().setCornerSquareType("dot");
    expect(useQrStore.getState().cornerSquareType).toBe("dot");
  });

  it("setCornerDotType changes corner dot type", () => {
    useQrStore.getState().setCornerDotType("square");
    expect(useQrStore.getState().cornerDotType).toBe("square");
  });

  it("setLogoSize updates logo size", () => {
    useQrStore.getState().setLogoSize(0.3);
    expect(useQrStore.getState().logoSize).toBe(0.3);
  });

  it("setLogoMargin updates logo margin", () => {
    useQrStore.getState().setLogoMargin(10);
    expect(useQrStore.getState().logoMargin).toBe(10);
  });

  it("setHideBackgroundDots toggles background dots visibility", () => {
    useQrStore.getState().setHideBackgroundDots(false);
    expect(useQrStore.getState().hideBackgroundDots).toBe(false);
  });

  it("setPhoneData updates phone data", () => {
    useQrStore.getState().setPhoneData("+15551234567");
    expect(useQrStore.getState().phoneData).toBe("+15551234567");
  });

  it("setBgColor updates background color", () => {
    useQrStore.getState().setBgColor("#CCCCCC");
    expect(useQrStore.getState().bgColor).toBe("#CCCCCC");
  });
});

describe("encodeQrData expanded", () => {
  it("encodes wifi with WEP encryption", () => {
    const state = {
      ...useQrStore.getState(),
      contentType: "wifi" as const,
      wifiData: { ssid: "Net", password: "key", encryption: "WEP" as const, hidden: true },
    };
    const result = encodeQrData(state);
    expect(result).toBe("WIFI:T:WEP;S:Net;P:key;H:true;;");
  });

  it("encodes wifi with no password (nopass)", () => {
    const state = {
      ...useQrStore.getState(),
      contentType: "wifi" as const,
      wifiData: { ssid: "Open", password: "", encryption: "nopass" as const, hidden: false },
    };
    expect(encodeQrData(state)).toBe("WIFI:T:nopass;S:Open;P:;H:false;;");
  });

  it("encodes email with only subject (no body)", () => {
    const state = {
      ...useQrStore.getState(),
      contentType: "email" as const,
      emailData: { to: "a@b.com", subject: "Test", body: "" },
    };
    expect(encodeQrData(state)).toBe("mailto:a@b.com?subject=Test");
  });

  it("encodes email with only body (no subject)", () => {
    const state = {
      ...useQrStore.getState(),
      contentType: "email" as const,
      emailData: { to: "a@b.com", subject: "", body: "Hello" },
    };
    expect(encodeQrData(state)).toBe("mailto:a@b.com?body=Hello");
  });

  it("encodes vcard with only first name", () => {
    const state = {
      ...useQrStore.getState(),
      contentType: "vcard" as const,
      vcardData: {
        firstName: "Alice",
        lastName: "",
        phone: "",
        email: "",
        organization: "",
        title: "",
        url: "",
      },
    };
    const result = encodeQrData(state);
    expect(result).toContain("N:;Alice");
    expect(result).toContain("FN:Alice");
    expect(result).not.toContain("TEL:");
  });

  it("encodes sms with empty message", () => {
    const state = {
      ...useQrStore.getState(),
      contentType: "sms" as const,
      smsData: { phone: "+1234", message: "" },
    };
    expect(encodeQrData(state)).toBe("smsto:+1234:");
  });

  it("encodes empty text data", () => {
    const state = {
      ...useQrStore.getState(),
      contentType: "text" as const,
      textData: "",
    };
    expect(encodeQrData(state)).toBe("");
  });
});

// ---------------------------------------------------------------------------
// Split Store expanded
// ---------------------------------------------------------------------------
import { useSplitStore } from "@/stores/split-store";

describe("useSplitStore expanded", () => {
  beforeEach(() => {
    revokeObjectURL.mockClear();
    useSplitStore.setState({
      mode: "grid",
      columns: 3,
      rows: 3,
      tileWidth: 200,
      tileHeight: 200,
      outputFormat: "original",
      quality: 90,
      imageDimensions: null,
      processing: false,
      error: null,
      tiles: [],
      zipBlobUrl: null,
    });
  });

  it("setOutputFormat updates format", () => {
    useSplitStore.getState().setOutputFormat("webp");
    expect(useSplitStore.getState().outputFormat).toBe("webp");
  });

  it("setQuality updates quality", () => {
    useSplitStore.getState().setQuality(50);
    expect(useSplitStore.getState().quality).toBe(50);
  });

  it("setProcessing updates processing flag", () => {
    useSplitStore.getState().setProcessing(true);
    expect(useSplitStore.getState().processing).toBe(true);
  });

  it("setError updates error", () => {
    useSplitStore.getState().setError("Something broke");
    expect(useSplitStore.getState().error).toBe("Something broke");
  });

  it("setImageDimensions updates dimensions", () => {
    useSplitStore.getState().setImageDimensions({ width: 1920, height: 1080 });
    expect(useSplitStore.getState().imageDimensions).toEqual({ width: 1920, height: 1080 });
  });

  it("setImageDimensions with null clears dimensions", () => {
    useSplitStore.getState().setImageDimensions({ width: 100, height: 100 });
    useSplitStore.getState().setImageDimensions(null);
    expect(useSplitStore.getState().imageDimensions).toBeNull();
  });

  it("getEffectiveGrid returns columns/rows in tile-size mode without image dimensions", () => {
    useSplitStore.getState().setMode("tile-size");
    // No image dimensions set, so should fall back to grid mode values
    const grid = useSplitStore.getState().getEffectiveGrid();
    expect(grid).toEqual({ columns: 3, rows: 3 });
  });

  it("getEffectiveGrid with tile-size computes ceil for non-exact divisions", () => {
    useSplitStore.getState().setMode("tile-size");
    useSplitStore.getState().setTileWidth(150);
    useSplitStore.getState().setTileHeight(200);
    useSplitStore.getState().setImageDimensions({ width: 400, height: 500 });
    const grid = useSplitStore.getState().getEffectiveGrid();
    // 400/150 = 2.67 -> ceil = 3, 500/200 = 2.5 -> ceil = 3
    expect(grid).toEqual({ columns: 3, rows: 3 });
  });

  it("getComputedTileDimensions in grid mode floors division", () => {
    useSplitStore.getState().setImageDimensions({ width: 1000, height: 700 });
    useSplitStore.getState().setColumns(3);
    useSplitStore.getState().setRows(3);
    const dims = useSplitStore.getState().getComputedTileDimensions();
    // 1000/3 = 333.33 -> floor = 333, 700/3 = 233.33 -> floor = 233
    expect(dims).toEqual({ width: 333, height: 233 });
  });

  it("setZipBlobUrl with null does not call revokeObjectURL when previous was null", () => {
    revokeObjectURL.mockClear();
    useSplitStore.getState().setZipBlobUrl(null);
    expect(revokeObjectURL).not.toHaveBeenCalled();
  });

  it("setColumns clears tiles on update", () => {
    useSplitStore
      .getState()
      .setTiles([{ row: 0, col: 0, label: "1", width: 100, height: 100, blobUrl: null }]);
    useSplitStore.getState().setColumns(4);
    expect(useSplitStore.getState().tiles).toEqual([]);
  });

  it("setRows clears tiles on update", () => {
    useSplitStore
      .getState()
      .setTiles([{ row: 0, col: 0, label: "1", width: 100, height: 100, blobUrl: null }]);
    useSplitStore.getState().setRows(5);
    expect(useSplitStore.getState().tiles).toEqual([]);
  });

  it("setTileWidth clears tiles on update", () => {
    useSplitStore
      .getState()
      .setTiles([{ row: 0, col: 0, label: "1", width: 100, height: 100, blobUrl: null }]);
    useSplitStore.getState().setTileWidth(300);
    expect(useSplitStore.getState().tiles).toEqual([]);
  });

  it("setTileHeight clears tiles on update", () => {
    useSplitStore
      .getState()
      .setTiles([{ row: 0, col: 0, label: "1", width: 100, height: 100, blobUrl: null }]);
    useSplitStore.getState().setTileHeight(400);
    expect(useSplitStore.getState().tiles).toEqual([]);
  });
});

// ---------------------------------------------------------------------------
// Pipeline Store expanded
// ---------------------------------------------------------------------------
import { usePipelineStore } from "@/stores/pipeline-store";

describe("usePipelineStore expanded", () => {
  beforeEach(() => {
    usePipelineStore.getState().reset();
  });

  it("reorderSteps with same ID is effectively a no-op", () => {
    usePipelineStore.getState().addStep("resize");
    const id = usePipelineStore.getState().steps[0].id;
    usePipelineStore.getState().reorderSteps(id, id);
    // Step should still be there
    expect(usePipelineStore.getState().steps).toHaveLength(1);
    expect(usePipelineStore.getState().steps[0].id).toBe(id);
  });

  it("reorderSteps with one invalid ID is a no-op", () => {
    usePipelineStore.getState().addStep("resize");
    usePipelineStore.getState().addStep("compress");
    const realId = usePipelineStore.getState().steps[0].id;
    usePipelineStore.getState().reorderSteps(realId, "nonexistent");
    // Order unchanged
    expect(usePipelineStore.getState().steps[0].toolId).toBe("resize");
  });

  it("updateStepSettings for nonexistent ID does not crash", () => {
    usePipelineStore.getState().addStep("resize");
    usePipelineStore.getState().updateStepSettings("nonexistent", { foo: "bar" });
    // Step should be unchanged
    expect(usePipelineStore.getState().steps[0].settings).toEqual({});
  });

  it("removeStep with nonexistent ID is a no-op", () => {
    usePipelineStore.getState().addStep("resize");
    usePipelineStore.getState().removeStep("nonexistent");
    expect(usePipelineStore.getState().steps).toHaveLength(1);
  });

  it("loadSteps with empty array clears all steps", () => {
    usePipelineStore.getState().addStep("resize");
    usePipelineStore.getState().addStep("compress");
    usePipelineStore.getState().loadSteps([]);
    expect(usePipelineStore.getState().steps).toEqual([]);
    expect(usePipelineStore.getState().expandedStepId).toBeNull();
  });

  it("loadSteps creates deep copies of settings", () => {
    const originalSettings = { quality: 80 };
    usePipelineStore.getState().loadSteps([{ toolId: "compress", settings: originalSettings }]);
    // Modifying original should not affect store
    originalSettings.quality = 50;
    expect(usePipelineStore.getState().steps[0].settings).toEqual({ quality: 80 });
  });
});

// ---------------------------------------------------------------------------
// Duplicate Store expanded
// ---------------------------------------------------------------------------
import { useDuplicateStore } from "@/stores/duplicate-store";

describe("useDuplicateStore expanded", () => {
  beforeEach(() => {
    useDuplicateStore.getState().reset();
  });

  it("overrideBest then reset clears all overrides", () => {
    useDuplicateStore.getState().overrideBest(0, 2);
    useDuplicateStore.getState().overrideBest(1, 3);
    useDuplicateStore.getState().reset();
    expect(useDuplicateStore.getState().bestOverrides).toEqual({});
  });

  it("setViewMode to overview preserves selectedGroupIndex", () => {
    useDuplicateStore.getState().setSelectedGroup(5);
    useDuplicateStore.getState().setViewMode("overview");
    expect(useDuplicateStore.getState().viewMode).toBe("overview");
    expect(useDuplicateStore.getState().selectedGroupIndex).toBe(5);
  });

  it("setScanning to false preserves other state", () => {
    useDuplicateStore.getState().setScanning(true);
    useDuplicateStore.getState().setViewMode("detail");
    useDuplicateStore.getState().setScanning(false);
    expect(useDuplicateStore.getState().scanning).toBe(false);
    expect(useDuplicateStore.getState().viewMode).toBe("detail");
  });
});

// ---------------------------------------------------------------------------
// Base64 Store expanded
// ---------------------------------------------------------------------------
import type { Base64Result } from "@/stores/base64-store";
import { useBase64Store } from "@/stores/base64-store";

describe("useBase64Store expanded", () => {
  beforeEach(() => {
    useBase64Store.getState().reset();
  });

  it("addResult followed by addError does not clear results", () => {
    const r: Base64Result = {
      filename: "ok.png",
      mimeType: "image/png",
      width: 10,
      height: 10,
      originalSize: 100,
      encodedSize: 133,
      overheadPercent: 33,
      base64: "x",
      dataUri: "data:image/png;base64,x",
    };
    useBase64Store.getState().addResult(r);
    useBase64Store.getState().addError({ filename: "bad.png", error: "fail" });
    expect(useBase64Store.getState().results).toHaveLength(1);
    expect(useBase64Store.getState().errors).toHaveLength(1);
  });

  it("setResults replaces previous results entirely", () => {
    const r1: Base64Result = {
      filename: "a.png",
      mimeType: "image/png",
      width: 1,
      height: 1,
      originalSize: 10,
      encodedSize: 13,
      overheadPercent: 30,
      base64: "a",
      dataUri: "data:image/png;base64,a",
    };
    useBase64Store.getState().addResult(r1);
    useBase64Store.getState().addResult({ ...r1, filename: "b.png" });

    const newResults: Base64Result[] = [{ ...r1, filename: "c.png" }];
    useBase64Store.getState().setResults(newResults, []);
    expect(useBase64Store.getState().results).toHaveLength(1);
    expect(useBase64Store.getState().results[0].filename).toBe("c.png");
  });

  it("setProgress with valid progress then null clears progress", () => {
    useBase64Store.getState().setProgress({ completed: 1, total: 3, currentFile: "a.png" });
    expect(useBase64Store.getState().progress).not.toBeNull();
    useBase64Store.getState().setProgress(null);
    expect(useBase64Store.getState().progress).toBeNull();
  });
});
