import { OutputAsset, OutputBundle, OutputChunk, Plugin } from "rollup"
import { TaprootPluginOptions } from "./models/TaprootPluginOptions"
import { Template } from "./models/TaprootTemplateParser"
import readdir from "recursive-readdir"
import fs from "fs"
import path from "path"
import { Page } from "./models/TaprootPageRenderer"
import { BuildHead } from "./head-builder"
import { cloneable } from "./helpers.ts/cloneable"

interface FileElements {
  scripts: string
  links: string
}

const determinePath = (fileName: string, canonical: string): string => {
  let emitPath: string

  if (canonical) {
    emitPath = canonical
  } else {
    emitPath = fileName.split(".")[0]
  }

  if (emitPath.endsWith("index")) {
    emitPath = emitPath.substring(0, emitPath.length - 5)
  }

  if (emitPath.endsWith("/")) {
    emitPath = emitPath.substring(0, emitPath.length - 1)
  }

  return emitPath
}

const buildTagPath = (tagRoot: string, tag: string): string =>
  `${tagRoot ?? "/tag/"}${encodeURI(tag.toLowerCase().replace(" ", "-"))}`

const getElements = (
  files: Record<string, (OutputChunk | OutputAsset)[]>
): FileElements => {
  // https://github.com/rollup/plugins/blob/master/packages/html/src/index.ts
  // TODO: Set these from options...
  const publicPath = "/"

  // TODO: Allow specifying attributes
  const attrs = ""

  const scripts = (files.js || [])
    .map(({ fileName }) => {
      // const attrs = makeHtmlAttributes(attributes.script);
      return `<script src="${publicPath}${fileName}"${attrs} type="module"></script>`
    })
    .join("\n")

  const links = (files.css || [])
    .map(({ fileName }) => {
      // const attrs = makeHtmlAttributes(attributes.link);
      return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`
    })
    .join("\n")

  return {
    scripts,
    links,
  }
}

const getFiles = (
  bundle: OutputBundle
): Record<string, (OutputChunk | OutputAsset)[]> => {
  const files = Object.values(bundle).filter(
    (file) => file.type === "chunk" || file.type === "asset"
  )
  const result = {} as ReturnType<typeof getFiles>
  for (const file of files) {
    const { fileName } = file
    const extension = path.extname(fileName).substring(1)
    result[extension] = (result[extension] || []).concat(file)
  }

  return result
}

const Taproot = (options: TaprootPluginOptions): Plugin => {
  const templates = new Map<String, Template>()
  let pagesToRender: Map<string, Page>

  return {
    name: "rollup-plugin-taproot",

    async buildStart(_options) {
      pagesToRender = new Map<string, Page>()
      const templateFiles = await readdir(options.TemplatesPath)

      for (const templateFile of templateFiles) {
        for (const templateParser of options.TemplateParsers) {
          if (!templateParser.FileMatcher.test(templateFile)) {
            continue
          }

          const source = fs.readFileSync(templateFile).toString()
          const templateKey = path.basename(templateFile).split(".")[0]
          const compiled = templateParser.CompileTemplate(source)
          templates.set(templateKey, compiled)
        }
      }

      console.log("Reading pages...")
      const pageFiles = await readdir(options.PagesPath)

      for (const pageFile of pageFiles) {
        for (const pageRenderer of options.PageRenderers) {
          if (!pageRenderer.FileMatcher.test(pageFile)) {
            continue
          }

          const source = fs.readFileSync(pageFile).toString()
          const fileName = pageFile.substring(
            pageFile.indexOf(`${options.PagesPath.substring(1)}/`) +
              options.PagesPath.length
          )

          // Clone it so any changes made here do not affect cached
          // data in the renderer.
          const rendered = cloneable.deepCopy(pageRenderer.Render(source))
          rendered.Data.Canonical = determinePath(
            fileName,
            rendered.Data.Canonical ?? ""
          )
          pagesToRender.set(fileName, rendered)
        }
      }

      const tags = new Map<string, Array<Page>>()

      for (const page of pagesToRender.values()) {
        for (const tag of page.Data.Tags ?? []) {
          if (!tags.has(tag)) {
            tags.set(tag, [])
          }

          tags.get(tag)?.push(page)
        }
      }

      for (const tag of tags.keys()) {
        const pages = tags.get(tag) ?? []
        const contents = `
<ul class="articles">
${pages
  .map(
    (page) => `
  <li>
    <article>
    <h2 slot="page-title">${page.Data.Title}</h2>
    ${
      page.Data.Description
        ? `<p slot="page-description">${page.Data.Description}</p>`
        : ""
    }
    <a href="/${page.Data.Canonical}">Read More</a>
    </article>
  </li>`
  )
  .join("\n")}
</ul>`

        const page: Page = {
          Contents: contents,
          Data: {
            Title: tag,
            Description: `Pages tagged with '${tag}'`,
            Canonical: determinePath(
              `${buildTagPath(options.TagRoot, tag).substring(1)}`,
              ""
            ),
            PageType: "website",
          },
        }

        pagesToRender.set(buildTagPath(options.TagRoot, tag), page)
      }
    },

    generateBundle(_options, bundle) {
      const assets = getFiles(bundle)
      const elements = getElements(assets)
      const currentYear = new Date().getFullYear().toString()

      for (const fileName of pagesToRender.keys()) {
        const taprootPage = pagesToRender.get(fileName) as Page

        console.log(JSON.stringify(taprootPage.Data))

        const outPath = taprootPage.Data.Canonical ?? ""
        const author = taprootPage.Data.Author
          ? options.Authors.get(taprootPage.Data.Author)
          : undefined

        const head = [
          BuildHead({
            Author: author,
            DateModified: taprootPage.Data.DateModified,
            DatePublished: taprootPage.Data.DatePublished,
            Publisher: options.Publisher,
            SocialImage: {
              Height: 100,
              Width: 100,
              MediaType: "image/png",
              Url: `${options.SiteRootUrl}${
                outPath ? `${outPath}/` : ""
              }social_image.png}`,
            },
            Title: taprootPage.Data.Title,
            Canonical: `${options.SiteRootUrl}${outPath}`,
            CharSet: options.CharSet ?? "UTF-8",
            Description: taprootPage.Data.Description,
            PageType: taprootPage.Data.PageType,
          }),
          elements.links,
          elements.scripts,
        ].join("\n")

        const template = templates.has(taprootPage.Data.Template ?? "")
          ? (taprootPage.Data.Template as string)
          : "default"
        const rendered = templates.get(template)?.Render({
          Author: author,
          Canonical: taprootPage.Data.Canonical,
          Contents: taprootPage.Contents,
          CSSVars: taprootPage.Data.CSSVars,
          CurrentYear: currentYear,
          DateModified: taprootPage.Data.DateModified,
          DatePublished: taprootPage.Data.DatePublished,
          Description: taprootPage.Data.Description,
          Head: head,
          HidePageHead: taprootPage.Data.HidePageHead,
          PageType: taprootPage.Data.PageType,
          SiteName: options.Publisher.Name,
          Tags:
            taprootPage.Data.Tags?.map((tag) => {
              return {
                Tag: tag,
                Url: buildTagPath(options.TagRoot, tag),
              }
            }) ?? [],
          Title: taprootPage.Data.Title,
        }) as string

        const htmlPath = `${outPath ? `${outPath}/` : ""}index.html`
        console.log(`Emitting file ${htmlPath}`)
        this.emitFile({
          type: "asset",
          fileName: htmlPath,
          source: rendered,
        })
      }
    },
  }
}

export { Taproot }
