import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { environment } from '../../../environments/environment';
import { finalize, Subscription, timeout } from 'rxjs';
import { AuthService } from '../../services/auth.service';
import {
  ParcelamentosService,
  ParcelamentoListItem,
  ParcelamentoDetail,
  ParcelamentoDetailResponse,
  ParcelamentoParcela,
  ParcelamentoAnnualRow,
  ParcelamentoAnnualMonth,
  ParcelamentoUpsertRequest,
  ParcelamentoMonthInput,
} from '../../services/parcelamentos.service';
import {
  ParcelamentosKpisComponent,
  ParcelamentoKpi,
} from './components/parcelamentos-kpis/parcelamentos-kpis';
import {
  ParcelamentosFiltersComponent,
  ParcelamentosFiltersModel,
  FilterChip,
} from './components/parcelamentos-filters/parcelamentos-filters';
import {
  ParcelamentosTableComponent,
  ParcelamentoSegment,
  ParcelamentoViewItem,
} from './components/parcelamentos-table/parcelamentos-table';
import {
  ParcelamentoCreateModalComponent,
  ParcelamentoCreateModel,
} from './components/parcelamento-create-modal/parcelamento-create-modal';
import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation';

type MonthOption = { value: number; label: string };
type ParcelamentoStatus = 'ativos' | 'futuros' | 'finalizados';
type AnnualMonthValue = { month: number; label: string; value: number | null };
type AnnualRow = { year: number; total: number; months: AnnualMonthValue[] };

@Component({
  selector: 'app-parcelamentos',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    ParcelamentosKpisComponent,
    ParcelamentosFiltersComponent,
    ParcelamentosTableComponent,
    ParcelamentoCreateModalComponent,
  ],
  templateUrl: './parcelamentos.html',
  styleUrls: ['./parcelamentos.scss'],
})
export class Parcelamentos implements OnInit, OnDestroy {
  loading = false;
  errorMessage = '';

  debugMode = !environment.production;

  items: ParcelamentoListItem[] = [];
  total = 0;
  page = 1;
  pageSize = 10;
  pageSizeOptions = [10, 20, 50, 100];

  filters: ParcelamentosFiltersModel = {
    anoRef: '',
    linha: '',
    cliente: '',
    competenciaAno: '',
    competenciaMes: '',
    search: '',
  };

  activeSegment: ParcelamentoSegment = 'todos';
  segmentCounts: Record<ParcelamentoSegment, number> = {
    todos: 0,
    ativos: 0,
    futuros: 0,
    finalizados: 0,
  };

  viewItems: ParcelamentoViewItem[] = [];
  kpiCards: ParcelamentoKpi[] = [];
  activeChips: FilterChip[] = [];

  isSysAdmin = false;

  detailOpen = false;
  detailLoading = false;
  detailError = '';
  selectedDetail: ParcelamentoDetail | null = null;

  annualRows: AnnualRow[] = [];
  readonly annualMonthHeaders = this.buildAnnualMonthHeaders();

  private detailRequestSub?: Subscription;
  private detailRequestToken = 0;
  private detailGuardTimer?: ReturnType<typeof setTimeout>;

  debugYearGroups: { year: number; months: Array<{ label: string; value: string }> }[] = [];

  createOpen = false;
  createSaving = false;
  createError = '';
  createModel: ParcelamentoCreateModel = this.buildCreateModel();

  editOpen = false;
  editLoading = false;
  editSaving = false;
  editError = '';
  editModel: ParcelamentoCreateModel | null = null;
  editId: string | null = null;

  deleteOpen = false;
  deleteLoading = false;
  deleteError = '';
  deleteTarget: ParcelamentoViewItem | null = null;

  readonly monthOptions: MonthOption[] = [
    { value: 1, label: '01 - Janeiro' },
    { value: 2, label: '02 - Fevereiro' },
    { value: 3, label: '03 - Marco' },
    { value: 4, label: '04 - Abril' },
    { value: 5, label: '05 - Maio' },
    { value: 6, label: '06 - Junho' },
    { value: 7, label: '07 - Julho' },
    { value: 8, label: '08 - Agosto' },
    { value: 9, label: '09 - Setembro' },
    { value: 10, label: '10 - Outubro' },
    { value: 11, label: '11 - Novembro' },
    { value: 12, label: '12 - Dezembro' },
  ];

  constructor(
    private parcelamentosService: ParcelamentosService,
    private authService: AuthService
  ) {}

  ngOnInit(): void {
    this.syncPermissions();
    this.load();
  }

