-- pg_orrery -- Orbital mechanics types and functions for PostgreSQL
--
-- Types: tle, eci_position, geodetic, topocentric, observer, pass_event
-- Provides SGP4/SDP4 propagation, coordinate transforms, pass prediction,
-- and GiST indexing on altitude bands for conjunction screening.
--
-- All propagation uses WGS-72 constants (matching TLE mean element fitting).
-- Coordinate output uses WGS-84 (matching modern geodetic standards).

-- ============================================================
-- Shell types (forward declarations)
-- ============================================================

CREATE TYPE tle;
CREATE TYPE eci_position;
CREATE TYPE geodetic;
CREATE TYPE topocentric;
CREATE TYPE observer;
CREATE TYPE pass_event;


-- ============================================================
-- TLE type: Two-Line Element set
-- ============================================================

CREATE FUNCTION tle_in(cstring) RETURNS tle
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION tle_out(tle) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION tle_recv(internal) RETURNS tle
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION tle_send(tle) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE tle (
    INPUT = tle_in,
    OUTPUT = tle_out,
    RECEIVE = tle_recv,
    SEND = tle_send,
    INTERNALLENGTH = 112,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE tle IS 'Two-Line Element set — parsed mean orbital elements for SGP4/SDP4 propagation';

-- TLE accessor functions

CREATE FUNCTION tle_epoch(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_epoch(tle) IS 'TLE epoch as Julian date (UTC)';

CREATE FUNCTION tle_norad_id(tle) RETURNS int4
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_norad_id(tle) IS 'NORAD catalog number';

CREATE FUNCTION tle_inclination(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_inclination(tle) IS 'Orbital inclination in degrees';

CREATE FUNCTION tle_eccentricity(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_eccentricity(tle) IS 'Orbital eccentricity (dimensionless)';

CREATE FUNCTION tle_raan(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_raan(tle) IS 'Right ascension of ascending node in degrees';

CREATE FUNCTION tle_arg_perigee(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_arg_perigee(tle) IS 'Argument of perigee in degrees';

CREATE FUNCTION tle_mean_anomaly(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_mean_anomaly(tle) IS 'Mean anomaly in degrees';

CREATE FUNCTION tle_mean_motion(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_mean_motion(tle) IS 'Mean motion in revolutions per day';

CREATE FUNCTION tle_bstar(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_bstar(tle) IS 'B* drag coefficient (1/earth-radii)';

CREATE FUNCTION tle_period(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_period(tle) IS 'Orbital period in minutes';

CREATE FUNCTION tle_age(tle, timestamptz) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_age(tle, timestamptz) IS 'TLE age in days (positive = past epoch, negative = before epoch)';

CREATE FUNCTION tle_perigee(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_perigee(tle) IS 'Perigee altitude in km above WGS-72 ellipsoid';

CREATE FUNCTION tle_apogee(tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_apogee(tle) IS 'Apogee altitude in km above WGS-72 ellipsoid';

CREATE FUNCTION tle_intl_desig(tle) RETURNS text
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_intl_desig(tle) IS 'International designator (COSPAR ID)';

CREATE FUNCTION tle_from_lines(text, text) RETURNS tle
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_from_lines(text, text) IS
    'Construct TLE from separate line1/line2 text columns';


-- ============================================================
-- ECI position type: True Equator Mean Equinox (TEME) frame
-- ============================================================

CREATE FUNCTION eci_in(cstring) RETURNS eci_position
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_out(eci_position) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_recv(internal) RETURNS eci_position
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_send(eci_position) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE eci_position (
    INPUT = eci_in,
    OUTPUT = eci_out,
    RECEIVE = eci_recv,
    SEND = eci_send,
    INTERNALLENGTH = 48,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE eci_position IS 'Earth-Centered Inertial position and velocity in TEME frame (km, km/s)';

-- ECI accessor functions

CREATE FUNCTION eci_x(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_y(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_z(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_vx(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_vy(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_vz(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION eci_speed(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION eci_speed(eci_position) IS 'Velocity magnitude in km/s';

CREATE FUNCTION eci_altitude(eci_position) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION eci_altitude(eci_position) IS 'Approximate geocentric altitude in km (radius - WGS72_AE)';


-- ============================================================
-- Geodetic type: WGS-84 latitude/longitude/altitude
-- ============================================================

CREATE FUNCTION geodetic_in(cstring) RETURNS geodetic
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION geodetic_out(geodetic) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION geodetic_recv(internal) RETURNS geodetic
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION geodetic_send(geodetic) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE geodetic (
    INPUT = geodetic_in,
    OUTPUT = geodetic_out,
    RECEIVE = geodetic_recv,
    SEND = geodetic_send,
    INTERNALLENGTH = 24,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE geodetic IS 'Geodetic coordinates on WGS-84 ellipsoid (lat/lon in degrees, altitude in km)';

CREATE FUNCTION geodetic_lat(geodetic) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION geodetic_lon(geodetic) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION geodetic_alt(geodetic) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;


-- ============================================================
-- Topocentric type: observer-relative az/el/range
-- ============================================================

CREATE FUNCTION topocentric_in(cstring) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION topocentric_out(topocentric) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION topocentric_recv(internal) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION topocentric_send(topocentric) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE topocentric (
    INPUT = topocentric_in,
    OUTPUT = topocentric_out,
    RECEIVE = topocentric_recv,
    SEND = topocentric_send,
    INTERNALLENGTH = 32,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE topocentric IS 'Topocentric coordinates relative to observer (azimuth, elevation, range, range rate)';

CREATE FUNCTION topo_azimuth(topocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION topo_azimuth(topocentric) IS 'Azimuth in degrees (0=N, 90=E, 180=S, 270=W)';

CREATE FUNCTION topo_elevation(topocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION topo_elevation(topocentric) IS 'Elevation in degrees (0=horizon, 90=zenith)';

CREATE FUNCTION topo_range(topocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION topo_range(topocentric) IS 'Slant range in km';

CREATE FUNCTION topo_range_rate(topocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION topo_range_rate(topocentric) IS 'Range rate in km/s (positive = receding)';


-- ============================================================
-- Observer type: ground station location
-- ============================================================

CREATE FUNCTION observer_in(cstring) RETURNS observer
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION observer_out(observer) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION observer_recv(internal) RETURNS observer
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION observer_send(observer) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE observer (
    INPUT = observer_in,
    OUTPUT = observer_out,
    RECEIVE = observer_recv,
    SEND = observer_send,
    INTERNALLENGTH = 24,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE observer IS 'Ground observer location (accepts: 40.0N 105.3W 1655m or decimal degrees)';

CREATE FUNCTION observer_lat(observer) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION observer_lat(observer) IS 'Latitude in degrees (positive = North)';

CREATE FUNCTION observer_lon(observer) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION observer_lon(observer) IS 'Longitude in degrees (positive = East)';

CREATE FUNCTION observer_alt(observer) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION observer_alt(observer) IS 'Altitude in meters above WGS-84 ellipsoid';

CREATE FUNCTION observer_from_geodetic(float8, float8, float8 DEFAULT 0.0) RETURNS observer
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION observer_from_geodetic(float8, float8, float8) IS
    'Construct observer from lat (deg), lon (deg), altitude (meters). Avoids text formatting round-trips.';


-- ============================================================
-- Pass event type: satellite visibility window
-- ============================================================

CREATE FUNCTION pass_event_in(cstring) RETURNS pass_event
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION pass_event_out(pass_event) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION pass_event_recv(internal) RETURNS pass_event
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION pass_event_send(pass_event) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE pass_event (
    INPUT = pass_event_in,
    OUTPUT = pass_event_out,
    RECEIVE = pass_event_recv,
    SEND = pass_event_send,
    INTERNALLENGTH = 48,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE pass_event IS 'Satellite pass event (AOS/MAX/LOS times, max elevation, AOS/LOS azimuths)';

CREATE FUNCTION pass_aos_time(pass_event) RETURNS timestamptz
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_aos_time(pass_event) IS 'Acquisition of signal time';

CREATE FUNCTION pass_max_el_time(pass_event) RETURNS timestamptz
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_max_el_time(pass_event) IS 'Maximum elevation time';

CREATE FUNCTION pass_los_time(pass_event) RETURNS timestamptz
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_los_time(pass_event) IS 'Loss of signal time';

CREATE FUNCTION pass_max_elevation(pass_event) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_max_elevation(pass_event) IS 'Maximum elevation in degrees';

CREATE FUNCTION pass_aos_azimuth(pass_event) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_aos_azimuth(pass_event) IS 'AOS azimuth in degrees (0=N)';

CREATE FUNCTION pass_los_azimuth(pass_event) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_los_azimuth(pass_event) IS 'LOS azimuth in degrees (0=N)';

CREATE FUNCTION pass_duration(pass_event) RETURNS interval
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_duration(pass_event) IS 'Pass duration (LOS - AOS)';


-- ============================================================
-- SGP4/SDP4 propagation functions
-- ============================================================

CREATE FUNCTION sgp4_propagate(tle, timestamptz) RETURNS eci_position
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION sgp4_propagate(tle, timestamptz) IS
    'Propagate TLE to a point in time using SGP4 (near-earth) or SDP4 (deep-space). Returns TEME ECI position/velocity.';

CREATE FUNCTION sgp4_propagate_safe(tle, timestamptz) RETURNS eci_position
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE PARALLEL SAFE;
COMMENT ON FUNCTION sgp4_propagate_safe(tle, timestamptz) IS
    'Like sgp4_propagate but returns NULL on error instead of raising an exception. For batch queries with potentially invalid TLEs.';

CREATE FUNCTION sgp4_propagate_series(tle, timestamptz, timestamptz, interval)
    RETURNS TABLE(t timestamptz, x float8, y float8, z float8, vx float8, vy float8, vz float8)
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE
    ROWS 100;
COMMENT ON FUNCTION sgp4_propagate_series(tle, timestamptz, timestamptz, interval) IS
    'Propagate TLE over a time range at regular intervals. Returns time series of TEME positions.';

CREATE FUNCTION tle_distance(tle, tle, timestamptz) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_distance(tle, tle, timestamptz) IS
    'Euclidean distance in km between two TLEs at a reference time';


-- ============================================================
-- Coordinate transform functions
-- ============================================================

CREATE FUNCTION eci_to_geodetic(eci_position, timestamptz) RETURNS geodetic
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION eci_to_geodetic(eci_position, timestamptz) IS
    'Convert TEME ECI position to WGS-84 geodetic coordinates at given time';

CREATE FUNCTION eci_to_topocentric(eci_position, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION eci_to_topocentric(eci_position, observer, timestamptz) IS
    'Convert TEME ECI position to topocentric (az/el/range) relative to observer';

CREATE FUNCTION subsatellite_point(tle, timestamptz) RETURNS geodetic
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION subsatellite_point(tle, timestamptz) IS
    'Subsatellite (nadir) point on WGS-84 ellipsoid at given time';

CREATE FUNCTION ground_track(tle, timestamptz, timestamptz, interval)
    RETURNS TABLE(t timestamptz, lat float8, lon float8, alt float8)
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE
    ROWS 100;
COMMENT ON FUNCTION ground_track(tle, timestamptz, timestamptz, interval) IS
    'Ground track as time series of subsatellite points (lat/lon in degrees, alt in km)';

CREATE FUNCTION observe(tle, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION observe(tle, observer, timestamptz) IS
    'Propagate TLE and compute observer-relative look angles in one call. Returns topocentric (az/el/range/range_rate).';

CREATE FUNCTION observe_safe(tle, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE PARALLEL SAFE;
COMMENT ON FUNCTION observe_safe(tle, observer, timestamptz) IS
    'Like observe() but returns NULL on propagation error. For batch queries with potentially invalid/decayed TLEs.';


-- ============================================================
-- Pass prediction functions
-- ============================================================

CREATE FUNCTION next_pass(tle, observer, timestamptz) RETURNS pass_event
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION next_pass(tle, observer, timestamptz) IS
    'Find the next satellite pass over observer (searches up to 7 days ahead)';

CREATE FUNCTION predict_passes(tle, observer, timestamptz, timestamptz, float8 DEFAULT 0.0)
    RETURNS SETOF pass_event
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE
    ROWS 10;
COMMENT ON FUNCTION predict_passes(tle, observer, timestamptz, timestamptz, float8) IS
    'Predict all satellite passes over observer in time window. Optional min_elevation in degrees.';

CREATE FUNCTION pass_visible(tle, observer, timestamptz, timestamptz) RETURNS boolean
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION pass_visible(tle, observer, timestamptz, timestamptz) IS
    'True if any pass occurs over observer in the time window';


-- ============================================================
-- GiST operator support functions
-- ============================================================

-- Overlap operator: do orbital keys overlap in altitude AND inclination?
CREATE FUNCTION tle_overlap(tle, tle) RETURNS boolean
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

-- Altitude distance operator (altitude-only, for KNN ordering)
CREATE FUNCTION tle_alt_distance(tle, tle) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE OPERATOR && (
    LEFTARG = tle,
    RIGHTARG = tle,
    FUNCTION = tle_overlap,
    COMMUTATOR = &&,
    RESTRICT = areasel,
    JOIN = areajoinsel
);

COMMENT ON OPERATOR && (tle, tle) IS 'Orbital key overlap (altitude band AND inclination range) — necessary condition for conjunction';

CREATE OPERATOR <-> (
    LEFTARG = tle,
    RIGHTARG = tle,
    FUNCTION = tle_alt_distance,
    COMMUTATOR = <->
);

COMMENT ON OPERATOR <-> (tle, tle) IS '2-D orbital distance in km: L2 norm of altitude-band gap and inclination gap (radians × Earth radius). Returns 0 when both dimensions overlap.';


-- ============================================================
-- GiST operator class for 2-D orbital indexing (altitude + inclination)
-- ============================================================

-- GiST internal support functions
CREATE FUNCTION gist_tle_compress(internal) RETURNS internal
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION gist_tle_decompress(internal) RETURNS internal
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION gist_tle_consistent(internal, tle, smallint, oid, internal) RETURNS boolean
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION gist_tle_union(internal, internal) RETURNS internal
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION gist_tle_penalty(internal, internal, internal) RETURNS internal
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION gist_tle_picksplit(internal, internal) RETURNS internal
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION gist_tle_same(internal, internal, internal) RETURNS internal
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION gist_tle_distance(internal, tle, smallint, oid, internal) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE OPERATOR CLASS tle_ops
    DEFAULT FOR TYPE tle USING gist AS
        OPERATOR  3   && ,
        OPERATOR  15  <-> (tle, tle) FOR ORDER BY float_ops,
        FUNCTION  1   gist_tle_consistent(internal, tle, smallint, oid, internal),
        FUNCTION  2   gist_tle_union(internal, internal),
        FUNCTION  3   gist_tle_compress(internal),
        FUNCTION  4   gist_tle_decompress(internal),
        FUNCTION  5   gist_tle_penalty(internal, internal, internal),
        FUNCTION  6   gist_tle_picksplit(internal, internal),
        FUNCTION  7   gist_tle_same(internal, internal, internal),
        FUNCTION  8   gist_tle_distance(internal, tle, smallint, oid, internal);


-- ============================================================
-- Heliocentric type: ecliptic J2000 position in AU
-- ============================================================

CREATE TYPE heliocentric;

CREATE FUNCTION heliocentric_in(cstring) RETURNS heliocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION heliocentric_out(heliocentric) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION heliocentric_recv(internal) RETURNS heliocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION heliocentric_send(heliocentric) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE heliocentric (
    INPUT = heliocentric_in,
    OUTPUT = heliocentric_out,
    RECEIVE = heliocentric_recv,
    SEND = heliocentric_send,
    INTERNALLENGTH = 24,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE heliocentric IS 'Heliocentric position in ecliptic J2000 frame (x, y, z in AU)';

CREATE FUNCTION helio_x(heliocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION helio_x(heliocentric) IS 'X component in AU (ecliptic J2000, vernal equinox direction)';

CREATE FUNCTION helio_y(heliocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION helio_y(heliocentric) IS 'Y component in AU (ecliptic J2000)';

CREATE FUNCTION helio_z(heliocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION helio_z(heliocentric) IS 'Z component in AU (ecliptic J2000, north ecliptic pole)';

CREATE FUNCTION helio_distance(heliocentric) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION helio_distance(heliocentric) IS 'Heliocentric distance in AU';


-- ============================================================
-- Star observation functions
-- ============================================================

CREATE FUNCTION star_observe(float8, float8, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION star_observe(float8, float8, observer, timestamptz) IS
    'Observe a star from (ra_hours J2000, dec_degrees J2000, observer, time). Returns topocentric az/el. Range is 0 (infinite distance).';

CREATE FUNCTION star_observe_safe(float8, float8, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE PARALLEL SAFE;
COMMENT ON FUNCTION star_observe_safe(float8, float8, observer, timestamptz) IS
    'Like star_observe but returns NULL on invalid inputs. For batch queries over star catalogs.';


-- ============================================================
-- Keplerian propagation functions
-- ============================================================

CREATE FUNCTION kepler_propagate(
    q_au float8, eccentricity float8,
    inclination_deg float8, arg_perihelion_deg float8,
    long_asc_node_deg float8, perihelion_jd float8,
    t timestamptz
) RETURNS heliocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION kepler_propagate(float8, float8, float8, float8, float8, float8, timestamptz) IS
    'Two-body Keplerian propagation from classical orbital elements. Returns heliocentric ecliptic J2000 position in AU. Handles elliptic, parabolic, and hyperbolic orbits.';


-- ============================================================
-- Comet observation
-- ============================================================

CREATE FUNCTION comet_observe(
    q_au float8, eccentricity float8,
    inclination_deg float8, arg_perihelion_deg float8,
    long_asc_node_deg float8, perihelion_jd float8,
    earth_x_au float8, earth_y_au float8, earth_z_au float8,
    obs observer, t timestamptz
) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION comet_observe(float8, float8, float8, float8, float8, float8, float8, float8, float8, observer, timestamptz) IS
    'Observe a comet/asteroid from orbital elements. Requires Earth heliocentric ecliptic J2000 position (AU). Returns topocentric az/el with geocentric range in km.';


-- ============================================================
-- VSOP87 planets, ELP82B Moon, Sun observation
-- ============================================================

CREATE FUNCTION planet_heliocentric(int4, timestamptz) RETURNS heliocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION planet_heliocentric(int4, timestamptz) IS
    'VSOP87 heliocentric ecliptic J2000 position (AU). Body IDs: 0=Sun, 1=Mercury, ..., 8=Neptune.';

CREATE FUNCTION planet_observe(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION planet_observe(int4, observer, timestamptz) IS
    'Observe a planet from (body_id 1-8, observer, time). Returns topocentric az/el with geocentric range in km.';

CREATE FUNCTION sun_observe(observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION sun_observe(observer, timestamptz) IS
    'Observe the Sun from (observer, time). Returns topocentric az/el with geocentric range in km.';

CREATE FUNCTION moon_observe(observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION moon_observe(observer, timestamptz) IS
    'Observe the Moon via ELP2000-82B from (observer, time). Returns topocentric az/el with geocentric range in km.';


-- ============================================================
-- Planetary moon observation
-- ============================================================

CREATE FUNCTION galilean_observe(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION galilean_observe(int4, observer, timestamptz) IS
    'Observe a Galilean moon of Jupiter via L1.2 theory. Body IDs: 0=Io, 1=Europa, 2=Ganymede, 3=Callisto.';

CREATE FUNCTION saturn_moon_observe(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION saturn_moon_observe(int4, observer, timestamptz) IS
    'Observe a Saturn moon via TASS 1.7. Body IDs: 0=Mimas, 1=Enceladus, 2=Tethys, 3=Dione, 4=Rhea, 5=Titan, 6=Iapetus, 7=Hyperion.';

CREATE FUNCTION uranus_moon_observe(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION uranus_moon_observe(int4, observer, timestamptz) IS
    'Observe a Uranus moon via GUST86. Body IDs: 0=Miranda, 1=Ariel, 2=Umbriel, 3=Titania, 4=Oberon.';

CREATE FUNCTION mars_moon_observe(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION mars_moon_observe(int4, observer, timestamptz) IS
    'Observe a Mars moon via MarsSat. Body IDs: 0=Phobos, 1=Deimos.';


-- ============================================================
-- Jupiter decametric radio burst prediction
-- ============================================================

CREATE FUNCTION io_phase_angle(timestamptz) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION io_phase_angle(timestamptz) IS
    'Io orbital phase angle in degrees [0,360). 0=superior conjunction (behind Jupiter). Standard Radio JOVE convention.';

CREATE FUNCTION jupiter_cml(observer, timestamptz) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION jupiter_cml(observer, timestamptz) IS
    'Jupiter Central Meridian Longitude, System III (1965.0), in degrees [0,360). Light-time corrected.';

CREATE FUNCTION jupiter_burst_probability(float8, float8) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION jupiter_burst_probability(float8, float8) IS
    'Estimated Jupiter decametric burst probability (0-1) from (io_phase_deg, cml_deg). Based on Carr et al. (1983) source regions A, B, C, D.';


-- ============================================================
-- Interplanetary transfer orbits (Lambert solver)
-- ============================================================

CREATE FUNCTION lambert_transfer(
    dep_body_id int4, arr_body_id int4,
    dep_time timestamptz, arr_time timestamptz,
    OUT c3_departure float8, OUT c3_arrival float8,
    OUT v_inf_departure float8, OUT v_inf_arrival float8,
    OUT tof_days float8, OUT transfer_sma float8
) RETURNS RECORD
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION lambert_transfer(int4, int4, timestamptz, timestamptz) IS
    'Solve Lambert transfer between two planets. Returns C3 (km^2/s^2), v_infinity (km/s), TOF (days), SMA (AU). Body IDs 1-8.';

CREATE FUNCTION lambert_c3(int4, int4, timestamptz, timestamptz) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION lambert_c3(int4, int4, timestamptz, timestamptz) IS
    'Departure C3 (km^2/s^2) for a Lambert transfer. Returns NULL if solver fails. For pork chop plots.';


-- ============================================================
-- DE ephemeris functions (optional high-precision)
-- ============================================================

CREATE FUNCTION planet_heliocentric_de(int4, timestamptz) RETURNS heliocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION planet_heliocentric_de(int4, timestamptz) IS
    'Heliocentric ecliptic J2000 position via JPL DE (sub-arcsecond). Falls back to VSOP87 if DE unavailable. Body IDs: 0=Sun, 1-8=Mercury-Neptune.';

CREATE FUNCTION planet_observe_de(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION planet_observe_de(int4, observer, timestamptz) IS
    'Observe planet via JPL DE. Falls back to VSOP87. Body IDs: 1-8 (Mercury-Neptune).';

CREATE FUNCTION sun_observe_de(observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION sun_observe_de(observer, timestamptz) IS
    'Observe Sun via JPL DE. Falls back to VSOP87.';

CREATE FUNCTION moon_observe_de(observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION moon_observe_de(observer, timestamptz) IS
    'Observe Moon via JPL DE. Falls back to ELP2000-82B.';

CREATE FUNCTION lambert_transfer_de(
    dep_body_id int4, arr_body_id int4,
    dep_time timestamptz, arr_time timestamptz,
    OUT c3_departure float8, OUT c3_arrival float8,
    OUT v_inf_departure float8, OUT v_inf_arrival float8,
    OUT tof_days float8, OUT transfer_sma float8
) RETURNS RECORD
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION lambert_transfer_de(int4, int4, timestamptz, timestamptz) IS
    'Lambert transfer via JPL DE positions. Falls back to VSOP87. Body IDs 1-8.';

CREATE FUNCTION lambert_c3_de(int4, int4, timestamptz, timestamptz) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION lambert_c3_de(int4, int4, timestamptz, timestamptz) IS
    'Departure C3 via JPL DE. Falls back to VSOP87. For pork chop plots.';

CREATE FUNCTION galilean_observe_de(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION galilean_observe_de(int4, observer, timestamptz) IS
    'Observe Galilean moon with JPL DE parent position. L1.2 moon offsets. Body IDs: 0-3 (Io-Callisto).';

CREATE FUNCTION saturn_moon_observe_de(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION saturn_moon_observe_de(int4, observer, timestamptz) IS
    'Observe Saturn moon with JPL DE parent position. TASS 1.7 moon offsets. Body IDs: 0-7 (Mimas-Hyperion).';

CREATE FUNCTION uranus_moon_observe_de(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION uranus_moon_observe_de(int4, observer, timestamptz) IS
    'Observe Uranus moon with JPL DE parent position. GUST86 moon offsets. Body IDs: 0-4 (Miranda-Oberon).';

CREATE FUNCTION mars_moon_observe_de(int4, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION mars_moon_observe_de(int4, observer, timestamptz) IS
    'Observe Mars moon with JPL DE parent position. MarsSat moon offsets. Body IDs: 0-1 (Phobos-Deimos).';


-- Diagnostic function

CREATE FUNCTION pg_orrery_ephemeris_info(
    OUT provider text, OUT file_path text,
    OUT start_jd float8, OUT end_jd float8,
    OUT version int4, OUT au_km float8
) RETURNS RECORD
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE PARALLEL SAFE;
COMMENT ON FUNCTION pg_orrery_ephemeris_info() IS
    'Returns current ephemeris provider status: VSOP87 or JPL_DE with file path, JD range, version, and AU value.';


-- ============================================================
-- Orbit determination (TLE fitting from observations)
-- ============================================================

-- Fit TLE from ECI position/velocity ephemeris
-- weights: per-observation weighting (NULL = uniform)

CREATE FUNCTION tle_from_eci(
    positions eci_position[], times timestamptz[],
    seed tle DEFAULT NULL, fit_bstar boolean DEFAULT false,
    max_iter int4 DEFAULT 15,
    weights float8[] DEFAULT NULL,
    OUT fitted_tle tle, OUT iterations int4,
    OUT rms_final float8, OUT rms_initial float8, OUT status text,
    OUT condition_number float8, OUT covariance float8[], OUT nstate int4
) RETURNS RECORD
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE PARALLEL SAFE;
COMMENT ON FUNCTION tle_from_eci(eci_position[], timestamptz[], tle, boolean, int4, float8[]) IS
    'Fit a TLE from ECI position/velocity observations via differential correction. Optional per-observation weights for heterogeneous sensor fusion. Returns fitted TLE, RMS residuals, convergence status, condition number, and formal covariance matrix.';

-- Fit TLE from topocentric observations (az/el/range) — single observer
-- fit_range_rate: include range_rate as 4th residual component
-- weights: per-observation weighting (NULL = uniform)

CREATE FUNCTION tle_from_topocentric(
    observations topocentric[], times timestamptz[],
    obs observer,
    seed tle DEFAULT NULL, fit_bstar boolean DEFAULT false,
    max_iter int4 DEFAULT 15,
    fit_range_rate boolean DEFAULT false,
    weights float8[] DEFAULT NULL,
    OUT fitted_tle tle, OUT iterations int4,
    OUT rms_final float8, OUT rms_initial float8, OUT status text,
    OUT condition_number float8, OUT covariance float8[], OUT nstate int4
) RETURNS RECORD
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE PARALLEL SAFE;
COMMENT ON FUNCTION tle_from_topocentric(topocentric[], timestamptz[], observer, tle, boolean, int4, boolean, float8[]) IS
    'Fit a TLE from topocentric (az/el/range) observations via differential correction. Optional range_rate fitting and per-observation weights. Returns fitted TLE, RMS residuals, convergence status, condition number, and formal covariance matrix.';

-- Fit TLE from topocentric observations — multiple observers

CREATE FUNCTION tle_from_topocentric(
    observations topocentric[], times timestamptz[],
    observers observer[], observer_ids int4[],
    seed tle DEFAULT NULL, fit_bstar boolean DEFAULT false,
    max_iter int4 DEFAULT 15,
    fit_range_rate boolean DEFAULT false,
    weights float8[] DEFAULT NULL,
    OUT fitted_tle tle, OUT iterations int4,
    OUT rms_final float8, OUT rms_initial float8, OUT status text,
    OUT condition_number float8, OUT covariance float8[], OUT nstate int4
) RETURNS RECORD
    AS 'MODULE_PATHNAME', 'tle_from_topocentric_multi'
    LANGUAGE C STABLE PARALLEL SAFE;
COMMENT ON FUNCTION tle_from_topocentric(topocentric[], timestamptz[], observer[], int4[], tle, boolean, int4, boolean, float8[]) IS
    'Fit a TLE from topocentric observations collected by multiple ground stations. observer_ids[i] indexes into observers[]. Optional range_rate fitting and per-observation weights.';

-- Per-observation residuals diagnostic

CREATE FUNCTION tle_fit_residuals(
    fitted tle,
    positions eci_position[],
    times timestamptz[]
) RETURNS TABLE (
    t timestamptz,
    dx_km float8,
    dy_km float8,
    dz_km float8,
    pos_err_km float8
)
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION tle_fit_residuals(tle, eci_position[], timestamptz[]) IS
    'Compute per-observation position residuals (km) between a TLE and ECI observations. Useful for fit quality assessment.';

-- Fit TLE from RA/Dec observations — single observer
-- Uses Gauss method for initial orbit determination when no seed is provided.
-- RA in hours [0,24), Dec in degrees [-90,90] (matches star_observe convention).
-- RMS output is in radians for angles-only mode.

CREATE FUNCTION tle_from_angles(
    ra_hours float8[], dec_degrees float8[],
    times timestamptz[],
    obs observer,
    seed tle DEFAULT NULL, fit_bstar boolean DEFAULT false,
    max_iter int4 DEFAULT 15,
    weights float8[] DEFAULT NULL,
    OUT fitted_tle tle, OUT iterations int4,
    OUT rms_final float8, OUT rms_initial float8, OUT status text,
    OUT condition_number float8, OUT covariance float8[], OUT nstate int4
) RETURNS RECORD AS 'MODULE_PATHNAME' LANGUAGE C STABLE PARALLEL SAFE;
COMMENT ON FUNCTION tle_from_angles(float8[], float8[], timestamptz[], observer, tle, boolean, int4, float8[]) IS
    'Fit a TLE from angles-only (RA/Dec) observations via Gauss IOD + differential correction. RA in hours [0,24), Dec in degrees [-90,90]. RMS output in radians. Uses Gauss method for seed-free initial guess.';

-- Fit TLE from RA/Dec observations — multiple observers

CREATE FUNCTION tle_from_angles(
    ra_hours float8[], dec_degrees float8[],
    times timestamptz[],
    observers observer[], observer_ids int4[],
    seed tle DEFAULT NULL, fit_bstar boolean DEFAULT false,
    max_iter int4 DEFAULT 15,
    weights float8[] DEFAULT NULL,
    OUT fitted_tle tle, OUT iterations int4,
    OUT rms_final float8, OUT rms_initial float8, OUT status text,
    OUT condition_number float8, OUT covariance float8[], OUT nstate int4
) RETURNS RECORD
    AS 'MODULE_PATHNAME', 'tle_from_angles_multi'
    LANGUAGE C STABLE PARALLEL SAFE;
COMMENT ON FUNCTION tle_from_angles(float8[], float8[], timestamptz[], observer[], int4[], tle, boolean, int4, float8[]) IS
    'Fit a TLE from angles-only (RA/Dec) observations from multiple ground stations via Gauss IOD + differential correction.';
-- pg_orrery 0.6.0 -> 0.7.0 migration
--
-- Adds SP-GiST orbital trie index for satellite pass prediction.
-- 2-level trie: SMA (L0) + inclination (L1) with query-time RAAN filter.
-- The &? operator answers "might this satellite be visible?"

-- ============================================================
-- observer_window composite type (query parameter bundle)
-- ============================================================

CREATE TYPE observer_window AS (
    obs         observer,
    t_start     timestamptz,
    t_end       timestamptz,
    min_el      float8
);

COMMENT ON TYPE observer_window IS
    'Observation query parameters: observer location, time window, and minimum elevation angle (degrees). Used with the &? visibility cone operator.';

-- ============================================================
-- Visibility cone operator function
-- ============================================================

CREATE FUNCTION tle_visibility_possible(tle, observer_window) RETURNS boolean
    AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;

COMMENT ON FUNCTION tle_visibility_possible(tle, observer_window) IS
    'Could this satellite be visible from the observer during the time window? Combines altitude, inclination, and RAAN checks. Conservative superset — survivors need SGP4 propagation for ground truth.';

-- ============================================================
-- &? operator (visibility cone check)
-- ============================================================
-- The indexed column (tle) MUST be the left argument so PostgreSQL
-- can form a ScanKey and pass it to inner_consistent for pruning.

CREATE OPERATOR &? (
    LEFTARG   = tle,
    RIGHTARG  = observer_window,
    FUNCTION  = tle_visibility_possible,
    RESTRICT  = contsel,
    JOIN      = contjoinsel
);

COMMENT ON OPERATOR &? (tle, observer_window) IS
    'Visibility cone check: could this satellite be visible from the observer during the time window? Index-accelerated via SP-GiST orbital trie.';

-- ============================================================
-- SP-GiST support functions
-- ============================================================

CREATE FUNCTION spgist_tle_config(internal, internal) RETURNS void
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION spgist_tle_choose(internal, internal) RETURNS void
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION spgist_tle_picksplit(internal, internal) RETURNS void
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION spgist_tle_inner_consistent(internal, internal) RETURNS void
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION spgist_tle_leaf_consistent(internal, internal) RETURNS void
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

-- ============================================================
-- SP-GiST operator class (opt-in, not DEFAULT)
-- ============================================================

CREATE OPERATOR CLASS tle_spgist_ops
    FOR TYPE tle USING spgist AS
        OPERATOR  1   &? (tle, observer_window),
        FUNCTION  1   spgist_tle_config(internal, internal),
        FUNCTION  2   spgist_tle_choose(internal, internal),
        FUNCTION  3   spgist_tle_picksplit(internal, internal),
        FUNCTION  4   spgist_tle_inner_consistent(internal, internal),
        FUNCTION  5   spgist_tle_leaf_consistent(internal, internal);
-- pg_orrery 0.7.0 -> 0.8.0 migration
--
-- Adds orbital_elements type for comets/asteroids, MPC MPCORB.DAT parser,
-- and small_body_observe()/small_body_heliocentric() observation functions.

-- ============================================================
-- orbital_elements type
-- ============================================================

CREATE TYPE orbital_elements;

CREATE FUNCTION orbital_elements_in(cstring) RETURNS orbital_elements
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION orbital_elements_out(orbital_elements) RETURNS cstring
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION orbital_elements_recv(internal) RETURNS orbital_elements
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE FUNCTION orbital_elements_send(orbital_elements) RETURNS bytea
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

CREATE TYPE orbital_elements (
    INPUT = orbital_elements_in,
    OUTPUT = orbital_elements_out,
    RECEIVE = orbital_elements_recv,
    SEND = orbital_elements_send,
    INTERNALLENGTH = 72,
    ALIGNMENT = double,
    STORAGE = plain
);

COMMENT ON TYPE orbital_elements IS
    'Classical Keplerian orbital elements for comets and asteroids (epoch, q, e, inc, omega, Omega, tp, H, G). 72 bytes, fixed-size.';


-- ============================================================
-- Accessor functions
-- ============================================================

CREATE FUNCTION oe_epoch(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_epoch(orbital_elements) IS 'Osculation epoch (Julian date)';

CREATE FUNCTION oe_perihelion(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_perihelion(orbital_elements) IS 'Perihelion distance q (AU)';

CREATE FUNCTION oe_eccentricity(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_eccentricity(orbital_elements) IS 'Eccentricity';

CREATE FUNCTION oe_inclination(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_inclination(orbital_elements) IS 'Inclination (degrees)';

CREATE FUNCTION oe_arg_perihelion(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_arg_perihelion(orbital_elements) IS 'Argument of perihelion (degrees)';

CREATE FUNCTION oe_raan(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_raan(orbital_elements) IS 'Longitude of ascending node (degrees)';

CREATE FUNCTION oe_tp(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_tp(orbital_elements) IS 'Time of perihelion passage (Julian date)';

CREATE FUNCTION oe_h_mag(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_h_mag(orbital_elements) IS 'Absolute magnitude H (NaN if unknown)';

CREATE FUNCTION oe_g_slope(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_g_slope(orbital_elements) IS 'Slope parameter G (NaN if unknown)';

CREATE FUNCTION oe_semi_major_axis(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_semi_major_axis(orbital_elements) IS 'Semi-major axis a = q/(1-e) in AU. NULL for parabolic/hyperbolic orbits (e >= 1).';

CREATE FUNCTION oe_period_years(orbital_elements) RETURNS float8
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_period_years(orbital_elements) IS 'Orbital period in years = a^1.5 (Kepler third law). NULL for parabolic/hyperbolic orbits (e >= 1).';


-- ============================================================
-- MPC MPCORB.DAT parser
-- ============================================================

CREATE FUNCTION oe_from_mpc(text) RETURNS orbital_elements
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION oe_from_mpc(text) IS
    'Parse one MPCORB.DAT fixed-width line into orbital_elements. Converts MPC packed epoch, computes perihelion distance and tp from (a, e, M).';


-- ============================================================
-- Observation functions
-- ============================================================

CREATE FUNCTION small_body_heliocentric(orbital_elements, timestamptz) RETURNS heliocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION small_body_heliocentric(orbital_elements, timestamptz) IS
    'Heliocentric ecliptic J2000 position of a comet/asteroid from its orbital elements at a given time.';

CREATE FUNCTION small_body_observe(orbital_elements, observer, timestamptz) RETURNS topocentric
    AS 'MODULE_PATHNAME' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
COMMENT ON FUNCTION small_body_observe(orbital_elements, observer, timestamptz) IS
    'Observe a comet/asteroid from orbital elements. Auto-fetches Earth via VSOP87. Returns topocentric az/el with geocentric range in km.';
