import { bootstrapApplication } from '@angular/platform-browser';
import { Component, OnInit, AfterViewInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ApiService, ForecastPoint, WeatherPoint } from './app/api.service';
import { ChartPanelComponent } from './app/chart-panel.component';
import { TablePanelComponent } from './app/table-panel.component';
import { HeaderComponent } from './app/header.component';

type Station = {
  station_id: string;
  decimal_id?: string;
  latitude?: number;
  longitude?: number;
  location?: string;
  device_type?: string;
  last_update?: string;
  name?: string;
  station_alias?: string;
};

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, FormsModule, ChartPanelComponent, TablePanelComponent, HeaderComponent],
  templateUrl: './app.component.html',
})
export class AppComponent implements OnInit, AfterViewInit {
  constructor(private api: ApiService) {}

  onlineDevices = 0;
  serverTime = '';
  stations: Station[] = [];
  mapType = 'satellite';

  decimalId = '';
  interval = '1hour';
  start = '';
  end = '';
  // 默认英卓 V4
  provider = 'imdroid_mix';
  legendMode = 'combo_standard';
  showPastForecast = false;
  showPanels = false;
  selectedLocation = '';
  selectedTitle = '';
  isLoading = false;

  history: WeatherPoint[] = [];
  forecast: ForecastPoint[] = [];

  private map: any;
  private layers: any = {};
  private stationSource: any;
  private clusterSource: any;
  private stationLayer: any;
  private clusterLayer: any;
  private CLUSTER_THRESHOLD = 10;
  private tileOverlayGroup: any;
  private tileLastList: any[] = [];
  private refreshTimer: any;
  private mapEventsBound = false;
  tileTimes: string[] = [];
  tileIndex = -1;
  tileZ = 7; tileY = 40; tileX = 102;
  tileDt = '';
  tileProduct: 'none'|'radar'|'rain' = 'radar';
  isMapCollapsed = false;

  async ngOnInit() {
    await Promise.all([this.loadStatus(), this.loadStations()]);
    const now = new Date();
    const pad = (n:number)=> String(n).padStart(2,'0');
    const toLocal = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
    const end = new Date(now.getTime() + 4*3600*1000);      // 当前时间后4小时
    const start = new Date(now.getTime() - 24*3600*1000);   // 当前时间前24小时
    this.end = toLocal(end);
    this.start = toLocal(start);

    // 每10分钟检查并刷新数据（状态与站点），无刷新页面
    this.refreshTimer = setInterval(() => { this.refreshDataTick(); }, 10 * 60 * 1000);
  }

  ngAfterViewInit() { this.initMap(); this.reloadTileTimesAndShow(); }

  private async loadStatus() {
    const s = await this.api.getStatus();
    if (s) { this.onlineDevices = s.online_devices || 0; this.serverTime = s.server_time || ''; }
  }

  private async loadStations() { this.stations = await this.api.getStations(); this.updateStationsOnMap(); }

  private async refreshDataTick() {
    try {
      await Promise.all([this.loadStatus(), this.loadStations()]);
      // 若已选择设备并显示图表，则同时刷新历史与预报数据（不触发滚动/动画）
      if (this.showPanels && this.decimalId) {
        await this.query(true);
      }
    } catch {}
  }

  private getTiandituKey(): string {
    const anyWin = (window as any);
    return anyWin.TIANDITU_KEY || '0c260b8a094a4e0bc507808812cefdac';
  }

  get wh65lpCount(): number {
    return (this.stations || []).filter(s => s.device_type === 'WH65LP').length;
  }

