/**
 * DashboardComponent WIDGET
 * 
 * DashboardComponent component widget to show component output data. Will draw the widget accordantly to the DashboardComponent configuration (TABLE or CHART)
 
 * Input's:
 *  - dashboardComponent  : The DashboardComponent instance to draw. If present, dashboardComponentId input will be IGNORED.
 *  - dashboardComponentId: The DashboardComponent id. 
 *
 * Event's:
 *  <NO EVENTS>
 *
 * Usage
 *  - In the intended *.component.html insert the code:
 *    <dashboard-component-widget [dashboardComponent]="_dashboardComponent()"></dashboard-component-widget>
 *    OR
 *    <dashboard-component-widget [dashboardComponentId]="_dashboardComponentId()"></dashboard-component-widget>
 *  - In the *.component.ts insert the code:
 *    import { DashboardComponentWidgetComponent } from 'app/entities/dashboard-component/factory/widgets/dashboard-component-widget.component';
 *    ...
 *    add component imports, @Component({ imports: [ DashboardComponentWidgetComponent ] })
 *    ...
 *    _dashboardComponent= input<IDashboardComponent | null>(null);
 *    OR
 *    _dashboardComponentId= input<long | null>(null);
 */
 
import { Component, Output, Input, inject, OnInit, EventEmitter  } from '@angular/core';
import { RouterModule } from '@angular/router';
import { HttpResponse } from '@angular/common/http';
import { map, tap, filter, Subscription, skip } from 'rxjs';
import dayjs from 'dayjs'
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';

import SharedModule from 'app/shared/shared.module';
import { IDashboardComponent } from '../../dashboard-component.model';
import { IDashboardComponentDetailValue } from '../../dashboard-component-detail-value.model';
import { DashboardComponentService } from '../../service/dashboard-component.service';
import { NestedAccordionComponent, AccordionItem } from './nested-accordion.component';
import { ResilientEnvironmentService } from 'app/resilient/resilient-environment/service/resilient-environment.service';
import { FormatHelper } from 'app/shared/utils/format-helper';
import { IPeriodVersion } from 'app/entities/period-version/period-version.model';
import { PeriodService } from 'app/entities/period/service/period.service';

@Component({
  standalone: true,
  selector: 'dashboard-component-accordion-table',
  templateUrl: './dashboard-component-accordion-table.component.html',
  styleUrl: './dashboard-component-accordion-table.component.css',
  imports: [SharedModule, RouterModule, NestedAccordionComponent, MatProgressSpinnerModule]
})
export class DashboardComponentAccordionTable implements OnInit {
  @Input() dashboardComponent: IDashboardComponent | null = null; // DashboardComponent instance, if the consumer already loaded a DashboardComponent instance.
  @Input() dashboardComponentId: number | null = null;            // DashboardComponent Id, if the consumer doesn't have an instance
  @Input() periodVersionId: number | null = null;                 // PeriodVersion Id, needed to retrive the Output data for the component
  @Input() showTitle: boolean = false;
  
  subscriptionWorkOrganization!: Subscription;  // Store the subscription
  subscriptionPeriod!: Subscription;  // Store the subscription to PeriodSelector change
  dashboardComponentDetailValues: Map<string, IDashboardComponentDetailValue[]> = new Map();
  selectedOrganizationId?: number;
  isExporting: boolean = false;

  data: AccordionItem[] = [];
  labels: string[] = [];
  title: string = "XPTO TITLE";
  
  date: string = "myDate";
  version: string = "myVersion";
  periodVersion: IPeriodVersion | null = null;
  
  protected dashboardComponentService = inject(DashboardComponentService);
  protected envService = inject(ResilientEnvironmentService);
  protected periodService = inject(PeriodService);
  
  ngOnInit(): void {
    // Get the currently selected ORGANIZATION, by ResilientEnvironmentService
    this.subscriptionWorkOrganization = this
      .envService
      .selectedWorkOrganization
      /* .pipe(first())		// READS the Observable only once. Doesn't react to later changes on the subscription */
      .subscribe(org => {
        if(org){
          // Ignore if current selected didn't change. This prevents duplication of requests
          if (this.selectedOrganizationId != org.id) {
            // Set selected
            this.selectedOrganizationId = org.id;
            
            //Reload
            this.build();
          }
        }
      });
      
    // Listen to changes in PeriodSelector
    this.subscriptionPeriod = this
      .envService
      .selectedPeriod
      .pipe( skip(1)) // Ignore the current value. Just want to react to changes
      .subscribe(period => {
        if(period){
          // Calculate the latest periodVersionId
          this.periodService
            .lastVersion(period.id)
            .subscribe({
              next: (version: HttpResponse<IPeriodVersion>) => {
                if (version.body) {
                  const periodVersion = version.body;
                  this.periodVersionId = periodVersion.id;
                } else {
                  this.periodVersionId = null;
                }
                
                //Fire Re-Loading of the dashboard
                this.build();
                
              },
            });
        } else {
          // Clear the component. Nothing to show
          this.data = [];
        }
      });
      
    // Load periodVersion to show component footer info 
    if (this.periodVersionId != null) {
      this.periodService
        .getVersion(this.periodVersionId)
        .subscribe({
          next: (version: HttpResponse<IPeriodVersion>) => {
            if (version.body) {
              this.periodVersion = version.body;
            }
          },
        });
    }
  
    // If a DashboardComponent was given, just build
    if (this.dashboardComponent) {
      this.dashboardComponentId = this.dashboardComponent?.id;
      this.title = this.dashboardComponent?.name ?? "";
      
      this.build();
    } else if (this.dashboardComponentId) {
      // Must load the DashboardComponent first, and then build
      this.build();
    } else {
      // Unable to have a DashboardComponent. EXCEPTION
    }
  }
  
