use crate::model::{Bank, Pattern, Project};
use crate::state::{CopiedStepData, CopiedSteps};

fn annotate_copy_name(name: &Option<String>) -> Option<String> {
    match name {
        Some(n) if !n.ends_with(" (copy)") => Some(format!("{n} (copy)")),
        Some(n) => Some(n.clone()),
        None => Some("(copy)".to_string()),
    }
}

pub fn shift_patterns_up(
    project: &mut Project,
    bank: usize,
    range: std::ops::RangeInclusive<usize>,
) -> Option<Vec<(usize, usize)>> {
    if *range.start() == 0 {
        return None;
    }
    let slice_start = range.start() - 1;
    let slice_end = *range.end();
    project.banks[bank].patterns[slice_start..=slice_end].rotate_left(1);
    Some((slice_start..=slice_end).map(|p| (bank, p)).collect())
}

pub fn shift_patterns_down(
    project: &mut Project,
    bank: usize,
    range: std::ops::RangeInclusive<usize>,
) -> Option<Vec<(usize, usize)>> {
    if *range.end() >= crate::model::MAX_PATTERNS - 1 {
        return None;
    }
    let slice_start = *range.start();
    let slice_end = range.end() + 1;
    project.banks[bank].patterns[slice_start..=slice_end].rotate_right(1);
    Some((slice_start..=slice_end).map(|p| (bank, p)).collect())
}

pub fn copy_pattern(project: &Project, bank: usize, pattern: usize) -> Pattern {
    project.banks[bank].patterns[pattern].clone()
}

pub fn paste_pattern(
    project: &mut Project,
    bank: usize,
    pattern: usize,
    source: &Pattern,
) {
    let mut pat = source.clone();
    pat.name = annotate_copy_name(&source.name);
    project.banks[bank].patterns[pattern] = pat;
}

pub fn copy_patterns(project: &Project, bank: usize, indices: &[usize]) -> Vec<Pattern> {
    indices
        .iter()
        .map(|&i| project.banks[bank].patterns[i].clone())
        .collect()
}

pub fn paste_patterns(
    project: &mut Project,
    bank: usize,
    start: usize,
    sources: &[Pattern],
) -> usize {
    let mut count = 0;
    for (i, src) in sources.iter().enumerate() {
        let target = start + i;
        if target >= crate::model::MAX_PATTERNS {
            break;
        }
        let mut pat = src.clone();
        pat.name = annotate_copy_name(&src.name);
        project.banks[bank].patterns[target] = pat;
        count += 1;
    }
    count
}

pub fn copy_bank(project: &Project, bank: usize) -> Bank {
    project.banks[bank].clone()
}

pub fn paste_bank(project: &mut Project, bank: usize, source: &Bank) -> usize {
    let mut b = source.clone();
    b.name = annotate_copy_name(&source.name);
    project.banks[bank] = b;
    project.banks[bank].patterns.len()
}

pub fn copy_banks(project: &Project, indices: &[usize]) -> Vec<Bank> {
    indices
        .iter()
        .map(|&i| project.banks[i].clone())
        .collect()
}

pub fn paste_banks(project: &mut Project, start: usize, sources: &[Bank]) -> usize {
    let mut count = 0;
    for (i, src) in sources.iter().enumerate() {
        let target = start + i;
        if target >= crate::model::MAX_BANKS {
            break;
        }
        let mut b = src.clone();
        b.name = annotate_copy_name(&src.name);
        project.banks[target] = b;
        count += 1;
    }
    count
}

pub fn copy_steps(
    project: &Project,
    bank: usize,
    pattern: usize,
    indices: &[usize],
) -> CopiedSteps {
    let pat = project.pattern_at(bank, pattern);
    let mut steps = Vec::new();

    for &idx in indices {
        if let Some(step) = pat.step(idx) {
            steps.push(CopiedStepData {
                script: pat.resolve_script(idx).unwrap_or("").to_string(),
                active: step.active,
                source: step.source,
                original_index: idx,
                name: step.name.clone(),
            });
        }
    }

    CopiedSteps {
        bank,
        pattern,
        steps,
    }
}

