//! Tipos fundacionais e utilitários para Agent Teams paralelos na TUI (T-01..T-05).
//! Nenhuma I/O real ocorre aqui — apenas definições de tipos e funções puras.

use std::path::PathBuf;

use crate::tui::runner::{required_sections_for, StageEvent, StageResult, TokenUsageSample};
use crate::tui::stage::Stage;

/// Status de um slot de agente numa execução paralela.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum AgentSlotStatus {
    Aguardando,
    EmExecucao,
    Concluido,
    Erro,
}

impl AgentSlotStatus {
    pub fn is_terminal(self) -> bool {
        matches!(self, AgentSlotStatus::Concluido | AgentSlotStatus::Erro)
    }

    pub fn label(self) -> &'static str {
        match self {
            AgentSlotStatus::Aguardando => "Aguardando",
            AgentSlotStatus::EmExecucao => "Em execução",
            AgentSlotStatus::Concluido => "Concluído",
            AgentSlotStatus::Erro => "Erro",
        }
    }
}

/// Handle de um agente em execução paralela.
///
/// Não implementa `Clone` porque `Receiver<StageEvent>` não é `Clone`.
pub struct AgentHandle {
    pub task_id: String,
    pub task_label: String,
    pub rx: std::sync::mpsc::Receiver<StageEvent>,
    pub status: AgentSlotStatus,
    pub trace: Vec<String>,
    pub usage: Option<TokenUsageSample>,
    pub result: Option<StageResult>,
    pub fragment_path: PathBuf,
}

/// Referência a uma task que pode ser executada em paralelo.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TaskRef {
    pub id: String,
    pub title: String,
    pub independent: bool,
}

/// Resultado serializado de um slot após conclusão.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SlotResult {
    pub task_id: String,
    pub task_label: String,
    pub status: AgentSlotStatus,
    pub fragment: String,
}

/// Indica se uma etapa suporta execução paralela de Agent Teams.
///
/// Apenas `Execution` e `Review` suportam paralelismo real — as demais
/// etapas são sequenciais por design (contrato SDD).
pub fn supports_parallel(stage: Stage) -> bool {
    matches!(stage, Stage::Execution | Stage::Review)
}

/// Planeja os slots paralelos a partir do Markdown de tasks (T-02).
pub fn plan_parallel_slots(tasks_md: &str) -> Vec<TaskRef> {
    let independent_ids = extract_independent_ids(tasks_md);
    parse_backlog_table(tasks_md, &independent_ids)
}

/// Extrai IDs de tasks declaradas como independentes na seção
/// "Tasks independentes entre si".
fn extract_independent_ids(md: &str) -> std::collections::HashSet<String> {
    let mut ids = std::collections::HashSet::new();
    let mut in_section = false;

    for line in md.lines() {
        let trimmed = line.trim();

        // Detecta entrada na seção de independentes (h2, h3 ou h4)
        if trimmed.starts_with('#') {
            let heading = trimmed.trim_start_matches('#').trim().to_lowercase();
            in_section = heading.contains("independentes");
            continue;
        }

        if !in_section {
            continue;
        }

        // Extrai todos os padrões T-<dígitos> da linha
        let mut rest = trimmed;
        while let Some(pos) = rest.find("T-") {
            let after = &rest[pos + 2..];
            let digits: String = after.chars().take_while(|c| c.is_ascii_digit()).collect();
            if !digits.is_empty() {
                ids.insert(format!("T-{}", digits));
            }
            // avança além do "T-" encontrado para evitar loop infinito
            rest = &rest[pos + 2..];
        }
    }

    ids
}

/// Faz parse da tabela de Backlog e retorna as tasks com flag `independent`.
fn parse_backlog_table(
    md: &str,
    independent_ids: &std::collections::HashSet<String>,
) -> Vec<TaskRef> {
    let mut tasks = Vec::new();
    let mut in_backlog = false;

    for line in md.lines() {
        let trimmed = line.trim();

        // Detecta início da seção Backlog
        if trimmed.starts_with('#') {
            let heading = trimmed.trim_start_matches('#').trim().to_lowercase();
            in_backlog = heading == "backlog";
            continue;
        }

        if !in_backlog {
            continue;
        }

        // Linha de tabela com task: deve começar com "| T-"
        if !trimmed.starts_with("| T-") {
            continue;
        }

        // Divide a linha em células; pipe inicial e final geram células vazias
        let cols: Vec<&str> = trimmed.split('|').collect();
        // cols[0] = "" (antes do primeiro pipe)
        // cols[1] = ID, cols[2] = Título, cols[3] = Estimativa, cols[4] = Dependências
        if cols.len() < 5 {
            continue;
        }

        let id = cols[1].trim().to_string();
        let title = cols[2].trim().to_string();
        let deps = cols[4].trim();

        // Valida que o ID tem o formato T-<dígitos>
        if !id.starts_with("T-") || id[2..].chars().any(|c| !c.is_ascii_digit()) {
            continue;
        }

        let independent = independent_ids.contains(&id) || has_no_declared_dependency(deps);

        tasks.push(TaskRef {
            id,
            title,
            independent,
        });
    }

    tasks
}