  ngOnDestroy(): void {
    this.cancelDetailRequest();
  }

  @HostListener('document:keydown.escape')
  onEscape(): void {
    if (this.detailOpen) this.closeDetails();
    if (this.createOpen) this.closeCreateModal();
    if (this.editOpen) this.closeEditModal();
    if (this.deleteOpen) this.cancelDelete();
  }

  private syncPermissions(): void {
    this.isSysAdmin = this.authService.hasRole('sysadmin');
  }

  get totalPages(): number {
    return Math.max(1, Math.ceil((this.total || 0) / (this.pageSize || 10)));
  }

  get pageNumbers(): number[] {
    const total = this.totalPages;
    const current = this.page;
    const max = 5;
    let start = Math.max(1, current - 2);
    let end = Math.min(total, start + (max - 1));
    start = Math.max(1, end - (max - 1));
    const pages: number[] = [];
    for (let i = start; i <= end; i++) pages.push(i);
    return pages;
  }

  get pageStart(): number {
    return this.total === 0 ? 0 : (this.page - 1) * this.pageSize + 1;
  }

  get pageEnd(): number {
    return this.total === 0 ? 0 : Math.min(this.page * this.pageSize, this.total);
  }

  get competenciaInvalid(): boolean {
    const ano = this.parseNumber(this.filters.competenciaAno);
    const mes = this.parseNumber(this.filters.competenciaMes);
    const hasAno = ano !== null;
    const hasMes = mes !== null;
    return (hasAno || hasMes) && !(hasAno && hasMes);
  }

  load(): void {
    this.loading = true;
    this.errorMessage = '';

    const anoRef = this.parseNumber(this.filters.anoRef);
    const competenciaAno = this.parseNumber(this.filters.competenciaAno);
    const competenciaMes = this.parseNumber(this.filters.competenciaMes);
    const sendCompetencia = competenciaAno !== null && competenciaMes !== null;

    this.parcelamentosService
      .list({
        anoRef: anoRef ?? undefined,
        linha: this.filters.linha?.trim() || undefined,
        cliente: this.filters.cliente?.trim() || undefined,
        competenciaAno: sendCompetencia ? competenciaAno ?? undefined : undefined,
        competenciaMes: sendCompetencia ? competenciaMes ?? undefined : undefined,
        page: this.page,
        pageSize: this.pageSize,
      })
      .subscribe({
        next: (res) => {
          try {
            const anyRes: any = res ?? {};
            const items = Array.isArray(anyRes.items)
              ? anyRes.items.filter(Boolean)
              : Array.isArray(anyRes.Items)
                ? anyRes.Items.filter(Boolean)
                : [];
            this.items = items;
            this.total = typeof anyRes.total === 'number'
              ? anyRes.total
              : (typeof anyRes.Total === 'number' ? anyRes.Total : 0);
            this.loading = false;
            this.updateDerived();
          } catch (e) {
            console.error('Erro ao processar parcelamentos', e);
            this.items = [];
            this.total = 0;
            this.loading = false;
            this.errorMessage = 'Erro ao processar parcelamentos.';
            this.updateDerivedSafe();
          }
        },
        error: () => {
          this.items = [];
          this.total = 0;
          this.loading = false;
          this.errorMessage = 'Erro ao carregar parcelamentos.';
          this.updateDerivedSafe();
        },
      });
  }

  applyFilters(): void {
    if (this.competenciaInvalid) {
      this.errorMessage = 'Informe ano e mes para filtrar competencia.';
      return;
    }
    this.page = 1;
    this.load();
  }

  clearFilters(): void {
    this.filters = {
      anoRef: '',
      linha: '',
      cliente: '',
      competenciaAno: '',
      competenciaMes: '',
      search: '',
    };
    this.page = 1;
    this.errorMessage = '';
    this.load();
  }

  refresh(): void {
    this.load();
  }

  onPageSizeChange(size: number): void {
    this.pageSize = size;
    this.page = 1;
    this.load();
  }

  goToPage(p: number): void {
    this.page = Math.max(1, Math.min(this.totalPages, p));
    this.load();
  }

  setSegment(segment: ParcelamentoSegment): void {
    this.activeSegment = segment;
    this.updateDerivedSafe();
  }

  onSearchChange(term: string): void {
    this.filters.search = term;
    this.updateDerivedSafe();
  }

