import {
  Component,
  OnInit,
  ElementRef,
  signal,
  effect,
  inject
} from '@angular/core';

import { FormsModule, NgModel } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatRadioModule } from '@angular/material/radio';
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { MatListModule } from '@angular/material/list';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatTabsModule } from '@angular/material/tabs';

import {
  LineStyleConfig,
  LineStyleDef,
  LineStyleSelectComponent
} from './linestyle-select.component';
import { SignalKPreferredPathsComponent } from './signalk-preferredpaths.component';
import { SettingsFacade } from '../settings.facade';
import { WakeLockService } from 'src/app/lib/services';
import { defaultConfig } from 'src/app/app.config';
import { SettingsOptions } from '../settings.facade';
import { S57Service } from '../../map/ol';
import { AppFacade } from 'src/app/app.facade';
import { Convert, TARGET_UNIT } from 'src/app/lib/convert';
import {
  CENTER_OFFSET_LIMIT,
  clampCenterOffset
} from 'src/app/lib/follow-offset';
import { RadarAPIService, SKRadar } from '../../radar/radar-api.service';

interface PreferredPathsResult {
  save: boolean;
  value: { [key: string]: string };
}

//** Settings **
@Component({
  selector: 'settings-dialog',
  imports: [
    FormsModule,
    MatDialogModule,
    MatCheckboxModule,
    MatRadioModule,
    MatCardModule,
    MatListModule,
    MatButtonModule,
    MatIconModule,
    MatTooltipModule,
    MatSliderModule,
    MatSlideToggleModule,
    MatSelectModule,
    MatFormFieldModule,
    MatInputModule,
    MatMenuModule,
    MatToolbarModule,
    MatTabsModule,
    SignalKPreferredPathsComponent,
    LineStyleSelectComponent
  ],
  templateUrl: './settings-dialog.html',
  styleUrls: ['./settings-dialog.css']
})
export class SettingsDialog implements OnInit {
  protected show = {
    favourites: signal<boolean>(false)
  };

  protected options: SettingsOptions;
  protected readonly centerOffsetLimit = CENTER_OFFSET_LIMIT;

  protected aisStateFilter = {
    moored: false,
    anchored: false
  };

  // model to hold numbers that require unit conversion
  protected formattedNumberModel = {
    rangeCirclesDistance: 1000
  };

  protected unitsChangedSignal = signal<number>(0);
  protected radarList = signal<SKRadar[]>([]);

  private saveOnClose = false;

  protected facade = inject(SettingsFacade);
  protected myElement = inject(ElementRef);
  protected dialogRef = inject(MatDialogRef<SettingsDialog>);
  protected wakeLock = inject(WakeLockService);
  private s57 = inject(S57Service);
  protected app = inject(AppFacade);
  protected radarApi = inject(RadarAPIService);

  constructor() {
    this.options = new SettingsOptions();

    effect(() => {
      this.unitsChangedSignal();
      this.formatNumberModel();
    });
  }

  ngOnInit() {
    this.facade.refresh();
    this.facade.settings.selections.aisState.forEach((i: string) => {
      if (i in this.aisStateFilter) {
        this.aisStateFilter[i] = true;
      }
    });
    this.refreshRadars();
  }

  protected refreshRadars() {
    this.radarApi
      .listRadars()
      .then((rl) => {
        this.radarList.set(rl);
      })
      .catch(() => {
        this.radarList.set([]);
      });
  }

  // format numbers in model used for form fields
  formatNumberModel() {
    this.formattedNumberModel = {
      rangeCirclesDistance:
        this.facade.settings.units.distance === 'kilometer'
          ? Math.floor(this.facade.settings.vessels.rangeCirclesDistance / 1000)
          : Math.floor(
              Convert.transform(
                this.facade.settings.vessels.rangeCirclesDistance,
                'm',
                'naut-mile'
              )
            )
    };
  }

  raiseChange() {
    this.app.alignUnitPrefs(this.app.serverConfig.unitPreferences());
    this.unitsChangedSignal.update((current) => current + 1);
  }

  /**
   * handle dialog close action
   */
  handleClose() {
    if (this.saveOnClose) {
      this.persistModel();
    }
    this.dialogRef.close();
  }

  /** apply wakelock state */
  doWakelock(checked: boolean) {
    this.persistModel();
    if (checked) {
      this.wakeLock.disable();
    } else {
      this.wakeLock.enable();
    }
  }

  /** apply S57 Options  */
  doS57(numericAttrib?: any) {
    this.facade.settings.map.s57Options.depthUnit =
      this.facade.settings.units.depth;
    if (numericAttrib) {
      this.parseNumber(numericAttrib);
    } else {
      this.persistModel();
    }
    this.s57.setOptions(this.facade.settings.map.s57Options);
  }

  /** apply RADAR Options  */
  doRadar() {
    this.radarApi
      .init(this.facade.settings.radars.deviceId)
      .then((id: string) => {
        if (id) {
          this.persistModel();
        }
      });
  }

  /** handle line style change */
  onLineStyle(
    lineType: 'cog' | 'heading',
    value: { lineStyle: LineStyleDef; config: LineStyleConfig }
  ) {
    const l = this.facade.settings.vessels.selfLines[lineType];
    if (l) {
      l.color = value.config.color;
      l.dash = value.config.dash;
      l.weight = value.config.weight;
      this.app.selfLines.update((current) => {
        const c = Object.assign({}, current);
        c[lineType] = value.lineStyle;
        return c;
      });
      this.persistModel();
    }
  }

