import {BASE} from '@/services/api.ts';
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';

const API = BASE + '/api'

const COLORS: Record<string, string> = {
	'Cargo':     '#00ff00',
	'Military':  '#ff0000',
	'Private':   '#8450ea',
	'Passenger': '#009dff',
	'Unknown':   '#fad106',
}

const SPEED_UNITS = {
	knots: { label: 'KTS', convert: (v: number) => Math.round(v) },
	kph:   { label: 'KPH', convert: (v: number) => Math.round(v * 1.852) },
	mph:   { label: 'MPH', convert: (v: number) => Math.round(v * 1.15078) },
}

const ALTITUDE_UNITS = {
	meters: { label: 'M',   convert: (v: number) => Math.round(v * 0.3048) },
	feet:   { label: 'FT',  convert: (v: number) => v },
}

const VERTICAL_UNITS = {
	mps: { label: 'M/S',  convert: (v: number) => Math.round(v * 0.3048) },
	fps: { label: 'FT/S', convert: (v: number) => v },
}

const UNIT_KEY = 'at_units'
function getUnits() {
	const s = localStorage.getItem(UNIT_KEY)
	return s ? JSON.parse(s) : { speed: 'knots', altitude: 'meters', vertical: 'mps' }
}
function saveUnits(p: any) { localStorage.setItem(UNIT_KEY, JSON.stringify(p)) }

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]
}

function buildPlaneIcon(plane: any, shapes: any): string {
	const color = COLORS[plane.class] || COLORS['Unknown']
	const shape = shapes[plane.typecode]
	if(!shape) return `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><polygon points="12,2 22,22 12,17 2,22" fill="${color}" stroke="#000" stroke-width="1"/></svg>`)}`
	const paths = (Array.isArray(shape.path) ? shape.path : [shape.path]).map((p: string) => `<path d="${p}" fill="${color}" stroke="#000" stroke-width="${shape.stroke || 1}"/>`).join('')
	return `data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="${shape.w * 2}" height="${shape.h * 2}" viewBox="${shape.viewBox}">${paths}</svg>`)}`
}