fn has_no_declared_dependency(deps: &str) -> bool {
    let normalized = deps.trim();
    normalized.is_empty() || matches!(normalized, "—" | "-" | "–" | "Nenhuma" | "nenhuma")
}

/// Compõe o artefato final consolidado a partir dos resultados dos slots (T-03).
pub fn compose_parallel_artifact(stage: Stage, results: &[SlotResult]) -> String {
    let mut out = String::new();
    out.push_str(&format!("# {} — Agent Teams Paralelos\n\n", stage.label()));
    out.push_str("## Rastreabilidade\n");
    out.push_str("- Origem: `docs/<slug>/04-tasks.md`\n");
    out.push_str(&format!("- Etapa: `{}`\n", stage.key()));
    out.push_str("- Modo: execução paralela por AgentSlot\n\n");
    out.push_str(&traceability_table(results));
    out.push('\n');

    match stage {
        Stage::Execution => append_execution_sections(&mut out, results),
        Stage::Review => append_review_sections(&mut out, results),
        _ => append_generic_sections(&mut out, stage, results),
    }
    ensure_required_sections(&mut out, stage);
    out
}

fn append_execution_sections(out: &mut String, results: &[SlotResult]) {
    out.push_str("## Tarefa\n");
    out.push_str(&format!(
        "- Executar {} task(s) por Agent Teams paralelos.\n",
        results.len()
    ));
    out.push_str("- Cada task recebeu exatamente um AgentSlot no artefato consolidado.\n\n");

    out.push_str("## Resumo da implementação\n");
    append_slot_fragments(out, results);

    out.push_str("## Arquivos alterados\n");
    out.push_str(
        "- Consolidado a partir dos fragmentos emitidos pelos AgentSlots; detalhes ficam nos fragmentos de cada task.\n\n",
    );

    out.push_str("## Testes e evidências\n");
    append_status_evidence(out, results);

    out.push_str("## Riscos e pendências\n");
    append_risks(out, results);
}

fn append_review_sections(out: &mut String, results: &[SlotResult]) {
    out.push_str("## Escopo revisado\n");
    out.push_str(&format!(
        "- Review paralela de {} task(s) planejadas.\n\n",
        results.len()
    ));

    out.push_str("## Evidências\n");
    append_status_evidence(out, results);

    out.push_str("## Achados\n");
    append_slot_fragments(out, results);

    out.push_str("## Testes\n");
    let errors = results
        .iter()
        .filter(|result| result.status == AgentSlotStatus::Erro)
        .count();
    if errors == 0 {
        out.push_str("- Todos os AgentSlots concluíram sem erro reportado.\n\n");
    } else {
        out.push_str(&format!(
            "- {} AgentSlot(s) falharam; revisar pendências antes de aprovar.\n\n",
            errors
        ));
    }

    out.push_str("## Veredito\n");
    if errors == 0 {
        out.push_str("- Aprovado para checkpoint humano.\n\n");
    } else {
        out.push_str("- Aprovado com ressalvas: há AgentSlots em erro registrados acima.\n\n");
    }
}

fn append_generic_sections(out: &mut String, stage: Stage, results: &[SlotResult]) {
    for section in required_sections_for(stage) {
        if *section == "Rastreabilidade" {
            continue;
        }
        out.push_str(&format!("## {section}\n"));
        append_slot_fragments(out, results);
    }
}

fn append_slot_fragments(out: &mut String, results: &[SlotResult]) {
    if results.is_empty() {
        out.push_str("- Nenhum AgentSlot foi planejado.\n\n");
        return;
    }

    for result in results {
        out.push_str(&format!(
            "### {} — {}\n\n",
            result.task_id, result.task_label
        ));
        match result.status {
            AgentSlotStatus::Concluido => {
                let fragment = result.fragment.trim();
                if fragment.is_empty() {
                    out.push_str("> [ERRO] slot concluiu sem fragmento materializado\n\n");
                } else {
                    out.push_str(fragment);
                    out.push_str("\n\n");
                }
            }
            _ => {
                out.push_str("> [ERRO] slot falhou — fragmento ausente\n\n");
            }
        }
    }
}

fn append_status_evidence(out: &mut String, results: &[SlotResult]) {
    if results.is_empty() {
        out.push_str("- Nenhum AgentSlot executado.\n\n");
        return;
    }
    for result in results {
        out.push_str(&format!(
            "- `{}`: {}.\n",
            result.task_id,
            result.status.label()
        ));
    }
    out.push('\n');
}

