import {
  PDFDocument,
  PDFForm,
  PDFTextField,
  PDFCheckBox,
  PDFDropdown,
  PDFOptionList,
  PDFRadioGroup,
} from '@cantoo/pdf-lib';
import { registerFeature } from '../registry';
import { featureModal, pdfSource, footerButtons, deliver, type LoadedFile } from '../common';
import { bufferOf, baseName } from '../../core/util';
import { toast } from '../../ui/toast';
import { icon } from '../../ui/icons';
import { field, textInput, checkbox, select } from '../../ui/controls';
import { h } from '../../core/dom';

/** Writes the current UI value back onto its captured field reference. */
type FieldBinding = () => void;

registerFeature({
  id: 'fill-form',
  title: 'Fill a form',
  desc: 'Detect fillable fields and type into them.',
  icon: 'field',
  category: 'forms',
  run(ctx) {
    const m = featureModal('Fill a form', 'Type into the form fields — nothing is uploaded.', 'field', 580);

    let loaded: LoadedFile | null = null;
    const area = h('div.fill-area');

    // Built lazily when a PDF is selected; kept so submit() saves the very
    // document whose fields the bindings were captured from.
    let builtDoc: PDFDocument | null = null;
    let builtForm: PDFForm | null = null;
    let bindings: FieldBinding[] = [];
    let flatten = false;

    m.body.append(
      pdfSource(ctx, {
        multiple: false,
        onChange: (files) => {
          loaded = files[0] ?? null;
          void buildFields();
        },
      }),
      area,
    );

    const { primary } = footerButtons(m, 'Fill & download', () => void submit(), 'field');
    primary.disabled = true;

    async function buildFields() {
      area.replaceChildren();
      bindings = [];
      builtDoc = null;
      builtForm = null;
      flatten = false;
      primary.disabled = true;
      if (!loaded) return;

      let doc: PDFDocument;
      try {
        doc = await PDFDocument.load(bufferOf(loaded.bytes), { ignoreEncryption: true });
      } catch (e) {
        console.error(e);
        toast('Couldn’t open that PDF — it may be damaged.', { type: 'error' });
        return;
      }

      const form = doc.getForm();
      const fields = form.getFields();

      if (fields.length === 0) {
        const empty = h('div.fill-empty', h('p.hint', 'No fillable fields detected in this PDF.'));
        if (ctx.loadIntoEditor && loaded) {
          const src = loaded;
          empty.append(
            h('button.btn btn-soft',
              { onclick: () => { ctx.loadIntoEditor?.(src.bytes, src.name); m.close(); } },
              icon('field', 16), 'Open in editor to add fields'),
          );
        }
        area.appendChild(empty);
        return;
      }

      const list = h('div.fill-list', {
        style: { maxHeight: '52vh', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: '12px' },
      });

      for (const f of fields) {
        const name = f.getName();
        const kind = f.constructor.name;

        if (kind === 'PDFTextField' && f instanceof PDFTextField) {
          const tf = f;
          let value = tf.getText() ?? '';
          const input = textInput({ value, autofill: true, oninput: (v) => { value = v; } });
          list.appendChild(field(name, input));
          bindings.push(() => { try { tf.setText(value); } catch (e) { console.error(e); } });
        } else if (kind === 'PDFCheckBox' && f instanceof PDFCheckBox) {
          const cb = f;
          let value = cb.isChecked();
          list.appendChild(checkbox(name, value, (v) => { value = v; }));
          bindings.push(() => { try { value ? cb.check() : cb.uncheck(); } catch (e) { console.error(e); } });
        } else if (kind === 'PDFDropdown' && f instanceof PDFDropdown) {
          const dd = f;
          const options = dd.getOptions();
          let value = dd.getSelected()[0] ?? options[0] ?? '';
          list.appendChild(field(name, select({
            options: options.map((o) => ({ value: o, label: o })),
            value,
            onchange: (v) => { value = v; },
          })));
          bindings.push(() => { try { if (value) dd.select(value); } catch (e) { console.error(e); } });
        } else if (kind === 'PDFOptionList' && f instanceof PDFOptionList) {
          const ol = f;
          const options = ol.getOptions();
          let value = ol.getSelected()[0] ?? options[0] ?? '';
          list.appendChild(field(name, select({
            options: options.map((o) => ({ value: o, label: o })),
            value,
            onchange: (v) => { value = v; },
          })));
          bindings.push(() => { try { if (value) ol.select(value); } catch (e) { console.error(e); } });
        } else if (kind === 'PDFRadioGroup' && f instanceof PDFRadioGroup) {
          const rg = f;
          const options = rg.getOptions();
          let value = rg.getSelected() ?? options[0] ?? '';
          list.appendChild(field(name, select({
            options: options.map((o) => ({ value: o, label: o })),
            value,
            onchange: (v) => { value = v; },
          })));
          bindings.push(() => { try { if (value) rg.select(value); } catch (e) { console.error(e); } });
        }
      }

      area.append(list, checkbox('Flatten after filling', false, (v) => { flatten = v; }));
      builtDoc = doc;
      builtForm = form;
      primary.disabled = false;
    }

    async function submit() {
      if (!loaded || !builtDoc || !builtForm || bindings.length === 0) return;
      m.setBusy(true, 'Filling…');
      try {
        for (const apply of bindings) apply();
        if (flatten) builtForm.flatten();
        const bytes = await builtDoc.save({ useObjectStreams: true });
        const name = baseName(loaded.name) + '-filled.pdf';
        m.close();
        deliver(bytes, name);
      } catch (e) {
        m.setBusy(false);
        console.error(e);
        toast('Filling failed — the form may be damaged.', { type: 'error' });
      }
    }
  },
});
