/**
 * ZDDC — shared naming convention library
 *
 * Canonical implementation of all ZDDC filename, folder name, tracking number,
 * revision, and status logic.  Included in every tool's build via shared/zddc.js.
 *
 * Exposed as window.zddc (plain global) so it works with every tool's module
 * pattern (archive globals, classifier IIFE, transmittal IIFE, mdedit globals).
 *
 * Public API
 * ----------
 *   zddc.parseFilename(str)        → ParsedFile | null
 *   zddc.parseFolder(str)          → ParsedFolder | null
 *   zddc.parseRevision(str)        → ParsedRevision
 *   zddc.formatFilename(parts)     → string
 *   zddc.formatFolder(parts)       → string
 *   zddc.compareRevisions(a, b)    → number  (-1 | 0 | 1)
 *   zddc.isValidStatus(str)        → boolean
 *   zddc.STATUSES                  → string[]
 *
 * ParsedFile   { trackingNumber, revision, status, title, extension }
 * ParsedFolder { date, trackingNumber, status, title }
 * ParsedRevision { base, modifier, modifierType, modifierNumber, isDraft, full }
 */

(function (root) {
    'use strict';

    // ── Valid status codes ───────────────────────────────────────────────────

    /**
     * Complete list of valid ZDDC document status codes.
     * '---' denotes an unknown or not-yet-assigned status.
     */
    var STATUSES = [
        '---',
        'IFA', 'IFB', 'IFC', 'IFD', 'IFI', 'IFP', 'IFR', 'IFU',
        'REC',
        'RSA', 'RSB', 'RSC', 'RSD', 'RSI',
    ];

    var STATUS_SET = {};
    for (var _i = 0; _i < STATUSES.length; _i++) {
        STATUS_SET[STATUSES[_i]] = true;
    }

    function isValidStatus(str) {
        return !!STATUS_SET[str];
    }

    // ── Filename parsing ─────────────────────────────────────────────────────

    /**
     * Canonical file regex.
     * Matches: TRACKING_REVISION (STATUS) - TITLE.EXT
     *
     * Tracking number: no underscores, no whitespace.
     * Revision:        no whitespace, no parentheses.
     * Status:          anything inside parentheses (validated separately).
     * Title:           everything up to the last dot.
     * Extension:       after the last dot (lowercased by parseFilename).
     */
    var FILE_RE = /^([^_\s]+)_([^\s()_]+)\s*\(([^)]+)\)\s*-\s*(\S.*\S|\S)\.\s*([^\s.]+)$/;

    /**
     * Parse a ZDDC filename.
     *
     * @param {string} filename
     * @returns {{ trackingNumber: string, revision: string, status: string,
     *             title: string, extension: string, valid: boolean } | null}
     *   null only if filename is falsy.
     *   `valid` is true when all fields matched the ZDDC pattern.
     */
    function parseFilename(filename) {
        if (!filename) { return null; }

        var match = filename.match(FILE_RE);

        if (!match) {
            var lastDot = filename.lastIndexOf('.');
            return {
                trackingNumber: '',
                revision:       '',
                status:         '',
                title:          lastDot > 0 ? filename.substring(0, lastDot) : filename,
                extension:      lastDot > 0 ? filename.substring(lastDot + 1).toLowerCase() : '',
                valid:          false,
            };
        }

        return {
            trackingNumber: match[1].trim(),
            revision:       match[2].trim(),
            status:         match[3].trim(),
            title:          match[4].trim(),
            extension:      match[5].toLowerCase(),
            valid:          true,
        };
    }

    // ── Folder name parsing ──────────────────────────────────────────────────

    /**
     * Transmittal folder regex.
     * Matches: YYYY-MM-DD_TRACKING (STATUS) - TITLE
     */
    var FOLDER_RE = /^(\d{4}-\d{2}-\d{2})_([^_\s(]+)\s*\(([^)]+)\)\s*-\s*(.+)$/;

    /**
     * Parse a ZDDC transmittal folder name.
     *
     * @param {string} foldername
     * @returns {{ date: string, trackingNumber: string, status: string,
     *             title: string, valid: boolean } | null}
     *   null only if foldername is falsy.
     */
    function parseFolder(foldername) {
        if (!foldername) { return null; }

        var match = foldername.match(FOLDER_RE);

        if (!match) {
            return {
                date:            '',
                trackingNumber:  '',
                status:          '',
                title:           foldername,
                valid:           false,
            };
        }

        return {
            date:           match[1],
            trackingNumber: match[2].trim(),
            status:         match[3].trim(),
            title:          match[4].trim(),
            valid:          true,
        };
    }

    // ── Revision parsing ─────────────────────────────────────────────────────

    /**
     * Modifier sub-regex: +LETTER DIGITS  e.g. +C1, +B2, +N1, +Q1
     * The draft prefix (~) may appear inside the modifier: A+~C1
     */
    var MODIFIER_RE = /^\+(~?)([A-Za-z])(\d+)$/;

    /**
     * Parse a ZDDC revision string.
     *
     * Revision grammar:
     *   revision     = ['~'] base ['+' ['~'] modifier_letter modifier_number]
     *   base         = letter(s) | digit(s) | date(YYYY-MM-DD)
     *   modifier     = letter + digits   e.g. C1, B2, N1, Q1
     *
     * @param {string} revision
     * @returns {{
     *   base: string,
     *   modifier: string,       full modifier string e.g. '+C1', '' if none
     *   modifierType: string,   modifier letter e.g. 'C', '' if none
     *   modifierNumber: number, modifier number e.g. 1, 0 if none
     *   modifierIsDraft: boolean,
     *   isDraft: boolean,       true if base revision starts with ~
     *   full: string,           original input
     * }}
     */
    function parseRevision(revision) {
        var raw = (revision || '').toString();

        // Split on '+' to separate base from optional modifier
        var plusIdx = raw.indexOf('+');
        var basePart     = plusIdx === -1 ? raw : raw.substring(0, plusIdx);
        var modifierPart = plusIdx === -1 ? ''  : raw.substring(plusIdx);

        // Draft flag on the base part
        var isDraft = basePart.startsWith('~');
        var base    = isDraft ? basePart.substring(1) : basePart;

        // Parse modifier
        var modifier        = '';
        var modifierType    = '';
        var modifierNumber  = 0;
        var modifierIsDraft = false;

        if (modifierPart) {
            var mMatch = modifierPart.match(MODIFIER_RE);
            if (mMatch) {
                modifierIsDraft = mMatch[1] === '~';
                modifierType    = mMatch[2].toUpperCase();
                modifierNumber  = parseInt(mMatch[3], 10);
                modifier        = modifierPart;
            } else {
                // Unrecognised modifier — preserve as-is
                modifier = modifierPart;
            }
        }

        return {
            base:           base,
            modifier:       modifier,
            modifierType:   modifierType,
            modifierNumber: modifierNumber,
            modifierIsDraft: modifierIsDraft,
            isDraft:        isDraft,
            full:           raw,
        };
    }

    // ── Revision comparison ──────────────────────────────────────────────────

    /**
     * Classify a base revision string into a sort tier:
     *   0 = date (YYYY-MM-DD)
     *   1 = letter(s)   A, B, AA …
     *   2 = number(s)   0, 1, 2, 1.5 …
     *   3 = other
     */
    function _baseTier(base) {
        if (/^\d{4}-\d{2}-\d{2}$/.test(base)) { return 0; }
        if (/^[A-Za-z]+$/.test(base))          { return 1; }
        if (/^\d+(\.\d+)?$/.test(base))         { return 2; }
        return 3;
    }

    /**
     * Compare two base revision strings.
     * Sort order: dates < letters < numbers < other.
     */
    function _compareBase(a, b) {
        var ta = _baseTier(a);
        var tb = _baseTier(b);
        if (ta !== tb) { return ta - tb; }

        if (ta === 0) { return a < b ? -1 : a > b ? 1 : 0; }       // date lexicographic = chronological
        if (ta === 1) { return a.toUpperCase() < b.toUpperCase() ? -1 : a.toUpperCase() > b.toUpperCase() ? 1 : 0; }
        if (ta === 2) { return parseFloat(a) - parseFloat(b); }
        return a.localeCompare(b);
    }

    /**
     * Compare two ZDDC revision strings for sort ordering.
     *
     * Canonical order (ascending = older → newer):
     *   ~A  <  A  <  A+B1  <  A+C1  <  A+~C2  <  A+C2  <  A+N1  <  A+Q1
     *   <  ~B  <  B  < … < 0 < 1 < 2
     *
     * Rules:
     *   1. Compare base revisions first (dates < letters < numbers).
     *   2. For equal bases, draft (isDraft=true) comes before final.
     *   3. For equal base+draft, no-modifier < has-modifier.
     *   4. For equal base+draft+modifier presence:
     *      a. modifier draft comes before modifier final (modifierIsDraft).
     *      b. Sort modifier by type letter then by number.
     *
     * @param {string} a
     * @param {string} b
     * @returns {number}  negative if a < b, 0 if equal, positive if a > b
     */
    function compareRevisions(a, b) {
        var pa = parseRevision(a);
        var pb = parseRevision(b);

        // 1. Base revision
        var baseCmp = _compareBase(pa.base, pb.base);
        if (baseCmp !== 0) { return baseCmp; }

        // 2. Draft before final (for same base)
        if (pa.isDraft !== pb.isDraft) { return pa.isDraft ? -1 : 1; }

        // 3. No modifier before any modifier
        var aHasMod = pa.modifier !== '';
        var bHasMod = pb.modifier !== '';
        if (aHasMod !== bHasMod) { return aHasMod ? 1 : -1; }

        if (!aHasMod) { return 0; } // both have no modifier

        // 4. Compare modifiers: type → number → draft (draft is a tie-breaker only)
        // 4a. Modifier type letter (B < C < N < Q …)
        if (pa.modifierType !== pb.modifierType) {
            return pa.modifierType < pb.modifierType ? -1 : 1;
        }

        // 4b. Modifier number (1 < 2 …)
        if (pa.modifierNumber !== pb.modifierNumber) {
            return pa.modifierNumber - pb.modifierNumber;
        }

        // 4c. Draft of a modifier comes before the final modifier (same type+number)
        if (pa.modifierIsDraft !== pb.modifierIsDraft) {
            return pa.modifierIsDraft ? -1 : 1;
        }

        return 0;
    }

    // ── Filename / folder formatting ─────────────────────────────────────────

    /**
     * Build a ZDDC filename from its components.
     *
     * @param {{ trackingNumber: string, revision: string, status: string,
     *           title: string, extension: string }} parts
     * @returns {string}  e.g. "123456-EL-SPC-2623_A (IFR) - Specification.pdf"
     */
    function formatFilename(parts) {
        var tn  = (parts.trackingNumber || '').trim();
        var rev = (parts.revision       || '').trim();
        var st  = (parts.status         || '').trim();
        var ttl = (parts.title          || '').trim();
        var ext = (parts.extension      || '').replace(/^\./, '');

        if (!tn || !rev || !st || !ttl) { return ''; }

        var name = tn + '_' + rev + ' (' + st + ') - ' + ttl;
        return ext ? name + '.' + ext : name;
    }

    /**
     * Build a ZDDC transmittal folder name from its components.
     *
     * @param {{ date: string, trackingNumber: string, status: string,
     *           title: string }} parts
     * @returns {string}  e.g. "2025-10-31_123456-EM-SUB-0001 (IFR) - Title"
     */
    function formatFolder(parts) {
        var dt  = (parts.date           || '').trim();
        var tn  = (parts.trackingNumber || '').trim();
        var st  = (parts.status         || '').trim();
        var ttl = (parts.title          || '').trim();

        if (!dt || !tn || !st || !ttl) { return ''; }

        return dt + '_' + tn + ' (' + st + ') - ' + ttl;
    }

    // ── Filename / extension splitting ───────────────────────────────────────

    /**
     * Split a filename into its base name and extension (no leading dot).
     * Treats leading dot ('.gitignore') as no extension.
     *
     * @param {string} filename
     * @returns {{ name: string, extension: string }}
     */
    function splitExtension(filename) {
        if (!filename) { return { name: '', extension: '' }; }
        var lastDot = filename.lastIndexOf('.');
        if (lastDot <= 0) { return { name: filename, extension: '' }; }
        return {
            name:      filename.substring(0, lastDot),
            extension: filename.substring(lastDot + 1).toLowerCase(),
        };
    }

    /**
     * Join a base name and extension. Tolerant of either form ('pdf' or '.pdf').
     * Returns just the name when extension is empty.
     */
    function joinExtension(name, extension) {
        var ext = (extension || '').replace(/^\./, '');
        return ext ? name + '.' + ext : name;
    }

    // ── Public API ───────────────────────────────────────────────────────────

    root.zddc = {
        STATUSES:         STATUSES,
        isValidStatus:    isValidStatus,
        parseFilename:    parseFilename,
        parseFolder:      parseFolder,
        parseRevision:    parseRevision,
        formatFilename:   formatFilename,
        formatFolder:     formatFolder,
        compareRevisions: compareRevisions,
        splitExtension:   splitExtension,
        joinExtension:    joinExtension,
    };

}(typeof window !== 'undefined' ? window : this));
