jest.mock("@components/olmap/core/tileFeatureIndex", () => ({
  TileFeatureIndex: jest.fn(),
  clipLineStringPartsToExtent: jest.fn(),
  lineStringFromFlatCoordinates: jest.fn(),
}));

jest.mock("ol/View", () => ({
  __esModule: true,
  default: jest.fn().mockImplementation(() => ({
    cancelAnimations: jest.fn(),
    fit: jest.fn(),
    getAnimating: () => false,
    getProjection: () => "EPSG:3857",
    getZoom: () => 12,
  })),
}));

jest.mock("ol/layer/VectorTile", () => ({
  __esModule: true,
  default: jest.fn().mockImplementation(({ source }) => {
    let extent: number[] | undefined;
    const properties = new Map<string, unknown>();
    return {
      get: (key: string) => properties.get(key),
      getExtent: () => extent,
      getSource: () => source,
      set: (key: string, value: unknown) => properties.set(key, value),
      setExtent: (value: number[]) => {
        extent = value;
      },
    };
  }),
}));

jest.mock("ol/layer/Group", () => ({
  __esModule: true,
  default: jest.fn(),
}));

jest.mock("ol/layer/Tile", () => ({
  __esModule: true,
  default: jest.fn().mockImplementation(({ source }) => ({
    getOpacity: () => 1,
    getSource: () => source,
  })),
}));

jest.mock("ol/Map", () => ({
  __esModule: true,
  default: jest.fn().mockImplementation(({ layers, view }) => {
    let size: number[] | undefined;
    let renderComplete: (() => void) | undefined;
    return {
      getAllLayers: () => layers,
      getLayers: () => ({ clear: jest.fn() }),
      getSize: () => size,
      getView: () => view,
      once: (event: string, callback: () => void) => {
        if (event === "rendercomplete") renderComplete = callback;
      },
      render: () => renderComplete?.(),
      setSize: (value: number[]) => {
        size = value;
      },
      setTarget: jest.fn(),
      un: jest.fn(),
    };
  }),
}));

import { render, screen, waitFor } from "@testing-library/react";
import { createElement } from "react";
import {
  getPaddedDrawingExtent,
  renderEngineeringDrawing,
} from "./engineeringDrawing";
import SchemeDrawingDialog, {
  DrawingPreview,
  fitMapToFullNetwork,
} from "./SchemeDrawingDialog";
import type { SensorPlacementScheme, SensorPointRow } from "./types";

const createContext = () => ({
  arc: jest.fn(),
  beginPath: jest.fn(),
  clip: jest.fn(),
  closePath: jest.fn(),
  drawImage: jest.fn(),
  fill: jest.fn(),
  fillRect: jest.fn(),
  fillText: jest.fn(),
  lineTo: jest.fn(),
  moveTo: jest.fn(),
  rect: jest.fn(),
  restore: jest.fn(),
  save: jest.fn(),
  setTransform: jest.fn(),
  stroke: jest.fn(),
  strokeRect: jest.fn(),
  strokeText: jest.fn(),
});

const point = (
  node_id: string,
  sequence: number,
  map_x: number,
  map_y: number,
): SensorPointRow => ({
  node_id,
  sequence,
  map_x,
  map_y,
  project_x: map_x,
  project_y: map_y,
  longitude: 121,
  latitude: 31,
  elevation: 5,
  adjustment_status: "current",
});

const rows = [point("A", 1, 400, 200), point("B", 2, 500, 300)];

const scheme: SensorPlacementScheme = {
  id: 1,
  scheme_name: "测试方案",
  sensor_number: rows.length,
  min_diameter: 300,
  username: "tester",
  create_time: "2026-07-30T08:00:00+08:00",
  sensor_location: rows.map((row) => row.node_id),
  sensor_points: rows,
  can_edit: true,
};

