import type { ABC } from "../deps.ts";
import {
   extractPackagePath,
   getFilePath,
   getFile,
   getOneOf,
   getAbsolutePackageVersion,
   sortVersions,
} from "../utils.ts";

import { Hash, Path } from "../deps.ts";
import db, { IPackage } from "../db.ts";
import * as Storage from "../storage.ts";

const MAX_CACHE_AGE = 60 * 30; // 30 Minutes

const CACHE_CONTROL = "public, max-age=" + MAX_CACHE_AGE;

export default function views(g: ABC.Application) {
   g.get("/", async (ctx) => {
      ctx.response.headers.set("cache-control", CACHE_CONTROL);

      const search = ctx.queryParams.q;

      let packages: IPackage[] = [];
      if (search && search !== "") {
         packages = await db.package.find({
            name: RegExp(`${search}`),
         });
      } else {
         packages = await db.package.find({});
      }


      await ctx.render("index", {
         packages: packages.reverse(),
         search,
      });

      const etag =
         "W/" +
         Hash.createHash("sha3-256")
            .update(
               packages
                  .map((e) => {
                     const sorted = e.versions.sort(sortVersions).reverse();
                     return e.name + sorted[0];
                  })
                  .join(":")
            )
            .toString("base64");

      ctx.response.headers.set("cache-control", CACHE_CONTROL);
      ctx.response.headers.set("E-Tag", etag);
   });

   g.get("/package/:package", async (ctx) => {
      const [packageName, packageVersion] = extractPackagePath(
         ctx.params.package
      );

      const pkg = await db.package.findOne({ name: packageName });

      if (!pkg) {
         ctx.response.status = 404;
         ctx.response.body = "// Package not found!";
         return;
      }


      const readmeContentRaw = (await getOneOf(
         pkg.name,
         packageVersion || pkg.versions.sort(sortVersions).reverse()[0],
         [pkg.readme, "README.md", "readme.md", "Readme.md"]
      ))?.data;

      const readmeContent = readmeContentRaw
         ? new TextDecoder().decode(readmeContentRaw)
         : "";

      const etag =
         "W/" +
         Hash.createHash("sha3-256")
            .update(`${packageName}:${packageVersion}`)
            .toString("base64");

      await ctx.render("package", { pkg, version: packageVersion, readmeContent });
      ctx.response.headers.set("cache-control", CACHE_CONTROL);
      ctx.response.headers.set("E-Tag", etag);
   });

   async function handleBrowse(ctx: ABC.Context) {
      const E404 = () => {
         ctx.response.status = 404;
         ctx.response.body = "// Not found!";
      };

      let [packageName, packageVersion] = extractPackagePath(
         ctx.params.package
      );

      const pkg = await db.package.findOne({ name: packageName });
      packageVersion = getAbsolutePackageVersion(pkg, packageVersion);

      if (!packageVersion) return E404();

      const path = ctx.params.path || "";

      const fileContent = await getFile(packageName, packageVersion, path);
      if (fileContent) {
         await ctx.render("browse_file", {
            pkg,
            version: packageVersion,
            content: new TextDecoder().decode(fileContent.data),
            ext: Path.extname(path),
            path: `${packageName}@${packageVersion}/${path}`,
         });
      } else {
         const filesPath = getFilePath(
            packageName,
            packageVersion,
            path
         );
         if (!filesPath) return E404();

         console.log(filesPath);

         const filesItr = Storage.walkFiles(filesPath);

         const files: { name: string; size: number }[] = [];
         const directories: Set<string> = new Set();
         let readme: string | null = null;
         for await (const file of filesItr) {
            const relPath = Path.posix.relative(filesPath, file.path || "");
            console.log({ file, relPath, filesPath });
            if (relPath.indexOf("/") >= 0) {
               directories.add(relPath.split("/")[0]);
            } else {
               files.push({ name: relPath, size: -1 }); //TODO: Size is not implemented yet
               if (relPath.toLowerCase() === "readme.md") {
                  const readmeCont = await getFile(
                     packageName,
                     packageVersion,
                     Path.posix.join(path, relPath)
                  );
                  if (readmeCont) {
                     readme = new TextDecoder().decode(readmeCont?.data);
                  }
               }
            }
         }

         await ctx.render("browse_folder", {
            pkg,
            version: packageVersion,
            readme,
            files,
            directories: Array.from(directories.values()).map((e) => ({
               name: e,
            })),
            path: `${packageName}@${packageVersion}/${path}`,
         });
      }
   }

   g.get("/browse/:package", handleBrowse);
   g.get("/browse/:package/*path", handleBrowse);
}