import { __assign, __spreadArrays } from "tslib";
import PDFDocument from "../PDFDocument";
import { colorToComponents, setFillingColor } from "../colors";
import { toDegrees, rotateRectangle, reduceRotation, adjustDimsForRotation, degrees, } from "../rotations";
import { PDFRef, PDFWidgetAnnotation, PDFName, MethodNotImplementedError, AcroFieldFlags, PDFAcroTerminal, AnnotationFlags, } from "../../core";
import { assertIs, assertMultiple, assertOrUndefined } from "../../utils";
import { ImageAlignment } from '../image';
import { drawImage, rotateInPlace } from '../operations';
export var assertFieldAppearanceOptions = function (options) {
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.x, 'options.x', ['number']);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.y, 'options.y', ['number']);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.width, 'options.width', ['number']);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.height, 'options.height', ['number']);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.textColor, 'options.textColor', [
        [Object, 'Color'],
    ]);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.backgroundColor, 'options.backgroundColor', [
        [Object, 'Color'],
    ]);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.borderColor, 'options.borderColor', [
        [Object, 'Color'],
    ]);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.borderWidth, 'options.borderWidth', ['number']);
    assertOrUndefined(options === null || options === void 0 ? void 0 : options.rotate, 'options.rotate', [[Object, 'Rotation']]);
};
/**
 * Represents a field of a [[PDFForm]].
 *
 * This class is effectively abstract. All fields in a [[PDFForm]] will
 * actually be an instance of a subclass of this class.
 *
 * Note that each field in a PDF is represented by a single field object.
 * However, a given field object may be rendered at multiple locations within
 * the document (across one or more pages). The rendering of a field is
 * controlled by its widgets. Each widget causes its field to be displayed at a
 * particular location in the document.
 *
 * Most of the time each field in a PDF has only a single widget, and thus is
 * only rendered once. However, if a field is rendered multiple times, it will
 * have multiple widgets - one for each location it is rendered.
 *
 * This abstraction of field objects and widgets is defined in the PDF
 * specification and dictates how PDF files store fields and where they are
 * to be rendered.
 */
