-- ============================================================================
-- PostGIS Schema for GLAM Heritage Institutions
-- Database: glam_geo
-- ============================================================================

-- Enable PostGIS extensions (should already be enabled)
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;

-- ============================================================================
-- Administrative Boundaries
-- ============================================================================

-- Provinces (admin level 1)
CREATE TABLE IF NOT EXISTS provinces (
    id SERIAL PRIMARY KEY,
    province_code VARCHAR(10) NOT NULL UNIQUE,  -- e.g., "PV27" for Noord-Holland
    iso_code VARCHAR(5),                         -- ISO 3166-2 code, e.g., "NH"
    name VARCHAR(100) NOT NULL,
    name_local VARCHAR(100),
    country_code CHAR(2) NOT NULL DEFAULT 'NL',
    geom GEOMETRY(MULTIPOLYGON, 4326),
    centroid GEOMETRY(POINT, 4326),
    area_km2 NUMERIC(12, 2),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_provinces_geom ON provinces USING GIST (geom);
CREATE INDEX IF NOT EXISTS idx_provinces_country ON provinces (country_code);

-- Municipalities (admin level 2)
CREATE TABLE IF NOT EXISTS municipalities (
    id SERIAL PRIMARY KEY,
    municipality_code VARCHAR(10) NOT NULL,      -- CBS code
    name VARCHAR(100) NOT NULL,
    name_local VARCHAR(100),
    province_id INTEGER REFERENCES provinces(id),
    country_code CHAR(2) NOT NULL DEFAULT 'NL',
    geom GEOMETRY(MULTIPOLYGON, 4326),
    centroid GEOMETRY(POINT, 4326),
    area_km2 NUMERIC(12, 2),
    population INTEGER,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(country_code, municipality_code)
);

CREATE INDEX IF NOT EXISTS idx_municipalities_geom ON municipalities USING GIST (geom);
CREATE INDEX IF NOT EXISTS idx_municipalities_province ON municipalities (province_id);

-- Historical boundaries (for historical maps)
CREATE TABLE IF NOT EXISTS historical_boundaries (
    id SERIAL PRIMARY KEY,
    boundary_code VARCHAR(50) NOT NULL,
    name VARCHAR(200) NOT NULL,
    name_local VARCHAR(200),
    boundary_type VARCHAR(50) NOT NULL,  -- 'territory', 'county', 'diocese', etc.
    valid_from DATE,                      -- Start of validity period
    valid_to DATE,                        -- End of validity period (NULL = current)
    reference_year INTEGER NOT NULL,      -- e.g., 1500 for historical map
    country_code CHAR(2) NOT NULL DEFAULT 'NL',
    geom GEOMETRY(MULTIPOLYGON, 4326),
    centroid GEOMETRY(POINT, 4326),
    area_km2 NUMERIC(12, 2),
    source_dataset VARCHAR(100),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_historical_geom ON historical_boundaries USING GIST (geom);
CREATE INDEX IF NOT EXISTS idx_historical_year ON historical_boundaries (reference_year);
CREATE INDEX IF NOT EXISTS idx_historical_type ON historical_boundaries (boundary_type);

-- ============================================================================
-- Heritage Institutions
-- ============================================================================

-- Institution types enum
CREATE TYPE institution_type AS ENUM (
    'G',  -- Gallery
    'L',  -- Library
    'A',  -- Archive
    'M',  -- Museum
    'O',  -- Official institution
    'R',  -- Research center
    'C',  -- Corporation
    'U',  -- Unknown
    'B',  -- Botanical/Zoo
    'E',  -- Education provider
    'S',  -- Collecting society
    'F',  -- Features
    'I',  -- Intangible heritage group
    'X',  -- Mixed
    'P',  -- Personal collection
    'H',  -- Holy sites
    'D',  -- Digital platform
    'N',  -- NGO
    'T'   -- Taste/smell heritage
);

-- Main institutions table
CREATE TABLE IF NOT EXISTS institutions (
    id SERIAL PRIMARY KEY,
    ghcid_current VARCHAR(50) NOT NULL UNIQUE,   -- e.g., "NL-NH-AMS-M-RM"
    ghcid_uuid UUID NOT NULL UNIQUE,
    ghcid_numeric NUMERIC(20, 0),  -- Larger than BIGINT to handle 64-bit unsigned hashes
    
    -- Names
    name VARCHAR(500) NOT NULL,
    name_verified VARCHAR(500),
    name_source VARCHAR(50),
    
    -- Type and classification
    institution_type institution_type NOT NULL,
    type_name VARCHAR(100),
    wikidata_types TEXT[],
    
    -- Location
    geom GEOMETRY(POINT, 4326),
    address TEXT,
    city VARCHAR(100),
    province VARCHAR(100),
    province_id INTEGER REFERENCES provinces(id),
    municipality_id INTEGER REFERENCES municipalities(id),
    country_code CHAR(2) NOT NULL DEFAULT 'NL',
    
    -- Metadata
    description TEXT,
    website VARCHAR(500),
    phone VARCHAR(50),
    
    -- External identifiers
    wikidata_id VARCHAR(20),
    google_place_id VARCHAR(100),
    isil_code VARCHAR(20),
    
    -- Enrichment data (JSONB for flexibility)
    reviews JSONB,
    rating NUMERIC(2, 1),
    total_ratings INTEGER,
    photos JSONB,
    
    -- Genealogy/Archive linkage
    genealogiewerkbalk JSONB,
    
    -- Business status
    business_status VARCHAR(50),
    founding_year INTEGER,
    founding_decade INTEGER,
    
    -- Timestamps
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_institutions_geom ON institutions USING GIST (geom);
CREATE INDEX IF NOT EXISTS idx_institutions_ghcid ON institutions (ghcid_current);
CREATE INDEX IF NOT EXISTS idx_institutions_type ON institutions (institution_type);
CREATE INDEX IF NOT EXISTS idx_institutions_province ON institutions (province_id);
CREATE INDEX IF NOT EXISTS idx_institutions_municipality ON institutions (municipality_id);
CREATE INDEX IF NOT EXISTS idx_institutions_wikidata ON institutions (wikidata_id);
CREATE INDEX IF NOT EXISTS idx_institutions_country ON institutions (country_code);

-- Full text search on names and descriptions
CREATE INDEX IF NOT EXISTS idx_institutions_name_fts 
    ON institutions USING GIN (to_tsvector('simple', name || ' ' || COALESCE(description, '')));

-- ============================================================================
-- Archive Service Areas (werkgebied mapping)
-- ============================================================================

CREATE TABLE IF NOT EXISTS archive_service_areas (
    id SERIAL PRIMARY KEY,
    archive_isil VARCHAR(20) NOT NULL,
    archive_name VARCHAR(200) NOT NULL,
    archive_website VARCHAR(500),
    municipality_code VARCHAR(10) NOT NULL,
    municipality_name VARCHAR(100) NOT NULL,
    is_municipal BOOLEAN DEFAULT FALSE,
    is_provincial BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    UNIQUE(archive_isil, municipality_code)
);

CREATE INDEX IF NOT EXISTS idx_archive_service_isil ON archive_service_areas (archive_isil);
CREATE INDEX IF NOT EXISTS idx_archive_service_municipality ON archive_service_areas (municipality_code);

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

-- Find institutions within a bounding box
CREATE OR REPLACE FUNCTION find_institutions_in_bbox(
    min_lon FLOAT,
    min_lat FLOAT,
    max_lon FLOAT,
    max_lat FLOAT,
    inst_type institution_type DEFAULT NULL,
    max_results INTEGER DEFAULT 1000
)
RETURNS TABLE (
    id INTEGER,
    ghcid_current VARCHAR,
    name VARCHAR,
    institution_type institution_type,
    type_name VARCHAR,
    lon FLOAT,
    lat FLOAT,
    city VARCHAR,
    province VARCHAR,
    rating NUMERIC,
    wikidata_id VARCHAR
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        i.id,
        i.ghcid_current,
        i.name,
        i.institution_type,
        i.type_name,
        ST_X(i.geom)::FLOAT as lon,
        ST_Y(i.geom)::FLOAT as lat,
        i.city,
        i.province,
        i.rating,
        i.wikidata_id
    FROM institutions i
    WHERE i.geom && ST_MakeEnvelope(min_lon, min_lat, max_lon, max_lat, 4326)
      AND (inst_type IS NULL OR i.institution_type = inst_type)
    ORDER BY i.name
    LIMIT max_results;
END;
$$ LANGUAGE plpgsql;

-- Find institutions within a radius of a point
CREATE OR REPLACE FUNCTION find_institutions_near_point(
    lon FLOAT,
    lat FLOAT,
    radius_km FLOAT DEFAULT 10,
    inst_type institution_type DEFAULT NULL,
    max_results INTEGER DEFAULT 100
)
RETURNS TABLE (
    id INTEGER,
    ghcid_current VARCHAR,
    name VARCHAR,
    institution_type institution_type,
    type_name VARCHAR,
    distance_km FLOAT,
    city VARCHAR,
    province VARCHAR,
    rating NUMERIC
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        i.id,
        i.ghcid_current,
        i.name,
        i.institution_type,
        i.type_name,
        (ST_Distance(
            i.geom::geography,
            ST_SetSRID(ST_Point(lon, lat), 4326)::geography
        ) / 1000)::FLOAT as distance_km,
        i.city,
        i.province,
        i.rating
    FROM institutions i
    WHERE ST_DWithin(
        i.geom::geography,
        ST_SetSRID(ST_Point(lon, lat), 4326)::geography,
        radius_km * 1000
    )
    AND (inst_type IS NULL OR i.institution_type = inst_type)
    ORDER BY distance_km
    LIMIT max_results;
END;
$$ LANGUAGE plpgsql;

-- Find which municipality/province contains a point
CREATE OR REPLACE FUNCTION find_admin_for_point(
    lon FLOAT,
    lat FLOAT
)
RETURNS TABLE (
    province_code VARCHAR,
    province_name VARCHAR,
    municipality_code VARCHAR,
    municipality_name VARCHAR
) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        p.province_code,
        p.name as province_name,
        m.municipality_code,
        m.name as municipality_name
    FROM municipalities m
    JOIN provinces p ON m.province_id = p.id
    WHERE ST_Contains(m.geom, ST_SetSRID(ST_Point(lon, lat), 4326))
    LIMIT 1;
END;
$$ LANGUAGE plpgsql;

-- Get institutions by province as GeoJSON
CREATE OR REPLACE FUNCTION get_institutions_geojson(
    province_filter VARCHAR DEFAULT NULL,
    type_filter institution_type DEFAULT NULL
)
RETURNS JSON AS $$
BEGIN
    RETURN (
        SELECT json_build_object(
            'type', 'FeatureCollection',
            'features', COALESCE(json_agg(
                json_build_object(
                    'type', 'Feature',
                    'geometry', ST_AsGeoJSON(i.geom)::json,
                    'properties', json_build_object(
                        'id', i.id,
                        'ghcid', i.ghcid_current,
                        'name', i.name,
                        'type', i.institution_type,
                        'type_name', i.type_name,
                        'city', i.city,
                        'province', i.province,
                        'rating', i.rating,
                        'wikidata_id', i.wikidata_id,
                        'website', i.website
                    )
                )
            ), '[]')
        )
        FROM institutions i
        WHERE (province_filter IS NULL OR i.province = province_filter)
          AND (type_filter IS NULL OR i.institution_type = type_filter)
    );
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- Views for common queries
-- ============================================================================

-- Institutions with province info
CREATE OR REPLACE VIEW v_institutions_with_admin AS
SELECT 
    i.*,
    p.name as province_name,
    p.iso_code as province_iso,
    m.name as municipality_name
FROM institutions i
LEFT JOIN provinces p ON i.province_id = p.id
LEFT JOIN municipalities m ON i.municipality_id = m.id;

-- Institution counts by type and province
CREATE OR REPLACE VIEW v_institution_stats AS
SELECT 
    i.province,
    i.institution_type,
    i.type_name,
    COUNT(*) as count,
    ROUND(AVG(i.rating), 2) as avg_rating
FROM institutions i
WHERE i.geom IS NOT NULL
GROUP BY i.province, i.institution_type, i.type_name
ORDER BY i.province, count DESC;

-- ============================================================================
-- Metadata table
-- ============================================================================

CREATE TABLE IF NOT EXISTS geo_metadata (
    id SERIAL PRIMARY KEY,
    key VARCHAR(100) NOT NULL UNIQUE,
    value TEXT,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

INSERT INTO geo_metadata (key, value) VALUES 
    ('schema_version', '1.0.0'),
    ('created_at', NOW()::TEXT),
    ('description', 'PostGIS schema for GLAM heritage institutions')
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = NOW();

-- ============================================================================
-- Grants (adjust user as needed)
-- ============================================================================

-- Create API user if not exists
DO $$
BEGIN
    IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'glam_api') THEN
        CREATE ROLE glam_api WITH LOGIN PASSWORD 'glam_secret_2025';
    END IF;
END
$$;

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

-- Allow inserts for data loading (revoke in production if needed)
GRANT INSERT, UPDATE ON institutions, provinces, municipalities, historical_boundaries, archive_service_areas TO glam_api;
