import type { Block, CodeBlock, Table, Line, Node as NodeType } from "../scrapbox-parser/mod.ts";

/** Scrapbox記法をMarkdown記法に変える */
export const convert = (blocks: Block[]): string => {
  /** 変換後の文字列 */
  let latex = "";
  /** 現在解析している行のインデントの深さ
   *
   * この方法だと番号つき箇条書きをを区別できないが、我慢する
   */
  let level = 0;
  for (const block of blocks) {
    // タイトルは今のところ無視しておく
    if (block.type === "title") continue;
    // インデントを下げる
    if (block.indent > level) {
      for (let i = level; i < block.indent; i++) {
        latex += indent(`${ i !== level ? "\\item" : ""}\\begin{itemize}`, i * 2) + "\n";
      }
    }
    // インデントを上げる
    if (block.indent < level) {
      for (let i = level; i > block.indent; i--) {
        latex += indent("\\end{itemize}", (i - 1) * 2) + "\n";
      }
    }
    level = block.indent;
    latex += indent(`${level > 0 ? "\\item " : ""}${fromBlock(block)}`, level * 2) + "\n";
  }
  // 全てのインデントを閉じる
  for (let i = level; i > 0; i--) {
    latex += indent("\\end{itemize}", (i - 1) * 2) + "\n";
  }
  return latex;
};

/** Blockの変換 */
const fromBlock = (block: CodeBlock | Table | Line): string => {
  switch (block.type) {
    case "codeBlock":
      return fromCodeBlock(block);
    case "table":
      return fromTable(block);
    case "line":
      return fromLine(block);
  }
}

/** CodeBlock記法の変換
 *
 * indentは無視する
 */
const fromCodeBlock = (block: CodeBlock): string => 
`\\begin{lstlisting}[language=${escape(getFileType(block.fileName))},caption=${escape(block.fileName)},label=lang:${block.fileName},numbers=left]
${block.content}
\\end{lstlisting}`;

/** Table記法の変換
 *
 * indentは無視する
 */
const fromTable = (table: Table): string => {
  const caption = `\\caption{${escape(table.fileName)}}\\label{table:${escape(table.fileName)}}`;
  // columnsの最大長を計算する
  const maxCol = Math.max(...table.cells.map((row) => row.length));
  
  // 行を変換する
  const rows = table.cells
    .map(
      (row) => `     ${row.map(
        (column) => column.map((node) => fromNode(node)).join("")
      ).join(" & ")}\\\\`
    );
  
  return `\\begin{table}[htbp]
  ${caption}
  \\centering
  \\begin{tabular}{${"c".repeat(maxCol)}}
    ${rows.length === 0 ?
        // 空の場合
        "" :
        rows.length === 1 ?
          // 一行のみの場合は\midruleを入れない
          `    \\toprule
${rows.join("\n")}
    \\bottomrule` :
         `    \\toprule
${rows[0]}
    \\midrule
${rows.slice(1).join("\n")}
    \\bottomrule`
    }
  \\end{tabular}
\\end{table}`;
};

/** 行の変換 */
const fromLine = (line: Line): string =>
  line.nodes.map((node) => fromNode(node)).join("");
  
/** Nodeを変換する */
const fromNode = (node: NodeType): string => {
  switch (node.type) {
    case "quote":
      return `\\begin{quote}
${indent(node.nodes.map((node) => fromNode(node)).join(""), 2)}
\\end{quote}`;
    case "image":
    case "strongImage":
      return `\\begin{figure}[hbtp]
  iamge:\\url{${node.src}}
\\end{figure}`;
    case "icon":
    case "strongIcon":
      return `\\textit{${escape(node.path)}}`;
    case "strong":
     return `\\textbf{${node.nodes.map((node) => fromNode(node)).join("")}}`;
    case "formula":
      return `$${node.formula}$`;
    case "decoration": {
      let result = node.nodes.map((node) => fromNode(node)).join("");
      if (node.decos.includes("/")) result = `\\textit{${result}}`;
      if (node.decos.some((deco) => /\*-/.test(deco[0]))) {
        result = `\\textbf{${result}}`;
      }
      if (node.decos.includes("_")) result = `\\uline{${result}}`;
      if (node.decos.includes("-")) result = `\\sout{${result}}`;
      return result;
    }
    case "numberList":
      return `${node.number}. ${node.nodes.map((node) => fromNode(node)).join("")}`;
    case "helpfeel":
      return `\\lstinline!? ${escape(node.text)}!`;
    case "code":
      return `\\lstinline!${escape(node.text)}!`;
    case "commandLine":
      return `\\lstinline!${escape(node.symbol)} ${escape(node.text)}!`;
    case "link":
      return node.pathType === "absolute" ?
        node.content === "" ?
          `\\url{${node.href}}` :
          `\\href{${node.href}}{${escape(node.content)}}` :
        escape(node.href);
    case "googleMap":
      return `\\href{${node.url}}{${escape(node.place)}}`;
    case "hashTag":
      return escape(`#${node.href}`);
    case "blank":
    case "plain":
      return escape(node.text);
  }
};

const extensionData = [
  {
    extensions: ["javascript", "js"],
    fileType: "javascript",
  },
  {
    extensions: ["typescript", "ts"],
    fileType: "typescript",
  },
  {
    extensions: ["cpp", "hpp"],
    fileType: "C++",
  },
  {
    extensions: ["c", "cc", "h"],
    fileType: "C",
  },
  {
    extensions: ["cs", "csharp"],
    fileType: "cs",
  },
  {
    extensions: ["markdown", "md"],
    fileType: "markdown",
  },
  {
    extensions: ["htm", "html"],
    fileType: "html",
  },
  {
    extensions: ["json"],
    fileType: "json",
  },
  {
    extensions: ["xml"],
    fileType: "xml",
  },
  {
    extensions: ["yaml", "yml"],
    fileType: "yaml",
  },
  {
    extensions: ["toml"],
    fileType: "toml",
  },
  {
    extensions: ["ini"],
    fileType: "ini",
  },
  {
    extensions: ["tex", "sty"],
    fileType: "tex",
  },
  {
    extensions: ["svg"],
    fileType: "svg",
  },
];

/** ファイル名の拡張子から言語を取得する */
const getFileType = (filename: string): string => {
  const filenameExtention = filename.replace(/^.*\.(\w+)$/, "$1");
  return extensionData
    .find((data) => data.extensions.includes(filenameExtention))?.fileType ??
    "";
};

const escape =(text: string): string => text
  .replaceAll("\\", "\\textbackslash ")
  .replaceAll("#", "\\#")
  .replaceAll("%", "\\%")
  .replaceAll("$", "\\$")
  .replaceAll("&", "\\&")
  .replaceAll("~", "\\textasciitilde ")
  .replaceAll("^", "\\textasciicircum ")
  .replaceAll("_", "\\_")
  .replaceAll("{", "\\{")
  .replaceAll("}", "\\}")
  .replaceAll("|", "\\textbar ")
  .replaceAll("<", "\\textless ")
  .replaceAll(">", "\\textgreater ");
   	
const indent = (text: string, indentNum: number): string =>
  text.split("\n").map((line) =>`${" ".repeat(indentNum)}${line}`).join("\n");