import {promises as fsp} from 'fs';
import {basename, join} from 'path';
import fecha from 'fecha';
// @ts-expect-error
import htmlclean from 'htmlclean';
import marked from 'marked';
import nunjucks from 'nunjucks';
import wordWrap from 'wordwrap';
import {generateFeeds} from './feeds';
import {OfficialTopic, getTopicsFromMonth} from './official-topics';
import {Statistics} from './statistics';

// Regular expressions taken and slightly adapted from Tildes' Markdown code:
// https://gitlab.com/tildes/tildes/-/blob/89c7c13be2a7fc11d429ee1e340bf2079862eb98/tildes/tildes/lib/markdown.py#L285-296
const groupRegex = /\s(?<!\w)~([\w.]+)\b(?!~)/gm;
const mentionRegex = /\s(?<![\w\\])(?:\/?u\/|@)([\w-]+)\b/gm;

export const months: string[] = [
  'zeroary',
  'january',
  'february',
  'march',
  'april',
  'may',
  'june',
  'july',
  'august',
  'september',
  'october',
  'november',
  'december'
];

export interface Post {
  markdown: string;
  month: number;
  rawMarkdown: string;
  url: string;
}

export interface PostList {
  year: number;
  posts: Post[];
}

async function entry(): Promise<void> {
  nunjucks.configure(join(__dirname, '../pages/templates/'), {
    lstripBlocks: true,
    throwOnUndefined: true,
    trimBlocks: true
  });

  const dataDirectory: string = join(__dirname, '../pages/data/');
  const statistics: Statistics[] = JSON.parse(
    await fsp.readFile(join(dataDirectory, 'statistics.json'), 'utf8')
  );
  const officialTopics: OfficialTopic[] = JSON.parse(
    await fsp.readFile(join(dataDirectory, 'official-topics.json'), 'utf8')
  );

  const allPosts: PostList[] = [];
  // Get a list of all the years we have Markdown files for.
  const yearDirectories: string[] = await fsp.readdir(
    join(__dirname, '../pages/posts/')
  );
  for (const year of yearDirectories) {
    const postList: PostList = {
      year: Number(year),
      posts: []
    };
    // Create the output directory if it doesn't already exist.
    await fsp.mkdir(join(__dirname, `../../public/${year}/`), {
      recursive: true
    });
    // Get a list of all the Markdown files.
    const markdownFiles: string[] = await fsp.readdir(
      join(__dirname, `../pages/posts/${year}/`)
    );
    for (const file of markdownFiles) {
      // Get the filename without the extension.
      const fileBasename: string = basename(file, '.md');

      // Find the month index in our months array.
      const monthNumber: number = months.indexOf(fileBasename);

      // Read the actual Markdown.
      const markdown: string = await fsp.readFile(
        join(__dirname, `../pages/posts/${year}/${file}`),
        'utf8'
      );

      // Add the post to the list.
      postList.posts.push({
        markdown,
        month: monthNumber,
        rawMarkdown: markdown,
        url: `${year}/${fileBasename}.html`
      });

      // Get the statistics for that month.
      const statistic: Statistics = statistics.find(
        (value) => value.year === Number(year) && value.month === monthNumber
      )!;

      const topics: OfficialTopic[] = await getTopicsFromMonth(
        officialTopics,
        Number(year),
        monthNumber
      );

      let renderedHTML: string = marked(markdown);

      // Replace ~group with a link to that group.
      renderedHTML = renderedHTML.replace(
        groupRegex,
        ' <a class="link-group" href="https://tildes.net/~$1">~$1</a>'
      );

      // Replace @person with a link to that person.
      renderedHTML = renderedHTML.replace(
        mentionRegex,
        ' <a class="link-user" href="https://tildes.net/user/$1">@$1</a>'
      );

      // Render the post template.
      await renderTemplate(
        'post.html',
        join(__dirname, `../../public/${year}/${fileBasename}.html`),
        {
          fecha,
          months,
          officialTopics: topics,
          post: {
            month: monthNumber,
            renderedHTML,
            year: Number(year)
          },
          pluralize,
          publishedDate: new Date(Number(year), monthNumber, 0, 12, 0, 0),
          statistic
        }
      );
    }

    // Sort the posts to be ascending by month.
    postList.posts.sort((a, b) => a.month - b.month);
    allPosts.push(postList);
  }

  // Sort the post list to be descending by year.
  allPosts.sort((a, b) => b.year - a.year);
  await renderTemplate(
    'posts.html',
    join(__dirname, '../../public/posts.html'),
    {
      months,
      posts: allPosts
    }
  );

  // Sort the posts of the most recent year descending by month to select the most recent post.
  allPosts[0].posts.sort((a, b) => b.month - a.month);
  await renderTemplate(
    'index.html',
    join(__dirname, '../../public/index.html'),
    {
      months,
      newestPost: {
        year: allPosts[0].year,
        ...allPosts[0].posts[0]
      }
    }
  );

  await renderTemplate(
    'attributions.html',
    join(__dirname, '../../public/attributions.html')
  );

  await writeEmail(allPosts[0].posts[0].rawMarkdown);
  await generateFeeds(allPosts);
}

export async function renderTemplate(
  template: string,
  location: string,
  context: Record<string, unknown> = {}
): Promise<void> {
  const html: string = htmlclean(nunjucks.render(template, context));
  await fsp.writeFile(location, html);
}

export async function writeEmail(input: string): Promise<void> {
  await fsp.mkdir(join(__dirname, '../../temp/'), {recursive: true});
  await fsp.writeFile(
    join(__dirname, '../../temp/email.md'),
    wordWrap(72)('# Tildes Issue Log\n\n' + input)
  );
}

export function pluralize(
  singular: string,
  plural: string,
  count: number
): string {
  if (count === 1) {
    return singular;
  }

  return plural;
}

if (require.main === module) {
  void entry();
}