  openDetails(item: ParcelamentoListItem): void {
    const id = this.getItemId(item);
    if (!id) {
      this.detailOpen = true;
      this.detailLoading = false;
      this.detailError = 'Registro sem identificador para carregar detalhes.';
      this.selectedDetail = null;
      this.annualRows = [];
      return;
    }

    this.cancelDetailRequest();
    const currentToken = ++this.detailRequestToken;

    this.detailOpen = true;
    this.detailLoading = true;
    this.detailError = '';
    this.selectedDetail = null;
    this.startDetailGuard(currentToken, item);

    this.detailRequestSub = this.parcelamentosService
      .getById(id)
      .pipe(
        timeout(15000),
        finalize(() => {
          if (!this.isCurrentDetailRequest(currentToken)) return;
          this.clearDetailGuard();
          this.detailLoading = false;
        })
      )
      .subscribe({
      next: (res) => {
        if (!this.isCurrentDetailRequest(currentToken)) return;
        try {
          this.selectedDetail = this.normalizeDetail(res);
          this.prepareAnnual(this.selectedDetail);
          this.debugYearGroups = this.buildDebugYearGroups(this.selectedDetail);
        } catch {
          this.applyDetailFallback(item);
        }
        this.detailLoading = false;
      },
      error: () => {
        if (!this.isCurrentDetailRequest(currentToken)) return;
        this.applyDetailFallback(item);
        this.detailLoading = false;
      },
    });
  }

  closeDetails(): void {
    this.cancelDetailRequest();

    this.detailOpen = false;
    this.detailLoading = false;
    this.detailError = '';
    this.selectedDetail = null;
    this.debugYearGroups = [];
    this.annualRows = [];
  }

  openCreateModal(): void {
    this.createModel = this.buildCreateModel();
    this.createError = '';
    this.createOpen = true;
  }

  closeCreateModal(): void {
    this.createOpen = false;
    this.createSaving = false;
    this.createError = '';
  }

  saveNewParcelamento(model: ParcelamentoCreateModel): void {
    if (this.createSaving) return;
    this.createSaving = true;
    this.createError = '';
    const payload = this.buildUpsertPayload(model);
    this.parcelamentosService.create(payload)
      .pipe(finalize(() => (this.createSaving = false)))
      .subscribe({
        next: () => {
          this.createOpen = false;
          this.load();
        },
        error: () => {
          this.createError = 'Erro ao salvar parcelamento.';
        },
      });
  }

  openEdit(item: ParcelamentoListItem): void {
    const id = this.getItemId(item);
    if (!id) return;
    this.editOpen = true;
    this.editLoading = true;
    this.editError = '';
    this.editModel = this.buildCreateModel();
    this.editId = id;

    this.parcelamentosService
      .getById(id)
      .pipe(
        timeout(15000),
        finalize(() => (this.editLoading = false))
      )
      .subscribe({
        next: (res) => {
          const detail = this.normalizeDetail(res);
          this.editModel = this.buildEditModel(detail);
        },
        error: () => {
          this.editError = 'Erro ao carregar dados para editar.';
        },
      });
  }

  closeEditModal(): void {
    this.editOpen = false;
    this.editLoading = false;
    this.editSaving = false;
    this.editError = '';
    this.editModel = null;
    this.editId = null;
  }

  saveEditParcelamento(model: ParcelamentoCreateModel): void {
    if (this.editSaving || !this.editModel || !this.editId) return;
    this.editSaving = true;
    this.editError = '';
    const payload = this.buildUpsertPayload(model);
    this.parcelamentosService
      .update(this.editId, payload)
      .pipe(finalize(() => (this.editSaving = false)))
      .subscribe({
        next: () => {
          this.editOpen = false;
          this.load();
        },
        error: () => {
          this.editError = 'Erro ao atualizar parcelamento.';
        },
      });
  }

  openDelete(item: ParcelamentoViewItem): void {
    if (!this.isSysAdmin) return;
    this.deleteTarget = item;
    this.deleteError = '';
    this.deleteOpen = true;
  }

  cancelDelete(): void {
    this.deleteOpen = false;
    this.deleteLoading = false;
    this.deleteError = '';
    this.deleteTarget = null;
  }

  async confirmDelete(): Promise<void> {
    if (!this.deleteTarget || this.deleteLoading) return;
    if (!(await confirmDeletionWithTyping('este parcelamento'))) return;
    const id = this.getItemId(this.deleteTarget);
    if (!id) return;
    this.deleteLoading = true;
    this.deleteError = '';
    this.parcelamentosService
      .delete(id)
      .pipe(finalize(() => (this.deleteLoading = false)))
      .subscribe({
        next: () => {
          this.cancelDelete();
          this.load();
        },
        error: () => {
          this.deleteError = 'Erro ao excluir parcelamento.';
        },
      });
  }

