#!/usr/bin/env bash

# ================================
# Script: mkvnote
# Purpose: GUI tool for editing MKV metadata tags
# Focus: NMAAHC archival standards
# ================================

# ================================
# Section 1: Configuration and Constants
# ================================

# ================================
# Section 1.1: Tag Set Definitions
# ================================
MKV_TAG_SET_NMAAHC=(
    "COLLECTION"
    "TITLE"
    "CATALOG_NUMBER" 
    "DESCRIPTION"
    "DATE_DIGITIZED"
    "ENCODER_SETTINGS"
    "ENCODED_BY"
    "ORIGINAL_MEDIA_TYPE"
    "DATE_TAGGED"
    "_TAGGED_BY"
    "TERMS_OF_USE"
    "_PRE_TRANSFER_NOTES"
    "_TRANSFER_NOTES"
    "_ORIGINAL_FPS"
)

# ================================
# Section 1.2: Read Only and Special Tag Configuration
# ================================
RO_TAGS=("ENCODER" "VIDEO_STREAM_HASH" "AUDIO_STREAM_HASH")
MULTILINE_TAGS=("DESCRIPTION" "ENCODER_SETTINGS" "_PRE_TRANSFER_NOTES" "_TRANSFER_NOTES")

# ================================
# Section 2: Terminal Color Definitions
# ================================
BOLD=$(tput bold); RESET=$(tput sgr0); DIM=$(tput dim)
ERROR=$(tput setaf 1); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3)
BLUE=$(tput setaf 4); MAGENTA=$(tput setaf 5); CYAN=$(tput setaf 6); WHITE=$(tput setaf 7)

# ================================
# Section 3: Help and Usage Functions
# ================================

# ================================
# Section 3.1: Version Information
# ================================
VERSION="1.0.0.20260610"
SCRIPT_NAME=$(basename "$0")
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# ================================
# Section 3.2: Logging Functions
# ================================
LOG_FILE=""

_init_log() {
    local INPUT="$1"
    LOG_FILE="${INPUT%.mkv}_mkvnote_tags.log"
    echo "# mkvnote log - $(date -u +"%Y-%m-%dT%H:%M:%SZ")" > "${LOG_FILE}"
    echo "# Version: ${VERSION}" >> "${LOG_FILE}"
    echo "# Input: ${INPUT}" >> "${LOG_FILE}"
    echo "# ========================================" >> "${LOG_FILE}"
}

_log() {
    local LEVEL="$1"
    local MESSAGE="$2"
    [[ -n "${LOG_FILE}" ]] && echo "[$(date +"%H:%M:%S")] [${LEVEL}] ${MESSAGE}" >> "${LOG_FILE}"
}

_log_xml() {
    local XML_FILE="$1"
    [[ -n "${LOG_FILE}" ]] && {
        echo "" >> "${LOG_FILE}"
        echo "# ======== XML CONTENT ========" >> "${LOG_FILE}"
        cat "${XML_FILE}" >> "${LOG_FILE}"
        echo "" >> "${LOG_FILE}"
        echo "# ======== END XML ========" >> "${LOG_FILE}"
    }
}

