import gm from 'gm';
import axios from 'axios';
import sizeOf from 'image-size';
import path from 'node:path';
import fs from 'node:fs';
import PDFDocument from 'pdfkit';
import log from 'electron-log/renderer';
import { createCanvas, loadImage, GlobalFonts } from '@napi-rs/canvas';

import {
  getImagicPath,
  getTempPath,
  makeDirSync,
  getGmFontPath,
  getConfigData,
} from './utils';

import {
  DrawTrackItem,
  DrawTrackCircleOption,
  DrawTrackLineOption,
  DrawTrackTextOption,
} from './types';

GlobalFonts.registerFromPath(getGmFontPath(), 'song');

// macos install gm imagemagick https://github.com/aheckmann/gm/blob/master/README.md
const gmInst =
  process.platform === 'win32'
    ? gm.subClass({
        imageMagick: true,
        appPath: getImagicPath(),
      })
    : gm.subClass({ imageMagick: '7+' });

function cropImage(imgPath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const outpath = path.join(getTempPath(), '001.png');
    gmInst(imgPath)
      .crop(500, 200, 0, 0)
      .write(outpath, (err) => {
        if (!err) {
          return resolve(outpath);
        }
        return reject(err);
      });
  });
}

function writeData(content: string) {
  const outpath = path.join(getTempPath(), 'task.txt');
  fs.writeFileSync(outpath, content, { flag: 'a' });
}

async function drawCanvasTrack(
  imgPath: string,
  drawTrackList: DrawTrackItem[],
  outpath: string
) {
  makeDirSync(path.dirname(outpath));

  const imgBuf = await fs.promises.readFile(imgPath);
  const curImage = await loadImage(imgBuf).catch((e) => {
    console.dir(e);
  });
  if (!curImage) return;
  const canvas = createCanvas(curImage.width, curImage.height);
  const ctx = canvas.getContext('2d');

  const defaultColor = '#f53f3f';
  const defaultFontSize = 22;
  ctx.drawImage(curImage, 0, 0, curImage.width, curImage.height);

  drawTrackList.forEach((track) => {
    // text
    if (track.type === 'text') {
      const { x, y, text, color, fontSize } =
        track.option as DrawTrackTextOption;
      const fsize = fontSize || defaultFontSize;
      const textColor = color || defaultColor;
      const ny = y + fsize;
      ctx.font = `bold ${fsize}px song`;
      ctx.fillStyle = textColor;
      ctx.fillText(text, x, ny);
      return;
    }
    // circle
    if (track.type === 'circle') {
      const { x0, y0, x1, y1 } = track.option as DrawTrackCircleOption;
      const radiusX = (x1 - x0) / 2;
      const radiusY = (y1 - y0) / 2;
      const centerX = x0 + radiusX;
      const centerY = y0 + radiusY;
      ctx.beginPath();
      ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI);
      ctx.strokeStyle = defaultColor;
      ctx.lineWidth = 2;
      ctx.stroke();
      return;
    }

    // line
    if (track.type === 'line') {
      const { x0, y0, x1, y1 } = track.option as DrawTrackLineOption;
      ctx.beginPath();
      ctx.moveTo(x0, y0);
      ctx.lineTo(x1, y1);
      ctx.strokeStyle = defaultColor;
      ctx.lineWidth = 2;
      ctx.stroke();
    }
  });
  const imgBuffer = await canvas.encode('jpeg');
  await fs.promises.writeFile(outpath, imgBuffer);
}

function drawGmTrack(
  imgPath: string,
  drawTrackList: DrawTrackItem[],
  outpath: string
): Promise<string> {
  return new Promise((resolve, reject) => {
    const bname = path.basename(outpath);
    log.info(`开始绘制:${bname}`);
    const gmObj = gmInst(imgPath);

    makeDirSync(path.dirname(outpath));

    const defaultColor = '#f53f3f';
    const defaultFontSize = 22;
    gmObj.font(getGmFontPath());

    drawTrackList.forEach((track) => {
      // text
      if (track.type === 'text') {
        const { x, y, text, color, fontSize } =
          track.option as DrawTrackTextOption;
        const fsize = fontSize || defaultFontSize;
        const textColor = color || defaultColor;
        const ny = y + fsize;
        gmObj
          .stroke(textColor, 1)
          .fill(textColor)
          .fontSize(fsize)
          .drawText(x, ny, text);
        return;
      }
      // circle
      if (track.type === 'circle') {
        const { x0, y0, x1, y1 } = track.option as DrawTrackCircleOption;
        const rx = (x1 - x0) / 2;
        const ry = (y1 - y0) / 2;
        const cx = x0 + rx;
        const cy = y0 + ry;
        gmObj
          .stroke(defaultColor, 2)
          .fill('none')
          .drawEllipse(cx, cy, rx, ry, 0, 360);
        return;
      }

      // line
      if (track.type === 'line') {
        const { x0, y0, x1, y1 } = track.option as DrawTrackLineOption;
        gmObj.stroke(defaultColor, 2).fill('none').drawLine(x0, y0, x1, y1);
      }
    });

    gmObj.write(outpath, (err) => {
      log.info(`绘制完成:${bname}`);

      if (!err) {
        return resolve(outpath);
      }
      return reject(err);
    });
  });
}

interface TaskItem {
  url: string;
  outpath: string;
  drawTrackList: DrawTrackItem[];
}
async function drawTracks(taskList: TaskItem[]) {
  const downloads = taskList.map((item) =>
    drawCanvasTrack(item.url, item.drawTrackList, item.outpath)
  );
  await Promise.all(downloads);
}

async function downloadFile(url: string, outputPath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    axios({
      url,
      method: 'GET',
      responseType: 'arraybuffer',
    })
      .then((response) => {
        fs.writeFileSync(outputPath, Buffer.from(response.data, 'binary'));
        resolve(outputPath);
      })
      .catch(() => {
        reject();
      });
  });
}

async function downloadImage(url: string, outputPath: string) {
  makeDirSync(path.dirname(outputPath));
  await downloadFile(url, outputPath);
  const size = sizeOf(outputPath);

  return {
    url: outputPath,
    width: size.width || 100,
    height: size.height || 100,
  };
}

function joinPath(paths: string[]) {
  return path.join(...paths);
}

function clearFilesSync(files: string[]) {
  files.forEach((item) => {
    if (fs.existsSync(item)) fs.unlinkSync(item);
  });
}

interface ImageItem {
  url: string;
  width: number;
  height: number;
}
async function imagesToPdf(
  images: ImageItem[],
  outpath: string
): Promise<string> {
  return new Promise((resolve, reject) => {
    const doc = new PDFDocument({ autoFirstPage: false });

    makeDirSync(path.dirname(outpath));
    const steam = fs.createWriteStream(outpath);
    doc.pipe(steam);

    images.forEach((image) => {
      const { url, width, height } = image;
      doc.addPage({ size: [width, height] });
      doc.image(url, 0, 0, { width, height });
    });

    doc.end();

    steam.on('finish', () => {
      resolve(outpath);
    });
    steam.on('error', (err) => {
      reject(err);
    });
  });
}

function logger(content: string, type?: 'info' | 'error') {
  if (type === 'error') {
    log.error(content);
  } else {
    log.info(content);
  }
}

const commonApi = {
  cropImage,
  drawGmTrack,
  drawCanvasTrack,
  drawTracks,
  joinPath,
  downloadImage,
  imagesToPdf,
  logger,
  getConfigData,
  writeData,
  clearFilesSync,
};

export type CommonApi = typeof commonApi;

export default commonApi;