  displayQtParcelas(item: ParcelamentoListItem): string {
    const atual = this.toNumber(item.parcelaAtual);
    const total = this.toNumber(item.totalParcelas);
    if (atual !== null && total !== null) return `${atual}/${total}`;
    const raw = (item.qtParcelas ?? '').toString().trim();
    if (raw) return raw;
    return '-';
  }

  formatMoney(value: any): string {
    const n = this.toNumber(value);
    if (n === null) return '-';
    return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(n);
  }

  formatNumber(value: any): string {
    const n = this.toNumber(value);
    if (n === null) return '-';
    return new Intl.NumberFormat('pt-BR', { maximumFractionDigits: 0 }).format(n);
  }

  formatCompetencia(value: any): string {
    const date = this.parseCompetenciaDate(value);
    if (!date) return value ? String(value) : '-';
    const label = new Intl.DateTimeFormat('pt-BR', { month: 'long' }).format(date);
    const cap = label.charAt(0).toUpperCase() + label.slice(1);
    return `${cap}/${date.getFullYear()}`;
  }

  formatCompetenciaShort(value: any): string {
    const date = this.parseCompetenciaDate(value);
    if (!date) return value ? String(value) : '-';
    let label = new Intl.DateTimeFormat('pt-BR', { month: 'short' }).format(date);
    label = label.replace('.', '');
    const cap = label.charAt(0).toUpperCase() + label.slice(1);
    return `${cap}/${date.getFullYear()}`;
  }

  private buildAnnualMonthHeaders(): Array<{ month: number; label: string }> {
    return Array.from({ length: 12 }, (_, idx) => {
      const date = new Date(2000, idx, 1);
      let label = new Intl.DateTimeFormat('pt-BR', { month: 'short' }).format(date);
      label = label.replace('.', '');
      label = label.charAt(0).toUpperCase() + label.slice(1);
      return { month: idx + 1, label };
    });
  }

  get detailStatus(): string {
    if (!this.selectedDetail) return '-';
    const atual = this.toNumber(this.selectedDetail.parcelaAtual);
    const total = this.toNumber(this.selectedDetail.totalParcelas);
    if (atual !== null && total !== null) return `${atual}/${total}`;
    const raw = (this.selectedDetail.qtParcelas ?? '').toString().trim();
    return raw || '-';
  }

  private parseCompetenciaDate(value: any): Date | null {
    if (!value) return null;
    const raw = String(value);
    const match = raw.match(/^(\d{4})-(\d{2})/);
    if (match) {
      const year = Number(match[1]);
      const month = Number(match[2]);
      return new Date(year, month - 1, 1);
    }
    const d = new Date(raw);
    if (Number.isNaN(d.getTime())) return null;
    return d;
  }

  private updateDerived(): void {
    const base = (this.items || []).map((item) => this.toViewItem(item));
    const searched = this.applySearch(base, this.filters.search);

    this.segmentCounts = {
      todos: searched.length,
      ativos: searched.filter((i) => i.status === 'ativos').length,
      futuros: searched.filter((i) => i.status === 'futuros').length,
      finalizados: searched.filter((i) => i.status === 'finalizados').length,
    };

    this.viewItems =
      this.activeSegment === 'todos'
        ? searched
        : searched.filter((i) => i.status === this.activeSegment);

    this.kpiCards = this.buildKpis(searched);
    this.activeChips = this.buildActiveChips();
  }

  private updateDerivedSafe(): void {
    try {
      this.updateDerived();
    } catch (e) {
      console.error('Erro ao atualizar parcelamentos', e);
      this.viewItems = [];
      this.kpiCards = [];
      this.activeChips = [];
      this.segmentCounts = {
        todos: 0,
        ativos: 0,
        futuros: 0,
        finalizados: 0,
      };
    }
  }

