import {
  Component,
  AfterViewInit,
  OnInit,
  OnDestroy,
  ViewChild,
  ElementRef,
  Inject,
} from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
import { PLATFORM_ID } from '@angular/core';
import { RouterModule } from '@angular/router';
import { firstValueFrom } from 'rxjs';

import { environment } from '../../../environments/environment';
import Chart from 'chart.js/auto';
import {
  ResumoService,
  ResumoResponse,
  LineTotal,
} from '../../services/resumo.service';
import { AuthService } from '../../services/auth.service';

// --- Interfaces (Mantidas intactas para não quebrar contrato) ---
type KpiCard = {
  key: string;
  title: string;
  value: string;
  icon: string;
  hint?: string;
};

type SerieMesDto = {
  mes: string;
  total: number;
};

type TopClienteDto = {
  cliente: string;
  linhas: number;
};

type MuregRecenteDto = {
  id: string;
  item: number;
  linhaAntiga?: string | null;
  linhaNova?: string | null;
  iccid?: string | null;
  dataDaMureg?: string | null;
  cliente?: string | null;
  mobileLineId: string;
};

type TrocaRecenteDto = {
  id: string;
  item: number;
  linhaAntiga?: string | null;
  linhaNova?: string | null;
  iccid?: string | null;
  dataTroca?: string | null;
  motivo?: string | null;
};

type VigenciaBucketsDto = {
  vencidos: number;
  aVencer0a30: number;
  aVencer31a60: number;
  aVencer61a90: number;
  acima90: number;
};

type DashboardKpisDto = {
  totalLinhas: number;
  clientesUnicos: number;
  ativos: number;
  bloqueados: number;
  reservas: number;
  bloqueadosPerdaRoubo: number;
  bloqueados120Dias: number;
  bloqueadosOutros: number;
  totalMuregs: number;
  muregsUltimos30Dias: number;
  totalTrocas: number;
  trocasUltimos30Dias: number;
  totalVigenciaLinhas: number;
  vigenciaVencidos: number;
  vigenciaAVencer30: number;
  userDataRegistros: number;
  userDataComCpf: number;
  userDataComEmail: number;
};

type DashboardDto = {
  kpis: DashboardKpisDto;
  topClientes: TopClienteDto[];
  serieMuregUltimos12Meses: SerieMesDto[];
  serieTrocaUltimos12Meses: SerieMesDto[];
  muregsRecentes: MuregRecenteDto[];
  trocasRecentes: TrocaRecenteDto[];
  serieVigenciaEncerramentosProx12Meses: SerieMesDto[];
  vigenciaBuckets: VigenciaBucketsDto;
};

type InsightsChartSeries = {
  labels?: string[] | null;
  values?: number[] | null;
  totals?: Array<number | null> | null;
};

type InsightsKpisVivo = {
  qtdLinhas?: number | null;
  totalFranquiaGb?: number | null;
  totalFranquiaLine?: number | null;
  totalBaseMensal?: number | null;
  totalAdicionaisMensal?: number | null;
  totalGeralMensal?: number | null;
  mediaPorLinha?: number | null;
  minPorLinha?: number | null;
  maxPorLinha?: number | null;
};

type InsightsKpisTravel = {
  comTravel?: number | null;
  semTravel?: number | null;
  totalValue?: number | null;
};

type InsightsKpisAdicionais = {
  totalLinesWithAnyPaidAdditional?: number | null;
  totalLinesWithNoPaidAdditional?: number | null;
};

type InsightsLineTotal = {
  tipo?: string | null;
  qtdLinhas?: number | null;
  valorTotalLine?: number | null;
  lucroTotalLine?: number | null;
};

type DashboardGeralInsightsDto = {
  kpis?: {
    totalLinhas?: number | null;
    totalAtivas?: number | null;
    vivo?: InsightsKpisVivo | null;
    travelMundo?: InsightsKpisTravel | null;
    adicionais?: InsightsKpisAdicionais | null;
    totaisLine?: InsightsLineTotal[] | null;
  } | null;
  charts?: {
    linhasPorFranquia?: InsightsChartSeries | null;
    adicionaisPagosPorServico?: InsightsChartSeries | null;
    travelMundo?: InsightsChartSeries | null;
    tipoChip?: InsightsChartSeries | null;
  } | null;
};

type DashboardLineListItemDto = {
  linha?: string | null;
  cliente?: string | null;
  usuario?: string | null;
  skil?: string | null;
  planoContrato?: string | null;
  status?: string | null;
  franquiaLine?: number | null;
  gestaoVozDados?: number | null;
  skeelo?: number | null;
  vivoNewsPlus?: number | null;
  vivoTravelMundo?: number | null;
  vivoSync?: number | null;
  vivoGestaoDispositivo?: number | null;
  tipoDeChip?: string | null;
};

type ResumoTopCliente = {
  cliente: string;
  linhas: number;
};

type ResumoTopPlano = {
  plano: string;
  linhas: number;
};

type ResumoTopReserva = {
  ddd: string;
  total: number;
  linhas: number;
};

type ResumoDiferencaPjPf = {
  pfLinhas: number | null;
  pjLinhas: number | null;
  totalLinhas: number | null;
};

type ClientDashboardOverview = {
  hasData: boolean;
  totalLinhas: number;
  ativas: number;
  bloqueadas: number;
  reservas: number;
  franquiaLineTotalGb: number;
  planosContratados: number;
  usuariosComLinha: number;
  outrosStatus: number;
};