  private initMap() {
    const ol: any = (window as any).ol; if (!ol) return;
    const tk = this.getTiandituKey();
    const mkLayer = (url: string) => new ol.layer.Tile({ source: new ol.source.XYZ({ url }) });
    this.layers = {
      satellite: new ol.layer.Group({ layers: [
        mkLayer(`https://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`),
        mkLayer(`https://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`)
      ]}),
      vector: new ol.layer.Group({ layers: [
        mkLayer(`https://t{0-7}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`),
        mkLayer(`https://t{0-7}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`)
      ], visible: false}),
      terrain: new ol.layer.Group({ layers: [
        mkLayer(`https://t{0-7}.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`),
        mkLayer(`https://t{0-7}.tianditu.gov.cn/cta_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cta&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`)
      ], visible: false}),
      hybrid: new ol.layer.Group({ layers: [
        mkLayer(`https://t{0-7}.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`),
        mkLayer(`https://t{0-7}.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${tk}`)
      ], visible: false})
    };

    this.stationSource = new ol.source.Vector();
    this.clusterSource = new ol.source.Cluster({ distance: 60, minDistance: 20, source: this.stationSource });
    this.clusterLayer = new ol.layer.Vector({ source: this.clusterSource, style: (f:any)=> this.createClusterStyle(f) });
    this.stationLayer = new ol.layer.Vector({ source: this.stationSource, visible: false, style: (f:any)=> this.createStationStyle(f) });
    this.tileOverlayGroup = new ol.layer.Group({ layers: [], zIndex: 999, visible: true });

    this.map = new ol.Map({ target: 'map', layers: [
      this.layers.satellite,
      this.layers.vector,
      this.layers.terrain,
      this.layers.hybrid,
      this.tileOverlayGroup,
      this.clusterLayer,
      this.stationLayer
    ], view: new ol.View({ center: ol.proj.fromLonLat([108, 35]), zoom: 5, minZoom: 3, maxZoom: 18 }) });

    this.map.getView().on('change:resolution', () => {
      const z = this.map.getView().getZoom();
      this.updateClusterDistance(z);
      this.updateLayerVisibility(z);
    });

    if (this.stations?.length) this.updateStationsOnMap();
  }

  switchLayer(layerType: string) {
    const layers = this.layers; if (!layers) return;
    Object.keys(layers).forEach(key => { if (layers[key].setVisible) layers[key].setVisible(key === layerType); });
  }

  private updateClusterDistance(zoom: number) {
    if (!this.clusterSource) return;
    const distance = zoom < this.CLUSTER_THRESHOLD ? 60 : 20;
    this.clusterSource.setDistance(distance);
  }

  private updateLayerVisibility(zoom: number) {
    if (!this.clusterLayer || !this.stationLayer) return;
    const showCluster = zoom < this.CLUSTER_THRESHOLD;
    this.clusterLayer.setVisible(showCluster);
    this.stationLayer.setVisible(!showCluster);
  }

  private markerIcon(isOnline: boolean) {
    const ol: any = (window as any).ol;
    const src = isOnline ? '/static/images/marker-online.svg' : '/static/images/marker-offline.svg';
    return new ol.style.Icon({ src, anchor: [0.5,1], anchorXUnits: 'fraction', anchorYUnits: 'fraction', scale: 0.9 });
  }

  private createStationStyle(feature: any) {
    const ol: any = (window as any).ol;
    const last = feature.get('lastUpdate');
    const online = last ? (new Date(last).getTime() > Date.now() - 5*60*1000) : false;
    const location = feature.get('location') || '';
    return new ol.style.Style({
      image: this.markerIcon(online),
      text: location ? new ol.style.Text({ text: location, offsetY: -28, fill: new ol.style.Fill({ color: '#111' }), stroke: new ol.style.Stroke({ color: '#fff', width: 3 }), font: '12px sans-serif' }) : undefined
    });
  }