# ================================
# Section 3.3: Help Menu Function
# ================================
_show_help() {
    cat << EOF

${BOLD}${CYAN}╔══════════════════════════════════════════════════════════════════════════════╗
║                    mkvnote - Matroska Metadata Editor                        ║
╚══════════════════════════════════════════════════════════════════════════════╝${RESET}

${BOLD}DESCRIPTION${RESET}
    GUI tool for editing MKV file metadata tags according to NMAAHC archival
    standards. Provides a Qt GUI (mkvnote-gui) for viewing and modifying tags
    embedded in Matroska video files.

    mkvnote accepts either a <mkv_file> or <csv_file> as an input. If an mkv file
    is used then a data entry GUI window will open. If a csv is used, the mkvnote
    will embed the metadata of the csv into the associated mkv files. Note that
    with a csv input the first column must be 'filename' and contain a path to
    the associated mkv file.

${BOLD}USAGE${RESET}
    ${GREEN}\$${RESET} ${SCRIPT_NAME} [-c|-i|-d] <mkv_file>
    ${GREEN}\$${RESET} ${SCRIPT_NAME} <csv_file>
    ${GREEN}\$${RESET} ${SCRIPT_NAME} [options]

${BOLD}OPTIONS${RESET}
    ${CYAN}-h, --help${RESET}            Show this help message and exit
    ${CYAN}-v, --version${RESET}         Show version information and exit

${BOLD}TAG CATEGORIES${RESET}
    ${MAGENTA}Read-Only (Technical):${RESET}
        ENCODER, VIDEO_STREAM_HASH, AUDIO_STREAM_HASH, ATTACHMENTS

    ${MAGENTA}NMAAHC Standard Tags:${RESET}
        COLLECTION, TITLE, CATALOG_NUMBER, DESCRIPTION, DATE_DIGITIZED,
        ENCODER_SETTINGS, ENCODED_BY, ORIGINAL_MEDIA_TYPE, DATE_TAGGED,
        _TAGGED_BY, TERMS_OF_USE, _PRE_TRANSFER_NOTES, _TRANSFER_NOTES,
        _ORIGINAL_FPS

${BOLD}OUTPUT${RESET}
    When tags are written (GUI or CSV mode), mkvnote creates a log file:
    ${CYAN}<filename>_mkvnote_tags.log${RESET}

    The log records timestamps, tag values, the XML written to the file,
    and mkvpropedit results — useful for auditing or debugging.

${BOLD}EXAMPLES${RESET}
    ${GREEN}\$${RESET} ${SCRIPT_NAME} video.mkv
    ${GREEN}\$${RESET} ${SCRIPT_NAME} /path/to/archive/preservation_master.mkv
    ${GREEN}\$${RESET} ${SCRIPT_NAME} --help

${BOLD}ADDITIONAL OPTIONS${RESET}

${CYAN}-c|--csv${RESET} matroska input and csv output

    To produce a csv output from a list of mkv files:
    ${GREEN}\$${RESET} ${SCRIPT_NAME} -c file1.mkv file2.mkv file3.mkv > my.csv

    The output can then be edited to write back into the source files with "$(basename "${0}") my.csv"

${CYAN}-i|--info${RESET} print info about each file

    Print global tags in a formatted table with aligned columns:

    ${GREEN}\$${RESET} ${SCRIPT_NAME} -i file1.mkv file2.mkv file3.mkv

${CYAN}-ii${RESET} print info with literal output

    Same as -i but adds literal TAG=(value) output for debugging. The
    parentheses make value boundaries visible, helpful for spotting
    trailing whitespace or empty strings that are hard to see in the
    formatted table:

    ${GREEN}\$${RESET} ${SCRIPT_NAME} -ii file1.mkv file2.mkv file3.mkv

    Line break example:
        -i output:   TITLE                 JPC Archive
                     DESCRIPTION           Line one
                                           Line two
        -ii output:  TITLE=(JPC Archive)
                     DESCRIPTION=(Line one
                     Line two)

    Empty string example:
        -i output:   DESCRIPTION           
        -ii output:  DESCRIPTION=()

    Trailing whitespace example:
        -i output:   DESCRIPTION           Some text
        -ii output:  DESCRIPTION=(Some text   )

${CYAN}-d|--drop${RESET} delete the global tags

    This option is mostly used for debugging. It will remove all global tags (except the ffmpeg ENCODER
    tag) from all input mkv files. Warning this is a destructive, irreversible process.

${CYAN}-s|--set${RESET} set a global tag

        Set a global tag KEY=VALUE in one or more mkv files.
        By default, overwrites any existing value for that key.
        Use -n or --no-overwrite to fail if the key already has a value.

        $ mkvnote -s KEY=VALUE file1.mkv file2.mkv
        $ mkvnote -s KEY=VALUE -n file1.mkv
        
        Example: mkvnote --set Lyrics="First I tried mkvnote ....." file.mkv

${CYAN}-s|--set${RESET} prevent overwriting data while setting a tag

        See the -s option above.

${CYAN}-r|--remove${RESET} remove a global tag

        Remove a global tag key from one or more mkv files.
        Has no effect if the key does not exist.

        $ mkvnote -r KEY file1.mkv file2.mkv

${BOLD}DEPENDENCIES${RESET}
    Required: mkvtoolnix (mkvpropedit, mkvextract), xmlstarlet, mediainfo, perl
    GUI:      mkvnote-gui (Qt-based, see mkvnote-gui)

${BOLD}ABOUT${RESET}
    Developed by Smithsonian NMAAHC in collaboration with Dave Rice
    ${DIM}https://github.com/NMAAHC/nmaahcmm${RESET}

EOF
}

# ================================
# Section 3.4: Version Function
# ================================
_show_version() {
    echo "${SCRIPT_NAME} version ${VERSION}"
    echo "Matroska Metadata Editor for NMAAHC archival standards"
}