  private buildKpis(list: ParcelamentoViewItem[]): ParcelamentoKpi[] {
    const totalContratado = list.reduce((sum, item) => sum + (item.valorComDescontoNumber ?? item.valorCheioNumber ?? 0), 0);
    const totalCheio = list.reduce((sum, item) => sum + (item.valorCheioNumber ?? 0), 0);
    const totalDesconto = list.reduce((sum, item) => sum + (item.descontoNumber ?? 0), 0);

    const parcelasEmAberto = list.reduce((sum, item) => {
      const total = this.toNumber(item.totalParcelas);
      const atual = this.toNumber(item.parcelaAtual);
      if (total === null || atual === null) return sum;
      return sum + Math.max(0, total - atual);
    }, 0);

    const competenciaAno = this.parseNumber(this.filters.competenciaAno);
    const competenciaMes = this.parseNumber(this.filters.competenciaMes);
    const totalMensalEstimado =
      competenciaAno !== null && competenciaMes !== null
        ? list.reduce((sum, item) => sum + (item.valorParcela ?? 0), 0)
        : null;

    const anoRef = this.parseNumber(this.filters.anoRef);
    const totalAnual = anoRef !== null || competenciaAno !== null ? totalContratado : null;

    return [
      {
        label: 'Total mensal (estimado)',
        value: totalMensalEstimado !== null ? this.formatMoney(totalMensalEstimado) : '-',
        hint: competenciaAno && competenciaMes ? 'Baseado nas parcelas da lista' : 'Selecione competencia',
        tone: 'brand',
      },
      {
        label: 'Total anual (ano selecionado)',
        value: totalAnual !== null ? this.formatMoney(totalAnual) : '-',
        hint: anoRef || competenciaAno ? 'Baseado nos contratos filtrados' : 'Selecione ano',
      },
      {
        label: 'Parcelamentos ativos',
        value: this.formatNumber(this.segmentCounts.ativos),
        hint: 'Status atual',
        tone: 'success',
      },
      {
        label: 'Parcelas em aberto',
        value: this.formatNumber(parcelasEmAberto),
        hint: 'Estimado por parcela atual',
      },
      {
        label: 'Valor total contratado',
        value: this.formatMoney(totalContratado || totalCheio),
        hint: totalDesconto ? `Desconto total: ${this.formatMoney(totalDesconto)}` : undefined,
      },
    ];
  }

  private buildActiveChips(): FilterChip[] {
    const chips: FilterChip[] = [];
    if (this.filters.anoRef) chips.push({ label: 'AnoRef', value: this.filters.anoRef });
    if (this.filters.linha) chips.push({ label: 'Linha', value: this.filters.linha });
    if (this.filters.cliente) chips.push({ label: 'Cliente', value: this.filters.cliente });
    const ano = this.filters.competenciaAno;
    const mes = this.filters.competenciaMes;
    if (ano && mes) {
      chips.push({ label: 'Competencia', value: `${String(mes).padStart(2, '0')}/${ano}` });
    }
    if (this.filters.search) chips.push({ label: 'Busca', value: this.filters.search });
    return chips;
  }

  private toViewItem(item: ParcelamentoListItem): ParcelamentoViewItem {
    const id = this.getItemId(item) ?? '';
    const status = this.resolveStatus(item);
    const valorCheio = this.toNumber(item.valorCheio);
    const desconto = this.toNumber(item.desconto);
    const valorComDesconto = this.toNumber(item.valorComDesconto);
    return {
      ...item,
      id,
      status,
      statusLabel: this.statusLabel(status),
      progressLabel: this.displayQtParcelas(item),
      valorParcela: this.computeParcelaValue(item),
      valorCheioNumber: valorCheio,
      descontoNumber: desconto,
      valorComDescontoNumber: valorComDesconto ?? (valorCheio !== null && desconto !== null ? Math.max(0, valorCheio - desconto) : null),
    };
  }

  private applySearch(list: ParcelamentoViewItem[], term: string): ParcelamentoViewItem[] {
    const search = this.normalizeText(term);
    if (!search) return list;
    return list.filter((item) => {
      const payload = [
        item.anoRef,
        item.item,
        item.linha,
        item.cliente,
        item.qtParcelas,
      ]
        .map((v) => (v ?? '').toString())
        .join(' ');
      return this.normalizeText(payload).includes(search);
    });
  }

  private resolveStatus(item: ParcelamentoListItem): ParcelamentoStatus {
    const total = this.toNumber(item.totalParcelas);
    const atual = this.toNumber(item.parcelaAtual);
    if (total !== null && atual !== null) {
      if (atual >= total) return 'finalizados';
      if (atual <= 0) return 'futuros';
      return 'ativos';
    }

    const parsed = this.parseQtParcelas(item.qtParcelas);
    if (parsed) {
      if (parsed.atual >= parsed.total) return 'finalizados';
      return 'ativos';
    }

    return 'ativos';
  }