  private createClusterStyle(feature: any) {
    const ol: any = (window as any).ol;
    const features = feature.get('features') || [];
    const size = features.length;
    const zoom = this.map.getView().getZoom();
    if (zoom < this.CLUSTER_THRESHOLD) {
      if (size > 1) {
        const radius = Math.min(16 + size * 0.8, 32);
        const fontSize = Math.min(11 + size/12, 16);
        return new ol.style.Style({
          image: new ol.style.Circle({ radius, fill: new ol.style.Fill({ color: 'rgba(0,123,255,0.8)' }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) }),
          text: new ol.style.Text({ text: String(size), fill: new ol.style.Fill({ color: '#fff' }), font: `bold ${fontSize}px Arial`, offsetY: 1 })
        });
      } else {
        const f0 = features[0];
        const last = f0?.get('lastUpdate');
        const online = last ? (new Date(last).getTime() > Date.now() - 5*60*1000) : false;
        const color = online ? 'rgba(0,123,255,0.8)' : 'rgba(108,117,125,0.8)';
        return new ol.style.Style({
          image: new ol.style.Circle({ radius: 6, fill: new ol.style.Fill({ color }), stroke: new ol.style.Stroke({ color: '#fff', width: 2 }) })
        });
      }
    }
    return this.createStationStyle(features[0]);
  }

