/**
 * Points the images of a blog export file at copies already in this CMS's
 * media library (the files `npm run import:wordpress` downloaded), so
 * importing the file sets featured, share and author images and serves the
 * images inside articles from the CMS instead of WordPress.
 *
 *   npm run export:link-images                                  # wordpress-import/blog-export.json, in place
 *   npm run export:link-images -- other.json --out linked.json
 *
 * For each image found in the media library (same year/month folder and file
 * name), it sets `url` (e.g. "/uploads/2026/05/banner.png") and `file` (the
 * library record), and swaps the image's address inside the article HTML.
 * `sourceUrl` is kept, so the original stays on record. Images not found are
 * listed and left as they were. Reads the database; changes nothing in it.
 *
 * The /uploads/ paths work on any CMS whose storage holds the same files: for
 * another server, copy `storage/uploads/` there (local storage driver).
 */
import "dotenv/config";

import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { parseArgs } from "node:util";

import type { BlogExportInput } from "@/features/blog-import/format";
import { createMediaMatcher } from "@/features/blog-import/media-match";
import { parseBlogExport } from "@/features/blog-import/service";
import { db } from "@/lib/db";

const { values: args, positionals } = parseArgs({
  allowPositionals: true,
  options: { out: { type: "string" } },
});

type ImageInput = NonNullable<
  BlogExportInput["posts"][number]["featuredImage"]
>;

/** `src="url"` / `href="url"` as it may appear in sanitized HTML. */
const attributeForms = (url: string) => [
  `"${url}"`,
  `"${url.replace(/&/g, "&amp;")}"`,
];

async function main() {
  const inputPath = path.resolve(
    positionals[0] ?? "wordpress-import/blog-export.json",
  );
  const outputPath = path.resolve(args.out ?? inputPath);
  const raw = await readFile(inputPath, "utf8");

  // Validate first, then edit the file as written (keeps its exact shape).
  const parsed = parseBlogExport(raw);
  if (!parsed.ok) {
    console.error(parsed.error);
    for (const issue of parsed.issues) console.error(`  ${issue}`);
    process.exitCode = 1;
    return;
  }
  const file = JSON.parse(raw.replace(/^﻿/, "")) as BlogExportInput;

  const media = await db.media.findMany({
    select: {
      url: true,
      filename: true,
      storageKey: true,
      mimeType: true,
      size: true,
      width: true,
      height: true,
    },
  });
  const findMedia = createMediaMatcher(media);
  console.log(`Media library: ${media.length} files.`);

  let linked = 0;
  const missing: string[] = [];

  /** Sets `url` and `file` on an image reference; returns the CMS URL or null. */
  function link(ref: ImageInput | null | undefined, usedBy: string) {
    if (!ref) return null;
    const row = findMedia(ref.sourceUrl);
    if (!row) {
      missing.push(`${usedBy}: ${ref.sourceUrl}`);
      return null;
    }
    ref.url = row.url;
    ref.file = {
      storageKey: row.storageKey,
      filename: row.filename,
      mimeType: row.mimeType,
      size: row.size,
      width: row.width,
      height: row.height,
    };
    linked++;
    return row.url;
  }

  for (const author of file.authors ?? []) {
    link(author.profileImage, `author ${author.slug}`);
  }

  for (const post of file.posts) {
    link(post.featuredImage, `${post.slug} featuredImage`);
    link(post.seo?.ogImage, `${post.slug} ogImage`);
    link(post.cta?.image, `${post.slug} ctaImage`);

    let content = post.content ?? "";
    for (const ref of post.images ?? []) {
      const url = link(ref, `${post.slug} content`);
      if (!url) continue;
      for (const form of attributeForms(ref.sourceUrl)) {
        content = content.split(form).join(`"${url}"`);
      }
    }
    post.content = content;
  }

  // The edited file must still be a valid export.
  const check = parseBlogExport(file);
  if (!check.ok) {
    throw new Error(
      `The linked file doesn't match the format: ${check.error}\n${check.issues.join("\n")}`,
    );
  }

  await writeFile(outputPath, JSON.stringify(file, null, 2) + "\n");

  const stillRemote = file.posts.flatMap((post) =>
    [
      ...(post.content ?? "").matchAll(
        /\ssrc="(https?:\/\/[^"]*\/wp-content\/[^"]+)"/g,
      ),
    ].map((match) => `${post.slug}: ${match[1]}`),
  );

  console.log(`Linked ${linked} images to the media library.`);
  if (missing.length) {
    console.log(
      `\nNot in the media library (${missing.length}), left as they were:`,
    );
    for (const line of missing) console.log(`  ${line}`);
  }
  if (stillRemote.length) {
    console.log(
      `\nImages inside articles still loading from WordPress (${stillRemote.length}):`,
    );
    for (const line of stillRemote) console.log(`  ${line}`);
  }
  console.log(`\nWrote ${outputPath}`);
}

main()
  .catch((error) => {
    console.error(error instanceof Error ? error.message : String(error));
    process.exitCode = 1;
  })
  .finally(() => db.$disconnect());
