import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of, throwError } from 'rxjs';
import { catchError, map, tap } from 'rxjs/operators';

export type SidebarPosition = 'inside-right' | 'inside-left' | 'left' | 'right' | 'top' | 'bottom';
export type SidebarAnimationStyle = 'slide' | 'fade' | 'expand';
export type SidebarBackgroundType = 'solid' | 'image';

export interface SidebarConfig {
  position: SidebarPosition;
  animationStyle: SidebarAnimationStyle;
  widthVw: number;
  backgroundType: SidebarBackgroundType;
  backgroundColor: string;
  backgroundImage: string;
  backgroundImageOpacity?: number;
  backgroundBlurPx?: number;
  backgroundBrightness?: number;
  backgroundWhiteSmokeOpacity?: number;
  backgroundDarkOverlayOpacity?: number;
  fontFamily: string;
  fontSizePx: number;
  textColor: string;
  autoHide: boolean;
  reappearOnSelection: boolean;
}

const STORAGE_KEY = 'smart_mapping_sidebar_config_v1';

interface SidebarConfigStorage {
  default?: Partial<SidebarConfig>;
  maps?: { [mapId: string]: Partial<SidebarConfig> };
}

const DEFAULT_CONFIG: SidebarConfig = {
  position: 'inside-right',
  animationStyle: 'slide',
  widthVw: 16,
  backgroundType: 'solid',
  backgroundColor: 'rgba(10, 10, 12, 0.88)',
  backgroundImage: '',
  backgroundImageOpacity: 0.9,
  backgroundBlurPx: 0,
  backgroundBrightness: 1,
  backgroundWhiteSmokeOpacity: 0,
  backgroundDarkOverlayOpacity: 0,
  fontFamily: 'Segoe UI, Tahoma, sans-serif',
  fontSizePx: 13,
  textColor: 'rgba(255, 255, 255, 0.92)',
  autoHide: false,
  reappearOnSelection: true,
};

@Injectable({ providedIn: 'root' })
export class SidebarConfigService {
  private schemaCache: SidebarConfigStorage | null = null;
  private activeMapId: string | null = null;
  private readonly apiBaseUrl = 'http://localhost:8080/api/maps';
  private readonly configSubject = new BehaviorSubject<SidebarConfig>(this.loadFromStorage());
  readonly config$: Observable<SidebarConfig> = this.configSubject.asObservable();

  constructor(private http: HttpClient) {}

  private removeUndefined<T extends Record<string, any>>(obj: T): Partial<T> {
    const cleaned: Partial<T> = {};
    Object.keys(obj || {}).forEach((key) => {
      const value = obj[key as keyof T];
      if (value !== undefined) {
        (cleaned as any)[key] = value;
      }
    });
    return cleaned;
  }

  /** Observable config stream for reactive consumers. */
  getConfig$(): Observable<SidebarConfig> {
    return this.config$;
  }

  /** Set which map context is currently active so consumers receive the correct per-map config. */
  setActiveMapId(mapId?: string | number | null): void {
    this.activeMapId = mapId !== null && mapId !== undefined ? String(mapId) : null;
    this.emitActiveConfig();

    if (this.activeMapId) {
      this.loadConfigForMap(this.activeMapId).subscribe({
        error: () => {
          // Keep UI resilient when backend is unavailable.
        }
      });
    }
  }

  getConfig(): SidebarConfig {
    return this.configSubject.value;
  }

  /** Replace the global/default config. */
  setConfig(config: SidebarConfig): void {
    const next = this.normalizeConfig(config);
    this.persist(next);
    this.emitActiveConfig();
  }

  /** Get the effective config for a specific map id. Falls back to default. */
  getConfigForMap(mapId?: string | number): SidebarConfig {
    if (!mapId) return this.getConfig();
    const schema = this.loadSchema();
    const mapCfg = schema.maps?.[String(mapId)];
    if (mapCfg) return { ...DEFAULT_CONFIG, ...schema.default, ...mapCfg } as SidebarConfig;
    return { ...DEFAULT_CONFIG, ...schema.default } as SidebarConfig;
  }

  /** Load map-specific config from backend and cache it locally for fast synchronous access. */
  loadConfigForMap(mapId: string | number): Observable<SidebarConfig> {
    const key = String(mapId);
    return this.http.get<Partial<SidebarConfig>>(`${this.apiBaseUrl}/${key}/sidebar-config`).pipe(
      map((remote) => this.normalizeConfig(remote)),
      tap((config) => this.upsertMapConfigCache(key, config, true)),
      catchError(() => {
        const fallback = this.getConfigForMap(key);
        if (this.activeMapId === key) {
          this.configSubject.next(fallback);
        }
        return of(fallback);
      })
    );
  }

