-- PostGIS International Boundary Schema for Heritage Custodian Service Areas
-- Migration: 002_postgis_boundaries.sql
-- Created: 2025-12-07
-- 
-- Stores administrative boundaries for computing heritage custodian service areas
-- Supports global coverage with multiple data sources (GADM, Natural Earth, OSM, CBS)
-- Enables spatial queries: point-in-polygon, intersection, containment

-- ============================================================================
-- Enable PostGIS Extension
-- ============================================================================

CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;

-- ============================================================================
-- Data Source Registry
-- ============================================================================
-- Track data sources for provenance and updates

CREATE TABLE IF NOT EXISTS boundary_data_sources (
    id SERIAL PRIMARY KEY,
    source_code VARCHAR(50) NOT NULL UNIQUE,       -- e.g., "GADM", "CBS", "OSM", "HALC"
    source_name VARCHAR(255) NOT NULL,              -- Full name
    source_url TEXT,                                -- Official website
    license_type VARCHAR(100),                      -- e.g., "CC-BY-4.0", "ODbL"
    license_url TEXT,
    description TEXT,
    coverage_scope VARCHAR(50),                     -- "GLOBAL", "EUROPE", "NETHERLANDS", etc.
    last_updated DATE,
    attribution_required TEXT,                      -- Required attribution text
    metadata JSONB DEFAULT '{}'::jsonb,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Insert known data sources
INSERT INTO boundary_data_sources (source_code, source_name, source_url, license_type, coverage_scope, description) VALUES
    ('GADM', 'Global Administrative Areas Database', 'https://gadm.org', 'CC-BY-NC-4.0', 'GLOBAL', 'Global administrative boundaries at multiple levels (country, region, district)'),
    ('NATURAL_EARTH', 'Natural Earth', 'https://www.naturalearthdata.com', 'Public Domain', 'GLOBAL', 'Free vector and raster map data at 1:10m, 1:50m, and 1:110m scales'),
    ('OSM', 'OpenStreetMap', 'https://www.openstreetmap.org', 'ODbL', 'GLOBAL', 'Crowdsourced global geographic data'),
    ('CBS', 'CBS Wijken en Buurten', 'https://www.cbs.nl/nl-nl/dossier/nederland-regionaal/geografische-data', 'CC-BY-4.0', 'NETHERLANDS', 'Official Dutch municipality boundaries from CBS'),
    ('HALC', 'Historical Atlas of the Low Countries', NULL, 'Academic', 'LOW_COUNTRIES', 'Historical administrative boundaries for Netherlands, Belgium, Luxembourg'),
    ('EUROSTAT', 'Eurostat NUTS/LAU', 'https://ec.europa.eu/eurostat/web/gisco', 'Eurostat', 'EUROPE', 'European statistical regions (NUTS) and local administrative units (LAU)')
ON CONFLICT (source_code) DO NOTHING;

-- ============================================================================
-- Countries Table
-- ============================================================================
-- ISO 3166-1 countries with boundaries

CREATE TABLE IF NOT EXISTS boundary_countries (
    id SERIAL PRIMARY KEY,
    iso_a2 CHAR(2) NOT NULL,                       -- ISO 3166-1 alpha-2 (e.g., "NL", "DE")
    iso_a3 CHAR(3) NOT NULL,                       -- ISO 3166-1 alpha-3 (e.g., "NLD", "DEU")
    iso_n3 CHAR(3),                                -- ISO 3166-1 numeric (e.g., "528")
    country_name VARCHAR(255) NOT NULL,            -- English name
    country_name_local VARCHAR(255),               -- Native language name
    
    -- Geometry
    geom GEOMETRY(MULTIPOLYGON, 4326),             -- WGS84 boundary polygon
    centroid GEOMETRY(POINT, 4326),                -- Computed centroid
    bbox BOX2D,                                    -- Bounding box for quick filtering
    
    -- Metadata
    source_id INTEGER REFERENCES boundary_data_sources(id),
    source_feature_id VARCHAR(100),                -- ID in source dataset
    area_km2 NUMERIC,                              -- Computed area
    
    -- Versioning
    valid_from DATE,
    valid_to DATE,                                 -- NULL = current
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    
    UNIQUE(iso_a2, valid_to)                       -- Allow multiple versions, only one current
);

CREATE INDEX IF NOT EXISTS idx_boundary_countries_iso_a2 ON boundary_countries(iso_a2);
CREATE INDEX IF NOT EXISTS idx_boundary_countries_geom ON boundary_countries USING GIST(geom);
CREATE INDEX IF NOT EXISTS idx_boundary_countries_centroid ON boundary_countries USING GIST(centroid);
CREATE INDEX IF NOT EXISTS idx_boundary_countries_valid ON boundary_countries(valid_from, valid_to);

-- ============================================================================
-- Administrative Level 1 (States/Provinces/Regions)
-- ============================================================================
-- ISO 3166-2 regions

CREATE TABLE IF NOT EXISTS boundary_admin1 (
    id SERIAL PRIMARY KEY,
    country_id INTEGER REFERENCES boundary_countries(id),
    
    -- Identifiers
    iso_3166_2 VARCHAR(10),                        -- e.g., "NL-NH", "DE-BY", "JP-13"
    admin1_code VARCHAR(20) NOT NULL,              -- Internal code (varies by source)
    geonames_id INTEGER,                           -- GeoNames ID for linking
    wikidata_id VARCHAR(20),                       -- Wikidata Q-number
    
    -- Names
    admin1_name VARCHAR(255) NOT NULL,             -- English/standard name
    admin1_name_local VARCHAR(255),                -- Native language name
    admin1_type VARCHAR(100),                      -- e.g., "Province", "State", "Prefecture", "Land"
    
    -- Geometry
    geom GEOMETRY(MULTIPOLYGON, 4326),
    centroid GEOMETRY(POINT, 4326),
    bbox BOX2D,
    
    -- Metadata
    source_id INTEGER REFERENCES boundary_data_sources(id),
    source_feature_id VARCHAR(100),
    area_km2 NUMERIC,
    
    -- Versioning
    valid_from DATE,
    valid_to DATE,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    
    UNIQUE(country_id, admin1_code, valid_to)
);

CREATE INDEX IF NOT EXISTS idx_boundary_admin1_country ON boundary_admin1(country_id);
CREATE INDEX IF NOT EXISTS idx_boundary_admin1_iso ON boundary_admin1(iso_3166_2);
CREATE INDEX IF NOT EXISTS idx_boundary_admin1_geonames ON boundary_admin1(geonames_id);
CREATE INDEX IF NOT EXISTS idx_boundary_admin1_geom ON boundary_admin1 USING GIST(geom);
CREATE INDEX IF NOT EXISTS idx_boundary_admin1_centroid ON boundary_admin1 USING GIST(centroid);

-- ============================================================================
-- Administrative Level 2 (Districts/Counties/Municipalities)
-- ============================================================================

CREATE TABLE IF NOT EXISTS boundary_admin2 (
    id SERIAL PRIMARY KEY,
    admin1_id INTEGER REFERENCES boundary_admin1(id),
    country_id INTEGER REFERENCES boundary_countries(id),
    
    -- Identifiers
    admin2_code VARCHAR(50) NOT NULL,              -- Internal code
    nuts_code VARCHAR(10),                         -- NUTS code (EU)
    lau_code VARCHAR(20),                          -- LAU code (EU)
    geonames_id INTEGER,
    wikidata_id VARCHAR(20),
    
    -- CBS-specific (Netherlands)
    cbs_gemeente_code VARCHAR(10),                 -- Dutch municipality code (GM0363)
    
    -- Names
    admin2_name VARCHAR(255) NOT NULL,
    admin2_name_local VARCHAR(255),
    admin2_type VARCHAR(100),                      -- e.g., "Municipality", "District", "County"
    
    -- Geometry
    geom GEOMETRY(MULTIPOLYGON, 4326),
    centroid GEOMETRY(POINT, 4326),
    bbox BOX2D,
    
    -- Metadata
    source_id INTEGER REFERENCES boundary_data_sources(id),
    source_feature_id VARCHAR(100),
    area_km2 NUMERIC,
    population INTEGER,                            -- If available from source
    
    -- Versioning
    valid_from DATE,
    valid_to DATE,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    
    UNIQUE(admin1_id, admin2_code, valid_to)
);

CREATE INDEX IF NOT EXISTS idx_boundary_admin2_admin1 ON boundary_admin2(admin1_id);
CREATE INDEX IF NOT EXISTS idx_boundary_admin2_country ON boundary_admin2(country_id);
CREATE INDEX IF NOT EXISTS idx_boundary_admin2_geonames ON boundary_admin2(geonames_id);
CREATE INDEX IF NOT EXISTS idx_boundary_admin2_cbs ON boundary_admin2(cbs_gemeente_code);
CREATE INDEX IF NOT EXISTS idx_boundary_admin2_geom ON boundary_admin2 USING GIST(geom);
CREATE INDEX IF NOT EXISTS idx_boundary_admin2_centroid ON boundary_admin2 USING GIST(centroid);

-- ============================================================================
-- Historical Boundaries
-- ============================================================================
-- Pre-modern administrative boundaries (e.g., HALC 1500 data)

CREATE TABLE IF NOT EXISTS boundary_historical (
    id SERIAL PRIMARY KEY,
    
    -- Identifiers
    historical_id VARCHAR(100) NOT NULL,           -- Source-specific ID
    halc_adm1_code VARCHAR(20),                    -- HALC ADM1 territory code (e.g., "VI")
    halc_adm2_name VARCHAR(255),                   -- HALC ADM2 district name
    wikidata_id VARCHAR(20),
    
    -- Location context
    modern_country_iso_a2 CHAR(2),                 -- Modern country this is within
    
    -- Names
    territory_name VARCHAR(255) NOT NULL,
    territory_name_historical VARCHAR(255),        -- Name in historical period
    territory_type VARCHAR(100),                   -- e.g., "County", "Duchy", "Lordship"
    
    -- Temporal extent
    period_start SMALLINT,                         -- Year (e.g., 1500)
    period_end SMALLINT,                           -- Year (e.g., 1795)
    period_description VARCHAR(255),               -- e.g., "Ancien Regime", "Medieval"
    
    -- Geometry
    geom GEOMETRY(MULTIPOLYGON, 4326),
    centroid GEOMETRY(POINT, 4326),
    bbox BOX2D,
    
    -- Metadata
    source_id INTEGER REFERENCES boundary_data_sources(id),
    source_feature_id VARCHAR(100),
    area_km2 NUMERIC,
    certainty VARCHAR(50),                         -- "HIGH", "MEDIUM", "LOW", "APPROXIMATE"
    notes TEXT,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    
    UNIQUE(historical_id, source_id)
);

CREATE INDEX IF NOT EXISTS idx_boundary_historical_halc ON boundary_historical(halc_adm1_code);
CREATE INDEX IF NOT EXISTS idx_boundary_historical_country ON boundary_historical(modern_country_iso_a2);
CREATE INDEX IF NOT EXISTS idx_boundary_historical_period ON boundary_historical(period_start, period_end);
CREATE INDEX IF NOT EXISTS idx_boundary_historical_geom ON boundary_historical USING GIST(geom);

-- ============================================================================
-- Custodian Service Areas (Computed/Custom)
-- ============================================================================
-- Stores pre-computed or custom service area geometries for heritage custodians

CREATE TABLE IF NOT EXISTS custodian_service_areas (
    id SERIAL PRIMARY KEY,
    
    -- Custodian link (GHCID)
    ghcid VARCHAR(100) NOT NULL,                   -- e.g., "NL-NH-HAA-A-NHA"
    ghcid_uuid UUID,                               -- UUID v5 of GHCID
    
    -- Service area identity
    service_area_name VARCHAR(255) NOT NULL,
    service_area_type VARCHAR(50) NOT NULL,        -- From ServiceAreaTypeEnum
    
    -- Geometry (can be computed union or custom-drawn)
    geom GEOMETRY(MULTIPOLYGON, 4326),
    centroid GEOMETRY(POINT, 4326),
    bbox BOX2D,
    
    -- Composition (which admin units make up this service area)
    admin2_ids INTEGER[],                          -- Array of boundary_admin2.id
    admin1_ids INTEGER[],                          -- Array of boundary_admin1.id
    historical_ids INTEGER[],                      -- Array of boundary_historical.id
    
    -- Properties
    is_historical BOOLEAN DEFAULT FALSE,
    is_custom BOOLEAN DEFAULT FALSE,               -- True if hand-drawn, not computed from admin units
    
    -- Temporal extent
    valid_from DATE,
    valid_to DATE,                                 -- NULL = current
    
    -- Display properties
    display_fill_color VARCHAR(20) DEFAULT '#3498db',
    display_fill_opacity NUMERIC(3,2) DEFAULT 0.20,
    display_line_color VARCHAR(20) DEFAULT '#2980b9',
    display_line_width NUMERIC(3,1) DEFAULT 2.0,
    
    -- Metadata
    source_dataset VARCHAR(100),                   -- Data provenance
    notes TEXT,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    
    UNIQUE(ghcid, valid_to)
);

CREATE INDEX IF NOT EXISTS idx_custodian_sa_ghcid ON custodian_service_areas(ghcid);
CREATE INDEX IF NOT EXISTS idx_custodian_sa_uuid ON custodian_service_areas(ghcid_uuid);
CREATE INDEX IF NOT EXISTS idx_custodian_sa_geom ON custodian_service_areas USING GIST(geom);
CREATE INDEX IF NOT EXISTS idx_custodian_sa_type ON custodian_service_areas(service_area_type);
CREATE INDEX IF NOT EXISTS idx_custodian_sa_historical ON custodian_service_areas(is_historical);

-- ============================================================================
-- GeoNames Settlements (for reverse geocoding and linking)
-- ============================================================================
-- Subset of GeoNames populated places for settlement lookups

CREATE TABLE IF NOT EXISTS geonames_settlements (
    geonames_id INTEGER PRIMARY KEY,
    
    -- Names
    name VARCHAR(200) NOT NULL,
    ascii_name VARCHAR(200),
    alternate_names TEXT,                          -- Comma-separated
    
    -- Location
    latitude NUMERIC(10, 6) NOT NULL,
    longitude NUMERIC(10, 6) NOT NULL,
    geom GEOMETRY(POINT, 4326),                    -- Computed from lat/lon
    
    -- Classification
    feature_class CHAR(1),                         -- 'P' = populated place
    feature_code VARCHAR(10),                      -- 'PPL', 'PPLA', 'PPLC', etc.
    
    -- Administrative hierarchy
    country_code CHAR(2),
    admin1_code VARCHAR(20),                       -- GeoNames admin1
    admin2_code VARCHAR(80),
    admin3_code VARCHAR(20),
    admin4_code VARCHAR(20),
    
    -- Metadata
    population INTEGER,
    elevation INTEGER,
    timezone VARCHAR(40),
    modification_date DATE,
    
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_geonames_country ON geonames_settlements(country_code);
CREATE INDEX IF NOT EXISTS idx_geonames_admin1 ON geonames_settlements(country_code, admin1_code);
CREATE INDEX IF NOT EXISTS idx_geonames_feature ON geonames_settlements(feature_class, feature_code);
CREATE INDEX IF NOT EXISTS idx_geonames_geom ON geonames_settlements USING GIST(geom);
CREATE INDEX IF NOT EXISTS idx_geonames_name ON geonames_settlements(name);
CREATE INDEX IF NOT EXISTS idx_geonames_ascii ON geonames_settlements(ascii_name);
CREATE INDEX IF NOT EXISTS idx_geonames_population ON geonames_settlements(population DESC);

-- ============================================================================
-- Spatial Functions
-- ============================================================================

-- Function: Find admin units containing a point
CREATE OR REPLACE FUNCTION find_admin_for_point(
    p_lon NUMERIC,
    p_lat NUMERIC,
    p_country_code CHAR(2) DEFAULT NULL
)
RETURNS TABLE (
    admin_level INTEGER,
    admin_code VARCHAR,
    admin_name VARCHAR,
    iso_code VARCHAR,
    geonames_id INTEGER
) AS $$
BEGIN
    RETURN QUERY
    
    -- Admin Level 1
    SELECT 
        1::INTEGER AS admin_level,
        a.admin1_code,
        a.admin1_name,
        a.iso_3166_2,
        a.geonames_id
    FROM boundary_admin1 a
    JOIN boundary_countries c ON a.country_id = c.id
    WHERE ST_Contains(a.geom, ST_SetSRID(ST_Point(p_lon, p_lat), 4326))
      AND a.valid_to IS NULL
      AND (p_country_code IS NULL OR c.iso_a2 = p_country_code)
    
    UNION ALL
    
    -- Admin Level 2
    SELECT 
        2::INTEGER,
        a.admin2_code,
        a.admin2_name,
        a.nuts_code,
        a.geonames_id
    FROM boundary_admin2 a
    JOIN boundary_countries c ON a.country_id = c.id
    WHERE ST_Contains(a.geom, ST_SetSRID(ST_Point(p_lon, p_lat), 4326))
      AND a.valid_to IS NULL
      AND (p_country_code IS NULL OR c.iso_a2 = p_country_code)
    
    ORDER BY admin_level;
END;
$$ LANGUAGE plpgsql;

-- Function: Find nearest settlement
CREATE OR REPLACE FUNCTION find_nearest_settlement(
    p_lon NUMERIC,
    p_lat NUMERIC,
    p_country_code CHAR(2) DEFAULT NULL,
    p_max_distance_km NUMERIC DEFAULT 50
)
RETURNS TABLE (
    geonames_id INTEGER,
    name VARCHAR,
    feature_code VARCHAR,
    population INTEGER,
    distance_km NUMERIC
) AS $$
DECLARE
    v_point GEOMETRY;
BEGIN
    v_point := ST_SetSRID(ST_Point(p_lon, p_lat), 4326);
    
    RETURN QUERY
    SELECT 
        g.geonames_id,
        g.name,
        g.feature_code,
        g.population,
        ROUND((ST_Distance(g.geom::geography, v_point::geography) / 1000)::NUMERIC, 2) AS distance_km
    FROM geonames_settlements g
    WHERE g.feature_class = 'P'
      AND g.feature_code IN ('PPL', 'PPLA', 'PPLA2', 'PPLA3', 'PPLA4', 'PPLC', 'PPLS', 'PPLG')
      AND (p_country_code IS NULL OR g.country_code = p_country_code)
      AND ST_DWithin(g.geom::geography, v_point::geography, p_max_distance_km * 1000)
    ORDER BY g.geom <-> v_point
    LIMIT 5;
END;
$$ LANGUAGE plpgsql;

-- Function: Compute service area from admin units
CREATE OR REPLACE FUNCTION compute_service_area_geometry(
    p_admin2_ids INTEGER[],
    p_admin1_ids INTEGER[] DEFAULT NULL
)
RETURNS GEOMETRY AS $$
DECLARE
    v_geom GEOMETRY;
BEGIN
    -- Union all admin2 geometries
    IF p_admin2_ids IS NOT NULL AND array_length(p_admin2_ids, 1) > 0 THEN
        SELECT ST_Union(geom) INTO v_geom
        FROM boundary_admin2
        WHERE id = ANY(p_admin2_ids)
          AND valid_to IS NULL;
    END IF;
    
    -- Add admin1 geometries if specified
    IF p_admin1_ids IS NOT NULL AND array_length(p_admin1_ids, 1) > 0 THEN
        SELECT ST_Union(COALESCE(v_geom, 'GEOMETRYCOLLECTION EMPTY'::GEOMETRY), ST_Union(geom))
        INTO v_geom
        FROM boundary_admin1
        WHERE id = ANY(p_admin1_ids)
          AND valid_to IS NULL;
    END IF;
    
    RETURN v_geom;
END;
$$ LANGUAGE plpgsql;

-- Function: Get service area GeoJSON for a custodian
CREATE OR REPLACE FUNCTION get_custodian_service_area_geojson(
    p_ghcid VARCHAR,
    p_include_historical BOOLEAN DEFAULT FALSE
)
RETURNS JSONB AS $$
DECLARE
    v_result JSONB;
BEGIN
    SELECT jsonb_build_object(
        'type', 'FeatureCollection',
        'features', COALESCE(jsonb_agg(
            jsonb_build_object(
                'type', 'Feature',
                'geometry', ST_AsGeoJSON(sa.geom)::jsonb,
                'properties', jsonb_build_object(
                    'ghcid', sa.ghcid,
                    'name', sa.service_area_name,
                    'type', sa.service_area_type,
                    'is_historical', sa.is_historical,
                    'valid_from', sa.valid_from,
                    'valid_to', sa.valid_to,
                    'fill_color', sa.display_fill_color,
                    'fill_opacity', sa.display_fill_opacity,
                    'line_color', sa.display_line_color,
                    'line_width', sa.display_line_width
                )
            )
        ), '[]'::jsonb)
    )
    INTO v_result
    FROM custodian_service_areas sa
    WHERE sa.ghcid = p_ghcid
      AND (p_include_historical OR sa.is_historical = FALSE);
    
    RETURN v_result;
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- Views for API Access
-- ============================================================================

-- Current admin1 boundaries
CREATE OR REPLACE VIEW v_current_admin1 AS
SELECT 
    a.id,
    c.iso_a2 AS country_code,
    a.iso_3166_2,
    a.admin1_code,
    a.admin1_name,
    a.admin1_type,
    a.geonames_id,
    a.wikidata_id,
    ST_AsGeoJSON(a.geom)::jsonb AS geometry,
    ST_AsGeoJSON(a.centroid)::jsonb AS centroid,
    a.area_km2,
    s.source_code AS data_source
FROM boundary_admin1 a
JOIN boundary_countries c ON a.country_id = c.id
LEFT JOIN boundary_data_sources s ON a.source_id = s.id
WHERE a.valid_to IS NULL AND c.valid_to IS NULL;

-- Current admin2 boundaries
CREATE OR REPLACE VIEW v_current_admin2 AS
SELECT 
    a2.id,
    c.iso_a2 AS country_code,
    a1.iso_3166_2 AS admin1_iso,
    a2.admin2_code,
    a2.admin2_name,
    a2.admin2_type,
    a2.cbs_gemeente_code,
    a2.nuts_code,
    a2.lau_code,
    a2.geonames_id,
    a2.wikidata_id,
    ST_AsGeoJSON(a2.geom)::jsonb AS geometry,
    ST_AsGeoJSON(a2.centroid)::jsonb AS centroid,
    a2.area_km2,
    a2.population,
    s.source_code AS data_source
FROM boundary_admin2 a2
JOIN boundary_admin1 a1 ON a2.admin1_id = a1.id
JOIN boundary_countries c ON a2.country_id = c.id
LEFT JOIN boundary_data_sources s ON a2.source_id = s.id
WHERE a2.valid_to IS NULL AND a1.valid_to IS NULL AND c.valid_to IS NULL;

-- Custodian service areas
CREATE OR REPLACE VIEW v_custodian_service_areas AS
SELECT 
    sa.ghcid,
    sa.ghcid_uuid,
    sa.service_area_name,
    sa.service_area_type,
    sa.is_historical,
    sa.is_custom,
    sa.valid_from,
    sa.valid_to,
    ST_AsGeoJSON(sa.geom)::jsonb AS geometry,
    ST_AsGeoJSON(sa.centroid)::jsonb AS centroid,
    sa.display_fill_color,
    sa.display_fill_opacity,
    sa.display_line_color,
    sa.display_line_width,
    sa.source_dataset,
    sa.notes
FROM custodian_service_areas sa
WHERE sa.valid_to IS NULL;

-- ============================================================================
-- Statistics
-- ============================================================================

CREATE OR REPLACE VIEW v_boundary_stats AS
SELECT 
    'countries' AS table_name,
    COUNT(*) AS total_rows,
    COUNT(*) FILTER (WHERE valid_to IS NULL) AS current_rows
FROM boundary_countries
UNION ALL
SELECT 'admin1', COUNT(*), COUNT(*) FILTER (WHERE valid_to IS NULL) FROM boundary_admin1
UNION ALL
SELECT 'admin2', COUNT(*), COUNT(*) FILTER (WHERE valid_to IS NULL) FROM boundary_admin2
UNION ALL
SELECT 'historical', COUNT(*), COUNT(*) FROM boundary_historical
UNION ALL
SELECT 'service_areas', COUNT(*), COUNT(*) FILTER (WHERE valid_to IS NULL) FROM custodian_service_areas
UNION ALL
SELECT 'geonames_settlements', COUNT(*), COUNT(*) FROM geonames_settlements;

-- Country coverage summary
CREATE OR REPLACE VIEW v_boundary_coverage AS
SELECT 
    c.iso_a2,
    c.country_name,
    COUNT(DISTINCT a1.id) AS admin1_count,
    COUNT(DISTINCT a2.id) AS admin2_count,
    (SELECT COUNT(*) FROM geonames_settlements g WHERE g.country_code = c.iso_a2) AS settlement_count,
    (SELECT COUNT(*) FROM custodian_service_areas sa WHERE sa.ghcid LIKE c.iso_a2 || '-%') AS service_area_count
FROM boundary_countries c
LEFT JOIN boundary_admin1 a1 ON a1.country_id = c.id AND a1.valid_to IS NULL
LEFT JOIN boundary_admin2 a2 ON a2.country_id = c.id AND a2.valid_to IS NULL
WHERE c.valid_to IS NULL
GROUP BY c.id, c.iso_a2, c.country_name
ORDER BY c.country_name;

-- ============================================================================
-- Permissions
-- ============================================================================

GRANT SELECT ON ALL TABLES IN SCHEMA public TO glam_api;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO glam_api;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO glam_api;

-- ============================================================================
-- Table Comments
-- ============================================================================

COMMENT ON TABLE boundary_data_sources IS 'Registry of data sources for boundary data (GADM, CBS, OSM, etc.)';
COMMENT ON TABLE boundary_countries IS 'Country boundaries with ISO 3166-1 codes';
COMMENT ON TABLE boundary_admin1 IS 'First-level administrative divisions (states, provinces, regions)';
COMMENT ON TABLE boundary_admin2 IS 'Second-level administrative divisions (municipalities, districts, counties)';
COMMENT ON TABLE boundary_historical IS 'Historical administrative boundaries (e.g., HALC 1500 data)';
COMMENT ON TABLE custodian_service_areas IS 'Pre-computed or custom service area geometries for heritage custodians';
COMMENT ON TABLE geonames_settlements IS 'GeoNames populated places for settlement lookups';

COMMENT ON FUNCTION find_admin_for_point IS 'Find administrative units containing a geographic point';
COMMENT ON FUNCTION find_nearest_settlement IS 'Find nearest GeoNames settlement to a point';
COMMENT ON FUNCTION compute_service_area_geometry IS 'Compute union geometry from admin unit IDs';
COMMENT ON FUNCTION get_custodian_service_area_geojson IS 'Get GeoJSON for a custodian service area';
