import { BASE } from '@/services/api.ts'
import { createApp, ref } from 'vue'
import Map from 'ol/Map'
import { Feature } from 'ol'
import Point from 'ol/geom/Point'
import LineString from 'ol/geom/LineString'
import { fromLonLat } from 'ol/proj'
import { Style, Icon, Stroke } from 'ol/style'
import { Vector as VectorLayer } from 'ol/layer'
import { Vector as VectorSource } from 'ol/source'
import { adjustedInterval } from '@ztimson/utils'
import AircraftPopup from '@/components/Aircraft.vue'
import { bringToFront } from './zindex'

const API = BASE + '/api'

function getAltColor(alt: number): [number, number, number] {
	const a = Math.max(0, alt)
	if (a < 10)    return [0, 0, 0]
	if (a < 10000) return [135, 206, 250]
	if (a < 25000) {
		const r = (a - 10000) / 15000
		return [Math.round(135 * (1-r)), Math.round(206 * (1-r)), Math.round(250 * (1-r) + 255 * r)]
	}
	if (a < 40000) {
		const r = (a - 25000) / 15000
		return [0, 0, Math.round(255 * (1-r) + 139 * r)]
	}
	const r = Math.min((a - 40000) / 10000, 1)
	return [Math.round(128 * r), 0, 139]
}

interface PopupInstance {
	planeRef: ReturnType<typeof ref>
	posRef:   ReturnType<typeof ref>
	unmount:  () => void
}

export class AirTrafficLayer {
	private map:          Map
	private layer!:       VectorLayer<VectorSource>
	private traceLayers:  Record<string, VectorLayer<VectorSource>> = {}
	private traceSegs:    Record<string, Feature[]>                 = {}
	private popups: any            = {}
	private historyCache: any                     = {}
	private data:         any[]                                     = []
	private clickHandler: ((e: any) => void) | null                 = null
	private refreshTimer: any                                       = null
	visible = false

	constructor(map: Map) { this.map = map }

	async show() {
		if (this.visible) return
		this.visible = true
		this.layer = new VectorLayer({ source: new VectorSource(), zIndex: 100 })
		this.map.addLayer(this.layer)
		await this._fetch()
		this._draw()
		this._attachClick()
		this.refreshTimer = adjustedInterval(async () => {
			try {
				await this._fetch()
				this._draw()
				this._refreshPopups()
			} catch (err) {
				console.error(err)
			}
		}, 1_000)
	}