  formatDate(date: dayjs.Dayjs | null | undefined): string {    
    if (!date) {
      return '';
    }
    
    // Ensure `date` is a Day.js object before formatting
    const parsedDate = dayjs(date);
  
    return parsedDate.isValid() ? parsedDate.format('DD/MM/YYYY') : 'Invalid Date';
  }
  
  private build(): void {
    if (this.dashboardComponentId && this.periodVersionId && this.selectedOrganizationId) {
      this.dashboardComponentService
        .loadOrganizationValuesForComponent(this.selectedOrganizationId, this.dashboardComponentId, this.periodVersionId)
        .pipe(
          map(
            (res: HttpResponse<any>) => {
              const data = res.body ?? {};

              // Check if the data is already a Map or if it's a plain object
              if (data instanceof Map) {
                return data;
              } else {
                // Convert object to Map if it's not. 
                // I had to do it like this, otherwise, it wouldn't be a Map().
                const map = new Map<string, IDashboardComponentDetailValue[]>();
                Object.keys(data).forEach(key => {
                  map.set(key, data[key]);
                });
                
                const reorderedMap = this.moveTopOrgToLast(map);
                
                return reorderedMap;
              }
            }
          )
        )
        .subscribe(
          (_dashboardComponentDetailValues: Map<string, IDashboardComponentDetailValue[]>) => {
              this.dashboardComponentDetailValues = _dashboardComponentDetailValues;
              this.buildTable();
          }
        );
    } else {
      //Clear table
      this.data = [];
      this.labels = [];
    }
  }
  
  private moveTopOrgToLast(map: Map<string, IDashboardComponentDetailValue[]>): Map<string, IDashboardComponentDetailValue[]> {
    if (!map.has("1")) return map; // If key "9" doesn't exist, return map as is

    const entries = Array.from(map.entries()); // Convert Map to array of entries
    const entryTopOrg = entries.find(([key]) => key === "1"); // Find the entry with key "1"
    const filteredEntries = entries.filter(([key]) => key !== "1"); // Remove "9" entry

    if (entryTopOrg) {
      filteredEntries.push(entryTopOrg); // Move it to the end
    }

    return new Map(filteredEntries); // Create a new Map with updated order
  }
  
  private buildTable(): void {
    // Clear previous table
    this.labels.length = 0;
    this.data.length = 0;
  
    const variableMap: Map<string, number[]> = new Map();
    const variableLabelValues: Map<string, string> = new Map();
    const variableHelpValues: Map<string, string> = new Map();
    const variableTargetValues: Map<string, number|null> = new Map();
    const variableColorValues: Map<string, string> = new Map();
    
    // First column header. Variable description column
    this.labels.push("");
    
    // Rebuild
    this.dashboardComponentDetailValues.forEach((value, key) => {
      let isFirst = true;
      
      value.forEach(item => {
        if (isFirst) this.labels.push(item?.organization?.code || 'undefined');  //Add company label. Get it from the first element.
        isFirst = false;
        
        const variableValues = variableMap.get(item?.dashboardComponentDetail?.variable?.id.toString() || '');
        
        if (variableValues !== undefined) {
          // Append value to array
          variableValues.push(item.value || 0);
        } else {
          // Insert first entry
          variableMap.set(item?.dashboardComponentDetail?.variable?.id.toString() || '', [item.value || 0]);
          variableLabelValues.set(item?.dashboardComponentDetail?.variable?.id.toString() || '', item?.dashboardComponentDetail?.name || 'undefined');
          variableHelpValues.set(item?.dashboardComponentDetail?.variable?.id.toString() || '', item?.dashboardComponentDetail?.help || '');
          variableColorValues.set(item?.dashboardComponentDetail?.variable?.id.toString() || '', item?.dashboardComponentDetail?.color || '');
          variableTargetValues.set(item?.dashboardComponentDetail?.variable?.id.toString() || '', item?.targetDashboardComponent?.id || null);
        }
      });
    });
    
    // Build row array (Label | Value 1 | Value 2 | .. )
    variableMap.forEach((value, key) => {
      let newDataRow : AccordionItem = {
        id: key,
        label: variableLabelValues.get(key) || "",
        help: variableHelpValues.get(key) || "",
        values: [],
        target: variableTargetValues.get(key) || undefined,
        loading: false,
        expanded: false,
        children: undefined,
      };
      
      value.forEach(val => {
        let roundedNumber = FormatHelper.formatDecimalValue(val);
        newDataRow.values.push(roundedNumber); 
      });
      
      this.data.push(newDataRow);
    });
    
  }

  export(): void {
    if (this.periodVersionId && this.dashboardComponentId && this.selectedOrganizationId) {
      this.isExporting = true;

      this.dashboardComponentService.export(this.selectedOrganizationId, this.dashboardComponentId, this.periodVersionId).subscribe((blob: Blob) => {
        const a = document.createElement('a');
        const objectUrl = URL.createObjectURL(blob);
        a.href = objectUrl;
        a.download = 'report.xlsx';
        a.click();
        URL.revokeObjectURL(objectUrl);

        this.isExporting = false;
      });
    }
  }
}