describe("engineering drawing map framing", () => {
  it("does not change the visible main-map view while opening the drawing", async () => {
    const cancelAnimations = jest.fn();
    const fit = jest.fn();
    const setCenter = jest.fn();
    const setResolution = jest.fn();
    const setRotation = jest.fn();
    const getAllLayers = jest.fn(() => [
      {
        get: (key: string) => (key === "value" ? "pipes" : undefined),
        getExtent: () => [0, 0, 1000, 1000],
        getSource: () => ({}),
      },
    ]);
    let renderComplete: (() => void) | undefined;
    const map = {
      getAllLayers,
      getSize: () => [1200, 800],
      getView: () => ({
        cancelAnimations,
        fit,
        getAnimating: () => false,
        getCenter: () => [500, 500],
        getResolution: () => 10,
        getRotation: () => 0,
        getProjection: () => "EPSG:3857",
        getZoom: () => 12,
        setCenter,
        setResolution,
        setRotation,
      }),
      once: (event: string, callback: () => void) => {
        if (event === "rendercomplete") renderComplete = callback;
      },
      un: jest.fn(),
      render: jest.fn(() => renderComplete?.()),
    };

    render(
      createElement(SchemeDrawingDialog, {
        open: true,
        onClose: jest.fn(),
        scheme,
        rows,
        network: "test",
        map: map as never,
        dirty: false,
      }),
    );

    await waitFor(() => expect(getAllLayers).toHaveBeenCalled());
    expect(screen.getByText(/简化管网线稿/)).toBeInTheDocument();
    expect(screen.queryByText("地图底图 + 管网")).not.toBeInTheDocument();
    expect(screen.queryByTestId("HubOutlinedIcon")).not.toBeInTheDocument();
    expect(screen.getByTestId("drawing-content-spacing")).toHaveStyle({
      paddingTop: "12px",
    });
    expect(screen.getByTestId("drawing-format-summary")).toHaveStyle({
      marginBottom: "12px",
    });
    expect(fit).not.toHaveBeenCalled();
    expect(setCenter).not.toHaveBeenCalled();
    expect(setResolution).not.toHaveBeenCalled();
    expect(setRotation).not.toHaveBeenCalled();
  });

  it("does not use a native image fallback for the generated preview", () => {
    render(
      createElement(DrawingPreview, {
        src: "blob:engineering-drawing-preview",
        label: "测试方案压力监测点布置图预览",
      }),
    );

    const preview = screen.getByRole("img", {
      name: "测试方案压力监测点布置图预览",
    });
    expect(preview.tagName).toBe("DIV");
    expect(preview).not.toHaveAttribute("src");
  });

  it("fits the OpenLayers map to the complete pipe layer extent", () => {
    const extent = [13500000, 3600000, 13600000, 3700000];
    const cancelAnimations = jest.fn();
    const fit = jest.fn();
    const map = {
      getAllLayers: () => [
        {
          get: (key: string) => (key === "value" ? "pipes" : undefined),
          getExtent: () => extent,
        },
      ],
      getSize: () => [1200, 800],
      getView: () => ({ cancelAnimations, fit }),
    };

    expect(fitMapToFullNetwork(map as never)).toEqual(extent);
    expect(cancelAnimations).toHaveBeenCalledTimes(1);
    expect(fit).toHaveBeenCalledWith(
      getPaddedDrawingExtent(extent, 1200 / 800),
      {
      padding: [0, 0, 0, 0],
      size: [1200, 800],
      },
    );
    expect(cancelAnimations.mock.invocationCallOrder[0]).toBeLessThan(
      fit.mock.invocationCallOrder[0],
    );
  });
});

describe("engineering drawing", () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });

  it("draws the complete network even when sensors occupy a local area", async () => {
    const context = createContext();
    jest
      .spyOn(HTMLCanvasElement.prototype, "getContext")
      .mockReturnValue(context as unknown as CanvasRenderingContext2D);

    await renderEngineeringDrawing({
      scheme,
      rows,
      network: "test",
      networkData: {
        nodes: ["A:junction:8000:4000", "B:junction:9000:4500"],
        links: ["P1:pipe:A:B"],
        extent: [0, 0, 10000, 5000],
      },
      dirty: false,
      width: 1600,
    });

    // The first path is the remote pipe. A sensor-derived extent would skip it,
    // leaving the title-block divider as the first path near the sheet bottom.
    const [pipeStartX, pipeStartY] = context.moveTo.mock.calls[0];
    const [pipeEndX, pipeEndY] = context.lineTo.mock.calls[0];
    expect(pipeStartX).toBeGreaterThan(1200);
    expect(pipeStartY).toBeLessThan(400);
    expect(pipeEndX).toBeGreaterThan(pipeStartX);
    expect(pipeEndY).toBeLessThan(pipeStartY);
  });

  it("uses one scale for linework coordinates", async () => {
    const context = createContext();
    jest
      .spyOn(HTMLCanvasElement.prototype, "getContext")
      .mockReturnValue(context as unknown as CanvasRenderingContext2D);

    await renderEngineeringDrawing({
      scheme,
      rows,
      network: "test",
      networkData: {
        nodes: ["A:junction:400:200", "B:junction:500:300"],
        links: ["P1:pipe:A:B"],
        extent: [0, 0, 1000, 1000],
      },
      dirty: false,
      width: 1600,
    });

    const [startX, startY] = context.moveTo.mock.calls[0];
    const [endX, endY] = context.lineTo.mock.calls[0];
    expect(Math.abs(endX - startX)).toBeCloseTo(Math.abs(endY - startY), 6);
    expect(context.drawImage).not.toHaveBeenCalled();
  });

  it("keeps visible space on the left and right of the complete network", async () => {
    const context = createContext();
    jest
      .spyOn(HTMLCanvasElement.prototype, "getContext")
      .mockReturnValue(context as unknown as CanvasRenderingContext2D);

    await renderEngineeringDrawing({
      scheme,
      rows,
      network: "test",
      networkData: {
        nodes: ["A:junction:0:0", "B:junction:1631:1000"],
        links: ["P1:pipe:A:B"],
        extent: [0, 0, 1631, 1000],
      },
      dirty: false,
      width: 1600,
    });

    const [startX] = context.moveTo.mock.calls[0];
    const [endX] = context.lineTo.mock.calls[0];
    expect(startX).toBeGreaterThan(100);
    expect(endX).toBeLessThan(1500);
  });

});