  private statusLabel(status: ParcelamentoStatus): string {
    if (status === 'finalizados') return 'Finalizado';
    if (status === 'futuros') return 'Futuro';
    return 'Ativo';
  }

  private computeParcelaValue(item: ParcelamentoListItem): number | null {
    const totalParcelas = this.toNumber(item.totalParcelas) ?? this.parseQtParcelas(item.qtParcelas)?.total ?? null;
    if (!totalParcelas) return null;
    const total = this.toNumber(item.valorComDesconto) ?? this.toNumber(item.valorCheio);
    if (total === null) return null;
    return total / totalParcelas;
  }

  private parseQtParcelas(value: any): { atual: number; total: number } | null {
    if (!value) return null;
    const raw = String(value);
    const parts = raw.split('/');
    if (parts.length < 2) return null;
    const atualStr = this.onlyDigits(parts[0]);
    const totalStr = this.onlyDigits(parts[1]);
    if (!atualStr || !totalStr) return null;
    return { atual: Number(atualStr), total: Number(totalStr) };
  }

  private prepareAnnual(detail: ParcelamentoDetail): void {
    this.annualRows = this.buildAnnualRows(detail);
  }

  private buildAnnualRows(detail: ParcelamentoDetail): AnnualRow[] {
    const fromApi = this.mapAnnualRows(detail.annualRows ?? []);
    if (fromApi.length) return fromApi;
    return this.buildAnnualRowsFromParcelas(detail.parcelasMensais ?? []);
  }

  private mapAnnualRows(rawRows?: ParcelamentoAnnualRow[] | null): AnnualRow[] {
    const rows = (rawRows ?? [])
      .filter((row): row is ParcelamentoAnnualRow => !!row)
      .map((row) => {
        const year = this.parseNumber(row.year);
        if (year === null) return null;

        const monthMap = new Map<number, number | null>();
        (row.months ?? []).forEach((m) => {
          const month = this.parseNumber(m.month);
          if (month === null) return;
          const value = this.toNumber(m.valor);
          monthMap.set(month, value);
        });

        const months = this.annualMonthHeaders.map((h) => ({
          month: h.month,
          label: h.label,
          value: monthMap.get(h.month) ?? null,
        }));

        const total = this.toNumber(row.total) ?? months.reduce((sum, m) => sum + (m.value ?? 0), 0);
        return { year, total, months };
      })
      .filter((row): row is AnnualRow => !!row)
      .sort((a, b) => a.year - b.year);

    return rows;
  }

  private buildAnnualRowsFromParcelas(parcelas: ParcelamentoParcela[]): AnnualRow[] {
    const map = new Map<number, Map<number, number>>();

    (parcelas ?? []).forEach((p) => {
      const parsed = this.parseCompetenciaParts(p.competencia);
      if (!parsed) return;
      const value = this.toNumber(p.valor) ?? 0;
      if (!map.has(parsed.year)) map.set(parsed.year, new Map());
      const monthMap = map.get(parsed.year)!;
      monthMap.set(parsed.month, (monthMap.get(parsed.month) ?? 0) + value);
    });

    return Array.from(map.entries())
      .sort((a, b) => a[0] - b[0])
      .map(([year, monthMap]) => {
        const months = this.annualMonthHeaders.map((h) => ({
          month: h.month,
          label: h.label,
          value: monthMap.get(h.month) ?? null,
        }));
        const total = months.reduce((sum, m) => sum + (m.value ?? 0), 0);
        return { year, total, months };
      });
  }

  private parseCompetenciaParts(value: any): { year: number; month: number } | null {
    const date = this.parseCompetenciaDate(value);
    if (!date) return null;
    return { year: date.getFullYear(), month: date.getMonth() + 1 };
  }

