import type {
  NetworkDrawingData,
  SensorPlacementScheme,
  SensorPointRow,
} from "./types";

export const A3_LANDSCAPE_WIDTH = 4961;
export const A3_LANDSCAPE_HEIGHT = 3508;

interface DrawingOptions {
  scheme: SensorPlacementScheme;
  rows: SensorPointRow[];
  network: string;
  networkData: NetworkDrawingData;
  dirty: boolean;
  width?: number;
}

interface ParsedNode {
  id: string;
  x: number;
  y: number;
}

type DrawingExtent = [number, number, number, number];
export const DRAWING_CONTENT_PADDING_RATIO = 0.04;

const toDrawingExtent = (extent: number[]): DrawingExtent => {
  if (
    extent.length !== 4 ||
    !extent.every(Number.isFinite) ||
    extent[0] >= extent[2] ||
    extent[1] >= extent[3]
  ) {
    throw new Error("地图范围不可用");
  }
  return [extent[0], extent[1], extent[2], extent[3]];
};

const parseNode = (value: string): ParsedNode | null => {
  const [id, , x, y] = value.split(":");
  const parsedX = Number(x);
  const parsedY = Number(y);
  if (!id || !Number.isFinite(parsedX) || !Number.isFinite(parsedY))
    return null;
  return { id, x: parsedX, y: parsedY };
};

const fitExtentToAspectRatio = (
  extent: DrawingExtent,
  targetAspectRatio: number,
): DrawingExtent => {
  const [minX, minY, maxX, maxY] = extent;
  const centerX = (minX + maxX) / 2;
  const centerY = (minY + maxY) / 2;
  let spanX = maxX - minX;
  let spanY = maxY - minY;

  if (spanX / spanY > targetAspectRatio) {
    spanY = spanX / targetAspectRatio;
  } else {
    spanX = spanY * targetAspectRatio;
  }

  return [
    centerX - spanX / 2,
    centerY - spanY / 2,
    centerX + spanX / 2,
    centerY + spanY / 2,
  ];
};

export const getPaddedDrawingExtent = (
  extent: number[],
  targetAspectRatio: number,
): DrawingExtent => {
  const fitted = fitExtentToAspectRatio(
    toDrawingExtent(extent),
    targetAspectRatio,
  );
  const [minX, minY, maxX, maxY] = fitted;
  const horizontalPadding =
    ((maxX - minX) * DRAWING_CONTENT_PADDING_RATIO) /
    (1 - DRAWING_CONTENT_PADDING_RATIO * 2);
  const verticalPadding =
    ((maxY - minY) * DRAWING_CONTENT_PADDING_RATIO) /
    (1 - DRAWING_CONTENT_PADDING_RATIO * 2);
  return [
    minX - horizontalPadding,
    minY - verticalPadding,
    maxX + horizontalPadding,
    maxY + verticalPadding,
  ];
};

const drawText = (
  context: CanvasRenderingContext2D,
  value: string,
  x: number,
  y: number,
  size: number,
  weight = 400,
  align: CanvasTextAlign = "left",
  outlineColor?: string,
  outlineWidth = 0,
) => {
  context.font = `${weight} ${size}px -apple-system, "PingFang SC", "Noto Sans SC", sans-serif`;
  context.textAlign = align;
  context.textBaseline = "middle";
  if (outlineColor && outlineWidth > 0) {
    context.strokeStyle = outlineColor;
    context.lineWidth = outlineWidth;
    context.lineJoin = "round";
    context.strokeText(value, x, y);
  }
  context.fillText(value, x, y);
};

