"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WriteTask = exports.DefaultWriteTaskOptions = exports.htmlEncode = void 0;
const he_1 = require("he");
const _path = __importStar(require("path"));
const Array_1 = require("./Array");
const DateTime_1 = require("./DateTime");
const DefaultExifToolOptions_1 = require("./DefaultExifToolOptions");
const DeleteAllTagsArgs_1 = require("./DeleteAllTagsArgs");
const ExifToolTask_1 = require("./ExifToolTask");
const File_1 = require("./File");
const FilenameCharsetArgs_1 = require("./FilenameCharsetArgs");
const Number_1 = require("./Number");
const Object_1 = require("./Object");
const Pick_1 = require("./Pick");
const String_1 = require("./String");
const Struct_1 = require("./Struct");
const VersionTask_1 = require("./VersionTask");
const successRE = /1 image files? (?:created|updated)/i;
const sep = String.fromCharCode(31); // < unit separator
// this is private because it's very special-purpose for just encoding ExifTool
// WriteTask args:
function htmlEncode(s) {
    return (
    // allowUnsafeSymbols is true because ExifTool doesn't care about &, <, >, ", ', * and `
    (0, he_1.encode)(s, { decimal: true, allowUnsafeSymbols: true })
        // `he` doesn't encode whitespaces (like newlines), but we need that:
        .replace(/\s/g, (m) => (m === " " ? " " : `&#${m.charCodeAt(0)};`)));
}
exports.htmlEncode = htmlEncode;
function enc(o, structValue = false) {
    if (o == null) {
        return "";
    }
    else if ((0, Number_1.isNumber)(o)) {
        return String(o);
    }
    else if ((0, String_1.isString)(o)) {
        // Structs need their own escaping here.
        // See https://exiftool.org/struct.html#Serialize
        return htmlEncode(structValue ? o.replace(/[,[\]{}|]/g, (ea) => "|" + ea) : o);
        // const s = htmlEncode(String(o))
    }
    else if ((0, DateTime_1.isDateOrTime)(o)) {
        return (0, DateTime_1.toExifString)(o);
    }
    else if (Array.isArray(o)) {
        const primitiveArray = o.every((ea) => (0, String_1.isString)(ea) || (0, Number_1.isNumber)(ea));
        return primitiveArray
            ? `${o.map((ea) => enc(ea)).join(sep)}`
            : `[${o.map((ea) => enc(ea)).join(",")}]`;
    }
    else if ((0, Struct_1.isStruct)(o)) {
        // See https://exiftool.org/struct.html#Serialize
        return `{${(0, Object_1.keys)(o)
            .map((k) => enc(k, true) + "=" + enc(o[k], true))
            .join(",")}}`;
    }
    else {
        throw new Error("cannot encode " + JSON.stringify(o));
    }
}
exports.DefaultWriteTaskOptions = {
    ...(0, Pick_1.pick)(DefaultExifToolOptions_1.DefaultExifToolOptions, "useMWG"),
};
class WriteTask extends ExifToolTask_1.ExifToolTask {
    constructor(sourceFile, args) {
        super(args);
        this.sourceFile = sourceFile;
        this.args = args;
    }
    static async for(filename, tags, extraArgs = [], options) {
        var _a, _b, _c;
        const sourceFile = _path.resolve(filename);
        const args = [
            ...FilenameCharsetArgs_1.Utf8FilenameCharsetArgs,
            `-sep`,
            `${sep}`,
            "-E", // < html encoding https://exiftool.org/faq.html#Q10
        ];
        if ((_a = options === null || options === void 0 ? void 0 : options.useMWG) !== null && _a !== void 0 ? _a : exports.DefaultWriteTaskOptions.useMWG) {
            args.push("-use", "MWG");
        }
        // ExifTool complains "Nothing to write" if the task will only remove values
        // and the file is missing.
        if ((extraArgs.length === 0 ||
            (0, Array_1.shallowArrayEql)(extraArgs, DeleteAllTagsArgs_1.DeleteAllTagsArgs)) &&
            Object.values(tags).every((ea) => ea == null) &&
            (await (0, File_1.isFileEmpty)(filename))) {
            // no-op!
            return new VersionTask_1.VersionTask();
        }
        // Special handling for GPSLatitude and GPSLongitude (due to differences
        // in EXIF, XMP, and MIE encodings). See
        // https://exiftool.org/forum/index.php?topic=14488.0 and
        // https://github.com/photostructure/exiftool-vendored.js/issues/131
        if ((0, Number_1.isNumber)(tags.GPSLatitude)) {
            (_b = tags.GPSLatitudeRef) !== null && _b !== void 0 ? _b : (tags.GPSLatitudeRef = tags.GPSLatitude >= 0 ? "N" : "S");
        }
        if ((0, Number_1.isNumber)(tags.GPSLongitude)) {
            (_c = tags.GPSLongitudeRef) !== null && _c !== void 0 ? _c : (tags.GPSLongitudeRef = tags.GPSLongitude >= 0 ? "E" : "W");
        }
        for (const key of (0, Object_1.keys)(tags)) {
            const val = tags[key];
            args.push(`-${key}=${enc(val)}`);
        }
        args.push(...extraArgs);
        args.push(sourceFile);
        // console.log("new WriteTask()", { sourceFile, args, tags })
        return new WriteTask(sourceFile, args);
    }
    toString() {
        return "WriteTask(" + this.sourceFile + ")";
    }
    parse(data, error) {
        // console.log(this.toString() + ".parse()", { data, error })
        if (error != null)
            throw error;
        if (this.errors.length > 0)
            throw new Error(this.errors.join(";"));
        data = data.trim();
        if (successRE.exec(data) != null) {
            return;
        }
        else {
            throw new Error("No success message: " + data);
        }
    }
}
exports.WriteTask = WriteTask;
//# sourceMappingURL=WriteTask.js.map