  private updateStationsOnMap() {
    const ol: any = (window as any).ol; if (!ol || !this.stationSource) return;
    this.stationSource.clear();
    (this.stations||[]).forEach(s => {
      if (typeof s.longitude !== 'number' || typeof s.latitude !== 'number') return;
      const f = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat([s.longitude, s.latitude])), lastUpdate: (s as any).last_update || '', stationId: s.station_id, location: s.location || '' });
      this.stationSource.addFeature(f);
    });
    // click to select
    if (this.map && !this.mapEventsBound) {
      this.map.on('singleclick', async (evt:any) => {
        const olAny: any = (window as any).ol;
        const features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer });
        if (!features || features.length===0) return;
        let f = features[0];
        const subs = f.get('features');
        if (subs && subs.length>0) {
          const view = this.map.getView();
          const ex = olAny.extent.createEmpty();
          for (const sf of subs) {
            olAny.extent.extend(ex, sf.getGeometry().getExtent());
          }
          view.fit(ex, { duration: 300, maxZoom: 14, padding: [40,40,40,40] });
          return;
        }
        const sid = f.get('stationId');
        const loc = f.get('location') || '';
        if (!sid) return;
        const hex = String(sid).slice(-6).toUpperCase();
        this.decimalId = hex;
        await this.query();
        this.scrollToChart();
      });
      this.map.on('pointermove', (evt:any) => {
        const features = this.map.getFeaturesAtPixel(evt.pixel, { layerFilter: (l:any)=> l===this.stationLayer || l===this.clusterLayer });
        const el = this.map.getTargetElement();
        if (el) el.style.cursor = (features && features.length>0) ? 'pointer' : '';
        this.showTileTooltip(evt);
      });
      this.mapEventsBound = true;
    }
  }

  async loadTileTimes(product: 'radar'|'rain') {
    try {
      const params = new URLSearchParams({ z: String(this.tileZ), y: String(this.tileY), x: String(this.tileX), limit: '60' });
      const path = product==='rain' ? '/api/rain/times' : '/api/radar/times';
      const r = await fetch(`${path}?${params.toString()}`);
      if (!r.ok) return;
      const j = await r.json();
      this.tileTimes = j.times || [];
      this.tileIndex = 0;
      this.tileDt = this.tileTimes[0] || '';
      if (this.tileDt) await this.renderTilesAt(this.tileDt);
    } catch {}
  }

  async renderTilesAt(dt: string) {
    try {
      const params = new URLSearchParams({ z: String(this.tileZ), dt: dt });
      const path = this.tileProduct==='rain' ? '/api/rain/tiles_at' : '/api/radar/tiles_at';
      const r = await fetch(`${path}?${params.toString()}`);
      if (!r.ok) { this.clearTileOverlays(); return; }
      const j = await r.json();
      const tiles = Array.isArray(j.tiles) ? j.tiles : [];
      if (tiles.length === 0) { this.clearTileOverlays(); return; }
      await this.renderTilesOnMap(this.tileProduct, tiles);
    } catch { this.clearTileOverlays(); }
  }

  clearTileOverlays() {
    if (!this.tileOverlayGroup) return;
    const coll = this.tileOverlayGroup.getLayers();
    if (coll) coll.clear();
  }

  addImageOverlayFromCanvas(canvas: HTMLCanvasElement, extent4326: [number,number,number,number]) {
    const ol: any = (window as any).ol; if (!ol || !this.map) return;
    const proj = this.map.getView().getProjection();
    const extentProj = ol.proj.transformExtent(extent4326, 'EPSG:4326', proj);
    const src = new ol.source.ImageStatic({ url: canvas.toDataURL('image/png'), imageExtent: extentProj, projection: proj });
    const layer = new ol.layer.Image({ source: src, opacity: 0.8, visible: true });
    this.tileOverlayGroup.getLayers().push(layer);
  }

  async renderTilesOnMap(product: 'none'|'radar'|'rain', tiles: any[]) {
    this.clearTileOverlays();
    const lastList: any[] = [];
    for (const t of tiles) {
      const w = t.width, h = t.height; if (!w||!h||!t.values) continue;
      const canvas = document.createElement('canvas'); canvas.width=w; canvas.height=h;
      const ctx = canvas.getContext('2d')!; const img = ctx.createImageData(w,h);
      const radarColors = [[0,0,255],[0,191,255],[0,255,255],[127,255,212],[124,252,0],[173,255,47],[255,255,0],[255,215,0],[255,165,0],[255,140,0],[255,69,0],[255,0,0],[220,20,60],[199,21,133],[139,0,139]];
      const rainEdges = [0,5,7.5,10,12.5,15,17.5,20,25,30,40,50,75,100, Infinity];
      const rainColors = [
        [126,212,121], [126,212,121], [110,200,109], [97,169,97], [81,148,76], [90,158,112],
        [143,194,254], [92,134,245], [66,87,240], [45,48,214], [26,15,166], [63,22,145], [191,70,148], [213,1,146], [213,1,146]
      ];
      for (let row=0; row<h; row++) {
        const srcRow = t.values[row] as (number|null)[];
        const dstRow = (h - 1 - row);
        for (let col=0; col<w; col++) {
          const v = srcRow[col];
          const off = (dstRow*w + col)*4;
          if (v==null || v===0) { img.data[off+3]=0; continue; }
          if (product==='rain') {
            let idx=0; while(idx<rainEdges.length-1 && !(v>=rainEdges[idx] && v<rainEdges[idx+1])) idx++;
            const c = rainColors[Math.min(idx, rainColors.length-1)]; img.data[off]=c[0]; img.data[off+1]=c[1]; img.data[off+2]=c[2]; img.data[off+3]=220;
          } else {
            let bin = Math.floor(Math.max(0, Math.min(75, v))/5); if (bin>=radarColors.length) bin=radarColors.length-1;
            const c = radarColors[bin]; img.data[off]=c[0]; img.data[off+1]=c[1]; img.data[off+2]=c[2]; img.data[off+3]=220;
          }
        }
      }
      ctx.putImageData(img,0,0);
      this.addImageOverlayFromCanvas(canvas, [t.west, t.south, t.east, t.north]);
      lastList.push({ product, meta: { west: t.west, south: t.south, east: t.east, north: t.north, width: w, height: h }, values: t.values });
    }
    this.tileLastList = lastList;
  }

  onProductChange() { this.reloadTileTimesAndShow(); }
  async reloadTileTimesAndShow() {
    if (this.tileProduct==='none') { this.clearTileOverlays(); this.tileTimes=[]; this.tileDt=''; return; }
    await this.loadTileTimes(this.tileProduct);
  }

  private makeStationIdFromHex(hexRaw: string): string | null {
    if (!hexRaw) return null;
    const hex = String(hexRaw).toUpperCase().replace(/[^0-9A-F]/g, '').padStart(6, '0').slice(-6);
    if (!hex) return null;
    return `RS485-${hex}`;
  }

  async query(suppressUX: boolean = false) {
    const dec = this.decimalId.trim();
    if (!dec) return;
    const sid = this.makeStationIdFromHex(dec);
    if (!sid) return;
    const toFmt = (s: string) => s.replace('T',' ') + ':00';
    const from = toFmt(this.start);
    const to = toFmt(this.end);
    this.isLoading = true;
    try {
      [this.history, this.forecast] = await Promise.all([
        this.api.getHistory(dec, from, to, this.interval),
        this.provider ? this.api.getForecast(sid, from, to, this.provider, 3) : Promise.resolve([])
      ]);
    } finally {
      this.isLoading = false;
    }
    this.showPanels = true;
    if (!suppressUX) this.isMapCollapsed = true;
    const st = this.stations.find(s => s.station_id === sid);
    const ol: any = (window as any).ol;
    if (!suppressUX) {
      if (st && ol && typeof st.longitude === 'number' && typeof st.latitude === 'number' && this.map) {
        this.map.getView().animate({ center: ol.proj.fromLonLat([st.longitude, st.latitude]), zoom: 11, duration: 400 });
      }
    }
    this.selectedLocation = (st && st.location) ? st.location : '';
    const titleName = st?.name || st?.station_alias || st?.station_id || '';
    this.selectedTitle = titleName ? `${titleName}${this.selectedLocation ? ` | ${this.selectedLocation}` : ''}` : (this.selectedLocation || '');
    if (!suppressUX) {
      setTimeout(()=>{ try{ this.map.updateSize(); }catch{} }, 300);
      this.scrollToChart();
    }
  }

  onSelectStation(ev: { stationId: string, hex: string }) {
    this.decimalId = ev.hex;
    this.query();
  }

  prevTile() {
    if (!this.tileTimes || this.tileTimes.length===0) return;
    if (this.tileIndex < this.tileTimes.length-1) {
      this.tileIndex += 1;
      this.tileDt = this.tileTimes[this.tileIndex];
      this.renderTilesAt(this.tileDt);
    }
  }

  nextTile() {
    if (!this.tileTimes || this.tileTimes.length===0) return;
    if (this.tileIndex > 0) {
      this.tileIndex -= 1;
      this.tileDt = this.tileTimes[this.tileIndex];
      this.renderTilesAt(this.tileDt);
    }
  }

  toggleMap() {
    this.isMapCollapsed = !this.isMapCollapsed;
    setTimeout(()=>{ try{ this.map.updateSize(); }catch{} }, 300);
  }

  private scrollToChart() {
    const el = document.getElementById('chartSection');
    if (el) { try { el.scrollIntoView({ behavior: 'smooth', block: 'start' }); } catch {} }
  }

  private showTileTooltip(evt:any) {
    const tip = document.getElementById('tileValueTooltip');
    if (!tip || !this.map || !this.tileLastList || this.tileLastList.length===0) { if (tip) tip.style.display='none'; return; }
    try {
      const coord = this.map.getEventCoordinate(evt.originalEvent);
      const lonlat = (window as any).ol.proj.transform(coord, this.map.getView().getProjection(), 'EPSG:4326');
      const lon = lonlat[0], lat = lonlat[1];
      let value: number|null = null; let unit = '';
      for (const it of this.tileLastList) {
        const { west,south,east,north,width,height } = it.meta;
        if (lon < west || lon > east || lat < south || lat > north) continue;
        const px = Math.floor((lon - west) / ((east - west) / width));
        const py = Math.floor((lat - south) / ((north - south) / height));
        if (px < 0 || px >= width || py < 0 || py >= height) continue;
        const v = it.values?.[py]?.[px];
        if (v != null) { value = Number(v); unit = it.product==='rain' ? 'mm' : 'dBZ'; break; }
      }
      if (value == null) { tip.style.display='none'; return; }
      tip.textContent = `${value.toFixed(1)} ${unit}`;
      const px = evt.pixel[0] + 12; const py = evt.pixel[1] + 12;
      tip.style.left = `${px}px`; tip.style.top = `${py}px`; tip.style.display='block';
    } catch { if (tip) tip.style.display='none'; }
  }
}

bootstrapApplication(AppComponent).catch(err => console.error(err));