  /** Persist a map-specific config to backend, while keeping an immediate local preview update. */
  saveConfigForMap(mapId: string | number, patch: Partial<SidebarConfig>): Observable<SidebarConfig> {
    const key = String(mapId);
    const existing = this.getConfigForMap(key);
    const safePatch = this.removeUndefined(patch as any) as Partial<SidebarConfig>;
    const next = this.normalizeConfig({ ...existing, ...safePatch });

    // Apply locally first so UI updates instantly.
    this.upsertMapConfigCache(key, next, true);

    return this.http.put<Partial<SidebarConfig>>(`${this.apiBaseUrl}/${key}/sidebar-config`, next).pipe(
      map((saved) => this.normalizeConfig(saved)),
      tap((savedConfig) => this.upsertMapConfigCache(key, savedConfig, true)),
      catchError((err) => throwError(() => err))
    );
  }

  updateConfig(patch: Partial<SidebarConfig>): void {
    const safePatch = this.removeUndefined(patch as any) as Partial<SidebarConfig>;
    const next: SidebarConfig = { ...this.configSubject.value, ...safePatch };
    this.setConfig(next);
  }

  /** Update or create a config for a specific map id. Does not change the global default. */
  updateConfigForMap(mapId: string | number, patch: Partial<SidebarConfig>): void {
    const key = String(mapId);
    const existing = this.getConfigForMap(key);
    const safePatch = this.removeUndefined(patch as any) as Partial<SidebarConfig>;
    const next = this.normalizeConfig({ ...existing, ...safePatch });
    this.upsertMapConfigCache(key, next, true);
  }

  /** Reset the config for a given map id (remove map override). */
  resetConfigForMap(mapId: string | number): void {
    const schema = this.loadSchema();
    const key = String(mapId);
    if (schema.maps && schema.maps[key]) {
      delete schema.maps[key];
      this.persistSchema(schema);
    }

    if (this.activeMapId === key) {
      this.configSubject.next(this.getConfigForMap(key));
    }

    // Best-effort remote reset; local reset is kept even if network fails.
    this.http.delete<void>(`${this.apiBaseUrl}/${key}/sidebar-config`).pipe(
      catchError(() => of(void 0))
    ).subscribe();
  }

  resetConfig(): void {
    this.setConfig(DEFAULT_CONFIG);
  }

  private emitActiveConfig(): void {
    if (this.activeMapId) {
      this.configSubject.next(this.getConfigForMap(this.activeMapId));
      return;
    }
    this.configSubject.next(this.loadFromStorage());
  }

  private normalizeConfig(config: Partial<SidebarConfig> | null | undefined): SidebarConfig {
    const safeConfig = this.removeUndefined((config || {}) as any) as Partial<SidebarConfig>;
    return { ...DEFAULT_CONFIG, ...safeConfig } as SidebarConfig;
  }

  private upsertMapConfigCache(mapId: string, config: SidebarConfig, emit: boolean): void {
    const schema = this.loadSchema();
    schema.maps = schema.maps || {};
    schema.maps[mapId] = this.removeUndefined(config as any) as Partial<SidebarConfig>;
    this.persistSchema(schema);

    if (emit && this.activeMapId === mapId) {
      this.configSubject.next({ ...config });
    }
  }

  private loadFromStorage(): SidebarConfig {
    try {
      const schema = this.loadSchema();
      const merged = { ...DEFAULT_CONFIG, ...(schema.default || {}) } as SidebarConfig;
      return merged;
    } catch {
      return DEFAULT_CONFIG;
    }
  }

  private loadSchema(): SidebarConfigStorage {
    if (this.schemaCache) return this.schemaCache;
    try {
      const raw = localStorage.getItem(STORAGE_KEY);
      if (!raw) {
        this.schemaCache = {};
        return this.schemaCache;
      }
      const parsed = JSON.parse(raw);
      // Backwards compatibility: if parsed looks like a SidebarConfig, treat as default
      if (parsed && typeof parsed === 'object' && (parsed.position || parsed.widthVw)) {
        this.schemaCache = { default: parsed } as SidebarConfigStorage;
        return this.schemaCache;
      }
      this.schemaCache = parsed as SidebarConfigStorage;
      return this.schemaCache;
    } catch {
      this.schemaCache = {};
      return this.schemaCache;
    }
  }

  private persistSchema(schema: SidebarConfigStorage): void {
    // Keep runtime cache authoritative even if storage quota/write fails.
    this.schemaCache = schema;
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(schema));
    } catch {
      // ignore
    }
  }

  private persist(config: SidebarConfig): void {
    try {
      // Write into the schema's default slot for backward compatibility
      const schema = this.loadSchema();
      schema.default = config;
      this.persistSchema(schema);
    } catch {
      // Ignore storage failures (private mode / quota) and keep runtime config.
    }
  }
}