var PDFField = /** @class */ (function () {
    function PDFField(acroField, ref, doc) {
        assertIs(acroField, 'acroField', [[PDFAcroTerminal, 'PDFAcroTerminal']]);
        assertIs(ref, 'ref', [[PDFRef, 'PDFRef']]);
        assertIs(doc, 'doc', [[PDFDocument, 'PDFDocument']]);
        this.acroField = acroField;
        this.ref = ref;
        this.doc = doc;
    }
    /**
     * Get the fully qualified name of this field. For example:
     * ```js
     * const fields = form.getFields()
     * fields.forEach(field => {
     *   const name = field.getName()
     *   console.log('Field name:', name)
     * })
     * ```
     * Note that PDF fields are structured as a tree. Each field is the
     * descendent of a series of ancestor nodes all the way up to the form node,
     * which is always the root of the tree. Each node in the tree (except for
     * the form node) has a partial name. Partial names can be composed of any
     * unicode characters except a period (`.`). The fully qualified name of a
     * field is composed of the partial names of all its ancestors joined
     * with periods. This means that splitting the fully qualified name on
     * periods and taking the last element of the resulting array will give you
     * the partial name of a specific field.
     * @returns The fully qualified name of this field.
     */
    PDFField.prototype.getName = function () {
        var _a;
        return (_a = this.acroField.getFullyQualifiedName()) !== null && _a !== void 0 ? _a : '';
    };
    /**
     * Returns `true` if this field is read only. This means that PDF readers
     * will not allow users to interact with the field or change its value. See
     * [[PDFField.enableReadOnly]] and [[PDFField.disableReadOnly]].
     * For example:
     * ```js
     * const field = form.getField('some.field')
     * if (field.isReadOnly()) console.log('Read only is enabled')
     * ```
     * @returns Whether or not this is a read only field.
     */
    PDFField.prototype.isReadOnly = function () {
        return this.acroField.hasFlag(AcroFieldFlags.ReadOnly);
    };
    /**
     * Prevent PDF readers from allowing users to interact with this field or
     * change its value. The field will not respond to mouse or keyboard input.
     * For example:
     * ```js
     * const field = form.getField('some.field')
     * field.enableReadOnly()
     * ```
     * Useful for fields whose values are computed, imported from a database, or
     * prefilled by software before being displayed to the user.
     */
    PDFField.prototype.enableReadOnly = function () {
        this.acroField.setFlagTo(AcroFieldFlags.ReadOnly, true);
    };
    /**
     * Allow users to interact with this field and change its value in PDF
     * readers via mouse and keyboard input. For example:
     * ```js
     * const field = form.getField('some.field')
     * field.disableReadOnly()
     * ```
     */
    PDFField.prototype.disableReadOnly = function () {
        this.acroField.setFlagTo(AcroFieldFlags.ReadOnly, false);
    };
    /**
     * Returns `true` if this field must have a value when the form is submitted.
     * See [[PDFField.enableRequired]] and [[PDFField.disableRequired]].
     * For example:
     * ```js
     * const field = form.getField('some.field')
     * if (field.isRequired()) console.log('Field is required')
     * ```
     * @returns Whether or not this field is required.
     */
    PDFField.prototype.isRequired = function () {
        return this.acroField.hasFlag(AcroFieldFlags.Required);
    };
    /**
     * Require this field to have a value when the form is submitted.
     * For example:
     * ```js
     * const field = form.getField('some.field')
     * field.enableRequired()
     * ```
     */
    PDFField.prototype.enableRequired = function () {
        this.acroField.setFlagTo(AcroFieldFlags.Required, true);
    };
    /**
     * Do not require this field to have a value when the form is submitted.
     * For example:
     * ```js
     * const field = form.getField('some.field')
     * field.disableRequired()
     * ```
     */
    PDFField.prototype.disableRequired = function () {
        this.acroField.setFlagTo(AcroFieldFlags.Required, false);
    };
    /**
     * Returns `true` if this field's value should be exported when the form is
     * submitted. See [[PDFField.enableExporting]] and
     * [[PDFField.disableExporting]].
     * For example:
     * ```js
     * const field = form.getField('some.field')
     * if (field.isExported()) console.log('Exporting is enabled')
     * ```
     * @returns Whether or not this field's value should be exported.
     */
    PDFField.prototype.isExported = function () {
        return !this.acroField.hasFlag(AcroFieldFlags.NoExport);
    };
    /**
     * Indicate that this field's value should be exported when the form is
     * submitted in a PDF reader. For example:
     * ```js
     * const field = form.getField('some.field')
     * field.enableExporting()
     * ```
     */
    PDFField.prototype.enableExporting = function () {
        this.acroField.setFlagTo(AcroFieldFlags.NoExport, false);
    };
    /**
     * Indicate that this field's value should **not** be exported when the form
     * is submitted in a PDF reader. For example:
     * ```js
     * const field = form.getField('some.field')
     * field.disableExporting()
     * ```
     */
    PDFField.prototype.disableExporting = function () {
        this.acroField.setFlagTo(AcroFieldFlags.NoExport, true);
    };
    /** @ignore */
    PDFField.prototype.needsAppearancesUpdate = function () {
        throw new MethodNotImplementedError(this.constructor.name, 'needsAppearancesUpdate');
    };
    /** @ignore */
    PDFField.prototype.defaultUpdateAppearances = function (_font) {
        throw new MethodNotImplementedError(this.constructor.name, 'defaultUpdateAppearances');
    };
    PDFField.prototype.markAsDirty = function () {
        this.doc.getForm().markFieldAsDirty(this.ref);
    };
    PDFField.prototype.markAsClean = function () {
        this.doc.getForm().markFieldAsClean(this.ref);
    };
    PDFField.prototype.isDirty = function () {
        return this.doc.getForm().fieldIsDirty(this.ref);
    };
    PDFField.prototype.createWidget = function (options) {
        var _a;
        var textColor = options.textColor;
        var backgroundColor = options.backgroundColor;
        var borderColor = options.borderColor;
        var borderWidth = options.borderWidth;
        var degreesAngle = toDegrees(options.rotate);
        var caption = options.caption;
        var x = options.x;
        var y = options.y;
        var width = options.width + borderWidth;
        var height = options.height + borderWidth;
        var hidden = Boolean(options.hidden);
        var pageRef = options.page;
        assertMultiple(degreesAngle, 'degreesAngle', 90);
        // Create a widget for this field
        var widget = PDFWidgetAnnotation.create(this.doc.context, this.ref);
        // Set widget properties
        var rect = rotateRectangle({ x: x, y: y, width: width, height: height }, borderWidth, degreesAngle);
        widget.setRectangle(rect);
        if (pageRef)
            widget.setP(pageRef);
        var ac = widget.getOrCreateAppearanceCharacteristics();
        if (backgroundColor) {
            ac.setBackgroundColor(colorToComponents(backgroundColor));
        }
        ac.setRotation(degreesAngle);
        if (caption)
            ac.setCaptions({ normal: caption });
        if (borderColor)
            ac.setBorderColor(colorToComponents(borderColor));
        var bs = widget.getOrCreateBorderStyle();
        if (borderWidth !== undefined)
            bs.setWidth(borderWidth);
        widget.setFlagTo(AnnotationFlags.Print, true);
        widget.setFlagTo(AnnotationFlags.Hidden, hidden);
        widget.setFlagTo(AnnotationFlags.Invisible, false);
        // Set acrofield properties
        if (textColor) {
            var da = (_a = this.acroField.getDefaultAppearance()) !== null && _a !== void 0 ? _a : '';
            var newDa = da + '\n' + setFillingColor(textColor).toString();
            this.acroField.setDefaultAppearance(newDa);
        }
        return widget;
    };
    PDFField.prototype.updateWidgetAppearanceWithFont = function (widget, font, _a) {
        var normal = _a.normal, rollover = _a.rollover, down = _a.down;
        this.updateWidgetAppearances(widget, {
            normal: this.createAppearanceStream(widget, normal, font),
            rollover: rollover && this.createAppearanceStream(widget, rollover, font),
            down: down && this.createAppearanceStream(widget, down, font),
        });
    };
    PDFField.prototype.updateOnOffWidgetAppearance = function (widget, onValue, _a) {
        var normal = _a.normal, rollover = _a.rollover, down = _a.down;
        this.updateWidgetAppearances(widget, {
            normal: this.createAppearanceDict(widget, normal, onValue),
            rollover: rollover && this.createAppearanceDict(widget, rollover, onValue),
            down: down && this.createAppearanceDict(widget, down, onValue),
        });
    };
    PDFField.prototype.updateWidgetAppearances = function (widget, _a) {
        var normal = _a.normal, rollover = _a.rollover, down = _a.down;
        widget.setNormalAppearance(normal);
        if (rollover) {
            widget.setRolloverAppearance(rollover);
        }
        else {
            widget.removeRolloverAppearance();
        }
        if (down) {
            widget.setDownAppearance(down);
        }
        else {
            widget.removeDownAppearance();
        }
    };
    // // TODO: Do we need to do this...?
    // private foo(font: PDFFont, dict: PDFDict) {
    //   if (!dict.lookup(PDFName.of('DR'))) {
    //     dict.set(PDFName.of('DR'), dict.context.obj({}));
    //   }
    //   const DR = dict.lookup(PDFName.of('DR'), PDFDict);
    //   if (!DR.lookup(PDFName.of('Font'))) {
    //     DR.set(PDFName.of('Font'), dict.context.obj({}));
    //   }
    //   const Font = DR.lookup(PDFName.of('Font'), PDFDict);
    //   Font.set(PDFName.of(font.name), font.ref);
    // }
    PDFField.prototype.createAppearanceStream = function (widget, appearance, font) {
        var _a;
        var context = this.acroField.dict.context;
        var _b = widget.getRectangle(), width = _b.width, height = _b.height;
        // TODO: Do we need to do this...?
        // if (font) {
        //   this.foo(font, widget.dict);
        //   this.foo(font, this.doc.getForm().acroForm.dict);
        // }
        // END TODO
        var Resources = font && { Font: (_a = {}, _a[font.name] = font.ref, _a) };
        var stream = context.formXObject(appearance, {
            Resources: Resources,
            BBox: context.obj([0, 0, width, height]),
            Matrix: context.obj([1, 0, 0, 1, 0, 0]),
        });
        var streamRef = context.register(stream);
        return streamRef;
    };
    /**
     * Create a FormXObject of the supplied image and add it to context.
     * The FormXObject size is calculated based on the widget (including
     * the alignment).
     * @param widget The widget that should display the image.
     * @param alignment The alignment of the image.
     * @param image The image that should be displayed.
     * @returns The ref for the FormXObject that was added to the context.
     */
    PDFField.prototype.createImageAppearanceStream = function (widget, image, alignment) {
        // NOTE: This implementation doesn't handle image borders.
        // NOTE: Acrobat seems to resize the image (maybe even skewing its aspect
        //       ratio) to fit perfectly within the widget's rectangle. This method
        //       does not currently do that. Should there be an option for that?
        var _a;
        var _b;
        var context = this.acroField.dict.context;
        var rectangle = widget.getRectangle();
        var ap = widget.getAppearanceCharacteristics();
        var bs = widget.getBorderStyle();
        var borderWidth = (_b = bs === null || bs === void 0 ? void 0 : bs.getWidth()) !== null && _b !== void 0 ? _b : 0;
        var rotation = reduceRotation(ap === null || ap === void 0 ? void 0 : ap.getRotation());
        var rotate = rotateInPlace(__assign(__assign({}, rectangle), { rotation: rotation }));
        var adj = adjustDimsForRotation(rectangle, rotation);
        var imageDims = image.scaleToFit(adj.width - borderWidth * 2, adj.height - borderWidth * 2);
        // Support borders on images and maybe other properties
        var options = {
            x: borderWidth,
            y: borderWidth,
            width: imageDims.width,
            height: imageDims.height,
            //
            rotate: degrees(0),
            xSkew: degrees(0),
            ySkew: degrees(0),
        };
        if (alignment === ImageAlignment.Center) {
            options.x += (adj.width - borderWidth * 2) / 2 - imageDims.width / 2;
            options.y += (adj.height - borderWidth * 2) / 2 - imageDims.height / 2;
        }
        else if (alignment === ImageAlignment.Right) {
            options.x = adj.width - borderWidth - imageDims.width;
            options.y = adj.height - borderWidth - imageDims.height;
        }
        var imageName = this.doc.context.addRandomSuffix('Image', 10);
        var appearance = __spreadArrays(rotate, drawImage(imageName, options));
        ////////////
        var Resources = { XObject: (_a = {}, _a[imageName] = image.ref, _a) };
        var stream = context.formXObject(appearance, {
            Resources: Resources,
            BBox: context.obj([0, 0, rectangle.width, rectangle.height]),
            Matrix: context.obj([1, 0, 0, 1, 0, 0]),
        });
        return context.register(stream);
    };
    PDFField.prototype.createAppearanceDict = function (widget, appearance, onValue) {
        var context = this.acroField.dict.context;
        var onStreamRef = this.createAppearanceStream(widget, appearance.on);
        var offStreamRef = this.createAppearanceStream(widget, appearance.off);
        var appearanceDict = context.obj({});
        appearanceDict.set(onValue, onStreamRef);
        appearanceDict.set(PDFName.of('Off'), offStreamRef);
        return appearanceDict;
    };
    return PDFField;
}());
export default PDFField;
//# sourceMappingURL=PDFField.js.map