function buildNavballSvg(data: any): string {
	const heading         = data.heading ?? 0
	const airspeedKnots   = data.speed || 0
	const verticalRateFps = data.climb || 0
	const groundSpeedFps  = airspeedKnots * 1.68781
	const pitchRad        = groundSpeedFps > 0 ? Math.atan(verticalRateFps / groundSpeedFps) : 0
	const pitch           = Math.max(-25, Math.min(25, pitchRad * (180 / Math.PI)))

	const pitchLines: string[] = []
	for (let p = -30; p <= 30; p += 5) {
		if (p === 0) continue
		const isMajor = p % 10 === 0
		const [start, end] = [85, 115]
		if (isMajor) {
			pitchLines.push(`
                <text x="${start - Math.abs(p) - 5}" y="${104 + pitch * 2.5 - p * 2.5}" fill="#fff" font-size="8" font-weight="bold" text-anchor="end">${Math.abs(p)}</text>
                <line x1="${start - Math.abs(p)}" y1="${100 + pitch * 2.5 - p * 2.5}" x2="${end + Math.abs(p)}" y2="${100 + pitch * 2.5 - p * 2.5}" stroke="#fff" stroke-width="2"/>
                <text x="${end + Math.abs(p) + 5}" y="${104 + pitch * 2.5 - p * 2.5}" fill="#fff" font-size="8" font-weight="bold">${Math.abs(p)}</text>
            `)
		} else {
			pitchLines.push(`<line x1="${start}" y1="${100 + pitch * 2.5 - p * 2.5}" x2="${end}" y2="${100 + pitch * 2.5 - p * 2.5}" stroke="#fff" stroke-width="2"/>`)
		}
	}

	const rollTicks: string[] = []
	for (const angle of [-90, -60, -45, -30, -20, -10, 0, 10, 20, 30, 45, 60, 90]) {
		const rad = angle * Math.PI / 180
		const a   = Math.abs(angle)
		if (a === 45) {
			rollTicks.push(`<circle cx="${100 + 78 * Math.sin(rad)}" cy="${100 - 78 * Math.cos(rad)}" r="2.5" fill="#fff"/>`)
		} else {
			const inner = (a === 0 || a % 30 === 0) ? 72 : 77
			const sw    = (a === 0 || a % 30 === 0) ? 2.5 : 1.5
			rollTicks.push(`<line x1="${100 + inner * Math.sin(rad)}" y1="${100 - inner * Math.cos(rad)}" x2="${100 + 85 * Math.sin(rad)}" y2="${100 - 85 * Math.cos(rad)}" stroke="#fff" stroke-width="${sw}"/>`)
		}
	}

	const compassLabels = [-60, -45, -30, -15, 0, 15, 30, 45, 60].map(offset => {
		const rad = offset * Math.PI / 180
		const x   = 100 + 90 * Math.sin(rad)
		const y   = 100 - 90 * Math.cos(rad)
		const h   = Math.round((offset + 360) % 360)
		const lbl = ({ 0: 'N', 90: 'E', 180: 'S', 270: 'W' } as any)[h] || ''
		return lbl ? `<text x="${x}" y="${y + 4}" text-anchor="middle" fill="#0f0" font-size="13" font-weight="bold">${lbl}</text>` : ''
	}).join('')

	return `
        <svg width="200" height="220" viewBox="0 0 200 220">
            <defs>
                <clipPath id="navballClip"><circle cx="100" cy="100" r="85"/></clipPath>
                <linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
                    <stop offset="0%"   style="stop-color:#1e90ff"/>
                    <stop offset="100%" style="stop-color:#4169e1"/>
                </linearGradient>
                <linearGradient id="groundGrad" x1="0%" y1="0%" x2="0%" y2="100%">
                    <stop offset="0%"   style="stop-color:#8b4513"/>
                    <stop offset="100%" style="stop-color:#654321"/>
                </linearGradient>
            </defs>
            <rect x="77" y="5" width="46" height="18" fill="rgba(0,0,0,0.95)" stroke="#0f0" stroke-width="2" rx="2"/>
            <text x="100" y="18" text-anchor="middle" fill="#0f0" font-size="14" font-weight="bold">${heading.toFixed(0).padStart(3, '0')}°</text>
            <g transform="translate(0,25)" clip-path="url(#navballClip)">
                <rect x="15" y="${15 + pitch * 2.5 - 200}" width="170" height="285" fill="url(#skyGrad)"/>
                <rect x="15" y="${100 + pitch * 2.5}" width="170" height="285" fill="url(#groundGrad)"/>
                <line x1="15" y1="${100 + pitch * 2.5}" x2="185" y2="${100 + pitch * 2.5}" stroke="#fff" stroke-width="3"/>
                ${pitchLines.join('')}
            </g>
            <g transform="translate(0,25)">${rollTicks.join('')}</g>
            <circle cx="100" cy="125" r="85" fill="none" stroke="#0f0" stroke-width="2.5"/>
            <line x1="40" y1="125" x2="80"  y2="125" stroke="#ff0" stroke-width="3.5"/>
            <line x1="120" y1="125" x2="160" y2="125" stroke="#ff0" stroke-width="3.5"/>
            <circle cx="100" cy="125" r="4" fill="none" stroke="#ff0" stroke-width="2.5"/>
            <g transform="rotate(${-heading} 100 125)">${compassLabels}</g>
            <polygon points="100,37 95,30 105,30" fill="#ff0"/>
        </svg>
    `
}

function buildGaugeHtml(label: string, value: any, unit: string, type: string): string {
	return `
        <div style="margin-bottom:12px">
            <div style="color:#888;font-size:11px;font-weight:bold;margin-bottom:3px">${label}</div>
            <div class="at-gauge" data-gauge="${type}" style="padding:6px;background:rgba(0,0,0,0.95);border:2px solid #0f0;border-radius:3px;cursor:pointer;display:flex;align-items:end;justify-content:center;gap:4px">
                <span style="font-size:20px;font-weight:bold;color:#0f0">${value}</span>
                <span style="font-size:11px;color:#0f0;margin-bottom:2px">${unit}</span>
            </div>
        </div>
    `
}

