'use client';

/**
 * Painel de rastreabilidade requisito → entrega → tarefa (FASE 2 PMO).
 * Consome `GET /v1/projetos/{id}/rastreabilidade`.
 */

import {
    AlertTriangle,
    FileText,
    GitBranch,
    Link2Off,
    ListTodo,
    Package,
} from 'lucide-react';
import Link from 'next/link';
import React, { useMemo, useState } from 'react';
import {
    Alert,
    Button,
    Collapse,
    Drawer,
    Empty,
    List,
    Segmented,
    Select,
    Space,
    Table,
    Tag,
    Typography,
} from 'antd';
import type { ColumnsType } from 'antd/es/table';

import { ICON_SIZE_MD } from '@/components/icons';
import ContentCard from '@/components/layouts/ContentCard';
import MetricCard from '@/components/layouts/MetricCard';
import MetricsGrid from '@/components/layouts/MetricsGrid';
import { useIsMobile } from '@/hooks/useIsMobile';
import { useMutationCache } from '@/hooks/useMutationCache';
import { useQueryCache } from '@/hooks/useQueryCache';
import { useResponsiveDrawerWidth } from '@/hooks/useResponsiveDrawerWidth';
import { API_ENDPOINTS } from '@/lib/api/endpoints';
import apiClient from '@/lib/api/client';
import { queryKeys } from '@/lib/cache/queryKeys';
import { message } from '@/lib/feedback/message';
import { notifyApiError } from '@/lib/feedback/notifyApiError';
import { projetosProjetoCanonical } from '@/lib/routes/projetosProjetoCanonical';
import { formatDateOnly } from '@/lib/utils/export';

import type {
    RastreabilidadeEdgeDto,
    RastreabilidadeEdgesListDto,
    RastreabilidadeImpactDto,
    RastreabilidadeMatrizItem,
    RastreabilidadePayload,
} from './rastreabilidade.types';
import { resolveRastreabilidadeHref } from './resolveRastreabilidadeHref';
import { RastreabilidadeEdgesCards } from './RastreabilidadeEdgesCards';
import { RastreabilidadeMatrizLista } from './RastreabilidadeMatrizLista';
import styles from './ProjetoRastreabilidadePanel.module.scss';
import { LoadingState } from '@/components/ui/LoadingState';

const { Text } = Typography;

export type ProjetoRastreabilidadePanelProps = {
    projetoId: string;
};

function requisitoLabel(item: RastreabilidadeMatrizItem): string {
    const codigo = item.requisito.codigo?.trim();
    const titulo = item.requisito.titulo?.trim() || `Requisito #${item.requisito.id}`;
    return codigo ? `${codigo} — ${titulo}` : titulo;
}

function coberturaTags(item: RastreabilidadeMatrizItem): React.ReactNode {
    const { tem_entrega: temEntrega, tem_tarefa: temTarefa } = item.cobertura;
    return (
        <div className={styles.coverageTags}>
            <Tag color={temEntrega ? 'success' : 'warning'}>
                {temEntrega ? 'Com entrega' : 'Sem entrega'}
            </Tag>
            <Tag color={temTarefa ? 'success' : 'default'}>
                {temTarefa ? 'Com tarefa' : 'Sem tarefa'}
            </Tag>
            {!temEntrega && !temTarefa ? <Tag color="error">Órfão</Tag> : null}
        </div>
    );
}

function edgeLabel(e: RastreabilidadeEdgeDto): string {
    return `${e.source_node_type}#${e.source_node_id} —[${e.edge_type}]→ ${e.target_node_type}#${e.target_node_id}`;
}

/**
 * Cards de resumo, matriz expansível, engine de arestas e impacto.
 */
