import { bufferOf } from './util';

export interface QpdfResult {
  ok: boolean;
  code: number;
  log: string;
  output?: Uint8Array;
}

let worker: Worker | null = null;
let seq = 0;
const pending = new Map<number, (r: QpdfResult) => void>();

function getWorker(): Worker {
  if (!worker) {
    worker = new Worker(new URL('../workers/qpdf.worker.ts', import.meta.url), { type: 'module' });
    worker.onmessage = (e: MessageEvent<QpdfResult & { id: number }>) => {
      const { id, ...rest } = e.data;
      pending.get(id)?.(rest);
      pending.delete(id);
    };
  }
  return worker;
}

/** Run qpdf with the given argv against an input file; returns produced output bytes. */
export function runQpdf(
  args: string[],
  input: Uint8Array,
  opts: { inName?: string; outName?: string } = {},
): Promise<QpdfResult> {
  const id = ++seq;
  const w = getWorker();
  const buf = bufferOf(input);
  return new Promise((resolve) => {
    pending.set(id, resolve);
    w.postMessage(
      { id, args, input: new Uint8Array(buf), inName: opts.inName ?? 'in.pdf', outName: opts.outName },
      [buf],
    );
  });
}

/* ---------- high-level operations ---------- */

export interface EncryptOptions {
  userPassword: string;
  ownerPassword?: string;
  keyLength?: 40 | 128 | 256; // default 256
  allowPrinting?: boolean;
  allowModify?: boolean;
  allowCopy?: boolean;
  allowAnnotate?: boolean;
}

export async function encryptPdf(input: Uint8Array, o: EncryptOptions): Promise<QpdfResult> {
  const keyLen = o.keyLength ?? 256;
  const owner = o.ownerPassword || o.userPassword;
  const args = ['--encrypt', o.userPassword, owner, String(keyLen)];
  // permission restrictions (default: allow) — qpdf uses --print=none/full etc.
  args.push('--print=' + (o.allowPrinting === false ? 'none' : 'full'));
  args.push('--modify=' + (o.allowModify === false ? 'none' : 'all'));
  args.push('--extract=' + (o.allowCopy === false ? 'n' : 'y'));
  args.push('--annotate=' + (o.allowAnnotate === false ? 'n' : 'y'));
  args.push('--', 'in.pdf', 'out.pdf');
  return runQpdf(args, input, { outName: 'out.pdf' });
}

export async function decryptPdf(input: Uint8Array, password: string): Promise<QpdfResult> {
  const args = [`--password=${password}`, '--decrypt', 'in.pdf', 'out.pdf'];
  return runQpdf(args, input, { outName: 'out.pdf' });
}

export interface PermissionOptions {
  ownerPassword: string;
  allowPrinting?: boolean;
  allowModify?: boolean;
  allowCopy?: boolean;
  allowAnnotate?: boolean;
  keyLength?: 128 | 256;
}

/** Apply permission flags with an owner password (empty user password = opens freely). */
export async function setPermissions(input: Uint8Array, o: PermissionOptions): Promise<QpdfResult> {
  const keyLen = o.keyLength ?? 256;
  const args = ['--encrypt', '', o.ownerPassword, String(keyLen)];
  args.push('--print=' + (o.allowPrinting === false ? 'none' : 'full'));
  args.push('--modify=' + (o.allowModify === false ? 'none' : 'all'));
  args.push('--extract=' + (o.allowCopy === false ? 'n' : 'y'));
  args.push('--annotate=' + (o.allowAnnotate === false ? 'n' : 'y'));
  args.push('--', 'in.pdf', 'out.pdf');
  return runQpdf(args, input, { outName: 'out.pdf' });
}

/** Structural optimization + linearization (used by Compress). */
export async function optimizeStructure(input: Uint8Array, password?: string): Promise<QpdfResult> {
  const args: string[] = [];
  if (password) args.push(`--password=${password}`, '--decrypt');
  args.push(
    '--object-streams=generate',
    '--compress-streams=y',
    '--recompress-flate',
    '--linearize',
    'in.pdf',
    'out.pdf',
  );
  return runQpdf(args, input, { outName: 'out.pdf' });
}

/** Attempt to repair a damaged PDF (qpdf reconstructs the xref table). */
export async function repairPdf(input: Uint8Array): Promise<QpdfResult> {
  // Rebuild structure; qpdf recovers cross-reference tables automatically.
  const args = ['--object-streams=generate', '--compress-streams=y', 'in.pdf', 'out.pdf'];
  return runQpdf(args, input, { outName: 'out.pdf' });
}

/** Check a PDF's structural health; returns qpdf's --check report in `log`. */
export async function checkPdf(input: Uint8Array): Promise<QpdfResult> {
  return runQpdf(['--check', 'in.pdf'], input, {});
}

/** Whether a PDF is encrypted (needs a password to open). */
export async function isEncrypted(input: Uint8Array): Promise<boolean> {
  const res = await runQpdf(['--is-encrypted', 'in.pdf'], input, {});
  // exit code 0 => encrypted, 2 => not encrypted
  return res.code === 0;
}
