/*
 * percent.ts
 *
 * Copyright (C) 2020-202 Posit Software, PBC
 */

import { extname } from "../../deno_ral/path.ts";

import { Metadata } from "../../config/types.ts";
import { pandocAttrKeyvalueFromText } from "../pandoc/pandoc-attr.ts";
import { kCellRawMimeType } from "../../config/constants.ts";
import { mdFormatOutput, mdRawOutput } from "./jupyter.ts";
import { lines, trimEmptyLines } from "../lib/text.ts";
import { asYamlText } from "./jupyter-fixups.ts";
import { kLangCommentChars } from "../lib/partition-cell-options.ts";

export const kJupyterPercentScriptExtensions = [
  ".py",
  ".jl",
  ".r",
  ".q",
];

export const kLanguageExtensions: Record<string, string> = {
  ".jl": "julia",
  ".r": "r",
  ".py": "python",
  ".q": "q",
};


export function isJupyterPercentScript(file: string, extensions?: string[]) {
  const ext = extname(file).toLowerCase();
  const availableExtensions = extensions ?? kJupyterPercentScriptExtensions;
  if (availableExtensions.includes(ext)) {
    const text = Deno.readTextFileSync(file);
    const cms = kLangCommentChars[kLanguageExtensions[ext]];
    // Use multiline mode (m) so ^ matches start of any line, not just start of file.
    // Group the alternation properly: (markdown|raw) not markdown|raw
    const pat = new RegExp(`^\\s*${cms}\\s*%%+\\s+\\[(markdown|raw)\\]`, "m");
    return !!text.match(pat);
  } else {
    return false;
  }
}

export function markdownFromJupyterPercentScript(file: string) {
  // determine language/kernel
  const ext = extname(file).toLowerCase();
  const language = kLanguageExtensions[ext];
  const cms =  kLangCommentChars[language];
  if (!language) {
    throw new Error(`Could not determine language from file extension ${ext}`);
  }

  // break into cells
  const cells: PercentCell[] = [];
  const activeCell = () => cells[cells.length - 1];
  for (const line of lines(Deno.readTextFileSync(file).trim())) {
    const header = percentCellHeader(line, language);
    if (header) {
      cells.push({ header, lines: [] });
    } else {
      activeCell()?.lines.push(line);
    }
  }

  // resolve markdown and raw cells
  const isTripleQuote = (line: string) => !!line.match(/^"{3,}\s*$/);
  const asCell = (lines: string[]) => lines.join("\n") + "\n\n";
  const stripPrefix = (line: string) => line.replace(new RegExp(`^${cms}\\s?`), "");
  const cellContent = (cellLines: string[]) => {
    if (
      cellLines.length > 2 && isTripleQuote(cellLines[0]) &&
      isTripleQuote(cellLines[cellLines.length - 1])
    ) {
      return asCell(cellLines.slice(1, cellLines.length - 1));
    } else {
      // commented
      return asCell(cellLines.map(stripPrefix));
    }
  };

  return cells.reduce((markdown, cell) => {
    const cellLines = trimEmptyLines(cell.lines);
    if (cell.header.type === "code") {
      if (cell.header.metadata) {
        const yamlText = asYamlText(cell.header.metadata);
        cellLines.unshift(...lines(yamlText).map((line) => `${cms}| ${line}`));
      }
      markdown += asCell(["```{" + language + "}", ...cellLines, "```"]);
    } else if (cell.header.type === "markdown") {
      markdown += cellContent(cellLines);
    } else if (cell.header.type == "raw") {
      let rawContent = cellContent(cellLines);
      const format = cell.header?.metadata?.["format"];
      const mimeType = cell.header.metadata?.[kCellRawMimeType];
      if (typeof mimeType === "string") {
        const rawBlock = mdRawOutput(mimeType, lines(rawContent));
        rawContent = rawBlock || rawContent;
      } else if (typeof format === "string") {
        rawContent = mdFormatOutput(format, lines(rawContent));
      }
      markdown += rawContent;
    }
    return markdown;
  }, "");
}

interface PercentCell {
  header: PercentCellHeader;
  lines: string[];
}

interface PercentCellHeader {
  type: "code" | "raw" | "markdown";
  metadata?: Metadata;
}

function percentCellHeader(line: string, language: string): PercentCellHeader | undefined {
  const cms = kLangCommentChars[language];
  const match = line.match(
    new RegExp(`^\\s*${cms}\\s*%%+\\s*(?:\\[(markdown|raw)\\])?\\s*(.*)?$`),
  );
  if (match) {
    const type = match[1] || "code";
    const attribs = match[2] || "";
    if (["code", "raw", "markdown"].includes(type)) {
      return {
        type,
        metadata: parsePercentAttribs(attribs),
      } as PercentCellHeader;
    } else {
      throw new Error(`Invalid cell type: ${type}`);
    }
  }
}

function parsePercentAttribs(
  attribs: string,
): Metadata | undefined {
  // skip over title
  const match = attribs.match(/[\w\-]+=.*$/);
  if (match) {
    const keyValue = pandocAttrKeyvalueFromText(match[0], " ");
    const metadata: Metadata = {};
    keyValue.forEach((value) => {
      metadata[value[0]] = value[1];
    });
    return metadata;
  }
}