  private normalizeDetail(res: ParcelamentoDetailResponse): ParcelamentoDetail {
    const payload = (res ?? {}) as any;
    const parcelasRaw =
      payload.parcelasMensais ??
      payload.ParcelasMensais ??
      payload.parcelas ??
      payload.Parcelas ??
      payload.monthValues ??
      payload.MonthValues ??
      [];

    const parcelasMensais = Array.isArray(parcelasRaw)
      ? parcelasRaw
          .filter((p) => !!p && typeof p === 'object')
          .map((p: any) => ({
            competencia: (p.competencia ?? p.Competencia ?? '').toString(),
            valor: p.valor ?? p.Valor ?? null,
          }))
          .filter((p) => !!p.competencia)
      : [];

    const annualRowsRaw =
      payload.annualRows ??
      payload.AnnualRows ??
      payload.detalhamentoAnual ??
      payload.DetalhamentoAnual ??
      [];

    const annualRows: ParcelamentoAnnualRow[] = [];
    if (Array.isArray(annualRowsRaw)) {
      annualRowsRaw.forEach((row: any) => {
        if (!row || typeof row !== 'object') return;
        const year = this.parseNumber(row.year ?? row.Year);
        if (year === null) return;
        const monthsRaw = row.months ?? row.Months ?? [];
        const months: ParcelamentoAnnualMonth[] = [];
        if (Array.isArray(monthsRaw)) {
          monthsRaw.forEach((m: any) => {
            if (!m || typeof m !== 'object') return;
            const month = this.parseNumber(m.month ?? m.Month);
            if (month === null) return;
            const valor = (m.valor ?? m.Valor ?? m.value ?? m.Value ?? null) as number | string | null;
            months.push({
              month,
              valor,
            });
          });
        }
        const total = (row.total ?? row.Total ?? null) as number | string | null;
        annualRows.push({
          year,
          total,
          months,
        });
      });
    }

    return {
      id: payload.id ?? payload.Id ?? '',
      anoRef: payload.anoRef ?? payload.AnoRef ?? null,
      item: payload.item ?? payload.Item ?? null,
      linha: payload.linha ?? payload.Linha ?? null,
      cliente: payload.cliente ?? payload.Cliente ?? null,
      qtParcelas: payload.qtParcelas ?? payload.QtParcelas ?? null,
      parcelaAtual: payload.parcelaAtual ?? payload.ParcelaAtual ?? null,
      totalParcelas: payload.totalParcelas ?? payload.TotalParcelas ?? null,
      valorCheio: payload.valorCheio ?? payload.ValorCheio ?? null,
      desconto: payload.desconto ?? payload.Desconto ?? null,
      valorComDesconto: payload.valorComDesconto ?? payload.ValorComDesconto ?? null,
      parcelasMensais,
      annualRows,
    };
  }

  private buildCreateModel(): ParcelamentoCreateModel {
    const now = new Date();
    return {
      anoRef: now.getFullYear(),
      linha: '',
      cliente: '',
      item: null,
      qtParcelas: '',
      parcelaAtual: null,
      totalParcelas: 12,
      valorCheio: '',
      desconto: '',
      valorComDesconto: '',
      competenciaAno: now.getFullYear(),
      competenciaMes: now.getMonth() + 1,
      monthValues: [],
    };
  }

  private buildEditModel(detail: ParcelamentoDetail): ParcelamentoCreateModel {
    const parcelas = (detail.parcelasMensais ?? [])
      .filter((p) => !!p && !!p.competencia)
      .map((p) => ({
        competencia: p.competencia,
        valor: this.normalizeInputValue(p.valor),
      }))
      .sort((a, b) => a.competencia.localeCompare(b.competencia));

    const firstCompetencia = parcelas.length ? parcelas[0].competencia : '';
    const compParts = firstCompetencia.match(/^(\d{4})-(\d{2})/);
    const competenciaAno = compParts ? Number(compParts[1]) : null;
    const competenciaMes = compParts ? Number(compParts[2]) : null;

    return {
      anoRef: detail.anoRef ?? null,
      linha: detail.linha ?? '',
      cliente: detail.cliente ?? '',
      item: this.toNumber(detail.item) ?? null,
      qtParcelas: detail.qtParcelas ?? '',
      parcelaAtual: this.toNumber(detail.parcelaAtual),
      totalParcelas: this.toNumber(detail.totalParcelas),
      valorCheio: this.normalizeInputValue(detail.valorCheio),
      desconto: this.normalizeInputValue(detail.desconto),
      valorComDesconto: this.normalizeInputValue(detail.valorComDesconto),
      competenciaAno,
      competenciaMes,
      monthValues: parcelas,
    };
  }

  private buildUpsertPayload(model: ParcelamentoCreateModel): ParcelamentoUpsertRequest {
    const monthValues: ParcelamentoMonthInput[] = (model.monthValues ?? [])
      .filter((m) => !!m && !!m.competencia)
      .map((m) => ({
        competencia: m.competencia,
        valor: this.toNumber(m.valor),
      }));

    return {
      anoRef: this.toNumber(model.anoRef),
      item: this.toNumber(model.item),
      linha: model.linha?.trim() || null,
      cliente: model.cliente?.trim() || null,
      qtParcelas: model.qtParcelas?.trim() || null,
      parcelaAtual: this.toNumber(model.parcelaAtual),
      totalParcelas: this.toNumber(model.totalParcelas),
      valorCheio: this.toNumber(model.valorCheio),
      desconto: this.toNumber(model.desconto),
      valorComDesconto: this.toNumber(model.valorComDesconto),
      monthValues,
    };
  }

