import { forceArgs } from "../cli/forceArgs.ts";
import { selectMenuInteractive } from "../cli/selectMenu.ts";
import { TerminalBlock } from "../cli/TerminalLayout.ts";
import type { ITool } from "../types.ts";
import { loadPdf, savePdf } from "util/saveLoadPdf.ts";

import { PDFName, type PDFNumber, type PDFWidgetAnnotation } from "pdf-lib";
import { cliPrompt } from "../cli/prompts.ts";

type AcrobatVisibility =
  | "Visible"
  | "Hidden"
  | "VisibleButDoesNotPrint"
  | "HiddenButPrintable";

export function getAcrobatVisibility(
  widget: PDFWidgetAnnotation,
): AcrobatVisibility {
  const raw = widget.dict.lookup(PDFName.of("F")) as PDFNumber;
  const flags = raw?.asNumber?.() ?? 0;

  const isInvisible = (flags & (1 << 1)) !== 0;
  const isHidden = (flags & (1 << 2)) !== 0;
  const isNoPrint = (flags & (1 << 3)) !== 0;
  const isPrint = (flags & (1 << 2)) === 0 && !isNoPrint;

  if (isInvisible && isHidden) {
    return isPrint ? "HiddenButPrintable" : "Hidden";
  } else if (isNoPrint) {
    return "VisibleButDoesNotPrint";
  } else {
    return "Visible";
  }
}

export function setAcrobatVisibility(
  widget: PDFWidgetAnnotation,
  visibility: AcrobatVisibility,
) {
  let flags = 0;

  switch (visibility) {
    case "Visible":
      // No visibility bits set
      break;
    case "Hidden":
      flags |= 1 << 1; // Invisible
      flags |= 1 << 2; // Hidden
      break;
    case "VisibleButDoesNotPrint":
      flags |= 1 << 3; // NoPrint
      break;
    case "HiddenButPrintable":
      flags |= 1 << 1; // Invisible
      flags |= 1 << 2; // Hidden
      flags |= 1 << 3; // NoPrint — UNset this to allow print
      break;
  }

  widget.dict.set(PDFName.of("F"), widget.dict.context.obj(flags));
}

export class FieldVisibility implements ITool {
  name = "Field Visibility";
  description = "Change visibility of fields";
  block?: TerminalBlock;
  async run(pdfPath: string) {
    if (!this.block) this.block = new TerminalBlock();
    this.block.setPreserveHistory(false);
    [pdfPath] = await forceArgs([pdfPath], [[
      "Please provide path to PDF:",
      (e) => e?.endsWith(".pdf"),
    ]], this.block);

    const pdf = await loadPdf(pdfPath);
    const form = pdf.getForm();
    const fields = form.getFields();

    let changesMade = false;

    while (true) {
      const fieldAndVisibility = await selectMenuInteractive(
        `Select a field to change visibility (ESC to ${
          changesMade ? "continue" : "cancel"
        })`,
        fields.flatMap((f) => {
          const name = f.getName();
          const visibility = f.acroField.getWidgets().map((w, i, a) =>
            `${name}${a.length > 1 ? "#" + i : ""} :: ${
              getAcrobatVisibility(w)
            }`
          );
          return visibility;
        }),
      );
      if (!fieldAndVisibility) break;
      const visibility = await selectMenuInteractive(
        fieldAndVisibility,
        [
          "Visible",
          "Hidden",
          "HiddenButPrintable",
          "VisibleButDoesNotPrint",
        ] as AcrobatVisibility[],
      ) as AcrobatVisibility | null;
      if (!visibility) continue;

      const [fName, widgetIndex] = fieldAndVisibility.split("::")[0].trim()
        .split("#");
      const field = fields.find((f) => f.getName() === fName);
      if (!field) break;

      const widget = field.acroField.getWidgets()[Number(widgetIndex) || 0];
      setAcrobatVisibility(widget, visibility);
      changesMade = true;
    }

    if (changesMade) {
      const path = await cliPrompt(
        "Save to path (or hit enter to keep current):",
        this.block,
      ) || pdfPath;
      savePdf(pdf, path);
    }
  }
  help?: (() => Promise<void> | void) | undefined;
  done?: (() => Promise<void> | void) | undefined;
  setBlock(block: TerminalBlock) {
    this.block = block;
  }
}

export default new FieldVisibility();