@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [CommonModule, RouterModule],
  templateUrl: './dashboard.html',
  styleUrls: ['./dashboard.scss'],
})
export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
  // Chart Elements Refs
  @ViewChild('chartMureg12') chartMureg12?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartTroca12') chartTroca12?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartStatusPie') chartStatusPie?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartAdicionaisComparativo') chartAdicionaisComparativo?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartVigenciaMesAno') chartVigenciaMesAno?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartVigenciaSupervisao') chartVigenciaSupervisao?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartLinhasPorFranquia') chartLinhasPorFranquia?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartAdicionaisPagos') chartAdicionaisPagos?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartTipoChip') chartTipoChip?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartTravelMundo') chartTravelMundo?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartResumoPfPjLinhas') chartResumoPfPjLinhas?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartResumoTopPlanos') chartResumoTopPlanos?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartResumoTopClientes') chartResumoTopClientes?: ElementRef<HTMLCanvasElement>;
  @ViewChild('chartResumoReservaDdd') chartResumoReservaDdd?: ElementRef<HTMLCanvasElement>;

  loading = true;
  errorMsg: string | null = null;
  isCliente = false;

  kpis: KpiCard[] = [];

  // Chart Data Holders
  muregLabels: string[] = [];
  muregValues: number[] = [];
  trocaLabels: string[] = [];
  trocaValues: number[] = [];
  vigenciaLabels: string[] = [];
  vigenciaValues: number[] = [];

  vigBuckets: VigenciaBucketsDto = {
    vencidos: 0,
    aVencer0a30: 0,
    aVencer31a60: 0,
    aVencer61a90: 0,
    acima90: 0,
  };

  statusResumo = {
    total: 0,
    ativos: 0,
    bloqueadas: 0,
    perdaRoubo: 0,
    bloq120: 0,
    reservas: 0,
    outras: 0,
  };

  adicionaisComparativo = {
    com: 0,
    sem: 0,
    total: 0,
    pctCom: '0,0%',
    pctSem: '0,0%',
  };

  insightsLoading = false;
  insightsError: string | null = null;
  insights: DashboardGeralInsightsDto | null = null;
  private dashboardRaw: DashboardKpisDto | null = null;

  franquiaLabels: string[] = [];
  franquiaValues: number[] = [];
  adicionaisLabels: string[] = [];
  adicionaisValues: number[] = [];
  adicionaisTotals: Array<number | null> = [];
  travelLabels: string[] = [];
  travelValues: number[] = [];
  tipoChipLabels: string[] = [];
  tipoChipValues: number[] = [];
  private fallbackInsightsLoading = false;
  private readonly adicionaisLabelsPadrao = [
    'GESTÃO VOZ E DADOS',
    'SKEELO',
    'VIVO NEWS PLUS',
    'VIVO TRAVEL MUNDO',
    'VIVO SYNC',
    'VIVO GESTÃO DISPOSITIVO',
  ];

  resumoLoading = false;
  resumoError: string | null = null;
  resumo: ResumoResponse | null = null;
  resumoTopN = 5;
  resumoTopOptions = [5, 10, 15];
  clientOverview: ClientDashboardOverview = {
    hasData: false,
    totalLinhas: 0,
    ativas: 0,
    bloqueadas: 0,
    reservas: 0,
    franquiaLineTotalGb: 0,
    planosContratados: 0,
    usuariosComLinha: 0,
    outrosStatus: 0,
  };
  
  // Resumo Derived Data
  resumoTopClientes: ResumoTopCliente[] = [];
  resumoTopPlanos: ResumoTopPlano[] = [];
  resumoTopReserva: ResumoTopReserva[] = [];
  resumoPfPjLabels: string[] = [];
  resumoPfPjValues: number[] = [];
  resumoPlanosLabels: string[] = [];
  resumoPlanosValues: number[] = [];
  resumoClientesLabels: string[] = [];
  resumoClientesValues: number[] = [];
  resumoReservaLabels: string[] = [];
  resumoReservaValues: number[] = [];
  resumoDiferencaPjPf: ResumoDiferencaPjPf = {
    pfLinhas: null,
    pjLinhas: null,
    totalLinhas: null,
  };

  private viewReady = false;
  private dataReady = false;
  private resumoReady = false;
  private chartRetryCount = 0;
  private resumoChartRetryCount = 0;
  private readonly chartRetryLimit = 8;
  private readonly resumoChartRetryLimit = 8;
  
  // Chart Instances
  private chartMureg?: Chart;
  private chartTroca?: Chart;
  private chartPie?: Chart;
  private chartAdicionaisComparativoDoughnut?: Chart;
  private chartVigMesAno?: Chart;
  private chartVigSuper?: Chart;
  private chartFranquia?: Chart;
  private chartAdicionais?: Chart;
  private chartTravel?: Chart;
  private chartTipoChipDistribuicao?: Chart;
  private chartResumoPfPj?: Chart;
  private chartResumoPlanos?: Chart;
  private chartResumoClientes?: Chart;
  private chartResumoReserva?: Chart;

  private readonly baseApi: string;

  constructor(
    private http: HttpClient,
    private resumoService: ResumoService,
    private authService: AuthService,
    @Inject(PLATFORM_ID) private platformId: object
  ) {
    const raw = (environment.apiUrl || '').replace(/\/+$/, '');
    this.baseApi = raw.toLowerCase().endsWith('/api') ? raw : `${raw}/api`;
  }

  ngOnInit(): void {
    if (!isPlatformBrowser(this.platformId)) return;

    const isSysAdmin = this.authService.hasRole('sysadmin');
    const isGestor = this.authService.hasRole('gestor');
    this.isCliente = !(isSysAdmin || isGestor);

    if (this.isCliente) {
      this.loadClientDashboardData();
      return;
    }

    this.loadDashboard();
    this.loadInsights();
    this.loadResumoExecutive();
  }

  ngAfterViewInit(): void {
    this.viewReady = true;
    this.tryBuildCharts();
    this.tryBuildResumoCharts();
  }

  ngOnDestroy(): void {
    this.destroyCharts();
    this.destroyResumoCharts();
  }

  private async loadDashboard() {
    this.loading = true;
    this.errorMsg = null;
    this.dataReady = false;

    try {
      const dto = await this.fetchDashboardReal();
      this.applyDto(dto);

      this.dataReady = true;
      this.loading = false;

      this.tryBuildCharts();
    } catch (error) {
      this.loading = false;
      this.dashboardRaw = null;
      this.kpis = [];
      this.errorMsg = this.isNetworkError(error)
        ? 'Falha ao carregar o Dashboard. Verifique a conexão.'
        : null;
    }
  }

  private async loadClientDashboardData() {
    this.loading = true;
    this.errorMsg = null;
    this.dataReady = false;
    this.resumoLoading = true;
    this.resumoError = null;
    this.resumoReady = false;

    try {
      const [operacionais, reservas] = await Promise.all([
        this.fetchAllDashboardLines(false),
        this.fetchAllDashboardLines(true),
      ]);
      const allLines = [...operacionais, ...reservas];
      this.applyClientLineAggregates(allLines);

      this.loading = false;
      this.resumoLoading = false;
      this.dataReady = true;
      this.resumoReady = true;
      this.tryBuildCharts();
      this.tryBuildResumoCharts();
    } catch (error) {
      this.loading = false;
      this.resumoLoading = false;
      this.resumoReady = false;
      this.dataReady = false;
      this.errorMsg = this.isNetworkError(error)
        ? 'Falha ao carregar o Dashboard. Verifique a conexão.'
        : 'Falha ao carregar os dados do cliente.';
      this.clearClientDashboardState();
    }
  }

  private async fetchAllDashboardLines(onlyReserva: boolean): Promise<DashboardLineListItemDto[]> {
    const pageSize = 500;
    let page = 1;
    const all: DashboardLineListItemDto[] = [];

    while (true) {
      let params = new HttpParams()
        .set('page', String(page))
        .set('pageSize', String(pageSize));

      if (onlyReserva) {
        params = params.set('skil', 'RESERVA');
      }

      const response = await firstValueFrom(this.http.get<any>(`${this.baseApi}/lines`, { params }));
      const itemsRaw = this.readNode(response, 'items', 'Items');
      const items = Array.isArray(itemsRaw) ? (itemsRaw as DashboardLineListItemDto[]) : [];
      all.push(...items);

      const total = this.toNumberOrNull(this.readNode(response, 'total', 'Total'));
      if (!items.length) break;
      if (total !== null && all.length >= total) break;
      if (items.length < pageSize) break;

      page += 1;
    }

    return all;
  }

  private applyClientLineAggregates(
    allLines: DashboardLineListItemDto[]
  ): void {
    const planMap = new Map<string, number>();
    const userMap = new Map<string, number>();
    const franquiaBandMap = new Map<string, number>();

    let totalLinhas = 0;
    let ativas = 0;
    let bloqueadas = 0;
    let reservas = 0;
    let outrosStatus = 0;
    let franquiaLineTotalGb = 0;
    let eSim = 0;
    let simCard = 0;
    let outrosChip = 0;

    for (const line of allLines) {
      totalLinhas += 1;
      const isReserva = this.isReservaLine(line);
      const status = this.normalizeSeriesKey(this.readLineString(line, 'status', 'Status'));
      const planoContrato = this.readLineString(line, 'planoContrato', 'PlanoContrato').trim();
      const usuario = this.readLineString(line, 'usuario', 'Usuario').trim();
      const usuarioKey = this.normalizeSeriesKey(usuario);
      const franquiaLine = this.readLineNumber(line, 'franquiaLine', 'FranquiaLine');
      franquiaLineTotalGb += franquiaLine > 0 ? franquiaLine : 0;

      if (isReserva) {
        reservas += 1;
      } else if (status.includes('ATIV')) {
        ativas += 1;
      } else if (
        status.includes('BLOQUE') ||
        status.includes('PERDA') ||
        status.includes('ROUBO') ||
        status.includes('SUSPEN') ||
        status.includes('CANCEL')
      ) {
        bloqueadas += 1;
      } else {
        outrosStatus += 1;
      }

      if (!isReserva) {
        const planoKey = planoContrato || 'Sem plano';
        planMap.set(planoKey, (planMap.get(planoKey) ?? 0) + 1);

        if (this.shouldIncludeTopUsuario(usuarioKey, status)) {
          userMap.set(usuario, (userMap.get(usuario) ?? 0) + 1);
        }

        const faixa = this.resolveFranquiaLineBand(franquiaLine);
        franquiaBandMap.set(faixa, (franquiaBandMap.get(faixa) ?? 0) + 1);
      }

      const chipType = this.normalizeChipType(this.readLineString(line, 'tipoDeChip', 'TipoDeChip'));
      if (chipType === 'ESIM') {
        eSim += 1;
      } else if (chipType === 'SIMCARD') {
        simCard += 1;
      } else {
        outrosChip += 1;
      }
    }

    const topPlanos = Array.from(planMap.entries())
      .map(([plano, linhas]) => ({ plano, linhas }))
      .sort((a, b) => b.linhas - a.linhas || a.plano.localeCompare(b.plano, 'pt-BR'))
      .slice(0, this.resumoTopN);

    const topUsuarios = Array.from(userMap.entries())
      .map(([cliente, linhas]) => ({ cliente, linhas }))
      .sort((a, b) => b.linhas - a.linhas || a.cliente.localeCompare(b.cliente, 'pt-BR'))
      .slice(0, this.resumoTopN);

    const franquiaOrder = ['Sem franquia', 'Até 10 GB', '10 a 20 GB', '20 a 50 GB', 'Acima de 50 GB'];
    const franquiaLabels = franquiaOrder.filter((label) => (franquiaBandMap.get(label) ?? 0) > 0);
    this.franquiaLabels = franquiaLabels.length ? franquiaLabels : franquiaOrder;
    this.franquiaValues = this.franquiaLabels.map((label) => franquiaBandMap.get(label) ?? 0);

    this.tipoChipLabels = ['e-SIM', 'SIMCARD', 'Outros'];
    this.tipoChipValues = [eSim, simCard, outrosChip];
    this.travelLabels = [];
    this.travelValues = [];
    this.adicionaisLabels = [];
    this.adicionaisValues = [];
    this.adicionaisTotals = [];
    this.insights = null;
    this.rebuildAdicionaisComparativo(null);

    this.statusResumo = {
      total: totalLinhas,
      ativos: ativas,
      bloqueadas,
      perdaRoubo: 0,
      bloq120: 0,
      reservas,
      outras: outrosStatus,
    };

    this.clientOverview = {
      hasData: totalLinhas > 0,
      totalLinhas,
      ativas,
      bloqueadas,
      reservas,
      franquiaLineTotalGb,
      planosContratados: planMap.size,
      usuariosComLinha: userMap.size,
      outrosStatus,
    };

    this.resumoTopPlanos = topPlanos;
    this.resumoPlanosLabels = topPlanos.map((x) => x.plano);
    this.resumoPlanosValues = topPlanos.map((x) => x.linhas);

    this.resumoTopClientes = topUsuarios;
    this.resumoClientesLabels = topUsuarios.map((x) => x.cliente);
    this.resumoClientesValues = topUsuarios.map((x) => x.linhas);

    this.resumoTopReserva = [];
    this.resumoReservaLabels = [];
    this.resumoReservaValues = [];

    this.resumoPfPjLabels = [];
    this.resumoPfPjValues = [];
    this.resumoDiferencaPjPf = {
      pfLinhas: null,
      pjLinhas: null,
      totalLinhas: null,
    };

    this.resumo = null;
    this.rebuildPrimaryKpis();
  }

  private clearClientDashboardState() {
    this.clientOverview = {
      hasData: false,
      totalLinhas: 0,
      ativas: 0,
      bloqueadas: 0,
      reservas: 0,
      franquiaLineTotalGb: 0,
      planosContratados: 0,
      usuariosComLinha: 0,
      outrosStatus: 0,
    };
    this.statusResumo = {
      total: 0,
      ativos: 0,
      bloqueadas: 0,
      perdaRoubo: 0,
      bloq120: 0,
      reservas: 0,
      outras: 0,
    };
    this.franquiaLabels = [];
    this.franquiaValues = [];
    this.tipoChipLabels = [];
    this.tipoChipValues = [];
    this.resumoTopClientes = [];
    this.resumoTopPlanos = [];
    this.resumoTopReserva = [];
    this.resumoPlanosLabels = [];
    this.resumoPlanosValues = [];
    this.resumoClientesLabels = [];
    this.resumoClientesValues = [];
    this.resumoReservaLabels = [];
    this.resumoReservaValues = [];
    this.rebuildPrimaryKpis();
    this.destroyCharts();
    this.destroyResumoCharts();
  }

  private isNetworkError(error: unknown): boolean {
    if (error instanceof HttpErrorResponse) {
      return error.status === 0;
    }

    if (error instanceof TypeError) {
      const message = (error.message ?? '').toLowerCase();
      return message.includes('failed to fetch') || message.includes('network');
    }

    return false;
  }

  private loadInsights() {
    if (!isPlatformBrowser(this.platformId)) return;
    this.insightsLoading = true;
    this.insightsError = null;

    const url = `${this.baseApi}/dashboard/geral/insights`;
    this.http.get<any>(url).subscribe({
      next: (dto) => {
        this.applyInsights(dto || null);
        this.insightsLoading = false;
        this.tryBuildCharts();
      },
      error: () => {
        this.insightsLoading = false;
        this.insightsError = 'Falha nos insights.';
        this.clearInsightsData();
        void this.loadFallbackFromLinesIfNeeded(true);
      },
    });
  }

  private loadResumoExecutive() {
    if (!isPlatformBrowser(this.platformId)) return;
    this.resumoLoading = true;
    this.resumoError = null;
    this.resumoReady = false;

    this.resumoService.getResumo().subscribe({
      next: (dto) => {
        this.resumo = dto ? this.normalizeResumo(dto) : null;
        this.resumoLoading = false;
        this.resumoReady = true;
        this.buildResumoDerived();
        this.tryBuildResumoCharts();
      },
      error: () => {
        this.resumoLoading = false;
        this.resumoError = 'Falha ao carregar dados do resumo.';
        this.resumo = null;
        this.resumoReady = false;
        this.clearResumoDerived();
      },
    });
  }

  onResumoTopNChange() {
    if (this.isCliente) {
      void this.loadClientDashboardData();
      return;
    }
    this.buildResumoDerived();
    this.tryBuildResumoCharts();
  }

  private async fetchDashboardReal(): Promise<DashboardDto> {
    if (!isPlatformBrowser(this.platformId)) throw new Error('SSR não suportado');
    const url = `${this.baseApi}/relatorios/dashboard`;
    return await firstValueFrom(this.http.get<DashboardDto>(url));
  }

  private applyDto(dto: DashboardDto) {
    const k = dto.kpis;
    this.dashboardRaw = k;

    this.muregLabels = (dto.serieMuregUltimos12Meses || []).map(x => x.mes);
    this.muregValues = (dto.serieMuregUltimos12Meses || []).map(x => x.total);

    this.trocaLabels = (dto.serieTrocaUltimos12Meses || []).map(x => x.mes);
    this.trocaValues = (dto.serieTrocaUltimos12Meses || []).map(x => x.total);

    this.vigenciaLabels = (dto.serieVigenciaEncerramentosProx12Meses || []).map(x => x.mes);
    this.vigenciaValues = (dto.serieVigenciaEncerramentosProx12Meses || []).map(x => x.total);

    this.vigBuckets = dto.vigenciaBuckets || this.vigBuckets;

    this.statusResumo = {
      total: k.totalLinhas ?? 0,
      ativos: k.ativos ?? 0,
      bloqueadas: k.bloqueados ?? 0,
      perdaRoubo: k.bloqueadosPerdaRoubo ?? 0,
      bloq120: k.bloqueados120Dias ?? 0,
      reservas: k.reservas ?? 0,
      outras: k.bloqueadosOutros ?? 0,
    };

    this.rebuildPrimaryKpis();
  }

  private applyInsights(rawDto: DashboardGeralInsightsDto | null) {
    const dto = this.normalizeInsightsDto(rawDto);
    this.insights = dto;
    const charts = dto?.charts ?? {};
    const franquia = charts?.linhasPorFranquia ?? {};
    const adicionais = charts?.adicionaisPagosPorServico ?? {};
    const travel = charts?.travelMundo ?? {};
    const tipoChip = charts?.tipoChip ?? {};

    this.franquiaLabels = (franquia.labels ?? []).map((x) => String(x));
    this.franquiaValues = (franquia.values ?? []).map((x) => Number(x ?? 0));

    this.adicionaisLabels = (adicionais.labels ?? []).map((x) => String(x));
    this.adicionaisValues = (adicionais.values ?? []).map((x) => Number(x ?? 0));
    this.adicionaisTotals = (adicionais.totals ?? []).map((x) => x === null ? null : Number(x));

    this.travelLabels = (travel.labels && travel.labels.length) ? travel.labels.map((x) => String(x)) : ['Com Travel', 'Sem Travel'];
    this.travelValues = (travel.values ?? []).map((x) => Number(x ?? 0));
    this.tipoChipLabels = (tipoChip.labels && tipoChip.labels.length) ? tipoChip.labels.map((x) => String(x)) : ['e-SIM', 'SIMCARD'];
    this.tipoChipValues = (tipoChip.values && tipoChip.values.length)
      ? tipoChip.values.map((x) => Number(x ?? 0))
      : [0, 0];
    this.ensureAdicionaisSeriesCompleta();

    this.rebuildAdicionaisComparativo(dto?.kpis?.adicionais ?? null);

    this.rebuildPrimaryKpis();
    void this.loadFallbackFromLinesIfNeeded();
    if (this.resumoReady && this.resumo) {
      this.buildResumoDerived();
      this.tryBuildResumoCharts();
    }
  }

  private normalizeInsightsDto(rawDto: DashboardGeralInsightsDto | null): DashboardGeralInsightsDto {
    const raw = (rawDto ?? {}) as any;
    const kpisRaw = this.readNode(raw, 'kpis', 'Kpis') ?? {};
    const vivoRaw = this.readNode(kpisRaw, 'vivo', 'Vivo') ?? {};
    const travelRaw = this.readNode(kpisRaw, 'travelMundo', 'TravelMundo') ?? {};
    const adicionaisRaw = this.readNode(kpisRaw, 'adicionais', 'Adicionais') ?? {};
    const totaisLineRaw = this.readNode(kpisRaw, 'totaisLine', 'TotaisLine');
    const chartsRaw = this.readNode(raw, 'charts', 'Charts') ?? {};

    return {
      kpis: {
        totalLinhas: this.toNumberOrNull(this.readNode(kpisRaw, 'totalLinhas', 'TotalLinhas')),
        totalAtivas: this.toNumberOrNull(this.readNode(kpisRaw, 'totalAtivas', 'TotalAtivas')),
        vivo: {
          qtdLinhas: this.toNumberOrNull(this.readNode(vivoRaw, 'qtdLinhas', 'QtdLinhas')),
          totalFranquiaGb: this.toNumberOrNull(this.readNode(vivoRaw, 'totalFranquiaGb', 'TotalFranquiaGb')),
          totalFranquiaLine: this.toNumberOrNull(this.readNode(vivoRaw, 'totalFranquiaLine', 'TotalFranquiaLine')),
          totalBaseMensal: this.toNumberOrNull(this.readNode(vivoRaw, 'totalBaseMensal', 'TotalBaseMensal')),
          totalAdicionaisMensal: this.toNumberOrNull(this.readNode(vivoRaw, 'totalAdicionaisMensal', 'TotalAdicionaisMensal')),
          totalGeralMensal: this.toNumberOrNull(this.readNode(vivoRaw, 'totalGeralMensal', 'TotalGeralMensal')),
          mediaPorLinha: this.toNumberOrNull(this.readNode(vivoRaw, 'mediaPorLinha', 'MediaPorLinha')),
          minPorLinha: this.toNumberOrNull(this.readNode(vivoRaw, 'minPorLinha', 'MinPorLinha')),
          maxPorLinha: this.toNumberOrNull(this.readNode(vivoRaw, 'maxPorLinha', 'MaxPorLinha')),
        },
        travelMundo: {
          comTravel: this.toNumberOrNull(this.readNode(travelRaw, 'comTravel', 'ComTravel')),
          semTravel: this.toNumberOrNull(this.readNode(travelRaw, 'semTravel', 'SemTravel')),
          totalValue: this.toNumberOrNull(this.readNode(travelRaw, 'totalValue', 'TotalValue')),
        },
        adicionais: {
          totalLinesWithAnyPaidAdditional: this.toNumberOrNull(this.readNode(adicionaisRaw, 'totalLinesWithAnyPaidAdditional', 'TotalLinesWithAnyPaidAdditional')),
          totalLinesWithNoPaidAdditional: this.toNumberOrNull(this.readNode(adicionaisRaw, 'totalLinesWithNoPaidAdditional', 'TotalLinesWithNoPaidAdditional')),
        },
        totaisLine: this.normalizeLineTotals(totaisLineRaw),
      },
      charts: {
        linhasPorFranquia: this.normalizeChartSeries(this.readNode(chartsRaw, 'linhasPorFranquia', 'LinhasPorFranquia')),
        adicionaisPagosPorServico: this.normalizeChartSeries(this.readNode(chartsRaw, 'adicionaisPagosPorServico', 'AdicionaisPagosPorServico')),
        travelMundo: this.normalizeChartSeries(this.readNode(chartsRaw, 'travelMundo', 'TravelMundo')),
        tipoChip: this.normalizeChartSeries(this.readNode(chartsRaw, 'tipoChip', 'TipoChip')),
      },
    };
  }

  private normalizeChartSeries(rawSeries: any): InsightsChartSeries {
    const labelsRaw = this.readNode(rawSeries, 'labels', 'Labels');
    const valuesRaw = this.readNode(rawSeries, 'values', 'Values');
    const totalsRaw = this.readNode(rawSeries, 'totals', 'Totals');

    return {
      labels: Array.isArray(labelsRaw) ? labelsRaw.map((x: any) => String(x ?? '')) : [],
      values: Array.isArray(valuesRaw) ? valuesRaw.map((x: any) => Number(this.toNumberOrNull(x) ?? 0)) : [],
      totals: Array.isArray(totalsRaw)
        ? totalsRaw.map((x: any) => {
            const n = this.toNumberOrNull(x);
            return n === null ? null : Number(n);
          })
        : [],
    };
  }

  private normalizeLineTotals(rawTotals: any): InsightsLineTotal[] {
    if (!Array.isArray(rawTotals)) return [];

    return rawTotals
      .map((row: any) => ({
        tipo: String(this.readNode(row, 'tipo', 'Tipo') ?? '').trim() || null,
        qtdLinhas: this.toNumberOrNull(this.readNode(row, 'qtdLinhas', 'QtdLinhas')),
        valorTotalLine: this.toNumberOrNull(this.readNode(row, 'valorTotalLine', 'ValorTotalLine')),
        lucroTotalLine: this.toNumberOrNull(this.readNode(row, 'lucroTotalLine', 'LucroTotalLine')),
      }))
      .filter((row) => !!row.tipo);
  }

  private readNode(source: any, ...keys: string[]): any {
    if (!source || typeof source !== 'object') return undefined;

    for (const key of keys) {
      if (Object.prototype.hasOwnProperty.call(source, key)) {
        return source[key];
      }
    }

    const entries = Object.keys(source);
    for (const key of keys) {
      const found = entries.find((entry) => entry.toLowerCase() === key.toLowerCase());
      if (found) {
        return source[found];
      }
    }

    return undefined;
  }

  private buildResumoDerived() {
    if (!this.resumo) {
      this.clearResumoDerived();
      return;
    }

    const lineTotals = this.getEffectiveLineTotals();
    const pf = this.findLineTotal(lineTotals, ['PF', 'PESSOA FISICA']);
    const pj = this.findLineTotal(lineTotals, ['PJ', 'PESSOA JURIDICA']);

    const pfLinhas = this.toNumberOrNull(pf?.qtdLinhas) ?? 0;
    const pjLinhas = this.toNumberOrNull(pj?.qtdLinhas) ?? 0;
    this.resumoDiferencaPjPf = {
      pfLinhas,
      pjLinhas,
      totalLinhas: pfLinhas + pjLinhas,
    };
    const clientesMap = new Map<string, number>();
    for (const c of this.resumo.vivoLineResumos ?? []) {
      const cliente = (c.cliente ?? 'N/A').toString();
      const linhas = this.toNumberOrNull(c.qtdLinhas) ?? 0;
      clientesMap.set(cliente, (clientesMap.get(cliente) ?? 0) + linhas);
    }

    const clientesRaw: ResumoTopCliente[] = Array.from(clientesMap.entries()).map(([cliente, linhas]) => ({
      cliente,
      linhas,
    }));
    this.resumoTopClientes = [...clientesRaw]
      .sort((a, b) => b.linhas - a.linhas)
      .slice(0, this.resumoTopN);

    const planosSource = (this.resumo.planoContratoResumos && this.resumo.planoContratoResumos.length)
        ? this.resumo.planoContratoResumos
        : (this.resumo.macrophonyPlans ?? []);

    const planosRaw = planosSource.map((p) => ({
      plano: (p.planoContrato ?? 'Plano').toString(),
      linhas: this.toNumberOrNull(p.totalLinhas) ?? 0,
    }));
    
    const sortedPlanosLinhas = [...planosRaw].sort((a, b) => b.linhas - a.linhas);
    this.resumoTopPlanos = sortedPlanosLinhas.slice(0, this.resumoTopN);

    let reservaRaw = (this.resumo.reservaPorDdd ?? []).map((r: any) => ({
      ddd: (r.ddd ?? '-').toString(),
      total: this.toNumberOrNull(r.totalLinhas) ?? 0,
      linhas: this.toNumberOrNull(r.totalLinhas) ?? 0,
    }));

    if (!reservaRaw.length) {
      const fallbackLines = this.resumo.reservaLines ?? [];
      const dddMap = new Map<string, number>();
      fallbackLines.forEach((r: any) => {
        const key = (r.ddd ?? '-').toString();
        const qtd = this.toNumberOrNull(r.qtdLinhas) ?? 0;
        dddMap.set(key, (dddMap.get(key) ?? 0) + qtd);
      });
      reservaRaw = Array.from(dddMap.entries()).map(([ddd, total]) => ({ ddd, total, linhas: total }));
    }

    this.resumoTopReserva = reservaRaw.sort((a, b) => b.total - a.total).slice(0, this.resumoTopN);

    // Arrays for charts
    this.resumoPfPjLabels = ['Pessoa Física', 'Pessoa Jurídica'];
    this.resumoPfPjValues = [pfLinhas, pjLinhas];
    this.resumoPlanosLabels = this.resumoTopPlanos.map(p => p.plano);
    this.resumoPlanosValues = this.resumoTopPlanos.map(p => p.linhas);
    this.resumoClientesLabels = this.resumoTopClientes.map(c => c.cliente);
    this.resumoClientesValues = this.resumoTopClientes.map(c => c.linhas);
    this.resumoReservaLabels = this.resumoTopReserva.map(r => r.ddd);
    this.resumoReservaValues = this.resumoTopReserva.map(r => r.total);

    this.rebuildPrimaryKpis();
  }

  private clearResumoDerived() {
    this.resumoTopClientes = [];
    this.resumoTopPlanos = [];
    this.resumoTopReserva = [];
    this.resumoPfPjLabels = [];
    this.resumoPfPjValues = [];
    this.resumoPlanosLabels = [];
    this.resumoPlanosValues = [];
    this.resumoClientesLabels = [];
    this.resumoClientesValues = [];
    this.resumoReservaLabels = [];
    this.resumoReservaValues = [];
    this.resumoDiferencaPjPf = {
      pfLinhas: null,
      pjLinhas: null,
      totalLinhas: null,
    };
    this.destroyResumoCharts();
    this.rebuildPrimaryKpis();
  }

  private findLineTotal(list: LineTotal[], keywords: string[]): LineTotal | null {
    const keys = keywords.map((k) => k.toUpperCase());
    for (const item of list) {
      const label = (item.tipo ?? '').toString().toUpperCase();
      if (keys.some((k) => label.includes(k))) return item;
    }
    return null;
  }

  private getEffectiveLineTotals(): LineTotal[] {
    const fromInsights = (this.insights?.kpis?.totaisLine ?? [])
      .map((row) => ({
        tipo: row.tipo ?? null,
        qtdLinhas: row.qtdLinhas ?? null,
        valorTotalLine: row.valorTotalLine ?? null,
        lucroTotalLine: row.lucroTotalLine ?? null,
      }))
      .filter((row) => !!(row.tipo ?? '').toString().trim());

    if (fromInsights.length) return fromInsights;
    return Array.isArray(this.resumo?.lineTotais) ? (this.resumo?.lineTotais ?? []) : [];
  }

  private clearInsightsData() {
    this.insights = null;
    this.franquiaLabels = [];
    this.franquiaValues = [];
    this.adicionaisLabels = [];
    this.adicionaisValues = [];
    this.adicionaisTotals = [];
    this.travelLabels = [];
    this.travelValues = [];
    this.tipoChipLabels = [];
    this.tipoChipValues = [];
    this.rebuildAdicionaisComparativo(null);
    this.destroyInsightsCharts();
    this.rebuildPrimaryKpis();
    if (this.resumoReady && this.resumo) {
      this.buildResumoDerived();
      this.tryBuildResumoCharts();
    }
  }

  private ensureAdicionaisSeriesCompleta(): void {
    const existing = new Map<string, { label: string; value: number; total: number | null }>();

    this.adicionaisLabels.forEach((label, idx) => {
      const key = this.normalizeSeriesKey(label);
      existing.set(key, {
        label,
        value: this.adicionaisValues[idx] ?? 0,
        total: this.adicionaisTotals[idx] ?? null,
      });
    });

    const labels: string[] = [];
    const values: number[] = [];
    const totals: Array<number | null> = [];

    this.adicionaisLabelsPadrao.forEach((label) => {
      const key = this.normalizeSeriesKey(label);
      const row = existing.get(key);
      labels.push(row?.label ?? label);
      values.push(row?.value ?? 0);
      totals.push(row?.total ?? null);
    });

    this.adicionaisLabels = labels;
    this.adicionaisValues = values;
    this.adicionaisTotals = totals;
  }

  private async loadFallbackFromLinesIfNeeded(force = false): Promise<void> {
    if (this.isCliente) return;
    if (!isPlatformBrowser(this.platformId) || this.fallbackInsightsLoading) return;

    const syncIndex = this.adicionaisLabels.findIndex(
      (label) => this.normalizeSeriesKey(label) === this.normalizeSeriesKey('VIVO SYNC')
    );
    const syncValue = syncIndex >= 0 ? this.adicionaisValues[syncIndex] ?? 0 : 0;
    const hasTipoChipData = (this.tipoChipValues?.reduce((acc, value) => acc + (Number(value) || 0), 0) ?? 0) > 0;
    const needFallback = force || syncIndex < 0 || syncValue <= 0 || !hasTipoChipData;
    if (!needFallback) return;

    this.fallbackInsightsLoading = true;
    try {
      const pageSize = 500;
      let page = 1;
      let processed = 0;
      let total = 0;
      const additionalCounts = {
        gvd: 0,
        skeelo: 0,
        news: 0,
        travel: 0,
        sync: 0,
        dispositivo: 0,
      };
      let eSim = 0;
      let simCard = 0;

      do {
        const params = new HttpParams()
          .set('page', String(page))
          .set('pageSize', String(pageSize));

        const response = await firstValueFrom(
          this.http.get<any>(`${this.baseApi}/lines`, { params })
        );

        const itemsRaw = this.readNode(response, 'items', 'Items');
        const totalRaw = this.readNode(response, 'total', 'Total');
        const items = Array.isArray(itemsRaw) ? (itemsRaw as DashboardLineListItemDto[]) : [];
        const parsedTotal = this.toNumberOrNull(totalRaw);
        total = parsedTotal !== null
          ? Number(parsedTotal)
          : (processed + items.length + (items.length === pageSize ? 1 : 0));

        items.forEach((line) => {
          if (this.readLineNumber(line, 'gestaoVozDados', 'GestaoVozDados') > 0) additionalCounts.gvd += 1;
          if (this.readLineNumber(line, 'skeelo', 'Skeelo') > 0) additionalCounts.skeelo += 1;
          if (this.readLineNumber(line, 'vivoNewsPlus', 'VivoNewsPlus') > 0) additionalCounts.news += 1;
          if (this.readLineNumber(line, 'vivoTravelMundo', 'VivoTravelMundo') > 0) additionalCounts.travel += 1;
          if (this.readLineNumber(line, 'vivoSync', 'VivoSync') > 0) additionalCounts.sync += 1;
          if (this.readLineNumber(line, 'vivoGestaoDispositivo', 'VivoGestaoDispositivo') > 0) additionalCounts.dispositivo += 1;

          const chipType = this.normalizeChipType(this.readLineString(line, 'tipoDeChip', 'TipoDeChip'));
          if (chipType === 'ESIM') {
            eSim += 1;
          } else if (chipType === 'SIMCARD') {
            simCard += 1;
          }
        });

        processed += items.length;
        page += 1;
      } while (processed < total);

      this.adicionaisLabels = [...this.adicionaisLabelsPadrao];
      this.adicionaisValues = [
        additionalCounts.gvd,
        additionalCounts.skeelo,
        additionalCounts.news,
        additionalCounts.travel,
        additionalCounts.sync,
        additionalCounts.dispositivo,
      ];
      this.adicionaisTotals = this.adicionaisLabelsPadrao.map(() => null);
      this.tipoChipLabels = ['e-SIM', 'SIMCARD'];
      this.tipoChipValues = [eSim, simCard];
      this.tryBuildCharts();
    } catch {
      // Keep existing data if fallback fails.
    } finally {
      this.fallbackInsightsLoading = false;
    }
  }

  private normalizeSeriesKey(value: string): string {
    return (value ?? '')
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '')
      .toUpperCase()
      .replace(/[^A-Z0-9]/g, '');
  }

  private readLineNumber(line: DashboardLineListItemDto, camelCaseKey: keyof DashboardLineListItemDto, pascalCaseKey: string): number {
    const raw = this.readNode(line as any, String(camelCaseKey), pascalCaseKey);
    return this.toNumberOrNull(raw) ?? 0;
  }

  private readLineString(line: DashboardLineListItemDto, camelCaseKey: keyof DashboardLineListItemDto, pascalCaseKey: string): string {
    const raw = this.readNode(line as any, String(camelCaseKey), pascalCaseKey) ?? '';
    return String(raw);
  }

  private normalizeChipType(value: string | null | undefined): 'ESIM' | 'SIMCARD' | '' {
    const normalized = this.normalizeSeriesKey(value ?? '');
    if (!normalized) return '';
    if (normalized.includes('ESIM')) return 'ESIM';
    if (
      normalized.includes('SIM') ||
      normalized.includes('SIMCARD') ||
      normalized.includes('CHIP') ||
      normalized.includes('FISIC') ||
      normalized.includes('CARD')
    ) {
      return 'SIMCARD';
    }
    return '';
  }

  private isReservaLine(line: DashboardLineListItemDto): boolean {
    const cliente = this.normalizeSeriesKey(this.readLineString(line, 'cliente', 'Cliente'));
    const usuario = this.normalizeSeriesKey(this.readLineString(line, 'usuario', 'Usuario'));
    const skil = this.normalizeSeriesKey(this.readLineString(line, 'skil', 'Skil'));
    return cliente === 'RESERVA' || usuario === 'RESERVA' || skil === 'RESERVA';
  }

  private shouldIncludeTopUsuario(usuarioKey: string, statusKey: string): boolean {
    if (!usuarioKey) return false;

    const invalidUserTokens = [
      'SEMUSUARIO',
      'AGUARDANDOUSUARIO',
      'AGUARDANDO',
      'BLOQUEAR',
      'BLOQUEAD',
      'BLOQUEADO',
      'RESERVA',
      'NAOATRIBUIDO',
      'PENDENTE',
    ];
    if (invalidUserTokens.some((token) => usuarioKey.includes(token))) {
      return false;
    }

    const blockedStatusTokens = ['BLOQUE', 'PERDA', 'ROUBO', 'SUSPEN', 'CANCEL', 'AGUARD'];
    return !blockedStatusTokens.some((token) => statusKey.includes(token));
  }

  private resolveFranquiaLineBand(value: number): string {
    if (!Number.isFinite(value) || value <= 0) return 'Sem franquia';
    if (value < 10) return 'Até 10 GB';
    if (value < 20) return '10 a 20 GB';
    if (value < 50) return '20 a 50 GB';
    return 'Acima de 50 GB';
  }

  private extractDddFromLine(value: string | null | undefined): string | null {
    const digits = (value ?? '').replace(/\D/g, '');
    if (!digits) return null;

    if (digits.startsWith('55') && digits.length >= 12) {
      return digits.slice(2, 4);
    }

    if (digits.length >= 10) {
      return digits.slice(0, 2);
    }

    return null;
  }

  private destroyInsightsCharts() {
      try { this.chartFranquia?.destroy(); } catch {}
      try { this.chartAdicionais?.destroy(); } catch {}
      try { this.chartTravel?.destroy(); } catch {}
      try { this.chartTipoChipDistribuicao?.destroy(); } catch {}
  }

  private rebuildAdicionaisComparativo(adicionais: InsightsKpisAdicionais | null): void {
    const com = this.toNumberOrNull(adicionais?.totalLinesWithAnyPaidAdditional) ?? 0;
    const sem = this.toNumberOrNull(adicionais?.totalLinesWithNoPaidAdditional) ?? 0;
    const total = com + sem;

    this.adicionaisComparativo = {
      com,
      sem,
      total,
      pctCom: total > 0 ? this.formatPercent((com / total) * 100) : '0,0%',
      pctSem: total > 0 ? this.formatPercent((sem / total) * 100) : '0,0%',
    };
  }

  private rebuildPrimaryKpis() {
    if (this.isCliente) {
      const overview = this.clientOverview;
      const cards: KpiCard[] = [
        {
          key: 'linhas_ativas',
          title: 'Linhas Ativas',
          value: this.formatInt(overview.ativas),
          icon: 'bi bi-check2-circle',
          hint: 'Status ativo',
        },
        {
          key: 'franquia_line_total',
          title: 'Franquia Line Total',
          value: this.formatDataAllowance(overview.franquiaLineTotalGb),
          icon: 'bi bi-wifi',
          hint: 'Franquia contratada',
        },
        {
          key: 'planos_contratados',
          title: 'Planos Contratados',
          value: this.formatInt(overview.planosContratados),
          icon: 'bi bi-diagram-3-fill',
          hint: 'Planos ativos na base',
        },
        {
          key: 'usuarios_com_linha',
          title: 'Usuários com Linha',
          value: this.formatInt(overview.usuariosComLinha),
          icon: 'bi bi-people-fill',
          hint: 'Usuários vinculados',
        },
      ];

      this.kpis = cards;
      return;
    }

    const cards: KpiCard[] = [];
    const used = new Set<string>();
    const add = (key: string, title: string, value: string, icon: string, hint?: string) => {
      if (used.has(key)) return;
      used.add(key);
      cards.push({ key, title, value, icon, hint });
    };

    const insights = this.insights?.kpis;
    const dashboard = this.dashboardRaw;
    if (dashboard) {
      add('linhas_total', 'Total de Linhas', this.formatInt(dashboard.totalLinhas), 'bi bi-sim-fill', 'Base geral');
      add('linhas_ativas', 'Linhas Ativas', this.formatInt(dashboard.ativos), 'bi bi-check2-circle', 'Status ativo');
      add('linhas_bloqueadas', 'Linhas Bloqueadas', this.formatInt(dashboard.bloqueados), 'bi bi-slash-circle', 'Todos os bloqueios');
      add('linhas_reserva', 'Linhas em Reserva', this.formatInt(dashboard.reservas), 'bi bi-inboxes-fill', 'Base de reserva');
      const franquiaVivoTotal = this.toNumberOrNull(insights?.vivo?.totalFranquiaGb)
        ?? this.toNumberOrNull(this.resumo?.vivoLineTotals?.franquiaTotal)
        ?? (this.resumo?.vivoLineResumos ?? []).reduce(
          (acc, row) => acc + (this.toNumberOrNull(row?.franquiaTotal) ?? 0),
          0
        );
      add(
        'franquia_vivo_total',
        'Total Franquia Vivo',
        this.formatDataAllowance(franquiaVivoTotal),
        'bi bi-diagram-3-fill',
        'Soma das franquias (Geral)'
      );

      const franquiaLineTotal = this.toNumberOrNull(insights?.vivo?.totalFranquiaLine)
        ?? this.toNumberOrNull(this.resumo?.vivoLineTotals?.franquiaLine)
        ?? (this.resumo?.vivoLineResumos ?? []).reduce(
          (acc, row) => acc + (this.toNumberOrNull(row?.franquiaLine) ?? 0),
          0
        );
      add(
        'franquia_line_total',
        'Total Franquia Line',
        this.formatDataAllowance(franquiaLineTotal),
        'bi bi-hdd-network-fill',
        'Soma da franquia line'
      );
      add('vig_vencidos', 'Vigencia Vencida', this.formatInt(dashboard.vigenciaVencidos), 'bi bi-exclamation-triangle-fill', 'Prioridade alta');
      add('vig_30', 'Vence em 30 dias', this.formatInt(dashboard.vigenciaAVencer30), 'bi bi-calendar2-week-fill', 'Prioridade');
      add('mureg_30', 'MUREG 30 dias', this.formatInt(dashboard.muregsUltimos30Dias), 'bi bi-arrow-repeat', 'Movimentacao');
      add('troca_30', 'Trocas 30 dias', this.formatInt(dashboard.trocasUltimos30Dias), 'bi bi-shuffle', 'Movimentacao');
      add('cadastros_total', 'Cadastros', this.formatInt(dashboard.userDataRegistros), 'bi bi-person-vcard-fill', 'Base de usuarios');
    }

    if (insights) {
      add(
        'travel_com',
        'Travel Ativo',
        this.formatInt(this.toNumberOrNull(insights.travelMundo?.comTravel) ?? 0),
        'bi bi-globe-americas',
        'Pagina Geral'
      );
      add(
        'adicional_pago',
        'Com Adicional Pago',
        this.formatInt(this.toNumberOrNull(insights.adicionais?.totalLinesWithAnyPaidAdditional) ?? 0),
        'bi bi-plus-circle-fill',
        'Pagina Geral'
      );
    }

    this.kpis = cards;
  }

  // --- CHART BUILDERS (Generic) ---
  private tryBuildCharts() {
    if (!isPlatformBrowser(this.platformId)) return;
    if (!this.viewReady || !this.dataReady) return;

    requestAnimationFrame(() => {
      const canvases = [
        this.chartStatusPie?.nativeElement,
        this.chartAdicionaisComparativo?.nativeElement,
        this.chartVigenciaMesAno?.nativeElement,
        this.chartVigenciaSupervisao?.nativeElement,
        this.chartMureg12?.nativeElement,
        this.chartTroca12?.nativeElement,
        this.chartLinhasPorFranquia?.nativeElement,
        this.chartAdicionaisPagos?.nativeElement,
        this.chartTipoChip?.nativeElement,
        this.chartTravelMundo?.nativeElement,
      ].filter(Boolean) as HTMLCanvasElement[];

      if (!canvases.length) return;
      if (canvases.some((c) => c.clientWidth === 0 || c.clientHeight === 0)) {
        this.scheduleChartRetry();
        return;
      }

      this.chartRetryCount = 0;
      this.buildCharts();
    });
  }

  private tryBuildResumoCharts() {
    if (!isPlatformBrowser(this.platformId)) return;
    if (!this.viewReady || !this.resumoReady || this.resumoLoading || !!this.resumoError) return;
    
    requestAnimationFrame(() => {
      const canvases = [
        this.chartResumoTopClientes?.nativeElement,
        this.chartResumoTopPlanos?.nativeElement,
        this.chartResumoPfPjLinhas?.nativeElement,
        this.chartResumoReservaDdd?.nativeElement,
      ].filter(Boolean) as HTMLCanvasElement[];

      if (!canvases.length) return;
      if (canvases.some((c) => c.clientWidth === 0 || c.clientHeight === 0)) {
        this.scheduleResumoChartRetry();
        return;
      }

      this.resumoChartRetryCount = 0;
      this.buildResumoCharts();
    });
  }

  private scheduleChartRetry(): void {
    if (this.chartRetryCount >= this.chartRetryLimit) return;
    this.chartRetryCount += 1;
    setTimeout(() => this.tryBuildCharts(), 120);
  }

  private scheduleResumoChartRetry(): void {
    if (this.resumoChartRetryCount >= this.resumoChartRetryLimit) return;
    this.resumoChartRetryCount += 1;
    setTimeout(() => this.tryBuildResumoCharts(), 120);
  }

  private buildCharts() {
    this.destroyCharts();
    const palette = this.getPalette();

    // 1. Status Pie
    if (this.chartStatusPie?.nativeElement) {
      const chartLabels = this.isCliente
        ? ['Ativas']
        : ['Ativos', 'Perda/Roubo', 'Bloq 120d', 'Reservas', 'Outros'];
      const chartData = this.isCliente
        ? [this.statusResumo.ativos]
        : [
            this.statusResumo.ativos,
            this.statusResumo.perdaRoubo,
            this.statusResumo.bloq120,
            this.statusResumo.reservas,
            this.statusResumo.outras,
          ];
      const chartColors = this.isCliente
        ? [palette.status.ativos]
        : [
            palette.status.ativos,
            palette.status.blocked,
            palette.status.purple,
            palette.status.reserve,
            '#cbd5e1',
          ];

      this.chartPie = new Chart(this.chartStatusPie.nativeElement, {
        type: 'doughnut',
        data: {
          labels: chartLabels,
          datasets: [{
            data: chartData,
            borderWidth: 0,
            backgroundColor: chartColors,
            hoverOffset: 4
          }]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          cutout: '70%',
          plugins: { legend: { display: false } }
        }
      });
    }

    if (this.chartAdicionaisComparativo?.nativeElement) {
      this.chartAdicionaisComparativoDoughnut = new Chart(this.chartAdicionaisComparativo.nativeElement, {
        type: 'doughnut',
        data: {
          labels: ['Com adicionais', 'Sem adicionais'],
          datasets: [{
            data: [this.adicionaisComparativo.com, this.adicionaisComparativo.sem],
            borderWidth: 0,
            backgroundColor: [palette.purple, palette.brand],
            hoverOffset: 4,
          }]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          cutout: '64%',
          plugins: {
            legend: { position: 'bottom' },
            tooltip: {
              callbacks: {
                label: (ctx) => ` ${ctx.label}: ${this.formatInt(ctx.raw as number)}`,
              },
            },
          },
        },
      });
    }

    // 2. MUREG & Troca (Bar charts similar to previous but cleaner)
    if (this.chartMureg12?.nativeElement) {
        this.chartMureg = this.createBarChart(this.chartMureg12.nativeElement, this.muregLabels, this.muregValues, palette.brand);
    }
    if (this.chartTroca12?.nativeElement) {
        this.chartTroca = this.createBarChart(this.chartTroca12.nativeElement, this.trocaLabels, this.trocaValues, palette.blue);
    }

    // 3. Vigencia & Insights
    if (this.chartVigenciaMesAno?.nativeElement) {
        this.chartVigMesAno = this.createBarChart(this.chartVigenciaMesAno.nativeElement, this.vigenciaLabels, this.vigenciaValues, palette.purple);
    }
    
    if (this.chartVigenciaSupervisao?.nativeElement) {
        this.chartVigSuper = new Chart(this.chartVigenciaSupervisao.nativeElement, {
            type: 'doughnut',
            data: {
                labels: ['Vencidos', '0-30d', '31-60d', '61-90d', '>90d'],
                datasets: [{
                    data: [this.vigBuckets.vencidos, this.vigBuckets.aVencer0a30, this.vigBuckets.aVencer31a60, this.vigBuckets.aVencer61a90, this.vigBuckets.acima90],
                    backgroundColor: [palette.brand, '#fbbf24', '#3b82f6', '#8b5cf6', '#cbd5e1'],
                    borderWidth: 0
                }]
            },
            options: { responsive: true, maintainAspectRatio: false, cutout: '65%', plugins: { legend: { position: 'right', labels: { boxWidth: 10, font: { size: 10 } } } } }
        });
    }

    if (this.chartTravelMundo?.nativeElement) {
        this.chartTravel = new Chart(this.chartTravelMundo.nativeElement, {
            type: 'pie',
            data: {
                labels: this.travelLabels,
                datasets: [{ data: this.travelValues, backgroundColor: [palette.blue, '#e2e8f0'], borderWidth: 0 }]
            },
            options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right' } } }
        });
    }

    if (this.chartLinhasPorFranquia?.nativeElement) {
        this.chartFranquia = this.createBarChart(
          this.chartLinhasPorFranquia.nativeElement,
          this.franquiaLabels,
          this.franquiaValues,
          palette.brand,
          true
        );
    }
    if (this.chartAdicionaisPagos?.nativeElement) {
        this.chartAdicionais = this.createHorizontalBarChart(
          this.chartAdicionaisPagos.nativeElement,
          this.adicionaisLabels,
          this.adicionaisValues,
          palette.purple,
          true
        );
    }

    if (this.chartTipoChip?.nativeElement) {
      this.chartTipoChipDistribuicao = new Chart(this.chartTipoChip.nativeElement, {
        type: 'doughnut',
        data: {
          labels: this.tipoChipLabels,
          datasets: [{
            data: this.tipoChipValues,
            backgroundColor: [palette.blue, palette.brand, '#94a3b8'],
            borderWidth: 0,
            hoverOffset: 4
          }]
        },
        options: {
          responsive: true,
          maintainAspectRatio: false,
          cutout: '64%',
          plugins: {
            legend: { position: 'bottom' },
            tooltip: {
              callbacks: {
                label: (ctx) => ` ${ctx.label}: ${this.formatInt(ctx.raw as number)}`
              }
            }
          }
        }
      });
    }
  }

  private buildResumoCharts() {
      this.destroyResumoCharts();
      const palette = this.getPalette();

      if (this.chartResumoTopClientes?.nativeElement && this.resumoClientesValues.length) {
        this.chartResumoClientes = new Chart(this.chartResumoTopClientes.nativeElement, {
          type: 'bar',
          data: {
            labels: this.resumoClientesLabels.map((label) => label.length > 16 ? `${label.slice(0, 16)}...` : label),
            datasets: [{
              label: 'Linhas',
              data: this.resumoClientesValues,
              backgroundColor: '#6a55ff',
              borderRadius: 8,
              borderWidth: 0,
            }]
          },
          options: {
            indexAxis: 'y',
            responsive: true,
            maintainAspectRatio: false,
            interaction: { mode: 'nearest', intersect: true },
            plugins: {
              legend: { display: false },
              tooltip: {
                callbacks: {
                  label: (ctx) => ` ${this.formatInt(ctx.raw as number)} linhas`,
                },
              },
            },
            scales: {
              x: { beginAtZero: true, border: { display: false } },
              y: { border: { display: false } },
            },
          },
        });
      }

      if (this.chartResumoTopPlanos?.nativeElement && this.resumoPlanosValues.length) {
        this.chartResumoPlanos = this.createBarChart(
          this.chartResumoTopPlanos.nativeElement,
          this.resumoPlanosLabels,
          this.resumoPlanosValues,
          palette.brand,
          true
        );
      }

      if (this.chartResumoReservaDdd?.nativeElement && this.resumoReservaValues.length) {
        this.chartResumoReserva = this.createBarChart(
          this.chartResumoReservaDdd.nativeElement,
          this.resumoReservaLabels,
          this.resumoReservaValues,
          palette.blue,
          true
        );
      }

      if (this.chartResumoPfPjLinhas?.nativeElement && this.resumoPfPjValues.length) {
        this.chartResumoPfPj = new Chart(this.chartResumoPfPjLinhas.nativeElement, {
          type: 'doughnut',
          data: {
            labels: this.resumoPfPjLabels,
            datasets: [{
              data: this.resumoPfPjValues,
              backgroundColor: [palette.blue, palette.purple],
              borderWidth: 0
            }]
          },
          options: {
            responsive: true,
            maintainAspectRatio: false,
            cutout: '62%',
            plugins: {
              legend: { position: 'bottom' },
              tooltip: {
                callbacks: { label: (ctx) => ` ${ctx.label}: ${this.formatInt(ctx.raw as number)}` }
              }
            }
          }
        });
      }

  }

  // Helper for consistent bar charts
  private createBarChart(
    canvas: HTMLCanvasElement,
    labels: string[],
    data: number[],
    color: string | string[],
    appendLinhasTooltip = false
  ) {
      return new Chart(canvas, {
          type: 'bar',
          data: {
              labels: labels.map(l => l.length > 15 ? l.substring(0,15)+'...' : l),
              datasets: [{
                  data: data,
                  backgroundColor: color,
                  borderRadius: 6,
                  borderWidth: 0,
                  barThickness: 24,
                  minBarLength: 6,
              }]
          },
          options: {
              responsive: true,
              maintainAspectRatio: false,
              interaction: { mode: 'nearest', intersect: true },
              plugins: {
                legend: { display: false },
                tooltip: {
                  callbacks: {
                    label: (ctx) =>
                      appendLinhasTooltip
                        ? ` ${this.formatInt(ctx.raw as number)} Linhas`
                        : ` ${this.formatInt(ctx.raw as number)}`
                  }
                }
              },
              scales: {
                  x: { grid: { display: false }, ticks: { font: { size: 10 } } },
                  y: { grid: { color: '#f1f5f9' }, beginAtZero: true, border: { display: false }, ticks: { display: false } }
              }
          }
      });
  }

  private createHorizontalBarChart(
    canvas: HTMLCanvasElement,
    labels: string[],
    data: number[],
    color: string,
    appendLinhasTooltip = false
  ) {
    return new Chart(canvas, {
        type: 'bar',
        data: {
            labels: labels.map(l => l.length > 15 ? l.substring(0,15)+'...' : l),
            datasets: [{
                data: data,
                backgroundColor: color,
                borderRadius: 4,
                borderWidth: 0,
                barThickness: 16,
                minBarLength: 6,
            }]
        },
        options: {
            indexAxis: 'y',
            responsive: true,
            maintainAspectRatio: false,
            interaction: { mode: 'nearest', intersect: true },
            plugins: {
              legend: { display: false },
              tooltip: {
                callbacks: {
                  label: (ctx) =>
                    appendLinhasTooltip
                      ? ` ${this.formatInt(ctx.raw as number)} Linhas`
                      : ` ${this.formatInt(ctx.raw as number)}`
                }
              }
            },
            scales: {
                x: { display: false },
                y: { grid: { display: false }, border: { display: false }, ticks: { autoSkip: false, font: { size: 10, weight: 'bold' } } }
            }
        }
    });
}

  private destroyCharts() {
    this.chartPie?.destroy();
    this.chartAdicionaisComparativoDoughnut?.destroy();
    this.chartMureg?.destroy();
    this.chartTroca?.destroy();
    this.chartVigMesAno?.destroy();
    this.chartVigSuper?.destroy();
    this.chartTravel?.destroy();
    this.chartFranquia?.destroy();
    this.chartAdicionais?.destroy();
    this.chartTipoChipDistribuicao?.destroy();

    this.chartPie = undefined;
    this.chartAdicionaisComparativoDoughnut = undefined;
    this.chartMureg = undefined;
    this.chartTroca = undefined;
    this.chartVigMesAno = undefined;
    this.chartVigSuper = undefined;
    this.chartTravel = undefined;
    this.chartFranquia = undefined;
    this.chartAdicionais = undefined;
    this.chartTipoChipDistribuicao = undefined;
  }

  private destroyResumoCharts() {
      this.chartResumoClientes?.destroy();
      this.chartResumoPlanos?.destroy();
      this.chartResumoReserva?.destroy();
      this.chartResumoPfPj?.destroy();

      this.chartResumoClientes = undefined;
      this.chartResumoPlanos = undefined;
      this.chartResumoReserva = undefined;
      this.chartResumoPfPj = undefined;
  }

  private normalizeResumo(data: ResumoResponse): ResumoResponse {
    // Helper to ensure arrays are arrays
    return {
      ...data,
      macrophonyPlans: Array.isArray(data.macrophonyPlans) ? data.macrophonyPlans : [],
      vivoLineResumos: Array.isArray(data.vivoLineResumos) ? data.vivoLineResumos : [],
      clienteEspeciais: Array.isArray(data.clienteEspeciais) ? data.clienteEspeciais : [],
      planoContratoResumos: Array.isArray(data.planoContratoResumos) ? data.planoContratoResumos : [],
      lineTotais: Array.isArray(data.lineTotais) ? data.lineTotais : [],
      gbDistribuicao: Array.isArray(data.gbDistribuicao) ? data.gbDistribuicao : [],
      reservaLines: Array.isArray(data.reservaLines) ? data.reservaLines : [],
    };
  }

  // --- Utils ---
  formatInt(v: any) {
    const n = this.toNumberOrNull(v);
    return n === null ? '0' : n.toLocaleString('pt-BR');
  }
  formatPercent(v: any) {
    const n = Number(v ?? 0);
    if (!Number.isFinite(n)) return '0,0%';
    return `${n.toLocaleString('pt-BR', { minimumFractionDigits: 1, maximumFractionDigits: 1 })}%`;
  }
  formatMoneySafe(v: any) {
    const n = this.toNumberOrNull(v);
    return n === null
      ? 'R$ 0,00'
      : new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(n);
  }

  formatGb(v: any) {
    const n = this.toNumberOrNull(v);
    if (n === null) return '0 GB';
    const value = n.toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
    return `${value} GB`;
  }

  formatDataAllowance(v: any) {
    const n = this.toNumberOrNull(v);
    if (n === null) return '0 GB';
    if (n >= 1024) {
      const tb = n / 1024;
      const valueTb = tb.toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
      return `${valueTb} TB`;
    }
    const valueGb = n.toLocaleString('pt-BR', { minimumFractionDigits: 0, maximumFractionDigits: 2 });
    return `${valueGb} GB`;
  }
  
  private toNumberOrNull(v: any) {
      if (v === null || v === undefined || v === '') return null;
      if (typeof v === 'number') return Number.isFinite(v) ? v : null;
      const raw = String(v).trim();
      if (!raw) return null;

      let cleaned = raw.replace(/[^\d,.-]/g, '');
      if (cleaned.includes(',') && cleaned.includes('.')) {
        if (cleaned.lastIndexOf(',') > cleaned.lastIndexOf('.')) {
          cleaned = cleaned.replace(/\./g, '').replace(',', '.');
        } else {
          cleaned = cleaned.replace(/,/g, '');
        }
      } else if (cleaned.includes(',')) {
        cleaned = cleaned.replace(/\./g, '').replace(',', '.');
      } else {
        cleaned = cleaned.replace(/,/g, '');
      }

      const n = Number(cleaned);
      return Number.isNaN(n) ? null : n;
  }

  trackByKpiKey = (_: number, item: KpiCard) => item.key;

  private getPalette() {
    return {
      brand: '#E33DCF',
      blue: '#030FAA',
      purple: '#6A55FF',
      dark: '#B832A8',
      status: {
        ativos: '#030FAA',
        blocked: '#B832A8',
        purple: '#6A55FF',
        reserve: '#F3B0E8'
      }
    };
  }
}