  private normalizeInputValue(value: any): string {
    const n = this.toNumber(value);
    if (n === null) return '';
    return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(n);
  }

  private parseNumber(value: any): number | null {
    if (value === null || value === undefined || value === '') return null;
    const n = Number(value);
    return Number.isNaN(n) ? null : n;
  }

  private toNumber(value: any): number | null {
    if (value === null || value === undefined) return null;
    if (typeof value === 'number') return Number.isFinite(value) ? value : null;
    const raw = String(value).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;
  }

  private normalizeText(value: any): string {
    return (value ?? '')
      .toString()
      .trim()
      .toUpperCase()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '');
  }

  private onlyDigits(value: string): string {
    let out = '';
    for (const ch of value ?? '') {
      if (ch >= '0' && ch <= '9') out += ch;
    }
    return out;
  }

  private getItemId(item: ParcelamentoListItem | null | undefined): string | null {
    if (!item) return null;
    const raw = (item as any).id ?? (item as any).Id ?? (item as any).parcelamentoId ?? (item as any).ParcelamentoId;
    if (raw === null || raw === undefined) return null;
    const value = String(raw).trim();
    return value ? value : null;
  }

  private cancelDetailRequest(): void {
    this.clearDetailGuard();
    this.detailRequestToken++;
    if (this.detailRequestSub) {
      this.detailRequestSub.unsubscribe();
      this.detailRequestSub = undefined;
    }
  }

  private isCurrentDetailRequest(token: number): boolean {
    return token === this.detailRequestToken;
  }

  private startDetailGuard(token: number, item: ParcelamentoListItem): void {
    this.clearDetailGuard();
    this.detailGuardTimer = setTimeout(() => {
      if (!this.isCurrentDetailRequest(token)) return;
      this.cancelDetailRequest();
      this.applyDetailFallback(item);
      this.detailLoading = false;
    }, 20000);
  }

  private clearDetailGuard(): void {
    if (this.detailGuardTimer) {
      clearTimeout(this.detailGuardTimer);
      this.detailGuardTimer = undefined;
    }
  }

  private applyDetailFallback(item: ParcelamentoListItem): void {
    this.detailError = '';
    this.selectedDetail = this.buildFallbackDetail(item);
    this.prepareAnnual(this.selectedDetail);
    this.debugYearGroups = this.buildDebugYearGroups(this.selectedDetail);
    this.detailLoading = false;
  }

  private buildFallbackDetail(item: ParcelamentoListItem): ParcelamentoDetail {
    return {
      id: this.getItemId(item) ?? '',
      anoRef: item.anoRef ?? null,
      item: item.item ?? null,
      linha: item.linha ?? null,
      cliente: item.cliente ?? null,
      qtParcelas: item.qtParcelas ?? null,
      parcelaAtual: item.parcelaAtual ?? null,
      totalParcelas: item.totalParcelas ?? null,
      valorCheio: item.valorCheio ?? null,
      desconto: item.desconto ?? null,
      valorComDesconto: item.valorComDesconto ?? null,
      parcelasMensais: [],
    };
  }

  private buildDebugYearGroups(detail: ParcelamentoDetail | null): { year: number; months: Array<{ label: string; value: string }> }[] {
    if (!detail) return [];
    const parcels = (detail.parcelasMensais ?? []).filter(
      (p): p is ParcelamentoParcela => !!p && typeof p === 'object'
    );
    const map = new Map<number, Map<number, ParcelamentoParcela>>();
    parcels.forEach((p) => {
      const parsed = this.parseCompetenciaParts(p.competencia);
      if (!parsed) return;
      if (!map.has(parsed.year)) map.set(parsed.year, new Map());
      map.get(parsed.year)!.set(parsed.month, p);
    });

    return Array.from(map.entries())
      .sort((a, b) => a[0] - b[0])
      .map(([year, monthMap]) => ({
        year,
        months: Array.from({ length: 12 }, (_, idx) => {
          const month = idx + 1;
          const item = monthMap.get(month);
          return {
            label: `${month.toString().padStart(2, '0')}/${year}`,
            value: this.formatMoney(item?.valor),
          };
        }),
      }));
  }

}