# ================================
# Section 3.5: Usage Function (Brief)
# ================================
_show_usage(){
    cat <<EOF

${BOLD}${CYAN}mkvnote${RESET} — Matroska Metadata Editor

${BOLD}USAGE${RESET}
    ${GREEN}\$${RESET} ${SCRIPT_NAME} <mkv_file>                        Open GUI editor for a single MKV file
    ${GREEN}\$${RESET} ${SCRIPT_NAME} <csv_file>                        Batch-embed metadata from CSV into MKV files
    ${GREEN}\$${RESET} ${SCRIPT_NAME} -i <mkv_file> ...                 Inspect tags (formatted table)
    ${GREEN}\$${RESET} ${SCRIPT_NAME} -c <mkv_file> ...                 Export tags to CSV
    ${GREEN}\$${RESET} ${SCRIPT_NAME} -d <mkv_file> ...                 Delete global tags
    ${GREEN}\$${RESET} ${SCRIPT_NAME} -s <KEY>=<VALUE> <mkv_file> ...   Add the specified metadata value to the file
    ${GREEN}\$${RESET} ${SCRIPT_NAME} -r <KEY> ...                      Delete the global tag named 'KEY'

${DIM}For edge-case debugging: ${SCRIPT_NAME} -ii <mkv_file> ...${RESET}
${DIM}Use -h or --help for full documentation and options${RESET}

EOF
exit
}

# ================================
# Section 4: Core Utility Functions
# ================================

# ================================
# Section 4.1: Dependency Validation
# ================================
_check_dependencies(){
    local DEPS_OK=YES
    for DEP in "$@"; do
        command -v "${DEP}" >/dev/null 2>&1 || { echo "${ERROR}✗${RESET} Missing: ${BOLD}${DEP}${RESET}"; DEPS_OK=NO; }
    done
    [[ "${DEPS_OK}" = "NO" ]] && { echo "${ERROR}Installation required. Exiting...${RESET}"; exit 1; }
}

# ================================
# Section 4.2: File and Text Processing Utilities
# ================================
_maketemp(){ mktemp -q -t "$(basename "$0")" || { echo "${ERROR}Can't create temp file${RESET}"; exit 1; }; }
_temprm(){ RM_FILE="${1}" ; if [[ -f "${RM_FILE}" ]] ; then rm "${RM_FILE}" ; fi }
_in_list(){ local item="$1"; shift; for x in "$@"; do [[ "$x" == "$item" ]] && return 0; done; return 1; }
_write_tag_to_xml() {
    local TAG_NAME="$1"
    local TAG_VALUE="$2"
    local XML_DRAFT="$3"
    local ESCAPED_VALUE
    ESCAPED_VALUE="$(printf '%s' "${TAG_VALUE}" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')"
    xml ed --omit-decl --inplace \
        --subnode "/Tags/Tag[(not(Targets) or count(Targets/*)=0) and not(Simple/Name='${TAG_NAME}')]" --type elem -n "Simple" \
        --subnode "/Tags/Tag[not(Targets) or count(Targets/*)=0]/Simple[not(Name)]" --type elem -n "Name" -v "${TAG_NAME}" \
        --subnode "/Tags/Tag[not(Targets) or count(Targets/*)=0]/Simple[not(String)]" --type elem -n "String" -v "${ESCAPED_VALUE}" \
        "${XML_DRAFT}"
}

_write_tags_to_mkv() {
    local XML_DRAFT="$1"
    local INPUT_FILE="$2"
    local MODE="${3:-global}"   # "global" or "all"
    
    _log "INFO" "Writing tags to MKV file"
    MKVPROPEDIT_OUTPUT=$(mkvpropedit --tags "${MODE}:${XML_DRAFT}" "${INPUT_FILE}" 2>&1)
    MKVPROPEDIT_STATUS=$?
    _log "DEBUG" "mkvpropedit exit status: ${MKVPROPEDIT_STATUS}"
    _log "DEBUG" "mkvpropedit output: ${MKVPROPEDIT_OUTPUT}"
    
    if [[ ${MKVPROPEDIT_STATUS} -eq 0 ]]; then
        _log "INFO" "Tags written successfully"
        _log "INFO" "mkvnote completed successfully"
        return 0
    else
        _log "ERROR" "mkvpropedit failed with status ${MKVPROPEDIT_STATUS}"
        _log "ERROR" "Output: ${MKVPROPEDIT_OUTPUT}"
        echo "${ERROR}✗ Error writing tags to ${INPUT_FILE}${RESET}"
        echo "  ${DIM}${MKVPROPEDIT_OUTPUT}${RESET}"
        echo "  ${DIM}See log file for details: ${LOG_FILE}${RESET}"
        return 1
    fi
}