fn append_risks(out: &mut String, results: &[SlotResult]) {
    let failed = results
        .iter()
        .filter(|result| result.status == AgentSlotStatus::Erro)
        .collect::<Vec<_>>();
    if failed.is_empty() {
        out.push_str("- Nenhuma pendência automática registrada pelos AgentSlots.\n\n");
        return;
    }
    out.push_str("- Resultado parcial aceito ou barreira concluída com falhas isoladas.\n");
    for result in failed {
        out.push_str(&format!(
            "- `{}` — {}: AgentSlot em erro; fragmento não disponível.\n",
            result.task_id, result.task_label
        ));
    }
    out.push('\n');
}

fn ensure_required_sections(out: &mut String, stage: Stage) {
    for section in required_sections_for(stage) {
        if out.contains(&format!("## {section}\n")) {
            continue;
        }
        out.push_str(&format!("## {section}\n"));
        out.push_str("- Seção preservada para compatibilidade com o contrato do artefato.\n\n");
    }
}

/// Gera a tabela de rastreabilidade em Markdown a partir dos resultados dos slots (T-03).
pub fn traceability_table(results: &[SlotResult]) -> String {
    let mut out = String::new();
    out.push_str("| Task ID | AgentSlot | Título | Status |\n");
    out.push_str("|---------|-----------|--------|--------|\n");
    for r in results {
        out.push_str(&format!(
            "| {} | agent-{} | {} | {} |\n",
            escape_markdown_cell(&r.task_id),
            escape_markdown_cell(&r.task_id),
            escape_markdown_cell(&r.task_label),
            r.status.label()
        ));
    }
    out
}