export function ProjetoRastreabilidadePanel({ projetoId }: ProjetoRastreabilidadePanelProps) {
    const isMobile = useIsMobile();
    const [drawerItem, setDrawerItem] = useState<RastreabilidadeMatrizItem | null>(null);
    const [impactNodeId, setImpactNodeId] = useState<number | null>(null);
    const [impactResult, setImpactResult] = useState<RastreabilidadeImpactDto | null>(null);
    const [impactLoading, setImpactLoading] = useState(false);
    /** null = default por viewport (mobile→lista, desktop→matriz). */
    const [viewOverride, setViewOverride] = useState<'lista' | 'matriz' | null>(null);
    const detalheDrawerWidth = useResponsiveDrawerWidth({ desktopWidth: 480 });
    const viewMode = viewOverride ?? (isMobile ? 'lista' : 'matriz');

    const {
        data,
        isLoading,
        isError,
        refetch,
        isFetching,
    } = useQueryCache<RastreabilidadePayload>({
        queryKey: queryKeys.projetos.rastreabilidade(projetoId),
        endpoint: API_ENDPOINTS.projetos.rastreabilidade(projetoId),
        enabled: Boolean(projetoId),
        staleTime: 1 * 60 * 1000,
        gcTime: 5 * 60 * 1000,
    });

    const syncMutation = useMutationCache<
        { data: { sync: Record<string, number>; payload: RastreabilidadePayload } },
        Record<string, never>
    >({
        endpoint: API_ENDPOINTS.projetos.rastreabilidadeSyncEdges(projetoId),
        method: 'POST',
        invalidateQueries: [
            queryKeys.projetos.rastreabilidade(projetoId),
            queryKeys.projetos.rastreabilidadeEdges(projetoId),
        ],
        onSuccess: (res) => {
            message.success(
                `Arestas sincronizadas (ativas: ${res?.data?.sync?.active ?? 0}).`,
            );
            void refetch();
            void refetchEdges();
        },
        onError: (err) => notifyApiError(err, 'Não foi possível sincronizar arestas'),
    });

    const criarEntregasOrfaosMutation = useMutationCache<
        {
            data: {
                contagens?: { criadas?: number; ignoradas?: number };
                criadas?: unknown[];
                ignoradas?: unknown[];
            };
        },
        { requisito_ids?: number[] }
    >({
        endpoint: API_ENDPOINTS.projetos.rastreabilidadeCriarEntregasOrfaos(projetoId),
        method: 'POST',
        buildBody: (v) => (v.requisito_ids?.length ? { requisito_ids: v.requisito_ids } : {}),
        invalidateQueries: [queryKeys.projetos.rastreabilidade(projetoId)],
        onSuccess: (res) => {
            const criadas = res?.data?.contagens?.criadas ?? res?.data?.criadas?.length ?? 0;
            const ignoradas = res?.data?.contagens?.ignoradas ?? res?.data?.ignoradas?.length ?? 0;
            if (criadas === 0 && ignoradas === 0) {
                message.info('Nenhum requisito órfão para gerar entrega.');
            } else if (criadas > 0) {
                message.success(
                    criadas === 1
                        ? '1 entrega criada a partir de requisito órfão.'
                        : `${criadas} entregas criadas a partir de requisitos órfãos.`,
                );
            } else {
                message.warning(
                    ignoradas === 1
                        ? '1 requisito foi ignorado (já tinha vínculo).'
                        : `${ignoradas} requisitos ignorados (já tinham vínculo).`,
                );
            }
            void refetch();
        },
        onError: (err) =>
            notifyApiError(err, 'Não foi possível criar entregas a partir dos órfãos'),
    });

    const {
        data: edgesData,
        isLoading: edgesLoading,
        refetch: refetchEdges,
    } = useQueryCache<{ data: RastreabilidadeEdgesListDto }>({
        queryKey: queryKeys.projetos.rastreabilidadeEdges(projetoId, { include_low: false }),
        endpoint: `${API_ENDPOINTS.projetos.rastreabilidadeEdges(projetoId)}?include_low=0`,
        enabled: Boolean(projetoId),
        unwrapApiEnvelope: false,
        staleTime: 30 * 1000,
    });

    const confirmEdgeMutation = useMutationCache<
        { data: RastreabilidadeEdgeDto },
        { edgeId: number }
    >({
        endpoint: (v) => API_ENDPOINTS.projetos.rastreabilidadeEdgeConfirm(projetoId, v.edgeId),
        method: 'POST',
        buildBody: () => ({}),
        invalidateQueries: [
            queryKeys.projetos.rastreabilidade(projetoId),
            queryKeys.projetos.rastreabilidadeEdges(projetoId),
        ],
        onSuccess: () => {
            message.success('Aresta confirmada');
            void refetchEdges();
        },
        onError: (err) => notifyApiError(err, 'Não foi possível confirmar a aresta'),
    });

    const rejectEdgeMutation = useMutationCache<
        { data: RastreabilidadeEdgeDto },
        { edgeId: number; reason: string }
    >({
        endpoint: (v) => API_ENDPOINTS.projetos.rastreabilidadeEdgeReject(projetoId, v.edgeId),
        method: 'POST',
        buildBody: (v) => ({ reason: v.reason }),
        invalidateQueries: [
            queryKeys.projetos.rastreabilidade(projetoId),
            queryKeys.projetos.rastreabilidadeEdges(projetoId),
        ],
        onSuccess: () => {
            message.success('Aresta rejeitada (não será recriada com a mesma evidência)');
            void refetchEdges();
        },
        onError: (err) => notifyApiError(err, 'Não foi possível rejeitar a aresta'),
    });

    const runImpact = async () => {
        if (!impactNodeId) return;
        setImpactLoading(true);
        try {
            const sp = new URLSearchParams({
                node_type: 'requirement',
                node_id: String(impactNodeId),
                depth: '2',
                max_nodes: '100',
            });
            const res = await apiClient.get<{ data: RastreabilidadeImpactDto }>(
                `${API_ENDPOINTS.projetos.rastreabilidadeImpact(projetoId)}?${sp}`,
            );
            setImpactResult(res.data?.data ?? null);
        } catch (err) {
            notifyApiError(err, 'Não foi possível analisar impacto');
        } finally {
            setImpactLoading(false);
        }
    };

    const resumo = data?.resumo;
    const matriz = data?.matriz ?? [];
    const gaps = data?.gaps ?? [];
    const orfaosCount = resumo?.requisitos_orfaos ?? 0;
    const entregasSemReq = data?.entregas_sem_requisito ?? [];
    const tipologico = data?.tipologico;
    const tipologicoGaps = tipologico?.gaps ?? [];
    const tipologicoMatriz = tipologico?.matriz_funcionalidade ?? [];
    const tipologicoResumo = tipologico?.resumo;
    const completeness = data?.completeness;
    const edgesSummary = data?.edges_summary;

    const columns: ColumnsType<RastreabilidadeMatrizItem> = useMemo(
        () => [
            {
                title: 'Requisito',
                key: 'requisito',
                ellipsis: true,
                render: (_: unknown, record) => (
                    <Space direction="vertical" size={0}>
                        <Text strong>{requisitoLabel(record)}</Text>
                        {record.requisito.status ? (
                            <Text type="secondary" style={{ fontSize: 12 }}>
                                {record.requisito.status}
                            </Text>
                        ) : null}
                    </Space>
                ),
            },
            {
                title: 'Entregas',
                key: 'entregas',
                width: 100,
                align: 'right',
                render: (_: unknown, record) => record.entregas.length,
            },
            {
                title: 'Tarefas',
                key: 'tarefas',
                width: 100,
                align: 'right',
                render: (_: unknown, record) => record.tarefas.length,
            },
            {
                title: 'Cobertura',
                key: 'cobertura',
                width: 260,
                render: (_: unknown, record) => coberturaTags(record),
            },
            {
                title: 'Detalhe',
                key: 'acoes',
                width: 110,
                render: (_: unknown, record) => (
                    <Button
                        type="link"
                        size="small"
                        onClick={() => setDrawerItem(record)}
                        data-testid={`rastreabilidade-abrir-detalhe-${record.requisito.id}`}
                    >
                        Ver ligações
                    </Button>
                ),
            },
        ],
        [],
    );

    if (isLoading && !data) {
        return (
            <div className={styles.root} data-testid="projeto-rastreabilidade-panel">
                <div style={{ textAlign: 'center', padding: 48 }}>
                    <LoadingState size="large" label="Carregando rastreabilidade…" />
                </div>
            </div>
        );
    }

    if (isError && !data) {
        return (
            <div className={styles.root} data-testid="projeto-rastreabilidade-panel">
                <Alert
                    type="error"
                    showIcon
                    message="Não foi possível carregar a rastreabilidade"
                    description="Verifique a conexão ou tente novamente. Se o problema continuar, peça ao administrador para confirmar o endpoint de rastreabilidade."
                    action={
                        <Button size="small" onClick={() => void refetch()} loading={isFetching}>
                            Tentar novamente
                        </Button>
                    }
                />
            </div>
        );
    }

    const semDados =
        (resumo?.total_requisitos ?? 0) === 0 &&
        matriz.length === 0 &&
        entregasSemReq.length === 0 &&
        gaps.length === 0;

    if (semDados) {
        return (
            <div className={styles.root} data-testid="projeto-rastreabilidade-panel">
                <Empty
                    image={Empty.PRESENTED_IMAGE_SIMPLE}
                    description={
                        <span>
                            Ainda não há requisitos neste projeto para montar a matriz de
                            rastreabilidade. Cadastre requisitos e vincule entregas/tarefas para
                            acompanhar a cobertura.
                        </span>
                    }
                >
                    <Link href={projetosProjetoCanonical.planejamentoRequisitos(projetoId)}>
                        <Button type="primary">Ir para requisitos</Button>
                    </Link>
                </Empty>
            </div>
        );
    }

    return (
        <div className={styles.root} data-testid="projeto-rastreabilidade-panel">
            {isError ? (
                <Alert
                    type="warning"
                    showIcon
                    style={{ marginBottom: 0 }}
                    message="Dados podem estar desatualizados"
                    description="A última atualização falhou. Você ainda vê o cache; tente novamente."
                    action={
                        <Button size="small" onClick={() => void refetch()} loading={isFetching}>
                            Atualizar
                        </Button>
                    }
                />
            ) : null}

            <MetricsGrid columns={isMobile ? 2 : 4} className={styles.metrics}>
                <MetricCard
                    icon={<FileText size={ICON_SIZE_MD} aria-hidden />}
                    label="Requisitos"
                    value={resumo?.total_requisitos ?? 0}
                    variant="projetos"
                />
                <MetricCard
                    icon={<Package size={ICON_SIZE_MD} aria-hidden />}
                    label="Com entrega"
                    value={resumo?.requisitos_com_entrega ?? 0}
                    variant="concluidos"
                />
                <MetricCard
                    icon={<Link2Off size={ICON_SIZE_MD} aria-hidden />}
                    label="Órfãos"
                    value={orfaosCount}
                    variant="planejamento"
                />
                <MetricCard
                    icon={<GitBranch size={ICON_SIZE_MD} aria-hidden />}
                    label="Entregas sem requisito"
                    value={resumo?.entregas_sem_requisito ?? 0}
                    variant="planejamento"
                />
            </MetricsGrid>

            {orfaosCount > 0 ? (
                <Alert
                    type="warning"
                    showIcon
                    style={{ marginBottom: 16 }}
                    message={
                        orfaosCount === 1
                            ? '1 requisito órfão (sem entrega e sem tarefa).'
                            : `${orfaosCount} requisitos órfãos (sem entrega e sem tarefa).`
                    }
                    description="Gera uma entrega por órfão com o título do requisito (ação humana explícita — ADR-0101)."
                    action={
                        <Button
                            type="primary"
                            size="small"
                            loading={criarEntregasOrfaosMutation.isPending}
                            onClick={() => void criarEntregasOrfaosMutation.mutateAsync({})}
                            data-testid="rastreabilidade-criar-entregas-orfaos"
                        >
                            Criar entregas dos órfãos
                        </Button>
                    }
                />
            ) : null}

            {isMobile && gaps.length > 0 ? (
                <ContentCard
                    title={
                        <Space size={8}>
                            <AlertTriangle size={ICON_SIZE_MD} aria-hidden />
                            <span>Precisa atenção</span>
                        </Space>
                    }
                    data-testid="rastreabilidade-gaps"
                >
                    <List
                        className={styles.gapList}
                        size="small"
                        dataSource={gaps}
                        renderItem={(gap) => {
                            const href = resolveRastreabilidadeHref(projetoId, gap.href_hint);
                            return (
                                <List.Item>
                                    <div className={styles.gapItem}>
                                        <Text>{gap.mensagem}</Text>
                                        {href ? (
                                            <Link href={href}>
                                                <Button type="link" size="small">
                                                    Abrir
                                                </Button>
                                            </Link>
                                        ) : null}
                                    </div>
                                </List.Item>
                            );
                        }}
                    />
                </ContentCard>
            ) : null}

            <div data-testid="rastreabilidade-engine">
            <ContentCard
                title="Traceability Engine"
                headerActions={
                    <Button
                        size="small"
                        loading={syncMutation.isPending}
                        onClick={() => void syncMutation.mutateAsync({})}
                    >
                        Sincronizar arestas
                    </Button>
                }
            >
                <Space direction="vertical" size={12} style={{ width: '100%' }}>
                    <Space wrap>
                        {data?.engine_version ? (
                            <Tag>engine {data.engine_version}</Tag>
                        ) : null}
                        <Tag color="blue">Arestas ativas {edgesSummary?.ativo ?? 0}</Tag>
                        <Tag>Total {edgesSummary?.total ?? 0}</Tag>
                        {(edgesSummary?.possible_delete ?? 0) > 0 ? (
                            <Tag color="warning">
                                Possible delete {edgesSummary?.possible_delete}
                            </Tag>
                        ) : null}
                    </Space>
                    {completeness ? (
                        <Space wrap>
                            <Tag>
                                Req→entrega {completeness.requirements_with_module_or_delivery_pct ?? 0}%
                            </Tag>
                            <Tag>Req→tarefa {completeness.requirements_with_task_pct ?? 0}%</Tag>
                            <Tag>Req→teste {completeness.requirements_with_test_pct ?? 0}%</Tag>
                            <Tag>
                                Feature→RN {completeness.features_with_business_rule_pct ?? 0}%
                            </Tag>
                        </Space>
                    ) : null}
                    <Typography.Paragraph type="secondary" style={{ marginBottom: 0 }}>
                        {completeness?.note ??
                            'Arestas projetadas de FKs existentes (evidence + confidence). Sem inventar relações.'}
                    </Typography.Paragraph>
                    <Space
                        direction={isMobile ? 'vertical' : 'horizontal'}
                        wrap={!isMobile}
                        align={isMobile ? 'stretch' : 'start'}
                        style={{ width: '100%' }}
                    >
                        <Select
                            placeholder="Requisito para impacto"
                            className={isMobile ? styles.impactSelect : undefined}
                            style={isMobile ? undefined : { minWidth: 280 }}
                            showSearch
                            optionFilterProp="label"
                            value={impactNodeId ?? undefined}
                            onChange={(v) => setImpactNodeId(v)}
                            options={matriz.map((m) => ({
                                value: m.requisito.id,
                                label: requisitoLabel(m),
                            }))}
                        />
                        <Button
                            disabled={!impactNodeId}
                            loading={impactLoading}
                            block={isMobile}
                            onClick={() => void runImpact()}
                        >
                            Analisar impacto
                        </Button>
                    </Space>
                    {impactResult ? (
                        <Alert
                            type="info"
                            showIcon
                            message={`Impacto: ${impactResult.direct.length} direto(s), ${impactResult.potential.length} potencial(is)`}
                            description={
                                <Space direction="vertical" size={4}>
                                    <Text type="secondary">{impactResult.disclaimer}</Text>
                                    <Text>
                                        Nós: {impactResult.nodes.length}
                                        {impactResult.truncated ? ' (truncado)' : ''} · arestas
                                        percorridas: {impactResult.edges_traversed}
                                    </Text>
                                    <List
                                        size="small"
                                        dataSource={impactResult.nodes.slice(0, 12)}
                                        renderItem={(n) => (
                                            <List.Item style={{ padding: '2px 0' }}>
                                                <Tag>{n.impact}</Tag>
                                                {n.type} #{n.id} (depth {n.depth})
                                            </List.Item>
                                        )}
                                    />
                                </Space>
                            }
                        />
                    ) : null}

                    <Typography.Text strong>
                        {isMobile ? 'Arestas' : 'Arestas (tabela)'}
                    </Typography.Text>
                    <div data-testid="rastreabilidade-edges-table">
                    {isMobile ? (
                        <RastreabilidadeEdgesCards
                            edges={(edgesData?.data?.items ?? []).slice(0, 40)}
                            loading={edgesLoading}
                            confirmPending={confirmEdgeMutation.isPending}
                            rejectPending={rejectEdgeMutation.isPending}
                            onConfirm={(edgeId) => confirmEdgeMutation.mutate({ edgeId })}
                            onReject={(edgeId) =>
                                rejectEdgeMutation.mutate({
                                    edgeId,
                                    reason: 'Rejeitado na UI de rastreabilidade',
                                })
                            }
                        />
                    ) : (
                    <Table
                        size="small"
                        rowKey="id"
                        loading={edgesLoading}
                        dataSource={(edgesData?.data?.items ?? []).slice(0, 40)}
                        pagination={false}
                        locale={{ emptyText: 'Nenhuma aresta materializada. Sincronize as arestas.' }}
                        columns={[
                            {
                                title: 'Relação',
                                key: 'rel',
                                render: (_: unknown, row: RastreabilidadeEdgeDto) => (
                                    <Space direction="vertical" size={0}>
                                        <Text>{edgeLabel(row)}</Text>
                                        <Text type="secondary" style={{ fontSize: 12 }}>
                                            {row.source} · {row.confidence} · {row.layer}
                                        </Text>
                                    </Space>
                                ),
                            },
                            {
                                title: 'Status',
                                dataIndex: 'status',
                                width: 130,
                                render: (s: string) => (
                                    <Tag color={s === 'possible_delete' ? 'warning' : 'default'}>
                                        {s}
                                    </Tag>
                                ),
                            },
                            {
                                title: '',
                                key: 'acoes',
                                width: 180,
                                render: (_: unknown, row: RastreabilidadeEdgeDto) =>
                                    row.status === 'rejected' ? null : (
                                        <Space size={4}>
                                            <Button
                                                size="small"
                                                loading={confirmEdgeMutation.isPending}
                                                onClick={() =>
                                                    confirmEdgeMutation.mutate({ edgeId: row.id })
                                                }
                                            >
                                                Confirmar
                                            </Button>
                                            <Button
                                                size="small"
                                                danger
                                                loading={rejectEdgeMutation.isPending}
                                                onClick={() =>
                                                    rejectEdgeMutation.mutate({
                                                        edgeId: row.id,
                                                        reason: 'Rejeitado na UI de rastreabilidade',
                                                    })
                                                }
                                            >
                                                Rejeitar
                                            </Button>
                                        </Space>
                                    ),
                            },
                        ]}
                    />
                    )}
                    </div>
                </Space>
            </ContentCard>
            </div>

            {!isMobile && gaps.length > 0 ? (
                <ContentCard
                    title={
                        <Space size={8}>
                            <AlertTriangle size={ICON_SIZE_MD} aria-hidden />
                            <span>Precisa atenção</span>
                        </Space>
                    }
                    data-testid="rastreabilidade-gaps"
                >
                    <List
                        className={styles.gapList}
                        size="small"
                        dataSource={gaps}
                        renderItem={(gap) => {
                            const href = resolveRastreabilidadeHref(projetoId, gap.href_hint);
                            return (
                                <List.Item>
                                    <div className={styles.gapItem}>
                                        <Text>{gap.mensagem}</Text>
                                        {href ? (
                                            <Link href={href}>
                                                <Button type="link" size="small">
                                                    Abrir
                                                </Button>
                                            </Link>
                                        ) : null}
                                    </div>
                                </List.Item>
                            );
                        }}
                    />
                </ContentCard>
            ) : null}

            {(tipologicoResumo?.total_funcionalidades ?? 0) > 0 || tipologicoGaps.length > 0 ? (
                <ContentCard
                    title="Rastreabilidade tipológica (funcionalidade → RN → requisito)"
                    data-testid="rastreabilidade-tipologica"
                >
                    <MetricsGrid>
                        <MetricCard
                            icon={<GitBranch size={ICON_SIZE_MD} aria-hidden />}
                            label="Funcionalidades"
                            value={tipologicoResumo?.total_funcionalidades ?? 0}
                            variant="projetos"
                        />
                        <MetricCard
                            icon={<Link2Off size={ICON_SIZE_MD} aria-hidden />}
                            label="Sem RN"
                            value={tipologicoResumo?.funcionalidades_sem_rn ?? 0}
                            variant="planejamento"
                        />
                        <MetricCard
                            icon={<FileText size={ICON_SIZE_MD} aria-hidden />}
                            label="RN sem funcionalidade"
                            value={tipologicoResumo?.regras_sem_funcionalidade ?? 0}
                            variant="planejamento"
                        />
                        <MetricCard
                            icon={<ListTodo size={ICON_SIZE_MD} aria-hidden />}
                            label="Requisitos sem funcionalidade"
                            value={tipologicoResumo?.requisitos_sem_funcionalidade ?? 0}
                            variant="planejamento"
                        />
                    </MetricsGrid>
                    {tipologicoGaps.length > 0 ? (
                        <List
                            className={styles.gapList}
                            size="small"
                            style={{ marginTop: 12 }}
                            dataSource={tipologicoGaps}
                            renderItem={(gap) => {
                                const href = resolveRastreabilidadeHref(projetoId, gap.href_hint);
                                return (
                                    <List.Item>
                                        <div className={styles.gapItem}>
                                            <Text>{gap.mensagem}</Text>
                                            {href ? (
                                                <Link href={href}>
                                                    <Button type="link" size="small">
                                                        Abrir
                                                    </Button>
                                                </Link>
                                            ) : null}
                                        </div>
                                    </List.Item>
                                );
                            }}
                        />
                    ) : null}
                    {tipologicoMatriz.length > 0 ? (
                        isMobile ? (
                            <Collapse
                                bordered={false}
                                className={styles.tipologicoLista}
                                data-testid="rastreabilidade-tipologica-lista"
                                items={tipologicoMatriz.slice(0, 30).map((row) => ({
                                    key: String(row.funcionalidade.id),
                                    label: (
                                        <div className={styles.matrizListaLabel}>
                                            <Text strong>
                                                {row.funcionalidade.codigo
                                                    ? `${row.funcionalidade.codigo} — ${row.funcionalidade.nome}`
                                                    : row.funcionalidade.nome}
                                            </Text>
                                            <Space size={4} wrap>
                                                <Tag
                                                    color={
                                                        row.cobertura.tem_rn ? 'success' : 'warning'
                                                    }
                                                >
                                                    {row.cobertura.tem_rn ? 'Com RN' : 'Sem RN'}
                                                </Tag>
                                                <Tag
                                                    color={
                                                        row.cobertura.tem_requisito
                                                            ? 'success'
                                                            : 'default'
                                                    }
                                                >
                                                    {row.cobertura.tem_requisito
                                                        ? 'Com requisito'
                                                        : 'Sem requisito'}
                                                </Tag>
                                            </Space>
                                            <Text type="secondary" style={{ fontSize: 12 }}>
                                                {row.regras_negocio.length} RN(s) ·{' '}
                                                {row.requisitos.length} requisito(s)
                                            </Text>
                                        </div>
                                    ),
                                    children: (
                                        <div className={styles.expandBlock}>
                                            <div>
                                                <div className={styles.expandLabel}>
                                                    Regras de negócio
                                                </div>
                                                {row.regras_negocio.length === 0 ? (
                                                    <Text type="secondary">Nenhuma RN.</Text>
                                                ) : (
                                                    <ul className={styles.treeList}>
                                                        {row.regras_negocio.map((rn) => (
                                                            <li key={rn.id}>
                                                                {rn.codigo
                                                                    ? `${rn.codigo} — ${rn.titulo}`
                                                                    : rn.titulo}
                                                            </li>
                                                        ))}
                                                    </ul>
                                                )}
                                            </div>
                                            <div>
                                                <div className={styles.expandLabel}>
                                                    Requisitos
                                                </div>
                                                {row.requisitos.length === 0 ? (
                                                    <Text type="secondary">
                                                        Nenhum requisito.
                                                    </Text>
                                                ) : (
                                                    <ul className={styles.treeList}>
                                                        {row.requisitos.map((r: any) => (
                                                            <li key={r.id}>
                                                                {r.codigo
                                                                    ? `${r.codigo} — ${r.titulo}`
                                                                    : r.titulo}
                                                            </li>
                                                        ))}
                                                    </ul>
                                                )}
                                            </div>
                                        </div>
                                    ),
                                }))}
                            />
                        ) : (
                        <Table
                            style={{ marginTop: 12 }}
                            size="small"
                            rowKey={(row: any) => String(row.funcionalidade.id)}
                            pagination={false}
                            dataSource={tipologicoMatriz.slice(0, 30)}
                            columns={[
                                {
                                    title: 'Funcionalidade',
                                    key: 'func',
                                    render: (_: unknown, row) =>
                                        row.funcionalidade.codigo
                                            ? `${row.funcionalidade.codigo} — ${row.funcionalidade.nome}`
                                            : row.funcionalidade.nome,
                                },
                                {
                                    title: 'RNs',
                                    width: 70,
                                    align: 'right' as const,
                                    render: (_: unknown, row) => row.regras_negocio.length,
                                },
                                {
                                    title: 'Requisitos',
                                    width: 90,
                                    align: 'right' as const,
                                    render: (_: unknown, row) => row.requisitos.length,
                                },
                                {
                                    title: 'Cobertura',
                                    width: 220,
                                    render: (_: unknown, row) => (
                                        <Space size={4} wrap>
                                            <Tag color={row.cobertura.tem_rn ? 'success' : 'warning'}>
                                                {row.cobertura.tem_rn ? 'Com RN' : 'Sem RN'}
                                            </Tag>
                                            <Tag color={row.cobertura.tem_requisito ? 'success' : 'default'}>
                                                {row.cobertura.tem_requisito ? 'Com requisito' : 'Sem requisito'}
                                            </Tag>
                                        </Space>
                                    ),
                                },
                            ]}
                        />
                        )
                    ) : null}
                </ContentCard>
            ) : null}

            <ContentCard
                title={
                    viewMode === 'lista'
                        ? 'Lista requisito → entrega → tarefa'
                        : 'Matriz requisito → entrega → tarefa'
                }
                headerActions={
                    <Space wrap size={8}>
                        <Segmented
                            size="small"
                            value={viewMode}
                            onChange={(v) => setViewOverride(v as 'lista' | 'matriz')}
                            options={[
                                { value: 'lista', label: 'Lista' },
                                { value: 'matriz', label: 'Matriz' },
                            ]}
                            data-testid="rastreabilidade-view-toggle"
                        />
                        <Button size="small" onClick={() => void refetch()} loading={isFetching}>
                            Atualizar
                        </Button>
                    </Space>
                }
            >
                {viewMode === 'lista' ? (
                    <RastreabilidadeMatrizLista
                        matriz={matriz}
                        requisitoLabel={requisitoLabel}
                        coberturaTags={coberturaTags}
                        onOpenDetail={setDrawerItem}
                    />
                ) : matriz.length === 0 ? (
                    <Empty
                        image={Empty.PRESENTED_IMAGE_SIMPLE}
                        description="Nenhum requisito na matriz. Cadastre requisitos para acompanhar a cobertura."
                    />
                ) : (
                    <Table<RastreabilidadeMatrizItem>
                        rowKey={(row: any) => String(row.requisito.id)}
                        columns={columns}
                        dataSource={matriz}
                        pagination={
                            matriz.length > 20
                                ? { pageSize: 20, showSizeChanger: true, size: 'small' }
                                : false
                        }
                        size="middle"
                        scroll={{ x: isMobile ? 640 : 720 }}
                        expandable={{
                            expandedRowRender: (record) => (
                                <div className={styles.expandBlock}>
                                    <div>
                                        <div className={styles.expandLabel}>Entregas</div>
                                        {record.entregas.length === 0 ? (
                                            <Text type="secondary">Nenhuma entrega vinculada.</Text>
                                        ) : (
                                            <ul className={styles.treeList}>
                                                {record.entregas.map((e) => (
                                                    <li key={e.id}>
                                                        <Text>
                                                            {e.nome || `Entrega #${e.id}`}
                                                            {e.status ? ` (${e.status})` : ''}
                                                        </Text>
                                                        {e.data_prevista ? (
                                                            <Text type="secondary">
                                                                {' '}
                                                                — {formatDateOnly(e.data_prevista)}
                                                            </Text>
                                                        ) : null}
                                                    </li>
                                                ))}
                                            </ul>
                                        )}
                                    </div>
                                    <div>
                                        <div className={styles.expandLabel}>Tarefas</div>
                                        {record.tarefas.length === 0 ? (
                                            <Text type="secondary">Nenhuma tarefa vinculada.</Text>
                                        ) : (
                                            <ul className={styles.treeList}>
                                                {record.tarefas.map((t) => (
                                                    <li key={t.id}>
                                                        <Space size={4} wrap>
                                                            <ListTodo
                                                                size={14}
                                                                aria-hidden
                                                            />
                                                            <Text>
                                                                {t.titulo || `Tarefa #${t.id}`}
                                                                {t.status ? ` (${t.status})` : ''}
                                                            </Text>
                                                        </Space>
                                                    </li>
                                                ))}
                                            </ul>
                                        )}
                                    </div>
                                </div>
                            ),
                            rowExpandable: (record) =>
                                record.entregas.length > 0 || record.tarefas.length > 0,
                        }}
                    />
                )}
            </ContentCard>

            {entregasSemReq.length > 0 ? (
                <ContentCard
                    title="Entregas sem requisito"
                    data-testid="rastreabilidade-entregas-sem-requisito"
                >
                    <List
                        size="small"
                        dataSource={entregasSemReq}
                        renderItem={(item) => (
                            <List.Item>
                                <Space wrap>
                                    <Text>{item.nome || `Entrega #${item.id}`}</Text>
                                    {item.status ? <Tag>{item.status}</Tag> : null}
                                </Space>
                                <Link
                                    href={projetosProjetoCanonical.planejamentoEntregas(projetoId)}
                                >
                                    <Button type="link" size="small">
                                        Ir para entregas
                                    </Button>
                                </Link>
                            </List.Item>
                        )}
                    />
                </ContentCard>
            ) : null}

            <Drawer
                title={drawerItem ? requisitoLabel(drawerItem) : 'Ligações'}
                open={Boolean(drawerItem)}
                onClose={() => setDrawerItem(null)}
                width={detalheDrawerWidth}
                destroyOnHidden
                data-testid="rastreabilidade-drawer-detalhe"
            >
                {drawerItem ? (
                    <>
                        <div className={styles.drawerMeta}>{coberturaTags(drawerItem)}</div>
                        <div className={styles.expandBlock}>
                            <div>
                                <div className={styles.expandLabel}>
                                    Entregas ({drawerItem.entregas.length})
                                </div>
                                {drawerItem.entregas.length === 0 ? (
                                    <Text type="secondary">Nenhuma entrega vinculada.</Text>
                                ) : (
                                    <List
                                        size="small"
                                        dataSource={drawerItem.entregas}
                                        renderItem={(e) => (
                                            <List.Item>
                                                <Text>
                                                    {e.nome || `Entrega #${e.id}`}
                                                    {e.status ? ` — ${e.status}` : ''}
                                                </Text>
                                            </List.Item>
                                        )}
                                    />
                                )}
                            </div>
                            <div>
                                <div className={styles.expandLabel}>
                                    Tarefas ({drawerItem.tarefas.length})
                                </div>
                                {drawerItem.tarefas.length === 0 ? (
                                    <Text type="secondary">Nenhuma tarefa vinculada.</Text>
                                ) : (
                                    <List
                                        size="small"
                                        dataSource={drawerItem.tarefas}
                                        renderItem={(t) => (
                                            <List.Item>
                                                <Text>
                                                    {t.titulo || `Tarefa #${t.id}`}
                                                    {t.status ? ` — ${t.status}` : ''}
                                                </Text>
                                            </List.Item>
                                        )}
                                    />
                                )}
                            </div>
                        </div>
                    </>
                ) : null}
            </Drawer>
        </div>
    );
}
