"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExifDate = void 0;
const luxon_1 = require("luxon");
const DateTime_1 = require("./DateTime");
const Maybe_1 = require("./Maybe");
const String_1 = require("./String");
const StrictExifRE = /^\d{1,4}:\d{1,2}:\d{1,2}|\d{1,4}-\d{1,2}-\d{1,2}$/;
const YearMonthRE = /^\d{1,4}[:-]\d{1,2}$/;
const YearOnlyRE = /^\d{1,4}$/;
const LooseExifRE = /^\S+\s+\S+\s+\S+$/;
/**
 * Encodes an ExifDate
 */
class ExifDate {
    year;
    month;
    day;
    rawValue;
    static from(exifOrIso) {
        if (typeof exifOrIso === "number") {
            return this.fromYear(exifOrIso);
        }
        return (
        // in order of strictness:
        this.fromExifStrict(exifOrIso) ??
            this.fromYearMonth(exifOrIso) ??
            this.fromYear(exifOrIso) ??
            this.fromISO(exifOrIso) ??
            this.fromExifLoose(exifOrIso));
    }
    static fromISO(text) {
        return StrictExifRE.test((0, String_1.toS)(text).trim())
            ? this.fromDateTime(luxon_1.DateTime.fromISO(text), text)
            : undefined;
    }
    static fromPatterns(text, fmts) {
        if ((0, String_1.blank)(text))
            return;
        text = (0, String_1.toS)(text).trim();
        for (const fmt of fmts) {
            const dt = luxon_1.DateTime.fromFormat(text, fmt);
            if ((0, DateTime_1.validDateTime)(dt)) {
                return this.fromDateTime(dt, text);
            }
        }
        return;
    }
    // These are all formats I've seen in the wild from exiftool's output.
    // More iterations might make sense, like "d MMM, y" or "MMM d, y", but I
    // want to be constrained in what I consider a valid date to lessen the
    // chance of misinterpreting a given value.
    static fromExifStrict(text) {
        return StrictExifRE.test((0, String_1.toS)(text).trim())
            ? this.fromPatterns(text, ["y:MM:dd", "y-MM-dd", "y:M:d"])
            : undefined;
    }
    static fromYearMonth(text) {
        const textStr = (0, String_1.toS)(text).trim();
        if (!YearMonthRE.test(textStr))
            return undefined;
        for (const fmt of ["y:MM", "y-MM", "y:M", "y-M"]) {
            const dt = luxon_1.DateTime.fromFormat(textStr, fmt);
            if ((0, DateTime_1.validDateTime)(dt)) {
                return new ExifDate(dt.year, dt.month, undefined, textStr);
            }
        }
        return undefined;
    }
    static fromYear(yearValue) {
        const textStr = (0, String_1.toS)(yearValue).trim();
        if (YearOnlyRE.test(textStr)) {
            const year = parseInt(textStr, 10);
            if (!isNaN(year) && year > 0 && year <= 9999) {
                return new ExifDate(year, undefined, undefined, textStr);
            }
        }
        return undefined;
    }
    static fromExifLoose(text) {
        // Unfortunately, Luxon parses "00" and "01" as _today_. So if we don't
        // three non-blank strings parts, reject.
        return LooseExifRE.test((0, String_1.toS)(text).trim())
            ? this.fromPatterns(text, ["MMM d y", "MMMM d y"])
            : undefined;
    }
    static fromEXIF(text) {
        return (0, Maybe_1.firstDefinedThunk)([
            () => this.fromExifStrict(text),
            () => this.fromExifLoose(text),
        ]);
    }
    static fromDateTime(dt, rawValue) {
        return (0, DateTime_1.validDateTime)(dt)
            ? new ExifDate(dt.year, dt.month, dt.day, rawValue)
            : undefined;
    }
    constructor(year, // full year (probably 2019-ish, but maybe Japanese 30-ish). See https://ericasadun.com/2018/12/25/iso-8601-yyyy-yyyy-and-why-your-year-may-be-wrong/
    month, // 1-12, (no crazy 0-11 nonsense from Date!)
    day, // 1-31
    rawValue) {
        this.year = year;
        this.month = month;
        this.day = day;
        this.rawValue = rawValue;
    }
    toDate() {
        return new Date(this.year, (this.month ?? 1) - 1, this.day ?? 1);
    }
    /**
     * @param deltaMs defaults to 12 hours, so toMillis() is in the middle of the day.
     *
     * @return the epoch milliseconds for this day in UTC, plus `deltaMs` milliseconds.
     */
    toMillis(deltaMs = 12 * DateTime_1.HourMs) {
        return this.toDate().getTime() + deltaMs;
    }
    toISOString() {
        return this.toString("-");
    }
    toExifString() {
        return this.toString(":");
    }
    toString(sep = "-") {
        if (this.month == null) {
            return `${this.year}`;
        }
        if (this.day == null) {
            return `${this.year}${sep}${(0, String_1.pad2)(this.month).join("")}`;
        }
        return `${this.year}${sep}${(0, String_1.pad2)(this.month, this.day).join(sep)}`;
    }
    toJSON() {
        return {
            _ctor: "ExifDate",
            year: this.year,
            month: this.month,
            day: this.day,
            rawValue: this.rawValue,
        };
    }
    static fromJSON(json) {
        return new ExifDate(json.year, json.month, json.day, json.rawValue);
    }
    /**
     * @returns true if this is a partial date (year-only or year-month)
     */
    isPartial() {
        return this.month == null || this.day == null;
    }
    /**
     * @returns true if this is a year-only date
     */
    isYearOnly() {
        return this.month == null;
    }
    /**
     * @returns true if this is a year-month date (no day)
     */
    isYearMonth() {
        return this.month != null && this.day == null;
    }
    /**
     * @returns true if this is a full date (year, month, and day)
     */
    isFullDate() {
        return this.month != null && this.day != null;
    }
}
exports.ExifDate = ExifDate;
//# sourceMappingURL=ExifDate.js.map