_set_tag(){
    local INPUT_FILE="${1}"
    local KEY="${TAG_SET_PAIR%%=*}"
    local VALUE="${TAG_SET_PAIR#*=}"
    local EXTRACTED_TAGS EXISTING_VALUE XML_DRAFT

    [[ -z "${KEY}" || -z "${VALUE}" ]] && { echo "${ERROR}✗ Invalid KEY=VALUE: ${TAG_SET_PAIR}${RESET}" >&2 ; return 1 ; }

    EXTRACTED_TAGS="$(_maketemp).xml"
    mkvextract tags "${INPUT_FILE}" > "${EXTRACTED_TAGS}" 2>/dev/null

    # Check for existing value if --no-overwrite
    if [[ "${NO_OVERWRITE}" == true ]] ; then
        EXISTING_VALUE="$(xmlstarlet sel -t -v \
            "//Tag[not(Targets) or count(Targets/*)=0]/Simple[Name='${KEY}']/String" \
            "${EXTRACTED_TAGS}" 2>/dev/null)"
        if [[ -n "${EXISTING_VALUE}" ]] ; then
            echo "${ERROR}✗ ${KEY} already set to '${EXISTING_VALUE}' in $(basename "${INPUT_FILE}") — skipping (--no-overwrite)${RESET}" >&2
            rm -f "${EXTRACTED_TAGS}"
            return 1
        fi
    fi

    # Build new XML, merge existing tags, set/overwrite KEY
    XML_DRAFT="$(_maketemp).xml"
    _write_tags_from_extracted "${EXTRACTED_TAGS}" "${KEY}" "${VALUE}" > "${XML_DRAFT}"

    mkvpropedit "${INPUT_FILE}" --tags "global:${XML_DRAFT}" \
        && echo "${GREEN}Set ${KEY}=${VALUE} in $(basename "${INPUT_FILE}")${RESET}" \
        || echo "${ERROR}mkvpropedit failed for $(basename "${INPUT_FILE}")${RESET}" >&2

    rm -f "${EXTRACTED_TAGS}" "${XML_DRAFT}"
}

_remove_tag(){
    local INPUT_FILE="${1}"
    local KEY="${TAG_REMOVE_KEY}"
    local EXTRACTED_TAGS XML_DRAFT

    [[ -z "${KEY}" ]] && { echo "${ERROR}Error: No key specified${RESET}" >&2 ; return 1 ; }

    EXTRACTED_TAGS="$(_maketemp).xml"
    mkvextract tags "${INPUT_FILE}" > "${EXTRACTED_TAGS}" 2>/dev/null

    # Check to see if that metadata key already exists
    local EXISTING_VALUE
    EXISTING_VALUE="$(xmlstarlet sel -t -v \
        "//Tag[not(Targets) or count(Targets/*)=0]/Simple[Name='${KEY}']/String" \
        "${EXTRACTED_TAGS}" 2>/dev/null)"
    if [[ -z "${EXISTING_VALUE}" ]] ; then
        echo "${YELLOW}! ${KEY} not found in $(basename "${INPUT_FILE}") — skipping${RESET}"
        rm -f "${EXTRACTED_TAGS}"
        return 0
    fi

    # Rebuild the XML without the removed key
    XML_DRAFT="$(_maketemp).xml"
    _write_tags_from_extracted "${EXTRACTED_TAGS}" "${KEY}" "" --remove > "${XML_DRAFT}"

    mkvpropedit "${INPUT_FILE}" --tags "global:${XML_DRAFT}" \
        && echo "${GREEN}Removed ${KEY} from $(basename "${INPUT_FILE}")${RESET}" \
        || echo "${ERROR}mkvpropedit failed for $(basename "${INPUT_FILE}")${RESET}" >&2

    rm -f "${EXTRACTED_TAGS}" "${XML_DRAFT}"
}