export const renderEngineeringDrawing = async ({
  scheme,
  rows,
  network,
  networkData,
  dirty,
  width = A3_LANDSCAPE_WIDTH,
}: DrawingOptions): Promise<HTMLCanvasElement> => {
  if (!rows.length) throw new Error("方案没有可出图的监测点");
  const height = Math.round((width * A3_LANDSCAPE_HEIGHT) / A3_LANDSCAPE_WIDTH);
  const scale = width / A3_LANDSCAPE_WIDTH;
  const px = (value: number) => value * scale;
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const context = canvas.getContext("2d");
  if (!context) throw new Error("无法创建工程图画布");

  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, width, height);
  context.strokeStyle = "#111827";
  context.lineWidth = px(8);
  context.strokeRect(px(70), px(70), width - px(140), height - px(140));

  const mapBox = {
    x: px(150),
    y: px(150),
    width: width - px(300),
    height: height - px(650),
  };
  context.save();
  context.beginPath();
  context.rect(mapBox.x, mapBox.y, mapBox.width, mapBox.height);
  context.clip();

  const extent = getPaddedDrawingExtent(
    networkData.extent,
    mapBox.width / mapBox.height,
  );
  context.fillStyle = "#fbfcfd";
  context.fillRect(mapBox.x, mapBox.y, mapBox.width, mapBox.height);

  const nodes = networkData.nodes
    .map(parseNode)
    .filter((node): node is ParsedNode => Boolean(node));
  const nodesById = new globalThis.Map(
    nodes.map((node) => [node.id, node] as const),
  );
  const [minX, minY, maxX, maxY] = extent;
  const toCanvas = (x: number, y: number) => ({
    x: mapBox.x + ((x - minX) / (maxX - minX)) * mapBox.width,
    y:
      mapBox.y + mapBox.height - ((y - minY) / (maxY - minY)) * mapBox.height,
  });

  context.strokeStyle = "#45677a";
  context.lineWidth = px(5);
  for (const link of networkData.links) {
    const [, , startId, endId] = link.split(":");
    const start = nodesById.get(startId);
    const end = nodesById.get(endId);
    if (!start || !end) continue;
    if (
      Math.max(start.x, end.x) < minX ||
      Math.min(start.x, end.x) > maxX ||
      Math.max(start.y, end.y) < minY ||
      Math.min(start.y, end.y) > maxY
    ) {
      continue;
    }
    const startPoint = toCanvas(start.x, start.y);
    const endPoint = toCanvas(end.x, end.y);
    context.beginPath();
    context.moveTo(startPoint.x, startPoint.y);
    context.lineTo(endPoint.x, endPoint.y);
    context.stroke();
  }

  context.fillStyle = "#668697";
  for (const node of nodes) {
    if (node.x < minX || node.x > maxX || node.y < minY || node.y > maxY) {
      continue;
    }
    const point = toCanvas(node.x, node.y);
    context.beginPath();
    context.arc(point.x, point.y, px(4), 0, Math.PI * 2);
    context.fill();
  }

  rows.forEach((row) => {
    const point = toCanvas(row.map_x, row.map_y);
    context.fillStyle = "#ffffff";
    context.beginPath();
    context.arc(point.x, point.y, px(36), 0, Math.PI * 2);
    context.fill();

    context.strokeStyle = "rgba(255,255,255,0.96)";
    context.lineWidth = px(14);
    context.stroke();
    context.strokeStyle = "#c9252d";
    context.lineWidth = px(5);
    context.stroke();

    context.fillStyle = "#a51f28";
    drawText(
      context,
      String(row.sequence),
      point.x,
      point.y,
      px(32),
      700,
      "center",
    );

    context.fillStyle = "#273746";
    drawText(
      context,
      row.node_id,
      point.x,
      point.y + px(62),
      px(25),
      650,
      "center",
      "rgba(255,255,255,0.98)",
      px(9),
    );
  });
  context.restore();

  context.strokeStyle = "#111827";
  context.lineWidth = px(4);
  context.strokeRect(mapBox.x, mapBox.y, mapBox.width, mapBox.height);

  for (let index = 0; index <= 4; index += 1) {
    const ratio = index / 4;
    const x = mapBox.x + ratio * mapBox.width;
    const y = mapBox.y + mapBox.height - ratio * mapBox.height;
    context.fillStyle = "#334155";
    drawText(
      context,
      (minX + ratio * (maxX - minX)).toFixed(0),
      x,
      mapBox.y + mapBox.height + px(32),
      px(20),
      500,
      "center",
    );
    drawText(
      context,
      (minY + ratio * (maxY - minY)).toFixed(0),
      mapBox.x - px(18),
      y,
      px(20),
      500,
      "right",
    );
  }

  const titleTop = height - px(420);
  context.strokeStyle = "#111827";
  context.lineWidth = px(4);
  context.strokeRect(px(150), titleTop, width - px(300), px(270));
  context.beginPath();
  context.moveTo(width - px(1700), titleTop);
  context.lineTo(width - px(1700), titleTop + px(270));
  context.stroke();

  context.fillStyle = "#111827";
  drawText(
    context,
    `${scheme.scheme_name} 压力监测点布置图`,
    px(220),
    titleTop + px(70),
    px(48),
    700,
  );
  drawText(
    context,
    `项目：${network}    监测点：${rows.length} 个    地图：EPSG:3857    经纬度：WGS84`,
    px(220),
    titleTop + px(145),
    px(26),
    500,
  );
  drawText(
    context,
    dirty ? "状态：未保存草稿" : "状态：当前方案",
    px(220),
    titleTop + px(215),
    px(26),
    dirty ? 700 : 500,
  );

  const infoX = width - px(1620);
  drawText(
    context,
    `创建人：${scheme.username}`,
    infoX,
    titleTop + px(55),
    px(24),
    500,
  );
  drawText(
    context,
    `创建时间：${new Date(scheme.create_time).toLocaleString("zh-CN")}`,
    infoX,
    titleTop + px(115),
    px(24),
    500,
  );
  drawText(
    context,
    `制图时间：${new Date().toLocaleString("zh-CN")}`,
    infoX,
    titleTop + px(175),
    px(24),
    500,
  );
  drawText(
    context,
    "图幅：A3 横向 300 DPI",
    infoX,
    titleTop + px(235),
    px(24),
    500,
  );

  context.strokeStyle = "#111827";
  context.lineWidth = px(5);
  const northX = width - px(260);
  const northY = px(270);
  context.beginPath();
  context.moveTo(northX, northY - px(70));
  context.lineTo(northX - px(32), northY + px(35));
  context.lineTo(northX, northY + px(15));
  context.lineTo(northX + px(32), northY + px(35));
  context.closePath();
  context.stroke();
  context.fillStyle = "#111827";
  drawText(context, "N", northX, northY - px(110), px(30), 700, "center");

  return canvas;
};

export const canvasToPngBlob = (canvas: HTMLCanvasElement) =>
  new Promise<Blob>((resolve, reject) => {
    canvas.toBlob((blob) => {
      if (blob) resolve(blob);
      else reject(new Error("PNG 生成失败"));
    }, "image/png");
  });
