import type {
  Browser,
  BrowserContext,
  ImageFile,
  PageOrFrame,
} from "../browser";
import type { Book } from "../library";

// リーダーのページ要素
const workTreeItemsSelector = `[class^=_worktree_] li[class^=_item_]`;

function Reader(page: PageOrFrame, readerUrl: string) {
  const workId = /^https:[/][/]play[.]dlsite[.]com[/]#[/]work[/]([^/]+)/.exec(
    readerUrl,
  )?.[1];

  if (!workId) {
    throw new Error(`workId is not included: ${readerUrl}`);
  }

  return {
    async load() {
      await page.goto(readerUrl);
    },
    async downloadUrl(): Promise<null | string> {
      const url = `https://www.dlsite.com/home/download/=/product_id/${workId}.html`;

      if (!workId.startsWith("B")) return url;

      const items = await page.waitForSelector(workTreeItemsSelector);
      const text = await items.textContent();

      return text?.match(/画像/) ? null : url;
    },
  };
}

export function DlsiteManiax(browser: Browser) {
  async function* getAllBooks(ctx: BrowserContext): AsyncGenerator<Book> {
    const totalCountEndpoint = "https://play.dlsite.com/api/product_count";
    const endpoint = "https://play.dlsite.com/api/purchases";
    const pager = {
      page: 1,
      perPage: 50,
      totalCount: Infinity,
    };

    const res = await ctx.request.get(totalCountEndpoint);
    const body: {
      user: number;
    } = await res.json();

    pager.totalCount = body.user;

    while ((pager.page - 1) * pager.perPage <= pager.totalCount) {
      const res = await ctx.request.get(`${endpoint}?page=${pager.page}`);

      if (!res.ok()) {
        throw new Error(`${res.status()} ${res.statusText()}`);
      }

      const body: {
        limit: number;
        works: Array<{
          workno: number;
          name: {
            ja_JP: string;
          };
          maker: {
            name: {
              ja_JP: string;
            };
          };
          author_name: string | null;
        }>;
      } = await res.json();

      for (const work of Object.values(body.works).flat()) {
        yield {
          id: NaN,
          platform: "dlsite-maniax",
          readerUrl: `https://play.dlsite.com/#/work/${work.workno}`,
          title: work.name.ja_JP || "",
          authors: [work.author_name || work.maker.name.ja_JP || ""],
        };

        process.stderr.write(".");
      }

      pager.page += 1;
      pager.perPage = body.limit;
    }
  }

  return {
    async *pull(): AsyncGenerator<Book> {
      const ctx = await browser.loadBrowserContext("dlsite-maniax");

      yield* getAllBooks(ctx);

      process.stderr.write(`\n`);
    },

    async getFiles(book: Book): Promise<Array<() => Promise<Blob>>> {
      const ctx = await browser.loadBrowserContext("dlsite-maniax");
      const page = await ctx.newPage();
      const reader = Reader(page, book.readerUrl);

      await reader.load();
      const downloadUrl = await reader.downloadUrl();

      if (downloadUrl) {
        const imageFile: ImageFile = { url: downloadUrl };

        return [
          async () => {
            const blob = await browser.drawImage(page, imageFile);

            process.stderr.write(".");

            return blob;
          },
        ];
      }

      // ページ数 … 画面に表示されている要素を辿る
      await page.waitForSelector(workTreeItemsSelector);
      const workTreeItems = await page.locator(workTreeItemsSelector).count();
      await page.click(workTreeItemsSelector);

      // 見開き表示の無効化 … 初回: 右下見開きボタンをクリックして無効化
      const spreadButton = page.getByRole("button", { name: "見開き" });
      await spreadButton.click();
      await Promise.all([
        spreadButton.waitFor({ state: "detached" }),
        page.mouse.click(0, 720 / 2),
      ]);
      await page.keyboard.press("ArrowRight");

      // ページ数だけ画面送りを繰り返し行い、canvasをそのままキャプチャしていく
      const files: Array<() => Promise<Blob>> = [];
      while (files.length < workTreeItems) {
        await page.waitForTimeout(1000);
        const n = Math.min(2, Math.max(0, workTreeItems - 1 - files.length));
        const canvas = page.locator("canvas").nth(n);
        await canvas.waitFor({ state: "visible" });
        const [width, height] = await Promise.all(
          ["width", "height"].map((d) => canvas.getAttribute(d).then(Number)),
        );
        await page.setViewportSize({ width, height });
        await page.waitForTimeout(1000);
        const buff = await canvas.screenshot();
        files.push(async () => new Blob([buff], { type: "image/png" }));

        process.stderr.write(".");

        await page.keyboard.press("ArrowLeft");
      }

      return files;
    },
    loginEndpoints: ["https://www.dlsite.com/home/login"],
    loginSuccessUrl: (url: URL) => url.origin === "https://www.dlsite.com",
    logoutEndpoints: ["https://www.dlsite.com/home/logout"],
  };
}

DlsiteManiax.siteUrl = (url: URL) =>
  url.href.startsWith("https://play.dlsite.com/#/work/");