_write_tags_from_extracted(){
    local EXTRACTED_XML="${1}"
    local KEY="${2}"
    local VALUE="${3}"
    local MODE="${4}"   # "--remove" to delete key, otherwise set/overwrite

    # Get all existing global tag key=value pairs
    declare -A TAGS
    while IFS='=' read -r k v ; do
        [[ -n "${k}" ]] && TAGS["${k}"]="${v}"
    done < <(xmlstarlet sel -t \
        -m "//Tag[not(Targets) or count(Targets/*)=0]/Simple" \
        -v "Name" -o "=" -v "String" -n \
        "${EXTRACTED_XML}" 2>/dev/null)

    # Apply the change
    if [[ "${MODE}" == "--remove" ]] ; then
        unset "TAGS[${KEY}]"
    else
        TAGS["${KEY}"]="${VALUE}"
    fi

    # Write the XML
    echo '<?xml version="1.0"?>'
    echo '<Tags>'
    echo '  <Tag>'
    echo '    <Targets />'
    for k in "${!TAGS[@]}" ; do
        echo "    <Simple>"
        echo "      <Name>${k}</Name>"
        echo "      <String>${TAGS[$k]}</String>"
        echo "    </Simple>"
    done
    echo '  </Tag>'
    echo '</Tags>'
}

SCRIPT_DIR="$(realpath -- "$(dirname -- "${BASH_SOURCE[0]}")")"
if [ -d "${SCRIPT_DIR}/../lib/mkvnote/bin" ] ; then
    export PATH="${SCRIPT_DIR}/../lib/mkvnote/bin:${PATH}"
fi

_check_dependencies csvprintf xmlstarlet mkvpropedit mkvextract xml2csv cowsay

if [[ ! -t 0 ]] && [[ $# -gt 0 ]]; then
    STDIN_TMP="$(_maketemp).csv"
    cat > "${STDIN_TMP}"
    set -- "${STDIN_TMP}" "$@"
fi

# Handle no arguments
if [[ $# -eq 0 ]]; then
    if [[ -t 0 ]]; then
        echo "${ERROR}✗ No input file provided${RESET}"
        _show_usage
        exit 1
    else
        if [[ -x "${SCRIPT_DIR}/mkvnote-gui" ]]; then
            "${SCRIPT_DIR}/mkvnote-gui"
        elif [[ -x "${SCRIPT_DIR}/_build/mkvnote-gui" ]]; then
            "${SCRIPT_DIR}/_build/mkvnote-gui"
        elif command -v mkvnote-gui &>/dev/null; then
            mkvnote-gui
        else
            echo "${ERROR}mkvnote-gui not found.${RESET}" >&2
            exit 1
        fi
        exit 0
    fi
fi

# Parse arguments
while [[ $# -gt 0 ]]; do
    case "$1" in
        -h|--help)
            _show_help
            exit 0
            ;;
        -v|--version)
            _show_version
            exit 0
            ;;
        -c|--csv)
            MKV2CSV="Y"
            ;;
        -i|--info)
            MKVINFO="Y"
            ;;
        -ii)
            MKVINFO="Y"
            MKVINFO_RAW="Y"
            ;;
        -d|--drop)
            MKVGLOBALRM="Y"
            ;;
        -s|--set)
            TAG_SET_PAIR="${2}"
            shift
            ;;
        -n|--no-overwrite)
            NO_OVERWRITE=true
            ;;
        -r|--remove)
            TAG_REMOVE_KEY="${2}"
            shift
            ;;
        -*)
            echo "${ERROR}✗ Unknown option: $1${RESET}"
            _show_usage
            exit 1
            ;;
        *)
            # If extra options, let's check to make sure they're files
            if [[ ! -f "${1}" ]]; then
                echo "${ERROR}✗ ${1} is expected to be a file, but it's not.${RESET}"
                _show_usage
                exit 1
            else
                break
            fi
            ;;
    esac
    shift
done

if [[ "${#}" -eq 0 ]]; then
    _show_usage
fi

INPUT_FILE="${1}"
INPUT_FILES=("$@")
EXTENSION="${INPUT_FILE##*.}"

if [[ -z "${INPUT_FILE}" ]]; then
    echo "${ERROR}✗ No input file provided${RESET}"
    _show_usage
    exit 1
fi

if [[ ! -f "${INPUT_FILE}" ]]; then
    echo "${ERROR}✗ File not found: ${INPUT_FILE}${RESET}"
    exit 1
fi

if [[ ! "${INPUT_FILE}" =~ \.[Mm][Kk][Vv]$ && ! "${INPUT_FILE}" =~ \.[Cc][Ss][Vv]$ && ! "${MKV2CSV}" = "Y" ]]; then
    echo "${YELLOW}! Warning: File does not have .mkv or .csv extensions${RESET}"
fi