	hide() {
		if (!this.visible) return
		this.visible = false
		if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null }
		if (this.clickHandler) { this.map.un('singleclick', this.clickHandler); this.clickHandler = null }
		for (const icao of Object.keys(this.popups)) this._closePopup(icao)
		this.map.removeLayer(this.layer)
		Object.values(this.traceLayers).forEach(l => this.map.removeLayer(l))
		this.traceLayers = {}
		this.traceSegs   = {}
	}

	private async _fetch() {
		const j = await fetch(`${API}/adsb`).then(r => r.ok ? r.json() : []);
		this.data = (j || []).map((a: any) => ({
			...a,
			icao:      a.hex,
			latitude:  a.lat,
			longitude: a.lon,
			heading:   a.track,
			speed:     a.gs,
			climb:     a.baro_rate ?? a.geom_rate ?? 0,
			name:      a.flight?.trim(),
		}))
	}

	private _draw() {
		const source = this.layer.getSource()!
		source.clear()
		for (const plane of this.data) {
			if (plane.latitude == null) continue
			const coord = fromLonLat([plane.longitude, plane.latitude])
			const f = new Feature({ geometry: new Point(coord) })
			f.set('icao',      plane.icao)
			f.set('planeData', plane)
			f.setStyle(new Style({
				image: new Icon({
					src:      `data:image/svg+xml,${encodeURIComponent(plane.icon)}`,
					scale:    1.25,
					rotation: (plane.heading ?? 0) * (Math.PI / 180),
					anchor:   [0.5, 0.5],
				}),
			}))
			source.addFeature(f)
		}
	}

	private async _fetchTrace(plane: any) {
		const icao = plane.icao
		if (!this.historyCache[icao]) {
			const j = await fetch(`${API}/adsb/${icao}`).then(r => r.json())
			this.historyCache[icao] = j.history || []
		}
		const history = [...this.historyCache[icao]]
		const cur     = { latitude: plane.latitude, longitude: plane.longitude, altitude: plane.alt_baro ?? plane.alt_geom ?? 0 }
		const last    = history[history.length - 1]
		if (!last || last.latitude !== cur.latitude || last.longitude !== cur.longitude) history.push(cur)
		if (history.length < 2) return

		let traceLayer = this.traceLayers[icao]
		let vs: VectorSource

		if (!traceLayer) {
			vs         = new VectorSource()
			traceLayer = new VectorLayer({ source: vs, zIndex: 99 })
			this.map.addLayer(traceLayer)
			this.traceLayers[icao] = traceLayer
			this.traceSegs[icao]   = []
		} else {
			vs = traceLayer.getSource()!
			;(this.traceSegs[icao] || []).forEach(f => vs.removeFeature(f))
		}

		const segs: Feature[] = []
		for (let i = 0; i < history.length - 1; i++) {
			const s   = history[i], e = history[i + 1]
			const sc  = getAltColor(s.altitude || 0)
			const ec  = getAltColor(e.altitude || 0)
			const avg = sc.map((v, idx) => Math.round((v + (ec[idx] as any)) / 2))
			const f   = new Feature(new LineString([fromLonLat([s.longitude, s.latitude]), fromLonLat([e.longitude, e.latitude])]))
			f.setStyle(new Style({ stroke: new Stroke({ color: `rgba(${avg.join(',')},0.8)`, width: 3 }) }))
			vs.addFeature(f)
			segs.push(f)
		}
		this.traceSegs[icao] = segs
	}

	private _calcPopupPos(plane: any): { x: number; y: number } {
		const pixel: any = this.map.getPixelFromCoordinate(fromLonLat([plane.longitude, plane.latitude]))
		if (!pixel) return { x: 10, y: 60 }
		const rect = (this.map.getTargetElement() as HTMLElement).getBoundingClientRect()
		return { x: rect.left + pixel[0] + 16, y: rect.top + pixel[1] - 16 }
	}

	private _openPopup(plane: any) {
		const icao = plane.icao
		if (this.popups[icao]) return

		const planeRef = ref(plane)
		const posRef   = ref(this._calcPopupPos(plane))

		const container = document.createElement('div')
		document.body.appendChild(container)

		const mobile = window.innerWidth <= 768;

		const app = createApp(AircraftPopup, {
			plane:          planeRef.value,
			position:       mobile ? {x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16} : {x: window.innerWidth - 360 - 16, y: 16},
			onClose:        () => this._closePopup(icao),
			onBringToFront: () => bringToFront(container),
		})

		app.mount(container)

		this.popups[icao] = {
			planeRef,
			posRef,
			unmount: () => { app.unmount(); container.remove() },
		}
		this._fetchTrace(plane)
	}

	private _closePopup(icao: string) {
		const popup = this.popups[icao]
		if (popup) { popup.unmount(); delete this.popups[icao] }
		const segs  = this.traceSegs[icao]
		const layer = this.traceLayers[icao]
		if (segs && layer) segs.forEach(f => layer.getSource()!.removeFeature(f))
		if (layer) { this.map.removeLayer(layer); delete this.traceLayers[icao] }
		delete this.traceSegs[icao]
	}

	private _refreshPopups() {
		for (const icao of Object.keys(this.popups)) {
			const plane = this.data.find(p => p.icao === icao)
			if (!plane) { this._closePopup(icao); continue }
			this.popups[icao].planeRef.value = plane
			this._fetchTrace(plane)
		}
	}

	private _attachClick() {
		this.clickHandler = (evt) => {
			const f = this.map.forEachFeatureAtPixel(evt.pixel, f => f.get('planeData') ? f : null)
			if (!f) return
			const plane = f.get('planeData')
			if (this.popups[plane.icao]) { this._closePopup(plane.icao); return }
			this._openPopup(plane)
		}
		this.map.on('singleclick', this.clickHandler)
	}
}
