const Koa = require("koa");
const koaStatic = require("koa-static");
const cors = require("@koa/cors");
const fetch = require("node-fetch");
const cookie = require("koa-cookie");
const app = new Koa();
const Router = require("koa-router");
const bodyParser = require("koa-bodyparser");
const router = new Router();
const fs = require("fs");
const rimraf = require("rimraf");
const parser = require("@observablehq/parser");
const CompilerModule = require("@alex.garcia/unofficial-observablehq-compiler");
const BASE = __dirname;

//To edit set this on your browser and refresh
//document.cookie = "secret=editor"
const gen_viewer_markup = (url, data, isEditor) => `<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" href="data:," />
    <link rel="stylesheet" href="/index.css" />
  </head>
  <body class="${
    isEditor ? "" : "IridiumViewOnly"
  }" data-hint-view-only="IridiumViewOnly">
    <div id="iridium-root-wrapper">
      <div id="iridium-root"></div>
    </div>
    <script src="/index.js"></script>
    <script>
      // Override with custom load, save, new, delete, list promises.
      Iridium.load = () => {
        return new Promise((yes, no) => {
          yes(${data});
        });
      };
      Iridium.save = (name,data) => {
        return new Promise((yes, no) => {
          fetch("/save", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify({
              content: data,
              path: "${url}"
            }),
          }).then(d=>{
            yes(true);
          })
        });
      };
    </script>
    <style>
      .IridiumTitle:after {
        content: "${url}";
      }
    </style>
  </body>
</html>
`;

app.use(bodyParser());

router.post("/proxy", async (ctx) => {
  ctx.body = await fetch(ctx.request.body.url, ctx.request.body.options)
    .then((d) => {
      return d.text();
    })
    .catch((d) => {});
});

router.get("/.list", async (ctx) => {
  ctx.body = fs.readdirSync(`${BASE}/.iridium-notebooks/`);
});

router.post("/save", async (ctx) => {
  const cookies = ctx.cookie;
  const editor = cookies && cookies.secret == "editor";
  if (editor) {
    const body = ctx.request.body;
    const notebook_url = body.path;
    const notebook_file_path = `${BASE}/.iridium-notebooks/${notebook_url}.ojs`;
    const compiled_file_path = `${BASE}/.iridium-compiled/${notebook_url}.js`;
    const ojs = cell_array_to_ojs(body.content);
    const js = source_to_esm(ojs);
    fs.writeFileSync(notebook_file_path, ojs);
    fs.writeFileSync(compiled_file_path, js);

    ctx.body = {
      status: `{"STATUS":"OK"}`,
    };
  } else {
    ctx.status = 403;
  }
});

const valid_url_only = (str) => {
  return (
    "/" +
    str
      .replace(/^\//, "")
      .replace(/\/.*$/, "")
      .replace(/[^a-zA-Z0-9]/g, "-")
  );
};

app.use(async (ctx, next) => {
  try {
    //try static routes first
    await next();
    const status = ctx.status || 404;
    if (status === 404) {
      ctx.throw(404);
    }
  } catch (err) {
    ctx.url = valid_url_only(ctx.url);
    const cookies = ctx.cookie;
    ctx.type = "html";
    const editor = cookies && cookies.secret == "editor";
    const notebook_url = ctx.url.replace(/^\//, "");
    const notebook_file_path = `${BASE}/.iridium-notebooks/${notebook_url}.ojs`;
    let notebook_content = "md`# 404!`";
    if (fs.existsSync(notebook_file_path)) {
      notebook_content = fs.readFileSync(notebook_file_path, {
        encoding: "utf8",
      });
    }
    ctx.body = gen_viewer_markup(
      notebook_url,
      JSON.stringify(notebook_content_to_cells(notebook_content), null, 4),
      editor
    );
  }
});

const notebook_content_to_cells = (source) => {
  var parsed = parser.parseModule(source);
  var ends = parsed.cells.map((d) => d.end);
  var contents = ends
    .map((d, i) => {
      return source.substring(i ? ends[i - 1] : 0, d);
    })
    .map((d, i) => {
      return {
        id: i,
        sourceCode: d
          .replace(/^\n+/g, "")
          .replace(/^\/\*PIN\*\//g, "")
          .replace(/^\n+/g, "")
          .replace(/;$/, ""),
        pin: d.startsWith("/*PIN*/") || d.startsWith("\n/*PIN*/"),
      };
    });

  return contents;
};

const cell_array_to_ojs = (cells) => {
  return cells
    .map((d) => {
      return `${d.pin ? "/*PIN*/" : ""}
${d.sourceCode};`;
    })
    .join("\n");
};

const source_to_esm = (source) => {
  const compiler = new CompilerModule.Compiler({
    resolveImportPath: (p) => {
      return p;
    },
  });
  return compiler.module(source);
};

app.use(cookie.default());
app.use(cors());
app.use(router.routes());
app.use(koaStatic(`${BASE}/editor`)); //static server for editor
app.use(koaStatic(`${BASE}/files`)); //static server for files
app.use(koaStatic(`${BASE}/.iridium-compiled`)); //static server for compiled js for imports
app.listen(80); //http