if [[ "${EXTENSION}" == "csv" ]] ; then
    if [[ "${MKVINFO}" == "Y" || "${MKV2CSV}" == "Y" ]]; then
        echo "${ERROR}! Error: -i or -c flags do not work with a CSV input.${RESET}"
        exit 1
    fi
    CSV_XML="$(_maketemp).xml"
    csvprintf -X -e UTF-8 -f "${INPUT_FILE}" > "${CSV_XML}"
    ELEMENT_LIST="$(xmlstarlet sel -t -m "/csv/row[1]/*" -v "name()" -n "${CSV_XML}")"
    FILENAME_FOUND=0
    echo "Examining the csv: ${CSV_XML}."
    while read -r ELEMENT_VALUE ; do
        if [[ "${ELEMENT_VALUE//_}" == "filename" ]] ; then
            FILENAME_COLUMN_NAME="${ELEMENT_VALUE}"
            ELEMENT_VALUE="filename"
        fi
        echo "Found ${ELEMENT_VALUE}."
        ADDITIONAL_COLUMNS+="_${ELEMENT_VALUE}"
        if [[ "${ELEMENT_VALUE}" == "filename" ]] ; then
            FILENAME_FOUND=1
        fi
    done < <(echo "${ELEMENT_LIST}")

    if [[ "${FILENAME_FOUND}" = "0" ]] ; then
        echo "${BOLD}${ERROR}Error, $(basename "${INPUT_FILE}") does not contain a column called 'filename'. Exiting.${RESET}"
        exit
    fi

    MISSING_FILE=0
    REPEATED_FILENAMES="$(xmlstarlet sel -t -m "/csv/row/___filename" -v . -n  "${CSV_XML}" | sort | uniq -c | grep -v "^ *1 ")"
    if [[ -n "${REPEATED_FILENAMES}" ]] ; then
        echo "${BOLD}${ERROR}Error, ${INPUT_FILE} contains multiple iterations of the same filename.${RESET}"
        echo "${REPEATED_FILENAMES}"
        echo "${BOLD}${ERROR}Comee back when it's fixed.${RESET}"
        exit 1
    fi
    ROW_COUNTER=1
    while read -r FILE_IN_CSV ; do
        ((ROW_COUNTER++))
        if [[ -z "${FILE_IN_CSV}" ]] ; then
            echo "${BOLD}${ERROR}Warning, the filename column on row ${ROW_COUNTER} is empty.${RESET}"
        elif [[ ! -f "${FILE_IN_CSV}" ]] ; then
            echo "${BOLD}${ERROR}Error, $(basename "${FILE_IN_CSV}") is referenced in the csv but not found.${RESET}"
            MISSING_FILE=1
            MISSING_FILE_LIST+="${FILE_IN_CSV} "
        fi
    done < <(xmlstarlet sel -t -m "/csv/row/${FILENAME_COLUMN_NAME}" -v . -n "${CSV_XML}")
    if [[ -z "${FILENAME_COLUMN_NAME}" ]] ; then
        FILENAME_COLUMN_NAME="filename"
    fi
    if [[ "${MISSING_FILE}" != "0" ]] ; then
        echo "${BOLD}${ERROR}Error, Some files in the csv, such as ${MISSING_FILE_LIST}, are not found. Exiting.${RESET}"
        exit
    else
        echo "${BOLD}Nice. All files from the csv are found. Let's tag.${RESET}"
    fi
    while read -r FILE_IN_CSV ; do
        # Initialize log for each MKV file
        _init_log "${FILE_IN_CSV}"
        _log "INFO" "CSV batch mode - tagging from ${INPUT_FILE}"
        
        XML_DRAFT="$(_maketemp).xml"
        mkvextract tags "${FILE_IN_CSV}" > "${XML_DRAFT}"
        EXISTING_TAGS="$(xmlstarlet sel -t -m '/Tags/Tag[not(Targets) or count(Targets/*)=0]/Simple/Name' -v . -n "${XML_DRAFT}" | awk '!seen[$0]++')"
        _log "INFO" "Extracting existing tags"
        _log "DEBUG" "Temp XML file: ${XML_DRAFT}"
        
        echo "${BOLD}Tagging ${FILE_IN_CSV} with: ${RESET}"
        echo "---------------------|---------------------"

        TAG_COUNT=0
        while read -r TAG_NAME ; do
            TAG_VALUE="$(xmlstarlet sel -t -m "/csv/row[${FILENAME_COLUMN_NAME}='${FILE_IN_CSV}']/${TAG_NAME}" -v "." -n "${CSV_XML}")"
            if [[ "${TAG_NAME//_}" == "filename" ]] ; then
                TAG_NAME="filename"
            fi

            if ! xmlstarlet sel -t -m '/Tags/Tag[not(Targets) or count(Targets/*)=0]' -v '.' -n "${XML_DRAFT}" | grep -q . ; then
              xmlstarlet ed -P -L \
                -s '/Tags' -t elem -n 'Tag' \
                -s '/Tags/Tag[last()]' -t elem -n 'Targets' \
                "${XML_DRAFT}"
              echo "Added a new <Tag> with empty <Targets> with ${FILE_IN_CSV}."
              _log "DEBUG" "Added new Tag element with empty Targets"
            fi

            if [[ -n "${TAG_VALUE// /}" && "${TAG_NAME//_}" != "filename" ]] ; then
                ((TAG_COUNT++))
                _log "INFO" "Setting tag: ${TAG_NAME}"
                TAG_VALUE="$(printf '%s' "${TAG_VALUE}" | sed 's/&amp;/\&/g; s/&lt;/</g; s/&gt;/>/g; s/&quot;/"/g; s/&apos;/'"'"'/g')"
                TAG_VALUE_ESC="$(printf '%s' "${TAG_VALUE}" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g')"
                if echo "${EXISTING_TAGS}" | grep -Fxq "${TAG_NAME}"; then
                    xml ed --omit-decl -P --inplace \
                        --update "(/Tags/Tag[Simple/Name='${TAG_NAME}'])[last()]/Simple[Name='${TAG_NAME}']/String" \
                        -v "${TAG_VALUE_ESC}" "${XML_DRAFT}"
                else
                    _write_tag_to_xml "${TAG_NAME}" "${TAG_VALUE}" "${XML_DRAFT}"
                fi
            fi
            printf "%-22s" "$TAG_NAME"
            echo "$TAG_VALUE" | sed 's/&amp;/\&/g; s/&lt;/</g; s/&gt;/>/g; s/&quot;/"/g' | sed '1n; s/^/                      /'

        done < <(xmlstarlet sel -t -m "/csv/row[${FILENAME_COLUMN_NAME}='${FILE_IN_CSV}']/*[normalize-space(.) != '']" -v "name()" -n "${CSV_XML}")
        echo

        _log "INFO" "XML build complete with ${TAG_COUNT} tags"
        _log_xml "${XML_DRAFT}"
        
        _write_tags_to_mkv "${XML_DRAFT}" "${FILE_IN_CSV}" "all"

    done < <(xmlstarlet sel -t -m "/csv/row/${FILENAME_COLUMN_NAME}" -v . -n "${CSV_XML}")

    exit
fi

if [[ "${MKV2CSV}" == "Y" ]] ; then
    INPUT_FILES=("$@")
    while [[ "$*" != "" ]] ; do
        INPUT_FILE="${1}"
        shift
        EXTRACTED_TAGS="$(_maketemp).xml"
        XML_DRAFT="$(_maketemp).xml"
        mkvextract tags "${INPUT_FILE}" > "${EXTRACTED_TAGS}"
        TAGS_FOUND+="$(xmlstarlet sel -t -m "/Tags/Tag[Targets='' or not(Targets) or (Targets/TargetTypeValue='50' and not(Targets/TrackUID))]/Simple" -v "Name" -n "${EXTRACTED_TAGS}")"$'\n'
    done
    UNIQUE_TAGS="$(awk 'NF && !seen[$0]++' <<< "$TAGS_FOUND")"

    CSV_XML="$(_maketemp).xml"
    echo "<csv/>" > "${CSV_XML}"
    for INPUT_FILE in "${INPUT_FILES[@]}"; do
        XML_ED_INSTRUCTIONS=()
        EXTRACTED_TAGS="$(_maketemp).xml"
        mkvextract tags "${INPUT_FILE}" > "${EXTRACTED_TAGS}"
        while read -r UNIQUE_TAG ; do
            UNIQUE_TAG_VALUE="$(xmlstarlet sel -t -m "/Tags/Tag[Targets='' or not(Targets) or (Targets/TargetTypeValue='50' and not(Targets/TrackUID))]/Simple[Name='${UNIQUE_TAG}']" -v "String" -n "${EXTRACTED_TAGS}")"
            if [[ "${UNIQUE_TAG}" == "${UNIQUE_TAG// /}" ]] ; then
                XML_ED_INSTRUCTIONS+=(--subnode "/csv/row[not(${UNIQUE_TAG})]" --type elem -n "${UNIQUE_TAG}" -v "${UNIQUE_TAG_VALUE}")
            else
                echo "${BOLD}${ERROR}Warning, ${INPUT_FILE} contains a TagName of '${UNIQUE_TAG}'; however TagName is not allowed to contain a space. This value will be ignored while making the csv. See ${EXTRACTED_TAGS} for the tag structure of this file if you like.${RESET}" >&2
            fi
        done <<< "${UNIQUE_TAGS}"

        xml ed --omit-decl --inplace \
            --subnode "/csv" --type elem -n "row" \
            --subnode "/csv/row[not(filename)]" --type elem -n "filename" -v "${INPUT_FILE}" "${XML_ED_INSTRUCTIONS[@]}" "${CSV_XML}"
        EXTRACTED_TAGS="$(_maketemp).txt"
        XML_DRAFT="$(_maketemp).xml"
    done
    CSV_HEADER=$(xmlstarlet sel -t -m "/csv/row[1]/*" -v "name()" -o "," "${CSV_XML}" | sed 's/,$//')
    echo "${CSV_HEADER}"
    xml2csv "${CSV_XML}"
    exit
fi

if [[ "$MKVINFO" == "Y" ]] ; then
    INPUT_FILES=("$@")
    while [[ "$*" != "" ]] ; do
        # get context about the input
        INPUT_FILE="${1}"
        shift
        echo ""
        echo "Global tag report for: ${CYAN}${INPUT_FILE}${RESET}"
        echo "---------------------|---------------------"
        mkvextract tags "${INPUT_FILE}" | xmlstarlet sel -t -m "Tags/Tag[not(Targets) or count(Targets/*)=0]" -m Simple  -v Name -o $'\t' -v String -o $'\037' | xmlstarlet unesc | awk -v RS=$'\037' -F'\t' '{n = split($2, lines, /\r?\n/); printf "%-21s %s\n", $1, lines[1]; for(i=2; i<=n; i++) printf "%-21s %s\n", "", lines[i]; }' 
        if [[ "${MKVINFO_RAW}" == "Y" ]] ; then
            echo ""
            echo "${DIM}Literal: TAG=(value)${RESET}"
            mkvextract tags "${INPUT_FILE}" | xmlstarlet sel -t -m "Tags/Tag[not(Targets) or count(Targets/*)=0]" -m Simple -v Name -o "=(" -v String -o ")" -n | xmlstarlet unesc
        fi
        echo
    done
    exit
fi

if [[ "$MKVGLOBALRM" == "Y" ]] ; then
    INPUT_FILES=("$@")
    echo "This process will delete all the global tags in all the input files. Are you sure? Type 'Y' to proceed, anything else to quit."
    read -p "Really? " DELETE_CHOICE
    if [[ "${DELETE_CHOICE}" =~ ^[Yy]$ ]] ; then
        while [[ "$*" != "" ]] ; do
            INPUT_FILE="${1}"
            shift
            echo "Deleting global tags for ${INPUT_FILE}..."
            TEMP_XML="$(_maketemp).xml"
            mkvextract tags "${INPUT_FILE}" | xmlstarlet ed -d "//Tag[Targets/*]" -d '//Simple[Name != "ENCODER"]' > "${TEMP_XML}"
            mkvpropedit --tags "global:${TEMP_XML}" "${INPUT_FILE}"
            _temprm "${TEMP_XML}"
            echo "Thx. Global tags are gone now from $(basename "${INPUT_FILE}")."
        done
    else
        echo "np"
    fi
    exit
fi

# set a tag
if [[ -n "${TAG_SET_PAIR}" ]] ; then
    for INPUT_FILE in "${INPUT_FILES[@]}" ; do
        _set_tag "${INPUT_FILE}"
    done
    exit 0
fi

# remove a tag
if [[ -n "${TAG_REMOVE_KEY}" ]] ; then
    for INPUT_FILE in "${INPUT_FILES[@]}" ; do
        _remove_tag "${INPUT_FILE}"
    done
    exit 0
fi

# gui mode
if [[ -x "${SCRIPT_DIR}/mkvnote-gui" ]]; then
    "${SCRIPT_DIR}/mkvnote-gui" "${INPUT_FILE}"
elif [[ -x "${SCRIPT_DIR}/_build/mkvnote-gui" ]]; then
    "${SCRIPT_DIR}/_build/mkvnote-gui" "${INPUT_FILE}"
elif [[ -x "/Applications/mkvnote.app/Contents/MacOS/mkvnote-gui" ]]; then
    "/Applications/mkvnote.app/Contents/MacOS/mkvnote-gui" "${INPUT_FILE}"
elif command -v mkvnote-gui &>/dev/null; then
    mkvnote-gui "${INPUT_FILE}"
else
    echo "${ERROR}✗ mkvnote-gui not found. Please install it alongside mkvnote.${RESET}" >&2
    exit 1
fi
