"""
Output writer module for CRISPR gRNA design.

Writes final guide designs to output files in standard formats.
"""

import os
import pandas as pd


# Column order for designGuides.txt
TXT_COLUMNS = [
    'chr', 'start', 'end', 'locus', 'strand',
    'GuideSequenceWithPAM', 'guideSet', 'RS3_score', 'specificity_score'
]

# Column order for designGuides.bed (BED6 format)
BED_COLUMNS = ['chr', 'start', 'end', 'locus', 'score', 'strand']


def validate_dataframe(guides_df):
    """
    Validate that the DataFrame has all required columns.

    Args:
        guides_df: DataFrame to validate

    Raises:
        ValueError: If required columns are missing
    """
    required_columns = {'chr', 'start', 'end', 'locus', 'strand',
                        'GuideSequenceWithPAM', 'guideSet', 'RS3_score'}

    missing = required_columns - set(guides_df.columns)
    if missing:
        raise ValueError(f"Missing required columns: {', '.join(sorted(missing))}")


def scale_score_to_bed(score, min_val=0, max_val=1000):
    """
    Scale a score from 0-1 to 0-1000 for BED format.

    Args:
        score: Score value (0-1 range)
        min_val: Minimum output value (default 0)
        max_val: Maximum output value (default 1000)

    Returns:
        Integer score in range [min_val, max_val]
    """
    if pd.isna(score):
        return 0  # Default score for missing values

    # Clip to valid range and scale
    clipped = max(0.0, min(1.0, float(score)))
    scaled = int(round(clipped * (max_val - min_val) + min_val))
    return scaled


def write_outputs(guides_df, output_dir):
    """
    Write designGuides.txt and designGuides.bed to output_dir.

    Args:
        guides_df: DataFrame with all required output columns
        output_dir: Directory to write output files

    Output files:
        designGuides.txt - Tab-separated file with columns:
            chr, start, end, locus, strand, GuideSequenceWithPAM,
            guideSet, RS3_score, specificity_score

        designGuides.bed - BED6 format with columns:
            chr, start, end, locus, score (RS3_score scaled to 0-1000), strand

    Raises:
        ValueError: If required columns are missing from guides_df
        OSError: If output_dir cannot be created or written to
    """
    # Validate input
    validate_dataframe(guides_df)

    # Create output directory if it doesn't exist
    os.makedirs(output_dir, exist_ok=True)

    # Prepare data for txt output
    df = guides_df.copy()

    # Ensure specificity_score column exists (may be optional)
    if 'specificity_score' not in df.columns:
        df['specificity_score'] = pd.NA

    # Write designGuides.txt
    txt_path = os.path.join(output_dir, 'designGuides.txt')
    txt_df = df[TXT_COLUMNS]
    txt_df.to_csv(txt_path, sep='\t', index=False)

    # Write designGuides.bed
    bed_path = os.path.join(output_dir, 'designGuides.bed')
    bed_df = df[['chr', 'start', 'end', 'locus', 'RS3_score', 'strand']].copy()
    bed_df['score'] = bed_df['RS3_score'].apply(scale_score_to_bed)
    bed_df = bed_df[BED_COLUMNS]
    # BED files don't have headers
    bed_df.to_csv(bed_path, sep='\t', index=False, header=False)


def read_design_guides_txt(file_path):
    """
    Read a designGuides.txt file back into a DataFrame.

    Args:
        file_path: Path to designGuides.txt file

    Returns:
        DataFrame with guide information
    """
    return pd.read_csv(file_path, sep='\t')


def read_design_guides_bed(file_path):
    """
    Read a designGuides.bed file back into a DataFrame.

    Args:
        file_path: Path to designGuides.bed file

    Returns:
        DataFrame with guide information
    """
    return pd.read_csv(file_path, sep='\t', header=None, names=BED_COLUMNS)
