import { uploadAndReplace } from "./replace.ts";
import { upload } from "../../takker/scrapbox-file-uploader/mod.ts";
import { parse, Node } from "../../takker/scrapbox-parser/mod.ts";
import { Scrapbox } from "../../takker/scrapbox-jp%2Ftypes/userscript.ts";
import { getPage, exportPages, patch, Socket, makeSocket, disconnect } from "../../takker/scrapbox-userscript-std/mod.ts";
declare const scrapbox: Scrapbox;

const getTargetedPages = async (project: string): Promise<string[]> => { 
  const result = await exportPages(scrapbox.Project.name, { metadata: false });
  if (!result.ok) {
    alert(`${result.value.name} ${result.value.message}`);
    throw Error(result.value.name);
  }
  
  return result.value.pages.flatMap(
    (page) => page.lines.some((line) => line.includes("pbs.twimg.com/media")) ? [page.title] : []
  );
};

const main = async () => {
  const project = scrapbox.Project.name;
  // 古いページから更新する
  const list = (await getTargetedPages(project)).reverse();
  let socket: Socket | undefined;
  try {
    for (let i = 0; i < list.length; i++) {
      const title = list[i];
      const result = await getPage(project, title);
      if (!result.ok) continue;
      
      const stack: [string, URL[]][] = [];
      const blocks = parse(result.value.lines.map((line) => line.text).join("\n"), { hasTitle: true });
      let lineNo = 0;
      let total = 0;
      for (const block of blocks) {
        switch (block.type) {
          case "title":
            lineNo++;
            break;
          case "codeBlock":
            lineNo += block.content.split("\n").length + 1;
            break;
          case "table":
            lineNo += block.cells.length + 1;
            break;
          case "line": {
            const urls = block.nodes.flatMap((node) => getURLs(node));
            if (urls.length > 0) {
              total += urls.length;
              stack.push([result.value.lines[lineNo].id, urls]);
            }
            lineNo++;
            break;
          }
        }
      }
      if (total === 0) continue;
      console.debug(`[${i}/${list.length}] replace ${total} links in "/${project}/${result.value.title}"`);
      const pairs: [string, string][] = [];
      let counter = 0;
      for (const [hash, urls] of stack) {
        for (const url of urls) {
          const permalink = await uploadAndReplace(project, result.value.title, hash, url);
          if (!permalink) continue;
          console.debug(`[${i}/${list.length}][${counter}/${total}] ${url} => ${permalink}`);
          pairs.push([`${url}`, permalink]);
          counter++;
        }
      }
      socket ??= await makeSocket();
      await patch(project, result.value.title, (lines) => lines.map((line) => {
          let text = line.text;
          for (const [b, a] of pairs) {
            text = text.replaceAll(b, a);
          }
          return text;
        }),
        { socket }
      );
      console.debug(`[${i}/${list.length}] replaced links in "/${project}/${result.value.title}"`);
    }
  } finally {
    if (socket) disconnect(socket);
  }
};

const getURLs = (node: Node): URL[] => {
  switch (node.type) {
    case "decoration":
    case "quote":
      return node.nodes.flatMap((node) => getURLs(node));
    case "image": {
      const url = new URL(node.src);
      if (url.hostname !== "pbs.twimg.com") return [];
      if (!url.pathname.startsWith("/media")) return [];
      return [url];
    }
    default:
      return [];
  }
};

await main();