import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Edge, Node } from 'reactflow';
import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import { slugify } from '../src/lib/slugger';
import OpenAI from 'openai';
import { runPromisesInBatchSequentially } from '../src/lib/promise';

// ERROR: `__dirname` is not defined in ES module scope
// https://iamwebwiz.medium.com/how-to-fix-dirname-is-not-defined-in-es-module-scope-34d94a86694d
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Usage: tsx ./scripts/editor-roadmap-content.ts <roadmapId>
const OPEN_AI_API_KEY = process.env.OPEN_AI_API_KEY;
console.log('OPEN_AI_API_KEY:', OPEN_AI_API_KEY);
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
const roadmapId = process.argv[2];

const allowedRoadmapIds = await fs.readdir(ROADMAP_CONTENT_DIR);
if (!roadmapId) {
  console.error('Roadmap Id is required');
  process.exit(1);
}

if (!allowedRoadmapIds.includes(roadmapId)) {
  console.error(`Invalid roadmap key ${roadmapId}`);
  console.error(`Allowed keys are ${allowedRoadmapIds.join(', ')}`);
  process.exit(1);
}

const roadmapFrontmatterDir = path.join(
  ROADMAP_CONTENT_DIR,
  roadmapId,
  `${roadmapId}.md`,
);
const roadmapFrontmatterRaw = await fs.readFile(roadmapFrontmatterDir, 'utf-8');
const { data } = matter(roadmapFrontmatterRaw);

const roadmapFrontmatter = data as RoadmapFrontmatter;
if (!roadmapFrontmatter) {
  console.error('Invalid roadmap frontmatter');
  process.exit(1);
}

if (roadmapFrontmatter.renderer !== 'editor') {
  console.error('Only Editor Rendered Roadmaps are allowed');
  process.exit(1);
}

const roadmapDir = path.join(
  ROADMAP_CONTENT_DIR,
  roadmapId,
  `${roadmapId}.json`,
);
const roadmapContent = await fs.readFile(roadmapDir, 'utf-8');
let { nodes, edges } = JSON.parse(roadmapContent) as {
  nodes: Node[];
  edges: Edge[];
};
const enrichedNodes = nodes
  .filter(
    (node) =>
      node?.type &&
      ['topic', 'subtopic'].includes(node.type) &&
      node.data?.label,
  )
  .map((node) => {
    // Because we only need the parent id and title for subtopics
    if (node.type !== 'subtopic') {
      return node;
    }

    const parentNodeId =
      edges.find((edge) => edge.target === node.id)?.source || '';
    const parentNode = nodes.find((n) => n.id === parentNodeId);

    return {
      ...node,
      parentId: parentNodeId,
      parentTitle: parentNode?.data?.label || '',
    };
  }) as (Node & { parentId?: string; parentTitle?: string })[];

const roadmapContentDir = path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content');
const stats = await fs.stat(roadmapContentDir).catch(() => null);
if (!stats || !stats.isDirectory()) {
  await fs.mkdir(roadmapContentDir, { recursive: true });
}

let openai: OpenAI | undefined;
if (OPEN_AI_API_KEY) {
  openai = new OpenAI({
    apiKey: OPEN_AI_API_KEY,
  });
}

function writeTopicContent(
  roadmapTitle: string,
  childTopic: string,
  parentTopic?: string,
) {
  let prompt = `I will give you a topic and you need to write a brief introduction for that with regards to "${roadmapTitle}". Your format should be as follows and be in strictly markdown format:

# (Put a heading for the topic without adding parent "Subtopic in Topic" or "Topic in Roadmap" etc.)

(Write me a brief introduction for the topic with regards to "${roadmapTitle}")
`;

  if (!parentTopic) {
    prompt += `First topic is: ${childTopic}`;
  } else {
    prompt += `First topic is: ${childTopic} under ${parentTopic}`;
  }

  return new Promise((resolve, reject) => {
    openai?.chat.completions
      .create({
        model: 'gpt-4',
        messages: [
          {
            role: 'user',
            content: prompt,
          },
        ],
      })
      .then((response) => {
        const article = response.choices[0].message.content;

        resolve(article);
      })
      .catch((err) => {
        reject(err);
      });
  });
}

async function writeNodeContent(node: Node & { parentTitle?: string }) {
  const nodeDirPattern = `${slugify(node.data.label)}@${node.id}.md`;
  if (!roadmapContentFiles.includes(nodeDirPattern)) {
    console.log(`Missing file for: ${nodeDirPattern}`);
    return;
  }

  const nodeDir = path.join(roadmapContentDir, nodeDirPattern);
  const nodeContent = await fs.readFile(nodeDir, 'utf-8');
  const isFileEmpty = !nodeContent.replace(`# ${node.data.label}`, '').trim();
  if (!isFileEmpty) {
    console.log(`❌ Ignoring ${nodeDirPattern}. Not empty.`);
    return;
  }

  const topic = node.data.label;
  const parentTopic = node.parentTitle;

  console.log(`⏳ Generating content for ${topic}...`);
  let newContentFile = '';
  if (OPEN_AI_API_KEY) {
    newContentFile = (await writeTopicContent(
      roadmapFrontmatter.title,
      topic,
      parentTopic,
    )) as string;
  } else {
    newContentFile = `# ${topic}`;
  }

  await fs.writeFile(nodeDir, newContentFile, 'utf-8');
  console.log(`✅ Content generated for ${topic}`);
}

let roadmapContentFiles = await fs.readdir(roadmapContentDir, {
  recursive: true,
});

if (!OPEN_AI_API_KEY) {
  console.log('----------------------------------------');
  console.log('OPEN_AI_API_KEY not found. Skipping openai api calls...');
  console.log('----------------------------------------');
}
const promises = enrichedNodes.map((node) => () => writeNodeContent(node));
await runPromisesInBatchSequentially(promises, 20);
console.log('✅ All content generated');