pub struct PasteResult {
    pub count: usize,
    pub compile_targets: Vec<usize>,
}

pub fn paste_steps(
    project: &mut Project,
    bank: usize,
    pattern: usize,
    cursor: usize,
    copied: &CopiedSteps,
) -> PasteResult {
    let pat_len = project.pattern_at(bank, pattern).length;
    let same_pattern = copied.bank == bank && copied.pattern == pattern;
    let mut compile_targets = Vec::new();

    for (i, data) in copied.steps.iter().enumerate() {
        let target = cursor + i;
        if target >= pat_len {
            break;
        }
        if let Some(step) = project.pattern_at_mut(bank, pattern).step_mut(target) {
            let source = if same_pattern { data.source } else { None };
            step.active = data.active;
            step.source = source;
            step.name = data.name.clone();
            if source.is_some() {
                step.script.clear();
            } else {
                step.script = data.script.clone();
            }
        }
        compile_targets.push(target);
    }

    PasteResult {
        count: copied.steps.len(),
        compile_targets,
    }
}

pub fn link_paste_steps(
    project: &mut Project,
    bank: usize,
    pattern: usize,
    cursor: usize,
    copied: &CopiedSteps,
) -> Option<usize> {
    if copied.bank != bank || copied.pattern != pattern {
        return None;
    }

    let pat_len = project.pattern_at(bank, pattern).length;

    for (i, data) in copied.steps.iter().enumerate() {
        let target = cursor + i;
        if target >= pat_len {
            break;
        }
        let source_idx = if data.source.is_some() {
            data.source
        } else {
            Some(data.original_index as u8)
        };
        if source_idx == Some(target as u8) {
            continue;
        }
        if let Some(step) = project.pattern_at_mut(bank, pattern).step_mut(target) {
            step.source = source_idx;
            step.script.clear();
        }
    }

    Some(copied.steps.len())
}

pub fn harden_steps(
    project: &mut Project,
    bank: usize,
    pattern: usize,
    indices: &[usize],
) -> usize {
    let pat = project.pattern_at(bank, pattern);
    let resolutions: Vec<(usize, String)> = indices
        .iter()
        .filter_map(|&idx| {
            let step = pat.step(idx)?;
            step.source?;
            let script = pat.resolve_script(idx)?.to_string();
            Some((idx, script))
        })
        .collect();

    let count = resolutions.len();
    for (idx, script) in resolutions {
        if let Some(s) = project.pattern_at_mut(bank, pattern).step_mut(idx) {
            s.source = None;
            s.script = script;
        }
    }

    count
}

pub fn duplicate_steps(
    project: &mut Project,
    bank: usize,
    pattern: usize,
    indices: &[usize],
) -> PasteResult {
    let pat = project.pattern_at(bank, pattern);
    let pat_len = pat.length;
    let paste_at = *indices.last().unwrap() + 1;

    let dupe_data: Vec<(bool, String, Option<u8>)> = indices
        .iter()
        .filter_map(|&idx| {
            let step = pat.step(idx)?;
            let script = pat.resolve_script(idx).unwrap_or("").to_string();
            let source = step.source;
            Some((step.active, script, source))
        })
        .collect();

    let mut compile_targets = Vec::new();
    for (i, (active, script, source)) in dupe_data.into_iter().enumerate() {
        let target = paste_at + i;
        if target >= pat_len {
            break;
        }
        if let Some(step) = project.pattern_at_mut(bank, pattern).step_mut(target) {
            step.active = active;
            step.source = source;
            if source.is_some() {
                step.script.clear();
            } else {
                step.script = script;
            }
        }
        compile_targets.push(target);
    }

    PasteResult {
        count: indices.len(),
        compile_targets,
    }
}
