"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TagDescriptions = void 0;
const node_fs_1 = require("node:fs");
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const Lazy_1 = require("./Lazy");
const ListXTask_1 = require("./ListXTask");
// Hand-curated descriptions for important tags.
// These take priority over ExifTool's built-in descriptions.
// Extracted from RequiredTags in mktags.ts
const CuratedDescriptions = {
    Aperture: {
        desc: "Calculated aperture value derived from FNumber or ApertureValue. Read-only composite tag. To write, modify FNumber or ApertureValue instead.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    ApertureValue: {
        desc: "Lens aperture in APEX units. Secondary source for Aperture composite (FNumber takes priority). Formula: ApertureValue = 2 × log₂(FNumber). To write aperture, prefer FNumber as it's more intuitive.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    Artist: {
        desc: "Image creator/photographer name. ExifTool trims trailing whitespace. When MWG module is loaded, this becomes a list-type tag synchronized with XMP-dc:Creator and IPTC:By-line.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    AvgBitrate: {
        desc: "Average bitrate for video/audio files, calculated from media data size divided by duration. Read-only composite tag for QuickTime-based formats (MOV, MP4, etc.).",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    ColorSpace: {
        desc: "Color space of image data. EXIF mandatory tag. Standard values: 1 (sRGB), 0xFFFF (Uncalibrated). Non-standard values: 2 (Adobe RGB, some cameras), 0xFFFD (Wide Gamut RGB, Sony), 0xFFFE (ICC Profile, Sony). Adobe RGB is typically indicated by 'Uncalibrated' with InteropIndex='R03'.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    Copyright: {
        desc: "Copyright notice for the image. MWG composite tag that reconciles EXIF:Copyright, IPTC:CopyrightNotice, and XMP-dc:Rights. Writing updates all three locations to maintain MWG synchronization.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    CreateDate: {
        desc: "When an image was digitized (captured by camera sensor). MWG composite tag that reconciles EXIF:CreateDate, IPTC digital creation fields, and XMP-xmp:CreateDate. Distinct from DateTimeOriginal (when photo was taken) - useful for scanned images. For MOV/MP4 videos, use CreateDate instead of DateTimeOriginal.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    DateTimeOriginal: {
        desc: "When a photo was taken (shutter actuation time). MWG composite tag that reconciles EXIF:DateTimeOriginal, IPTC:DateCreated/TimeCreated, and XMP-photoshop:DateCreated. This is the most commonly used timestamp for photos. Different from CreateDate (digitization) and ModifyDate (file modification).",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    Description: {
        desc: "Image caption or description. MWG composite tag that reconciles EXIF:ImageDescription, IPTC:Caption-Abstract, and XMP-dc:Description. Writing updates all three locations for MWG compliance.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    DOF: {
        desc: "Calculated depth of field based on focal length, aperture, and focus distance. WARNING: This value may be incorrect if the image has been resized. Read-only composite tag.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    Duration: {
        desc: "Video/audio duration. QuickTime: stored in time scale units, converted to seconds using TimeScale. ExifTool formats as 'H:MM:SS' or seconds. Some iPhone live-photo MOV videos may show key frame time instead of total duration.",
        see: "https://exiftool.org/TagNames/QuickTime.html",
    },
    ExifVersion: {
        desc: "EXIF specification version (e.g., '0232' for EXIF 2.32). EXIF mandatory tag. Stored as 4-byte ASCII without separators. ExifTool accepts '2.32' format when writing. Some files incorrectly include null terminators which ExifTool removes.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    ExposureCompensation: {
        desc: "Exposure bias/compensation in EV units (e.g., -0.67, +1.0). Also called ExposureBiasValue in EXIF spec. Signed value indicating deviation from metered exposure.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    ExposureProgram: {
        desc: "Camera exposure mode. Values: 0 (Not Defined), 1 (Manual), 2 (Program AE), 3 (Aperture-priority AE), 4 (Shutter speed priority AE), 5 (Creative/Slow speed), 6 (Action/High speed), 7 (Portrait), 8 (Landscape). Value 9 (Bulb) is non-standard but used by some Canon models.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    ExposureTime: {
        desc: "Shutter speed in seconds (e.g., '1/250'). Primary source for ShutterSpeed composite. To write shutter speed, use this tag directly. BulbDuration takes priority in ShutterSpeed composite if present and > 0.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    FileAccessDate: {
        desc: "File system access date/time. Not stored metadata - file system property. Read-only. Updated when file is read (including by ExifTool).",
        see: "https://exiftool.org/TagNames/File.html",
    },
    FileCreateDate: {
        desc: "File system creation date/time. Not stored metadata - file system property. Writable on some systems. Different from image capture date.",
        see: "https://exiftool.org/TagNames/File.html",
    },
    FileModifyDate: {
        desc: "File system modification date/time. Not stored metadata - file system property. Writable. Different from EXIF ModifyDate which tracks user edits.",
        see: "https://exiftool.org/TagNames/File.html",
    },
    FileName: {
        desc: "Name of the file. Not stored metadata - intrinsic file property. Writable: can rename files. May include full path to set Directory simultaneously.",
        see: "https://exiftool.org/TagNames/File.html",
    },
    FileSize: {
        desc: "Size of the file. Not stored metadata - intrinsic file property. Read-only. Uses SI prefixes by default (1 kB = 1000 bytes).",
        see: "https://exiftool.org/TagNames/File.html",
    },
    FileType: {
        desc: "File type determined from file content, not extension. Not stored metadata - intrinsic file property. Read-only.",
        see: "https://exiftool.org/TagNames/File.html",
    },
    Flash: {
        desc: "Flash status and mode encoded as EXIF bitfield. Combines fired status, return detection, mode (off/on/auto), and red-eye reduction into a single value. ExifTool decodes to human-readable strings.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    FNumber: {
        desc: "Lens aperture as f-number (e.g., 2.8, 5.6). Primary source for Aperture composite. To write aperture, use this tag - it's more intuitive than ApertureValue (which uses APEX units).",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    FocalLength: {
        desc: "Lens focal length in millimeters. Actual focal length, not 35mm equivalent. For 35mm equivalent, see FocalLengthIn35mmFormat or FocalLength35efl composite.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    GPSAltitude: {
        desc: "GPS altitude in meters. Always stored as positive value; sign determined by GPSAltitudeRef. Composite GPSAltitude combines this with Ref to return signed value with 'Above/Below Sea Level' text.",
        see: "https://exiftool.org/TagNames/GPS.html",
    },
    GPSAltitudeRef: {
        desc: "GPS altitude reference. Values: 0 (Above Sea Level, ellipsoidal), 1 (Below Sea Level, ellipsoidal), 2 (Above Sea Level, sea-level ref), 3 (Below Sea Level, sea-level ref). EXIF 3.0 clarifies 0-1 use ellipsoidal surface, 2-3 use sea-level reference. ExifTool also accepts negative numbers when writing.",
    },
    GPSDateTime: {
        desc: "GPS timestamp combining GPS:GPSDateStamp and GPS:GPSTimeStamp fields into a single UTC datetime. Read-only composite tag.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    GPSLatitude: {
        desc: "GPS latitude stored as three rationals (degrees, minutes, seconds). Always positive; hemisphere from GPSLatitudeRef. ExifTool accepts decimal degrees, DMS, or mixed formats when writing. Composite GPSLatitude returns signed decimal.",
        see: "https://exiftool.org/TagNames/GPS.html",
    },
    GPSLatitudeRef: {
        desc: "GPS latitude hemisphere. Valid values: 'N' (North), 'S' (South). When writing, ExifTool accepts signed numbers or direction strings.",
    },
    GPSLongitude: {
        desc: "GPS longitude stored as three rationals (degrees, minutes, seconds). Always positive; hemisphere from GPSLongitudeRef. ExifTool accepts decimal degrees, DMS, or mixed formats when writing. Composite GPSLongitude returns signed decimal.",
        see: "https://exiftool.org/TagNames/GPS.html",
    },
    GPSLongitudeRef: {
        desc: "GPS longitude hemisphere. Valid values: 'E' (East), 'W' (West). When writing, ExifTool accepts signed numbers or direction strings.",
    },
    GPSPosition: {
        desc: "Combined GPS latitude and longitude. Writable composite tag. When written, updates GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef. Accepts Google Maps coordinates (right-click format).",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    GPSTimeStamp: {
        desc: "UTC time of GPS fix. When writing, date is stripped off if present, and time is adjusted to UTC if it includes a timezone.",
        see: "https://exiftool.org/TagNames/GPS.html",
    },
    History: {
        desc: "Tracks editing history of the document. XMP-xmpMM (Media Management) struct type. Flattened fields: HistoryAction, HistoryChanged, HistoryInstanceID, HistoryParameters, HistorySoftwareAgent, HistoryWhen.",
        see: "https://exiftool.org/TagNames/XMP.html#xmpMM",
    },
    ImageDataHash: {
        desc: "Hash of image data (excluding metadata). Only generated if specifically requested via -api RequestAll=3. Hash algorithm configurable via ImageHashType option (MD5 default, also SHA256/SHA512). Supports JPEG, TIFF, PNG, RAW formats, and MOV/MP4 videos (includes audio). Excludes thumbnails/previews but includes JpgFromRaw.",
        see: "https://exiftool.org/TagNames/Extra.html",
    },
    ImageHeight: {
        desc: "Image height in pixels. File-level property derived from file analysis. Read-only.",
        see: "https://exiftool.org/TagNames/File.html",
    },
    ImageSize: {
        desc: "Image dimensions combining width and height from various metadata fields. Read-only composite derived from ImageWidth, ImageHeight, ExifImageWidth, ExifImageHeight, or RawImageCroppedSize.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    ImageUniqueID: {
        desc: "Unique identifier for the image, typically a 32-character hex string. Useful for image deduplication, tracking identity across edits, and linking related files (e.g., RAW + JPEG pairs). May also appear in MakerNotes and XMP.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    ImageWidth: {
        desc: "Image width in pixels. File-level property derived from file analysis. Read-only.",
        see: "https://exiftool.org/TagNames/File.html",
    },
    ISO: {
        desc: "Camera ISO sensitivity rating. In EXIF, this is an array (int16u[n]) that can contain multiple values. Historically called ISOSpeedRatings in EXIF 2.2, renamed to PhotographicSensitivity in EXIF 2.3.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    JpgFromRaw: {
        desc: "Embedded JPEG preview extracted from RAW files. Binary data type. Writable: can update existing embedded images, but cannot create or delete them. Access via BinaryField to get raw bytes or base64 encoding.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    Keywords: {
        desc: "Searchable subject terms for image content. MWG composite tag that reconciles IPTC:Keywords and XMP-dc:Subject. Multi-value fields use semicolon-space ('; ') separators when represented as string. IPTC constraint: max 64 characters per keyword.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    Lens: {
        desc: "Lens information (model name or focal length range). Sources vary by camera manufacturer and file format - may come from MakerNotes, XMP, or standard EXIF fields. For detailed lens identification from lookup tables, see LensID.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    LensID: {
        desc: "Identifies actual lens model using manufacturer-specific lookup tables from partial type information. Configurable: may be extended with user-defined lenses via ExifTool configuration. Read-only composite.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    Make: {
        desc: "Camera/device manufacturer. ExifTool automatically removes trailing whitespace. Used internally by ExifTool for vendor-specific tag handling.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    MediaCreateDate: {
        desc: "Creation date/time for QuickTime/MOV/MP4 media track. Stored as seconds since 1904-01-01 UTC. WARNING: Many cameras incorrectly store local time instead of UTC. For MOV/MP4 videos, use this tag instead of DateTimeOriginal.",
        see: "https://exiftool.org/TagNames/QuickTime.html",
    },
    Megapixels: {
        desc: "Total megapixels calculated from ImageSize composite field. Read-only composite.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    MeteringMode: {
        desc: "Light metering mode used for exposure calculation (e.g., Average, Center-weighted, Spot, Multi-segment, Partial).",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    MIMEType: {
        desc: "MIME type of the file. Not stored metadata - intrinsic file property. Read-only, determined from file content.",
        see: "https://exiftool.org/TagNames/File.html",
    },
    Model: {
        desc: "Camera/device model name. ExifTool automatically removes trailing whitespace. Used internally by ExifTool for vendor-specific tag handling.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    ModifyDate: {
        desc: "When the file was last modified by a user (not automatic processes). MWG composite tag that reconciles EXIF:ModifyDate and XMP-xmp:ModifyDate. Should reflect intentional user edits, not automatic metadata updates.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    Orientation: {
        desc: "Image orientation. Valid values: 1 (Horizontal/normal), 2 (Mirror horizontal), 3 (Rotate 180°), 4 (Mirror vertical), 5 (Mirror horizontal + rotate 270° CW), 6 (Rotate 90° CW), 7 (Mirror horizontal + rotate 90° CW), 8 (Rotate 270° CW). Most images use values 1, 3, 6, and 8.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    OffsetTime: {
        desc: "Timezone offset for ModifyDate (e.g., '+05:30', '-08:00', 'Z'). EXIF 2.31+ tag. Used by SubSecModifyDate composite to produce timezone-aware timestamps. Writing SubSecModifyDate automatically updates this field.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    OffsetTimeDigitized: {
        desc: "Timezone offset for CreateDate (e.g., '+05:30', '-08:00', 'Z'). EXIF 2.31+ tag. Used by SubSecCreateDate composite to produce timezone-aware timestamps. Writing SubSecCreateDate automatically updates this field.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    OffsetTimeOriginal: {
        desc: "Timezone offset for DateTimeOriginal (e.g., '+05:30', '-08:00', 'Z'). EXIF 2.31+ tag. Used by SubSecDateTimeOriginal composite to produce timezone-aware timestamps. Writing SubSecDateTimeOriginal automatically updates this field.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
    PersonInImage: {
        desc: "Name(s) of person(s) shown in the image. XMP-iptcExt namespace. Simpler alternative to MWG RegionInfo when face coordinates aren't needed. Multi-value field; array when multiple people identified.",
        see: "https://exiftool.org/TagNames/XMP.html#iptcExt",
    },
    PreviewImage: {
        desc: "Embedded preview image data extracted from the file. CRITICAL: Writable for updating existing embedded images, but cannot create or delete previews. Can only modify previews that already exist in the file.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    Rating: {
        desc: "Star rating for the image. MWG composite tag from XMP-xmp:Rating. Valid values: -1 (rejected), 0 (unrated), 1-5 (star rating). Note: Rating may appear in EXIF but that's non-standard per MWG.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    RegionInfo: {
        desc: "MWG face/region metadata structure containing AppliedToDimensions and RegionList. With struct=1 (default), contains nested objects. With struct=0, fields are flattened. Used by Lightroom, Picasa, Windows Photo Gallery, digiKam, and other photo organizers.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    RegionName: {
        desc: "Name(s) of identified region(s), typically person names for face regions. From XMP-mwg-rs namespace. Flattened from RegionInfo struct (requires struct=0). For Lightroom compatibility, also add names to Keywords/Subject.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    RegionType: {
        desc: "Type(s) of region(s) identified. Valid values: 'Face', 'Pet', 'BarCode', 'Focus'. From XMP-mwg-rs namespace.",
        see: "https://exiftool.org/TagNames/MWG.html",
    },
    Rotation: {
        desc: "Degrees of clockwise camera rotation for QuickTime/MP4 video files. Special writable: Writing this tag updates QuickTime MatrixStructure for all tracks with a non-zero image size simultaneously. Different from EXIF Orientation tag.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    ShutterSpeed: {
        desc: "Shutter speed combining ExposureTime, ShutterSpeedValue, and BulbDuration. Read-only composite tag. Format typically fractional (e.g., '1/250').",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    SubSecCreateDate: {
        desc: "Creation date with subsecond precision, merging EXIF:CreateDate, SubSecTimeDigitized, and OffsetTimeDigitized. Writable composite: writing updates all three fields simultaneously for high-precision timestamps with timezone information.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    SubSecDateTimeOriginal: {
        desc: "Original datetime with subsecond precision, combining EXIF:DateTimeOriginal, SubSecTimeOriginal, and OffsetTimeOriginal. Writable composite: writing updates all three fields simultaneously. Represents when the photo was originally taken with high precision.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    SubSecModifyDate: {
        desc: "Modification timestamp with subsecond precision, combining EXIF:ModifyDate, SubSecTime, and OffsetTime. Writable composite: writing updates all three fields simultaneously.",
        see: "https://exiftool.org/TagNames/Composite.html",
    },
    ThumbnailImage: {
        desc: "Embedded thumbnail image data. Binary data type. Writable for updating existing thumbnails, but cannot create or delete thumbnails.",
    },
    Title: {
        desc: "Image title. XMP-dc (Dublin Core) namespace - use this standard schema instead of non-standard XMP-xmp:Title. Supports language variants via RFC 3066 codes (e.g., 'Title-fr').",
        see: "https://exiftool.org/TagNames/XMP.html#dc",
    },
    WhiteBalance: {
        desc: "White balance mode. Standard EXIF values: 0 (Auto), 1 (Manual). MakerNotes often contain more detailed WhiteBalance with values like Daylight, Cloudy, Tungsten, Fluorescent, Flash, Custom, etc. EXIF:WhiteBalance has lower priority than MakerNotes version when both exist.",
        see: "https://exiftool.org/TagNames/EXIF.html",
    },
};
/**
 * Provides access to human-readable descriptions for ExifTool metadata tags.
 *
 * Descriptions are sourced from ExifTool's built-in tag database (via `-listx`)
 * and merged with hand-curated descriptions for important tags.
 *
 * **CAUTION**: The first call to an async method (e.g., `getAsync()`, `preload()`,
 * `getAll()`) may take several seconds as it launches ExifTool to retrieve tag
 * information.
 *
 * **SECOND CAUTION**: The in-memory cache can consume significant memory (several
 * MB).
 *
 * **THIRD CAUTION**: The on-disk cache can consume disk space (several MB).
 *
 * **FOURTH CAUTION**: The synchronous `get()` method only works after descriptions
 * are loaded! Be sure to call `preload()` during application initialization for
 * sync access later.
 *
 * **FIFTH CAUTION**: Tag descriptions may vary between ExifTool versions and languages.
 *
 * @example
 * ```typescript
 * import { exiftool } from "exiftool-vendored";
 *
 * const descriptions = new TagDescriptions(exiftool);
 *
 * // Preload during app initialization for sync access later
 * await descriptions.preload();
 *
 * // Sync lookup (instant if preloaded)
 * const desc = descriptions.get("DateTimeOriginal");
 * // => { desc: "When a photo was taken...", see: "https://..." }
 *
 * // Or use async lookup (auto-loads if needed)
 * const desc2 = await descriptions.getAsync("ISO");
 * ```
 */
class TagDescriptions {
    #exiftool;
    #options;
    // Cached value for sync access via get()
    #cacheSync = null;
    constructor(exiftool, options) {
        this.#exiftool = exiftool;
        this.#options = {
            language: "en",
            disableDiskCache: false,
            ...options,
        };
    }
    /**
     * Preload all tag descriptions into memory.
     * Call this during application initialization for instant sync access later.
     *
     * @returns Promise that resolves when descriptions are loaded
     */
    async preload() {
        await this.#cache();
    }
    /**
     * Whether descriptions have been loaded into memory.
     */
    get isLoaded() {
        return this.#cacheSync != null;
    }
    /**
     * Get the number of tag descriptions available.
     * Returns 0 if not yet loaded.
     */
    get size() {
        return this.#cacheSync?.size ?? 0;
    }
    /**
     * Returns curated descriptions only when language is English.
     */
    get #curated() {
        return this.#options.language === "en" ? CuratedDescriptions : {};
    }
    /**
     * Synchronous lookup of a tag description.
     * Returns undefined if descriptions haven't been loaded or tag is unknown.
     *
     * For guaranteed results, call `preload()` first or use `getAsync()`.
     *
     * @param tagName The tag name (e.g., "DateTimeOriginal", "ISO")
     * @returns Tag description or undefined
     */
    get(tagName) {
        return this.#curated[tagName] ?? this.#cacheSync?.get(tagName);
    }
    /**
     * Asynchronous lookup of a tag description.
     * Automatically loads descriptions if not yet loaded.
     *
     * @param tagName The tag name (e.g., "DateTimeOriginal", "ISO")
     * @returns Promise resolving to tag description or undefined
     */
    async getAsync(tagName) {
        return this.#curated[tagName] ?? (await this.#cache()).get(tagName);
    }
    /**
     * Get all loaded tag descriptions.
     * Automatically loads descriptions if not yet loaded.
     *
     * @returns Promise resolving to a readonly Map of tag names to descriptions
     */
    async getAll() {
        return await this.#cache();
    }
    /**
     * Clear the in-memory cache.
     * The disk cache is preserved; call `preload()` to reload from disk.
     */
    clear() {
        this.#cacheSync = null;
        this.#cache.clear();
    }
    #cache = (0, Lazy_1.lazy)(async () => {
        // Get ExifTool version for cache key
        const version = await this.#exiftool.version();
        const lang = this.#options.language ?? "en";
        // Try to load from disk cache first
        if (!this.#options.disableDiskCache) {
            const cached = this.#readDiskCache(version, lang);
            if (cached != null) {
                this.#cacheSync = cached;
                return cached;
            }
        }
        // Query ExifTool for tag information
        const xml = await this.#exiftool.enqueueTask(() => new ListXTask_1.ListXTask());
        // Parse XML and extract descriptions
        const parsed = this.#parseListX(xml, lang);
        // Merge with curated descriptions (curated takes priority, English only)
        for (const [name, desc] of Object.entries(this.#curated)) {
            parsed.set(name, desc);
        }
        // Write to disk cache
        if (!this.#options.disableDiskCache) {
            this.#writeDiskCache(version, lang, parsed);
        }
        this.#cacheSync = parsed;
        return parsed;
    });
    #getCacheDir() {
        return this.#options.cacheDir ?? (0, node_path_1.join)((0, node_os_1.tmpdir)(), "exiftool-vendored");
    }
    #getCacheFilename(version, lang) {
        return (0, node_path_1.join)(this.#getCacheDir(), `tag-descriptions-${version}-${lang}.json`);
    }
    #readDiskCache(version, lang) {
        try {
            const filename = this.#getCacheFilename(version, lang);
            if (!(0, node_fs_1.existsSync)(filename))
                return;
            const data = (0, node_fs_1.readFileSync)(filename, "utf-8");
            const parsed = JSON.parse(data);
            // Validate cache metadata
            if (parsed.version !== version || parsed.language !== lang) {
                return;
            }
            return new Map(Object.entries(parsed.descriptions));
        }
        catch {
            // Cache miss or corrupt - will regenerate
            return;
        }
    }
    #writeDiskCache(version, lang, descriptions) {
        try {
            const dir = this.#getCacheDir();
            if (!(0, node_fs_1.existsSync)(dir)) {
                (0, node_fs_1.mkdirSync)(dir, { recursive: true });
            }
            const filename = this.#getCacheFilename(version, lang);
            const data = JSON.stringify({
                version,
                language: lang,
                generatedAt: new Date().toISOString(),
                descriptions: Object.fromEntries(descriptions),
            }, null, 2);
            (0, node_fs_1.writeFileSync)(filename, data, "utf-8");
        }
        catch {
            // Non-fatal - cache write failure shouldn't break the app
        }
    }
    #parseListX(xml, lang) {
        const descriptions = new Map();
        // Simple regex-based parsing - more robust than DOM for large files
        // Format: <tag ... name='TagName' ...><desc lang='en'>Description</desc>...</tag>
        const tagRegex = /<tag[^>]*\sname=['"]([^'"]+)['"][^>]*>([\s\S]*?)<\/tag>/gi;
        const descRegex = new RegExp(`<desc\\s+lang=['"]${lang}['"]>([^<]+)</desc>`, "i");
        let match;
        while ((match = tagRegex.exec(xml)) !== null) {
            const tagName = match[1];
            const tagContent = match[2];
            if (tagName == null || tagContent == null)
                continue;
            const descMatch = descRegex.exec(tagContent);
            if (descMatch?.[1] != null) {
                const desc = this.#decodeXmlEntities(descMatch[1].trim());
                if (desc.length > 0) {
                    descriptions.set(tagName, { desc });
                }
            }
        }
        return descriptions;
    }
    #decodeXmlEntities(str) {
        return str
            .replace(/&lt;/g, "<")
            .replace(/&gt;/g, ">")
            .replace(/&amp;/g, "&")
            .replace(/&quot;/g, '"')
            .replace(/&apos;/g, "'");
    }
}
exports.TagDescriptions = TagDescriptions;
//# sourceMappingURL=TagDescriptions.js.map