fn escape_markdown_cell(input: &str) -> String {
    input.replace('|', "\\|").replace('\n', " ")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn supports_parallel_returns_true_for_execution() {
        assert!(supports_parallel(Stage::Execution));
    }

    #[test]
    fn supports_parallel_returns_true_for_review() {
        assert!(supports_parallel(Stage::Review));
    }

    #[test]
    fn supports_parallel_returns_false_for_idea() {
        assert!(!supports_parallel(Stage::Idea));
    }

    #[test]
    fn supports_parallel_returns_false_for_prd() {
        assert!(!supports_parallel(Stage::Prd));
    }

    #[test]
    fn supports_parallel_returns_false_for_techspec() {
        assert!(!supports_parallel(Stage::Techspec));
    }

    #[test]
    fn supports_parallel_returns_false_for_tasks() {
        assert!(!supports_parallel(Stage::Tasks));
    }

    #[test]
    fn supports_parallel_returns_false_for_refinement() {
        assert!(!supports_parallel(Stage::Refinement));
    }

    #[test]
    fn supports_parallel_returns_false_for_memory() {
        assert!(!supports_parallel(Stage::Memory));
    }

    // --- testes de plan_parallel_slots (T-02) ---

    #[test]
    fn empty_string_returns_empty() {
        assert!(plan_parallel_slots("").is_empty());
    }

    #[test]
    fn no_table_returns_empty() {
        let md = "# Título\n\nAlgum texto sem tabela.\n";
        assert!(plan_parallel_slots(md).is_empty());
    }

    #[test]
    fn identifies_independent_tasks() {
        let md = "\
## Backlog

| ID | Título | Estimativa | Dependências | Arquivos | Status |
|----|--------|-----------|-------------|----------|--------|
| T-01 | Criar módulo base | P (2h) | — | src/lib.rs | Todo |
| T-02 | Implementar parser | P (2h) | T-01 | src/parser.rs | Todo |
| T-03 | Adicionar testes | P (1h) | T-01 | tests/mod.rs | Todo |

### Tasks independentes entre si (podem rodar em paralelo)

- **Grupo A** (após T-01 concluída): T-02 e T-03 são independentes entre si.
";
        let result = plan_parallel_slots(md);
        assert_eq!(result.len(), 3);

        let t01 = result
            .iter()
            .find(|t| t.id == "T-01")
            .expect("T-01 presente");
        let t02 = result
            .iter()
            .find(|t| t.id == "T-02")
            .expect("T-02 presente");
        let t03 = result
            .iter()
            .find(|t| t.id == "T-03")
            .expect("T-03 presente");

        // T-01: dependência "—" → independent
        assert!(t01.independent, "T-01 deve ser independent (dep=—)");
        // T-02: listada como independente na seção → independent
        assert!(t02.independent, "T-02 deve ser independent (seção)");
        // T-03: listada como independente na seção → independent
        assert!(t03.independent, "T-03 deve ser independent (seção)");
    }

    #[test]
    fn task_with_explicit_dep_not_independent() {
        let md = "\
## Backlog

| ID | Título | Estimativa | Dependências | Arquivos | Status |
|----|--------|-----------|-------------|----------|--------|
| T-01 | Base | P (1h) | — | src/lib.rs | Todo |
| T-02 | Dependente | P (1h) | T-01 | src/dep.rs | Todo |
";
        let result = plan_parallel_slots(md);
        let t02 = result
            .iter()
            .find(|t| t.id == "T-02")
            .expect("T-02 presente");
        assert!(
            !t02.independent,
            "T-02 tem dep explícita, nao deve ser independent"
        );
    }

    #[test]
    fn preserves_declaration_order() {
        let md = "\
## Backlog

| ID | Título | Estimativa | Dependências | Arquivos | Status |
|----|--------|-----------|-------------|----------|--------|
| T-03 | Terceira | P (1h) | — | a.rs | Todo |
| T-01 | Primeira | P (1h) | — | b.rs | Todo |
| T-02 | Segunda | P (1h) | — | c.rs | Todo |
";
        let result = plan_parallel_slots(md);
        assert_eq!(result.len(), 3);
        assert_eq!(result[0].id, "T-03");
        assert_eq!(result[1].id, "T-01");
        assert_eq!(result[2].id, "T-02");
    }

    #[test]
    fn malformed_markdown_no_panic() {
        let result = plan_parallel_slots("não é um markdown válido\n### cabeçalho\n| lixo |");
        // não panics, retorna Vec (possivelmente vazio)
        let _ = result;
    }

    // --- testes de traceability_table e compose_parallel_artifact (T-03) ---

    fn slot(id: &str, label: &str, status: AgentSlotStatus, fragment: &str) -> SlotResult {
        SlotResult {
            task_id: id.to_string(),
            task_label: label.to_string(),
            status,
            fragment: fragment.to_string(),
        }
    }

    #[test]
    fn traceability_table_has_header() {
        let table = traceability_table(&[]);
        assert!(table.contains("| Task ID | AgentSlot | Título | Status |"));
        assert!(table.contains("|---------|-----------|--------|--------|"));
    }

    #[test]
    fn traceability_table_maps_all_statuses() {
        let results = vec![
            slot("T-01", "Alpha", AgentSlotStatus::Aguardando, ""),
            slot("T-02", "Beta", AgentSlotStatus::EmExecucao, ""),
            slot("T-03", "Gamma", AgentSlotStatus::Concluido, "ok"),
            slot("T-04", "Delta", AgentSlotStatus::Erro, ""),
        ];
        let table = traceability_table(&results);
        assert!(table.contains("Aguardando"));
        assert!(table.contains("Em execução"));
        assert!(table.contains("Concluído"));
        assert!(table.contains("Erro"));
    }

    #[test]
    fn compose_includes_rastreabilidade_no_topo() {
        let results = vec![
            slot("T-01", "Slot A", AgentSlotStatus::Concluido, "fragmento A"),
            slot("T-02", "Slot B", AgentSlotStatus::Concluido, "fragmento B"),
        ];
        let out = compose_parallel_artifact(Stage::Execution, &results);
        let table_pos = out.find("| Task ID |").expect("tabela presente");
        let frag_pos = out.find("### T-01").expect("fragmento presente");
        assert!(
            table_pos < frag_pos,
            "tabela de rastreabilidade deve aparecer antes dos fragmentos"
        );
        for section in required_sections_for(Stage::Execution) {
            assert!(
                out.contains(&format!("## {section}\n")),
                "seção obrigatória ausente: {section}"
            );
        }
    }

    #[test]
    fn compose_marks_error_slot() {
        let results = vec![
            slot("T-01", "Slot OK", AgentSlotStatus::Concluido, "conteúdo ok"),
            slot("T-02", "Slot Falho", AgentSlotStatus::Erro, ""),
        ];
        let out = compose_parallel_artifact(Stage::Review, &results);
        assert!(
            out.contains("[ERRO]"),
            "slot de erro deve conter marcador [ERRO]"
        );
        assert!(
            out.contains("conteúdo ok"),
            "slot concluído deve preservar fragmento"
        );
    }

    #[test]
    fn compose_is_pure_same_output_twice() {
        let results = vec![
            slot("T-01", "Alpha", AgentSlotStatus::Concluido, "frag"),
            slot("T-02", "Beta", AgentSlotStatus::Erro, ""),
        ];
        let first = compose_parallel_artifact(Stage::Execution, &results);
        let second = compose_parallel_artifact(Stage::Execution, &results);
        assert_eq!(
            first, second,
            "função pura: saída idêntica em chamadas repetidas"
        );
    }

    #[test]
    fn compose_empty_results() {
        // não deve entrar em pânico com slice vazio
        let out = compose_parallel_artifact(Stage::Execution, &[]);
        assert!(out.contains("# Execution"));
    }
}
