#!/bin/bash
#
# End-to-end pipeline processing of IXPE Level 1 -> Level 2
# Handles both single observations and automatically merges segmented observations.
#
# Write the version number
VERSION="1.0"
case "$1" in
    -v|--version)
        echo "$0 v$VERSION"
        exit 0
        ;;
esac
#
# History:
# Version 1.0.0 [2026-04-10]: Initial release.
#

# Help Message Trigger
print_help() {
 cat >&2 << _USAGE

      Usage:
           bash $0 ObsID DU DIR [OUTDIR] [KEEP_INTERM]
      Where:
           ObsID         = 8-digit Unique Observation Identification Number
           DU          = Detector Unit Number to process (1, 2, or 3)
           DIR         = Top level directory/folder containing subdir /ObsID/ that
                         contains production pipeline data in subdirectories
                         /event1/ /event_l2/ /auxil/ and /hk/
           OUTDIR      = (Optional) Output directory for processed files. 
                         Defaults to current directory (.) if omitted.
           KEEP_INTERM = (Optional) Keep intermediate files? (y/n). 
                         Defaults to 'n' if omitted.

      Note: If you execute the script without arguments, it will run in 
            interactive mode and prompt you for the inputs.

_USAGE
}

if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    print_help
    exit 0
fi

# Determine if we are running interactively or in automation mode
INTERACTIVE=0
if [ $# -eq 0 ] ; then
    INTERACTIVE=1
fi

OID=${1:-""}
DET=${2:-""}
TOP_ARG=${3:-""}
OUT_INPUT=${4:-""}
KEEP_INTERM_ARG=${5:-""}

if [ -z "$OID" ] ; then
 echo -n " Enter IXPE ObsID: "
 read OID
fi

# if the first digit is not a 0, add one:
if [ -n "`echo $OID | sed 's/0.*//'`" ]; then
 OID="0$OID"
fi

if [ -z "$DET" ] ; then
 echo -n " Enter IXPE DU number: "
 read DET
fi

# Strict check for valid DU integer
if ! [[ "$DET" =~ ^[1-3]$ ]] ; then
 echo "Error: '$DET' is not a valid DU number (must be 1, 2, or 3); exiting."
 exit 1
fi

if [ -n "$TOP_ARG" ] ; then
 TOP="$TOP_ARG/$OID"
else
 echo -n " Enter path to directory containing $OID: "
 read TOP_ARG
 TOP="$TOP_ARG/$OID"
fi

if [ ! -d "$TOP" ] ; then
 echo "Error: '$TOP' is not a valid ObsID directory; exiting."
 exit 1
fi

# Sanity check for required subdirectories
if [ ! -d "$TOP/event_l1" ] ; then
 echo "Error: '$TOP' does not contain an 'event_l1' subdirectory; exiting."
 exit 1
fi

# Output Directory and Intermediate Files Setup
if [ -n "$OUT_INPUT" ]; then
    OUTDIR="$OUT_INPUT"
elif [ $INTERACTIVE -eq 1 ]; then
    read -p " Enter output directory for all files (leave blank or enter '.' for current directory): " OUT_INPUT
    OUTDIR="${OUT_INPUT:-.}"
else
    OUTDIR="." # Default for automated runs
fi

if [ "$OUTDIR" != "." ] && [ "$OUTDIR" != "" ]; then
    if [ ! -d "$OUTDIR" ]; then
        echo " Output directory '$OUTDIR' does not exist. Creating it..."
        mkdir -p "$OUTDIR"
    fi
else
    OUTDIR="."
fi

if [ -n "$KEEP_INTERM_ARG" ]; then
    KEEP_INTERM="$KEEP_INTERM_ARG"
elif [ $INTERACTIVE -eq 1 ]; then
    read -p " Do you want to keep intermediate files after each segment? (y/n; default=n): " KEEP_INTERM
    KEEP_INTERM=${KEEP_INTERM:-n}
else
    KEEP_INTERM="n" # Default for automated runs
fi
echo
# -----------------------------------------------------------

OUTDIR=$(readlink -f "$OUTDIR")
FULL_TOP=$(readlink -f "$TOP")

# Echo Processing Parameters
echo
echo "=========================================================="
echo " IXPE Pipeline Processing Parameters:"
echo "   Observation ID     : $OID"
echo "   Detector Unit      : DU$DET"
echo "   Input Directory    : $FULL_TOP"
echo "   Output Directory   : $OUTDIR"
echo "   Keep Intermediates : $KEEP_INTERM"
echo "=========================================================="
echo

# -----------------------------------------------------------

# Step reporter
print_step() {
    local task_name=$1
    local status_text=$2
    local timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    printf "%s: [%s %s]\n" "$task_name" "$status_text" "$timestamp"
}

# Helper functions for FITS keywords and math
get_key() {
    fkeypar "$1" "$2" >/dev/null 2>&1
    pget fkeypar value | tr -d "'" | sed 's/^[ \t]*//;s/[ \t]*$//'
}
set_key() {
    fthedit "$1" "$2=$3" >/dev/null 2>&1
}
calc_sum() {
    echo "$1 $2" | awk '{printf "%.8f", $1 + $2}'
}
calc_div() {
    echo "$1 $2" | awk '{if ($2 != 0) printf "%.8f", $1 / $2; else print 0}'
}
calc_diff() {
    echo "$1 $2" | awk '{printf "%.8f", $1 - $2}'
}

# Core Pipeline Routine
process_segment() {
    local CID=$1
    local BID=$2
    local TOP_DIR=$3
    
    echo "---------------------------------------------------------"
    echo "Processing ObsID $CID | DU$DET"
    echo "---------------------------------------------------------"
    
    # Direct mapping of fits files
    level1=`echo "$TOP_DIR/event_l1/ixpe${CID}_det${DET}_evt1_v"??".fits"* 2>/dev/null | head -1`
    level2=`echo "$TOP_DIR/event_l2/ixpe${BID}_det${DET}_evt2_v"??".fits"* 2>/dev/null | head -1`
    
    # Check if SOC ran ixpeaspcorr (skip if faint/background source)
    taspcorr="+`ftlist ${level2}[1] K include=XPASPCOR | tail -1 | awk '{print $2}'`+"
    DOASPCOR=y
    if [ "$taspcorr" = "++" ]; then
        DOASPCOR=n
    fi

    if [ $DOASPCOR = y ]; then
        # x,ypixmean needed for ixpeaspcorr; only present in newer data
        xpixmean="+`ftlist ${level2}[1] K include=XPIXMEAN | tail -1 | awk '{print $2}'`+"
        ypixmean="+`ftlist ${level2}[1] K include=YPIXMEAN | tail -1 | awk '{print $2}'`+"
        if [ "$xpixmean" = "++" ]; then xpixmean="-"; else xpixmean=`echo $xpixmean | sed "s/+//g"`; fi
        if [ "$ypixmean" = "++" ]; then ypixmean="-"; else ypixmean=`echo $ypixmean | sed "s/+//g"`; fi
    fi
    
    # hk temperature file input to ixpeinterptemp
    hktemps=`echo "$TOP_DIR/hk/ixpe${CID}_all_pay_132${DET}_v"??".fits"* 2>/dev/null | head -1`
    # initial charge map file for ixpechrgcorr
    chrgmap=`echo "$TOP_DIR/auxil/ixpe${CID}_det${DET}_chgmap1_v"??".fits"* 2>/dev/null | head -1`
    # peak gain map file input to ixpegaincorrpkmap
    pkgainfile=`echo "$TOP_DIR/auxil/ixpe${CID}_det${DET}_ppg1_v"??".fits"* 2>/dev/null | head -1`
    # level1 attitude file (will be cp'd and gunzip'd)
    attitude=`echo "$TOP_DIR/hk/ixpe${CID}_det${DET}_att_v"??".fits"* 2>/dev/null | head -1`
    # attfile_in for ixpedet2j2000, ixpeboomdriftcorr, and ixpemkevt2gti
    attfile_in=$attitude
    # spacecraft adc0110 file for ixpeboomdriftcorr
    adc0110_file=`echo "$TOP_DIR/hk/ixpe${CID}_all_adc_0110_v"??".fits"* 2>/dev/null | head -1`
    # gti file for ixpemkevt2gti
    l2gtifile=`echo "$TOP_DIR/hk/ixpe${CID}_det${DET}_gti_v"??".fits"* 2>/dev/null | head -1`
    # detector service unit housekeeping file for ixpemkevt2gti
    dsuhkfile=`echo "$TOP_DIR/hk/ixpe${CID}_all_pay_1200_v"??".fits"* 2>/dev/null | head -1`

    echo level1=$level1 
    echo level2=$level2 
    echo hktemps=$hktemps
    echo chrgmap=$chrgmap 
    echo pkgainfile=$pkgainfile 
    echo attitude=$attitude 
    echo adc0110_file=$adc0110_file 
    echo l2gtifile=$l2gtifile 
    echo attfile_in=$attfile_in
    echo dsuhkfile=$dsuhkfile
    echo
    date
    echo    

    # Extraction blocks
    # need local copies of some because they are altered in the pipeline
    # or are best used if gunzipped first (g'zipped is assummed):

    l_level1=`basename "$level1"`
    if [ -f "$l_level1" ]; then
        gunzip -f "$l_level1"
    elif [ -f "`basename "$l_level1" .gz`" ]; then
        :
    else
        cp "$level1" .
        gunzip -f "`basename "$level1"`"
    fi
    level1=`echo ixpe${CID}_det${DET}_evt1*.fits`

    l_pkgainfile=`basename "$pkgainfile"`
    if [ -f "$l_pkgainfile" ]; then
        gunzip -f "$l_pkgainfile"
    elif [ -f "`basename "$l_pkgainfile" .gz`" ]; then
        :
    else
        cp "$pkgainfile" .
        gunzip -f "`basename "$pkgainfile"`"
    fi
    pkgainfile=`echo ixpe${CID}_det${DET}_ppg1*`

    l_attitude=`basename "$attitude"`
    if [ -f "$l_attitude" ]; then
        gunzip -f "$l_attitude"
    elif [ -f "`basename "$l_attitude" .gz`" ]; then
        :
    else
        cp "$attitude" .
        gunzip -f "`basename "$attitude"`"
    fi
    attitude=`echo ixpe${CID}_det${DET}_att_*`

    # ixpeevtrecon
    print_step "ixpeevtrecon" "START"
    if [ -s ${CID}_evtrecon_out.fits ]; then
        echo "${CID}_evtrecon_out.fits exists, skipping ixpeevtrecon"
    else
        echo `date +%T` evtrecon start
        if [ -s "$level1" ]; then
            rm -f ${CID}_evtrecon_out.fits ${CID}_recon.log
            ixpeevtrecon infile="$level1" outfile=${CID}_evtrecon_out.fits logFile=${CID}_recon.log
        else
            echo "$level1 does not exist, exiting"
            exit 1
        fi
        # following keywords omitted by evtrecon but needed later in pipeline
        ftlist "${level1}[1]" K include='S_V*,RA_OBJ,DEC_OBJ,FCW*,OBSERVER,XP*,OBJECT' \
            outfile=${CID}_fix.keys clobber=yes
        fmodhead ${CID}_evtrecon_out.fits[1] ${CID}_fix.keys
        fmodhead ${CID}_evtrecon_out.fits[0] ${CID}_fix.keys # boomdriftcorr looks in [0] for RA,DEC
        # now clean up ttype and tunit keywords
        rm -f ${CID}_ttu.keys
        ftlist "${level1}[1]" K include='ttype*,tunit*' outfile=${CID}_fix.keys clobber=yes
        head -50 ${CID}_fix.keys > ${CID}_ttu.keys
        fthedit ${CID}_evtrecon_out.fits[1] tunit* deleteall
        fthedit ${CID}_evtrecon_out.fits[1] @${CID}_ttu.keys
        rm -f ${CID}_fix.keys ${CID}_ttu.keys
    fi

    # ixpeinterptemp
    print_step "ixpeinterptemp" "START"
    if [ -s ${CID}_interp_hktemps.fits ]; then
        echo "${CID}_interp_hktemps.fits exists, skipping ixpeinterptemp"
    else
        echo `date +%T` interptemp start
        if [ -s "$hktemps" ]; then
            rm -f ${CID}_interp_hktemps.fits
            ixpeinterptemp infile="$hktemps" outfile=${CID}_interp_hktemps.fits 
            # > ${CID}_interptemp.log 2>&1
        else
            echo "$hktemps does not exist, exiting"
            exit 1
        fi
    fi

    # ixpegaincorrtemp
    print_step "ixpegaincorrtemp" "START"
    if [ -s ${CID}_gaincorrtemp_out.fits ]; then
        echo "${CID}_gaincorrtemp_out.fits exists, skipping ixpegaincorrtemp"
    else
        echo `date +%T` gaincorrtemp start
        if [ -s ${CID}_evtrecon_out.fits -a -s ${CID}_interp_hktemps.fits ]; then
            rm -f ${CID}_gaincorrtemp_out.fits ${CID}_gaincorrtemp.log
            ixpegaincorrtemp infile=${CID}_evtrecon_out.fits outfile=${CID}_gaincorrtemp_out.fits \
                hkfile=${CID}_interp_hktemps.fits tempgainfile=CALDB logfile=${CID}_gaincorrtemp.log
        else
            echo "${CID}_evtrecon_out.fits or ${CID}_interp_hktemps.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpechrgcorr
    print_step "ixpechrgcorr" "START"
    if [ -s ${CID}_chrgcorr_out.fits ]; then
        echo "${CID}_chrgcorr_out.fits exists, skipping ixpechrgcorr"
    else
        echo `date +%T` chrgcorr start
         # need to uncompress initmapfile !
        l_chrgmap=`basename "$chrgmap"`
        if [ -f "$l_chrgmap" ]; then
            gunzip -f "$l_chrgmap"
        elif [ ! -f "`basename "$l_chrgmap" .gz`" ]; then
            cp "$chrgmap" .
            gunzip -f "$l_chrgmap"
        fi
        chrgmap=`echo ixpe${CID}_det${DET}_chgmap1*`
        
        if [ -s ${CID}_gaincorrtemp_out.fits ]; then
            rm -f ${CID}_chrgcorr_out.fits ${CID}_chrgcorr.log
            # captured terminal output to log file
            ixpechrgcorr infile=${CID}_gaincorrtemp_out.fits outfile=${CID}_chrgcorr_out.fits \
                initmapfile="$chrgmap" > ${CID}_chrgcorr.log 2>&1
        else
            echo "${CID}_gaincorrtemp_out.fits does not exist, exiting"
            exit 1
        fi
    fi
    
    # need to check if the hdus have already been chopped, if so ftdelhdu fails
    hlist="+`ftlist "$pkgainfile" H | grep CAL`+"
    if [ "$hlist" != "++" ]; then
        rm -f ${CID}_tmp_pkg_*.fits
        mv "$pkgainfile" ${CID}_tmp_pkg_0.fits
        ftdelhdu ${CID}_tmp_pkg_0.fits[CALC_PEAK] ${CID}_tmp_pkg_1.fits
        ftdelhdu ${CID}_tmp_pkg_1.fits[CALD_PEAK] ${CID}_tmp_pkg_2.fits
        mv ${CID}_tmp_pkg_2.fits "$pkgainfile"
    fi

    # ixpegaincorrpkmap
    print_step "ixpegaincorrpkmap" "START"
    if [ -s ${CID}_gaincorrpkmap_out.fits ]; then
        echo "${CID}_gaincorrpkmap_out.fits exists, skipping ixpegaincorrpkmap"
    else
        echo `date +%T` gaincorrpkmap start
        if [ -s ${CID}_chrgcorr_out.fits ]; then
            rm -f ${CID}_gaincorrpkmap_out.fits ${CID}_gaincorrpkmap.log
            ixpegaincorrpkmap infile=${CID}_chrgcorr_out.fits outfile=${CID}_gaincorrpkmap_out.fits \
                pkgainfile="$pkgainfile" logfile=${CID}_gaincorrpkmap.log
        else
            echo "${CID}_chrgcorr_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpecalcstokes
    print_step "ixpecalcstokes" "START"
    if [ -s ${CID}_calcstokes_out.fits ]; then
        echo "${CID}_calcstokes_out.fits exists, skipping ixpecalcstokes"
    else
        echo `date +%T` calcstokes start
        if [ -s ${CID}_gaincorrpkmap_out.fits ]; then
            rm -f ${CID}_calcstokes_out.fits ${CID}_calcstokes.log
            ixpecalcstokes infile=${CID}_gaincorrpkmap_out.fits outfile=${CID}_calcstokes_out.fits \
                logfile=${CID}_calcstokes.log
        else
            echo "${CID}_gaincorrpkmap_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpeadjmod
    print_step "ixpeadjmod" "START"
    if [ -s ${CID}_adjmod_out.fits ]; then
        echo "${CID}_adjmod_out.fits exists, skipping ixpeadjmod"
    else
        echo `date +%T` adjmod start
        if [ -s ${CID}_calcstokes_out.fits ]; then
            rm -f ${CID}_adjmod_out.fits ${CID}_adjmod.log
            ixpeadjmod infile=${CID}_calcstokes_out.fits outfile=${CID}_adjmod_out.fits writedetqucols=no \
                logfile=${CID}_adjmod.log
        else
            echo "${CID}_calcstokes_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpeweights
    print_step "ixpeweights" "START"
    if [ -s ${CID}_weights_out.fits ]; then
        echo "${CID}_weights_out.fits exists, skipping ixpeweights"
    else
        echo `date +%T` weights start
        if [ -s ${CID}_adjmod_out.fits ]; then
            rm -f ${CID}_weights_out.fits ${CID}_weights.log
            ixpeweights infile=${CID}_adjmod_out.fits outfile=${CID}_weights_out.fits logfile=${CID}_weights.log
            # >>>> swap out STATUS2 column with original from evt1
            fdelcol ${CID}_weights_out.fits[1] STATUS2 confirm=no proceed=yes >/dev/null 2>&1
            faddcol ${CID}_weights_out.fits[1] "${level1}[1]" STATUS2 >/dev/null 2>&1
        else
            echo "${CID}_adjmod_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpedet2j2000
    print_step "ixpedet2j2000" "START"
    if [ -s ${CID}_det2j2000_out.fits ]; then
        echo "${CID}_det2j2000_out.fits exists, skipping ixpedet2j2000"
    else
        echo `date +%T` det2j2000 start
        if [ -s ${CID}_weights_out.fits ]; then
            rm -f ${CID}_det2j2000_out.fits 
            ixpedet2j2000 infile=${CID}_weights_out.fits outfile=${CID}_det2j2000_out.fits \
                attfile_in="$attitude" > ${CID}_det2j2000.log 2>&1
        else
            echo "${CID}_weights_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpeboomdriftcorr
    print_step "ixpeboomdriftcorr" "START"
    if [ -s ${CID}_boomdriftcorr_out.fits ]; then
        echo "${CID}_boomdriftcorr_out.fits exists, skipping ixpeboomdriftcorr"
    else
        echo `date +%T` boomdriftcorr start
        if [ -s ${CID}_det2j2000_out.fits ]; then
            ixpeboomdriftcorr infile=${CID}_det2j2000_out.fits outfile=${CID}_boomdriftcorr_out.fits \
                adc0110_file="$adc0110_file" attfile_in="$attitude" \
                attfile_out=${CID}_boom_att_out.fits 2>${CID}_boomdriftcorr.log
        else
            echo "${CID}_det2j2000_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # Insert ixpeflagbgd here because aspcorr wants to
    # omit bgd events from the x-ray aspect correction, i.e., only
    # want source events in the calculation

    # ixpeflagbgd
    print_step "ixpeflagbgd" "START"
    if [ -s ${CID}_flagbgd_out.fits ]; then
        echo "${CID}_flagbgd_out.fits exists, skipping ixpeflagbgd"
    else
        echo `date +%T` flagbgd start
        if [ -s ${CID}_boomdriftcorr_out.fits ]; then
            rm -f ${CID}_flagbgd_out.fits ${CID}_flagbgd.log
            ixpeflagbgd infile=${CID}_boomdriftcorr_out.fits \
                outfile=${CID}_flagbgd_out.fits 
                # > ${CID}_flagbgd.log 2>&1
        else
            echo "${CID}_boomdriftcorr_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpeaspcorr
    print_step "ixpeaspcorr" "START"
    # Need to set x,y_pix_mean and not use the default.
    # Production pipeline finds the mean centroid in all 3 detectors
    # and uses that for centering; now added to Level 2 header.
    if [ -s ${CID}_aspcorr_out.fits ]; then
        echo "${CID}_aspcorr_out.fits exists, skipping ixpeaspcorr"
    elif [ $DOASPCOR = n ]; then
        echo "extended or background-dominated source; skipping ixpeaspcorr"
        cp -p ${CID}_flagbgd_out.fits ${CID}_aspcorr_out.fits
    else
        echo `date +%T` aspcorr start
        if [ -s ${CID}_flagbgd_out.fits -a -s ${CID}_boom_att_out.fits ]; then
            ixpeaspcorr infile=${CID}_flagbgd_out.fits n=300 outfile=${CID}_aspcorr_out.fits \
                attfile_in=${CID}_boom_att_out.fits attfile_out=${CID}_asp_att_out.fits \
                x_pix_mean="$xpixmean" y_pix_mean="$ypixmean" 2>${CID}_aspcorr.log
        else
            echo "flagbgd_out.fits or boomdriftcorr_att_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # ixpemkevt2gti
    print_step "ixpemkevt2gti" "START"
    if [ -s ${CID}_mkevt2gti_out.fits ]; then
        echo "${CID}_mkevt2gti_out.fits exists, skipping ixpemkevt2gti"
    else
        echo `date +%T` mkevt2gti start
        
        if [ -s ${CID}_aspcorr_out.fits ]; then
            rm -f ${CID}_tmp_*.fits ${CID}_mkevt2gti_out.fits ${CID}_newgti.fits
            ixpemkevt2gti infile=${CID}_aspcorr_out.fits l2gtifile="$l2gtifile" attfile_in="$attitude" \
                dsuhkfile="$dsuhkfile" outfile=${CID}_newgti.fits > ${CID}_mkevt2gti.log 2>&1
            ftselect ${CID}_aspcorr_out.fits ${CID}_tmp_00.fits "gtifilter(\"${CID}_newgti.fits\")"
            ftdelhdu "${CID}_tmp_00.fits[GTI]" ${CID}_mkevt2gti_out.fits
            ftappend ${CID}_newgti.fits[GTI] ${CID}_mkevt2gti_out.fits
        else
            echo "${CID}_aspcorr_out.fits does not exist, exiting"
            exit 1
        fi
    fi

    # Final L2 filtering
    print_step "l2filter" "START"
    L2_FILE="$OUTDIR/level2_${CID}_det${DET}.fits"
    echo `date +%T` filtering start
    if [ -s ${CID}_mkevt2gti_out.fits ]; then
        rm -f ${CID}_tmp_*.fits "$L2_FILE"
        ftselect ${CID}_mkevt2gti_out.fits ${CID}_tmp_0.fits \
            'status.EQ.b0000000x00000000.AND.status2.EQ.b0x0000000000x00x.AND.pi.gt.0.AND.pi.le.374'
        if [ -s ${CID}_newgti.fits ]; then
            ftlist ${CID}_newgti.fits[1] K include='DEADC,*TIME,TST*,TELAPSE,DATE-*' outfile=${CID}_fix.keys clobber=yes
            fthedit ${CID}_tmp_0.fits[0] @${CID}_fix.keys
            fthedit ${CID}_tmp_0.fits[1] @${CID}_fix.keys
            fthedit ${CID}_tmp_0.fits[2] @${CID}_fix.keys
            rm ${CID}_fix.keys
        fi
        
        rm -f ${CID}_add.keys
        echo "IXPEPIPE = `basename $0` /  version of Level 1 to Level 2 user pipeline" >> ${CID}_add.keys
        val=`ls -l $CALDB/data/ixpe/gpd/caldb.indx | awk '{print $NF}' | sed "s'^.*index/caldb.indx''"`
        echo "UCALVERS = '$val' / CALDB version used by IXPEPIPE user pipeline" >> ${CID}_add.keys
        echo "HEASVERS = `ftversion` / HEASoft version used by IXPEPIPE user pipeline" >> ${CID}_add.keys
        fthedit ${CID}_tmp_0.fits[1] @${CID}_add.keys
        rm -f ${CID}_add.keys
        
        ftcopy "${CID}_tmp_0.fits[EVENTS][COL TRG_ID; TIME; STATUS; STATUS2; PI; W_MOM; X; Y; Q; U]" "$L2_FILE"
        rm ${CID}_tmp_0.fits
        
        # Creator Keywords added for compliance
        fparkey "'ixpepipeline'" "${L2_FILE}[0]" CREATOR >/dev/null 2>&1
        fparkey "'ixpepipeline'" "${L2_FILE}[1]" CREATOR >/dev/null 2>&1
        fparkey "'1.0.0'" "${L2_FILE}[0]" CREAT_ID >/dev/null 2>&1
        fparkey "'1.0.0'" "${L2_FILE}[1]" CREAT_ID >/dev/null 2>&1
    else
        echo "${CID}_mkevt2gti_out.fits does not exist, exiting"
        exit 1
    fi
    
    # Intermediates Zipping & Cleanup
    local N="00"
    if [ "$CID" != "$BID" ]; then
        N=${CID: -2}
    fi
    
    # Compress specific intermediate file patterns using front-loaded wildcard
    if [[ "$KEEP_INTERM" =~ ^[Yy] ]]; then
        ZIP_NAME="ixpe${BID}_det${DET}_${N}_intermediatefiles.zip"
        echo "Compressing intermediate files to $ZIP_NAME..."
        zip "$ZIP_NAME" ${CID}_*_out.fits ${CID}_interp_hktemps.fits ${CID}_newgti.fits \
            ${CID}_tmp_*.fits ${CID}_*.log ${CID}_*.keys >/dev/null 2>&1
        if [ $OUTDIR != "." -a `echo $OUTDIR | sed -e "s'/$''"` != `pwd` ]; then
           mv "$ZIP_NAME" "$OUTDIR/"
        fi
    fi
    
    echo "Cleaning up intermediate files..."
    # Explicitly remove the known intermediate files with corrected wildcards
    rm -f ${CID}_*_out.fits ${CID}_interp_hktemps.fits ${CID}_newgti.fits \
          ${CID}_tmp_*.fits ${CID}_*.log ${CID}_*.keys
    rm -f "$level1" "$pkgainfile" "$attitude" "$chrgmap" 2>/dev/null
 
    print_step "segment $CID" "END"
}

# --- Execution ---
SEGMENTED=0
BASE_OBSID=$OID

if [ "${OID: -2}" = "99" ]; then
    SEGMENTED=1
    BASE_OBSID=${OID%99}
    
    SEG_COUNT=0
    while true; do
        TEST_SEG=$((SEG_COUNT + 1))
        TEST_OID=$(printf "%s%02d" "$BASE_OBSID" "$TEST_SEG")
        TEST_FILE=`echo "$TOP/event_l1/ixpe${TEST_OID}_det${DET}_evt1_v"??".fits"* 2>/dev/null | head -1`
        
        if [ -f "$TEST_FILE" ]; then
            SEG_COUNT=$TEST_SEG
        else
            break
        fi
    done
    
    if [ "$SEG_COUNT" -eq 0 ]; then
        echo "Error: No segment files found in $TOP/event_l1/ matching pattern."
        exit 1
    fi
    NUM_SEGMENTS=$SEG_COUNT
fi

if [ $SEGMENTED -eq 1 ]; then
    for (( SEG=1; SEG<=NUM_SEGMENTS; SEG++ )); do
        CID=$(printf "%s%02d" "$BASE_OBSID" "$SEG")
        process_segment "$CID" "$OID" "$TOP"
    done
else
    process_segment "$OID" "$OID" "$TOP"
    exit 0
fi

# Merge Operations
print_step "merger" "START"
OUTPUT_FILE="$OUTDIR/level2_${OID}_det${DET}.fits"
GTI_MERGED="$OUTDIR/gti_merged_${OID}_det${DET}.fits"

SEG_FILES=""
GTI_FILES=""

for (( SEG=1; SEG<=NUM_SEGMENTS; SEG++ )); do
    CID=$(printf "%s%02d" "$BASE_OBSID" "$SEG")
    FILE="$OUTDIR/level2_${CID}_det${DET}.fits"
    SEG_FILES="${SEG_FILES}${FILE},"
    GTI_FILES="${GTI_FILES}${FILE}[GTI],"
done

SEG_FILES=${SEG_FILES%,}
GTI_FILES=${GTI_FILES%,}

TOT_ONTIME=0
TOT_LIVETIME=0

for (( SEG=1; SEG<=NUM_SEGMENTS; SEG++ )); do
    CID=$(printf "%s%02d" "$BASE_OBSID" "$SEG")
    FILE="$OUTDIR/level2_${CID}_det${DET}.fits"
    
    ONTIME=$(get_key "${FILE}[1]" ONTIME)
    LIVETIME=$(get_key "${FILE}[1]" LIVETIME)
    
    TOT_ONTIME=$(calc_sum "$TOT_ONTIME" "${ONTIME:-0}")
    TOT_LIVETIME=$(calc_sum "$TOT_LIVETIME" "${LIVETIME:-0}")
    
    if [ "$SEG" -eq 1 ]; then
        FIRST_TSTART=$(get_key "${FILE}[GTI]" TSTART)
        FIRST_DATE_OBS=$(get_key "${FILE}[0]" DATE-OBS)
    fi
    if [ "$SEG" -eq "$NUM_SEGMENTS" ]; then
        LAST_TSTOP=$(get_key "${FILE}[GTI]" TSTOP)
        LAST_DATE_END=$(get_key "${FILE}[0]" DATE-END)
    fi
done

DEADC=$(calc_div "$TOT_LIVETIME" "$TOT_ONTIME")
TELAPSE=$(calc_diff "$LAST_TSTOP" "$FIRST_TSTART")

rm -f "$OUTPUT_FILE" "$GTI_MERGED"
ftmerge "$SEG_FILES" "$OUTPUT_FILE" clobber=yes >/dev/null 2>&1
mgtime "$GTI_FILES" "$GTI_MERGED" OR >/dev/null 2>&1
fparkey "GTI" "${GTI_MERGED}[1]" EXTNAME >/dev/null 2>&1

fdelhdu "${OUTPUT_FILE}+2" N Y >/dev/null 2>&1
ftappend "${GTI_MERGED}[1]" "$OUTPUT_FILE" >/dev/null 2>&1

for EXT in 0 1; do
    fparkey "$TOT_ONTIME" "${OUTPUT_FILE}[${EXT}]" ONTIME >/dev/null 2>&1
    fparkey "$TOT_LIVETIME" "${OUTPUT_FILE}[${EXT}]" LIVETIME >/dev/null 2>&1
    fparkey "$DEADC" "${OUTPUT_FILE}[${EXT}]" DEADC >/dev/null 2>&1
    fparkey "$FIRST_TSTART" "${OUTPUT_FILE}[${EXT}]" TSTART >/dev/null 2>&1
    fparkey "$LAST_TSTOP" "${OUTPUT_FILE}[${EXT}]" TSTOP >/dev/null 2>&1
    fparkey "$TELAPSE" "${OUTPUT_FILE}[${EXT}]" TELAPSE >/dev/null 2>&1
    fparkey "'$OID'" "${OUTPUT_FILE}[${EXT}]" OBS_ID >/dev/null 2>&1
    fparkey "'2'" "${OUTPUT_FILE}[${EXT}]" FILE_LVL >/dev/null 2>&1
    fparkey "'ixpepipeline'" "${OUTPUT_FILE}[${EXT}]" CREATOR >/dev/null 2>&1
    fparkey "'1.0.0'" "${OUTPUT_FILE}[${EXT}]" CREAT_ID >/dev/null 2>&1
done

fparkey "'$FIRST_DATE_OBS'" "${OUTPUT_FILE}[0]" DATE-OBS >/dev/null 2>&1
fparkey "'$LAST_DATE_END'" "${OUTPUT_FILE}[0]" DATE-END >/dev/null 2>&1
fparkey "'$LAST_DATE_END'" "${OUTPUT_FILE}[1]" DATE-END >/dev/null 2>&1

rm -f "$GTI_MERGED"
print_step "merger" "END"