  /**
   * toggle display of favourites
   */
  toggleFavourites() {
    this.show.favourites.update((current) => !current);
  }

  /**
   * Parse the entered vessel centre offset, clamping it to a whole percentage
   * that keeps the vessel on screen. Negative values look behind the vessel.
   */
  parseCenterOffset(e: NgModel) {
    e.reset(typeof e.model === 'number' ? clampCenterOffset(e.model) : 0);
    this.persistModel();
  }

  /**
   * Parse entered number value and fall back to default if null.
   * Resultant number value is always positive unless allowNegative = true.
   */
  parseNumber(e: NgModel, allowNegative?: boolean) {
    if (typeof e.model !== 'number') {
      e.reset(this.fallbackToDefault());
      return;
    }
    if (!allowNegative && e.model < 0) {
      e.reset(Math.abs(e.model));
    }
    this.persistModel();
  }

  /**
   * Parse & convert the entered number value to SI units and fall back to default if null.
   * Resultant number value is always positive unless allowNegative = true.
   */
  parseFormattedNumber(e: NgModel, allowNegative?: boolean) {
    if (typeof e.model !== 'number') {
      return;
    }
    if (!allowNegative && e.model < 0) {
      e.reset(Math.abs(e.model));
    }
    this.facade.settings.vessels.rangeCirclesDistance =
      this.facade.settings.units.distance === 'kilometer'
        ? Math.floor(this.formattedNumberModel.rangeCirclesDistance * 1000)
        : Math.floor(
            Convert.nauticalMilesToKm(
              this.formattedNumberModel.rangeCirclesDistance
            ) * 1000
          );
    this.persistModel();
  }

  /**
   * Returns the fallback value for an invalid number entry.
   * @returns default value
   */
  private fallbackToDefault() {
    const dconfig = defaultConfig();
    if (
      typeof this.facade.settings.display.statusBar.referenceSpeed !== 'number'
    ) {
      return dconfig.display.statusBar.referenceSpeed;
    }
    if (
      typeof this.facade.settings.display.statusBar.tapFadeSpeed !== 'number'
    ) {
      return dconfig.display.statusBar.tapFadeSpeed;
    }
    if (typeof this.facade.settings.map.s57Options.shallowDepth !== 'number') {
      return dconfig.map.s57Options.shallowDepth;
    }
    if (typeof this.facade.settings.map.s57Options.safetyDepth !== 'number') {
      return dconfig.map.s57Options.safetyDepth;
    }
    if (typeof this.facade.settings.map.s57Options.deepDepth !== 'number') {
      return dconfig.map.s57Options.deepDepth;
    }
    if (
      typeof this.facade.fixedPosition[0] !== 'number' ||
      typeof this.facade.fixedPosition[1] !== 'number'
    ) {
      return 0;
    }
    if (typeof this.facade.settings.course.arrivalCircle !== 'number') {
      return dconfig.course.arrivalCircle;
    }
  }

  /**
   * Persist Settings after model change
   */
  persistModel(value?: string) {
    this.facade.applySettings();
    this.facade.emitChangeEvent(value);
  }

  protected get speedUnitSymbol(): string {
    return Convert.getSymbol(this.facade.settings.units.speed);
  }

  /**
   * Defer persisting Settings until dialog close
   */
  deferPersist(value?: string) {
    this.saveOnClose = true;
    this.facade.emitChangeEvent(value);
  }

  /**
   * Handle default intrument app selection
   */
  onInstrumentApp() {
    this.persistModel('pluginInstruments');
    this.facade.buildFavouritesList();
  }

  /**
   * Handle Preferred Paths component event
   * @param e
   */
  onPreferredPaths(e: PreferredPathsResult) {
    if (e.save) {
      this.facade.settings.units.preferredPaths = e.value as any;
      this.persistModel();
    }
  }

  /**
   * Handle favourites component event
   * @param e
   */
  onFavSelected(e: unknown, f) {
    this.facade.settings.display.plugins.favourites =
      f.selectedOptions.selected.map((i) => i.value);
    this.persistModel();
  }

  /**
   * Handle AIS state filter change
   */
  onAisStateFilter() {
    const s = [];
    for (const i in this.aisStateFilter) {
      if (this.aisStateFilter[i]) {
        s.push(i);
      }
    }
    this.facade.settings.selections.aisState = [].concat(s);
    this.persistModel();
  }

  /**
   * Handle custom resource path selection changes
   * @param f
   */
  onResPathSelected(f) {
    this.facade.settings.resources.paths = f.selectedOptions.selected.map(
      (i) => i.value
    );
    //ensure all selected paths have relevant 'selections' entry
    this.facade.settings.resources.paths.forEach((i) => {
      if (i in this.facade.settings.selections.resourceSets) {
        /* already has selection array */
      } else {
        this.facade.settings.selections.resourceSets[i] = [];
      } //create selection array
    });
    this.persistModel();
  }

  /**
   * delete auth token
   */
  clearAuthToken() {
    this.facade.clearToken();
  }

  renderSymbol(unit: TARGET_UNIT) {
    return Convert.getSymbol(unit);
  }
}