function buildPopupHtml(data: any): string {
	const prefs    = getUnits()
	const speed    = SPEED_UNITS[prefs.speed as keyof typeof SPEED_UNITS]
	const alt      = ALTITUDE_UNITS[prefs.altitude as keyof typeof ALTITUDE_UNITS]
	const vert     = VERTICAL_UNITS[prefs.vertical as keyof typeof VERTICAL_UNITS]
	const callsign = data.name?.trim()
	const altitude = data.alt_baro ?? data.alt_geom ?? 0

	return `
        <div style="font-family:monospace;color:#ccc;min-width:320px">
            <div style="padding:12px 16px;border-bottom:1px solid rgba(200,200,220,0.2)">
                <h3 style="margin:0;color:#7eeee1;font-size:16px">
                    ✈️ ${callsign
		? `<a href="https://www.flightaware.com/live/flight/${callsign}" target="_blank" style="color:#6bb6ff;text-decoration:none">${callsign.toUpperCase()}</a>`
		: 'N/A'}
                </h3>
                <div style="font-size:11px;color:#888;margin-top:4px">
                    ${data.country || ''} • ${data.class || 'Unknown'} • ${data.type || ''}
                    <span style="float:right">ICAO: ${(data.icao || '').toUpperCase()}</span>
                </div>
            </div>
            <div style="display:flex;gap:16px;padding:12px 16px">
                <div style="flex:0 0 110px">
                    ${buildGaugeHtml('Air Speed', speed.convert(data.speed || 0),                                                                      speed.label, 'speed')}
                    ${buildGaugeHtml('Altitude',  data.landed ? 'LANDED' : alt.convert(altitude), data.landed ? '' : alt.label,   'altitude')}
                    ${buildGaugeHtml('Climb',     vert.convert(data.climb || 0) >= 0 ? `+${vert.convert(data.climb || 0)}` : vert.convert(data.climb || 0), vert.label, 'vertical')}
                </div>
                <div style="flex:1">${buildNavballSvg(data)}</div>
            </div>
        </div>
    `
}

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

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

	// ── Public ────────────────────────────────────────────────────────────────

	async show() {
		if (this.visible) return
		this.visible = true

		if (!this.shapes) this.shapes = await fetch(`${API}/vehicles`).then(r => r.json())

		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 () => {
			await this._fetch()
			this._draw()
			this._refreshPopups()
		}, 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 ───────────────────────────────────────────────────────────────

	private async _fetch() {
		const j = await fetch(`${API}/adsb`).then(r => 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:      buildPlaneIcon(plane, this.shapes),
					scale:    1,
					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 = [...(<any>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 + <any>ec[idx]) / 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 _openPopup(plane: any) {
		const icao = plane.icao
		if (this.popups[icao]) return

		const el    = document.createElement('div')
		el.style.cssText = `
            position:absolute; z-index:9999; pointer-events:auto;
            background:rgba(0,0,0,0.88); border:1px solid rgba(200,200,220,0.3);
            border-radius:8px; box-shadow:0 4px 24px rgba(0,0,0,0.5); overflow:hidden;
        `

		const close         = document.createElement('button')
		close.innerHTML     = '✕'
		close.style.cssText = 'position:absolute;top:8px;right:8px;background:none;border:none;color:#888;font-size:14px;cursor:pointer;z-index:1'
		close.onclick       = () => this._closePopup(icao)

		el.innerHTML = buildPopupHtml(plane)
		el.appendChild(close)
		document.body.appendChild(el)
		this._positionPopup(el, plane)

		el.querySelectorAll('.at-gauge').forEach(g => {
			g.addEventListener('click', (e) => {
				const prefs = getUnits()
				const type  = (e.currentTarget as HTMLElement).dataset.gauge!
				if (type === 'speed') {
					const keys = Object.keys(SPEED_UNITS)
					prefs.speed = keys[(keys.indexOf(prefs.speed) + 1) % keys.length]
				} else if (type === 'altitude') {
					const keys = Object.keys(ALTITUDE_UNITS)
					prefs.altitude = keys[(keys.indexOf(prefs.altitude) + 1) % keys.length]
					prefs.vertical = prefs.altitude === 'meters' ? 'mps' : 'fps'
				} else if (type === 'vertical') {
					const keys = Object.keys(VERTICAL_UNITS)
					prefs.vertical = keys[(keys.indexOf(prefs.vertical) + 1) % keys.length]
					prefs.altitude = prefs.vertical === 'mps' ? 'meters' : 'feet'
				}
				saveUnits(prefs)
				el.innerHTML = buildPopupHtml(plane)
				el.appendChild(close)
			})
			g.addEventListener('mouseenter', e => (e.currentTarget as HTMLElement).style.borderColor = '#0ff')
			g.addEventListener('mouseleave', e => (e.currentTarget as HTMLElement).style.borderColor = '#0f0')
		})

		this.popups[icao] = { el, destroy: () => el.remove() }
		this._fetchTrace(plane)
	}

	private _positionPopup(el: HTMLDivElement, plane: any) {
		const pixel = this.map.getPixelFromCoordinate(fromLonLat([plane.longitude, plane.latitude]))
		if (!pixel) return
		const rect        = (this.map.getTargetElement() as HTMLElement).getBoundingClientRect()
		el.style.left     = `${rect.left + <any>pixel[0] + 16}px`
		el.style.top      = `${rect.top  + <any>pixel[1] - 16}px`
	}

	private _closePopup(icao: string) {
		const popup = this.popups[icao]
		if (popup) { popup.destroy(); 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 }
			const { el } = <any>this.popups[icao]
			el.innerHTML  = buildPopupHtml(plane)
			const close   = document.createElement('button')
			close.innerHTML     = '✕'
			close.style.cssText = 'position:absolute;top:8px;right:8px;background:none;border:none;color:#888;font-size:14px;cursor:pointer;z-index:1'
			close.onclick       = () => this._closePopup(icao)
			el.appendChild(close)
			this._positionPopup(el, 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)
	}
}
