-- ============================================================================
-- LUPMIS — Land Use Parcels schema
-- ============================================================================
-- Source: "LAND USE INFORMATION FOR LUPMIS" (LUSPA, February 2026, revised)
-- Implements the parcel-attribute table defined by Stephen / LUSPA, with a
-- PostGIS geometry column and the indices needed for typical access patterns
-- (spatial queries, lookup by zone / district / locality, time filtering).
--
-- Conventions:
--   • Identifiers are unquoted (lowercase) — PostgreSQL folds them to lower
--     case anyway, and this avoids the need for double-quotes in queries.
--   • Source column names are PascalCase / Mixed_Case in the spec; their
--     mapping to snake_case is shown in COMMENT ON COLUMN.
--   • Geometry is stored in EPSG:4326 (WGS 84) for portability with the
--     remote API. The MultiPolygon type accommodates parcels with islands
--     or multi-part shapes.
--
-- Run as a database superuser (or a role with CREATEEXTENSION privilege)
-- in the target database.
-- ============================================================================

-- PostGIS is required for the geometry column and spatial index.
CREATE EXTENSION IF NOT EXISTS postgis;

-- Drop existing table for clean re-runs in dev. Comment out for production.
-- DROP TABLE IF EXISTS public.landuse_parcels CASCADE;

-- ---------------------------------------------------------------------------
-- Table: public.landuse_parcels
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.landuse_parcels (
    id              BIGSERIAL    PRIMARY KEY,

    -- Spec field 1: UPN — Unique Parcel Number (Integer, up to 10 digits).
    -- 10-digit integers can exceed INTEGER's max (2,147,483,647), hence BIGINT.
    upn             BIGINT       NOT NULL,

    -- Spec field 2: Style — Colour Assign ID (Integer, 2 digits).
    -- References the colour palette defined in the Revised Zoning Guidelines
    -- and Planning Standards (2025). Optional FK to a lookup table.
    style           SMALLINT,

    -- Spec field 3: Landuse — Broad land use (Text, 50).
    landuse         VARCHAR(50),

    -- Spec field 4: Zone_Code — Zone acronym (Text, 5), e.g. "Re A".
    zone_code       VARCHAR(5),

    -- Spec field 5: Zone_Name — Zone name (Text, 50), e.g. "Residential Zone A".
    zone_name       VARCHAR(50),

    -- Spec field 6: Sector — Sector number of plan area (Text, 5).
    sector          VARCHAR(5),

    -- Spec field 7: Block — Block name within the sector (Text, 3).
    block           VARCHAR(3),

    -- Spec field 8: Parcel_No — Plot number for land registration (Text, 5).
    parcel_no       VARCHAR(5),

    -- Spec field 9: Prop_No — Property number for street addressing (Text, 5).
    prop_no         VARCHAR(5),

    -- Spec field 10: St_Name — Street name (Text, 18). From the Street Naming
    -- and Property Addressing System (SNPAS).
    st_name         VARCHAR(18),

    -- Spec field 11: Prop_Add — Property address (Text, 25).
    prop_add        VARCHAR(25),

    -- Spec field 12: Fac_Name — Facility name (Text, 100).
    fac_name        VARCHAR(100),

    -- Spec field 13: Min_Height — Minimum building height in storeys (Integer, 3).
    min_height      SMALLINT,

    -- Spec field 14: Max_Height — Maximum building height in storeys (Integer, 3).
    max_height      SMALLINT,

    -- Spec field 15: Eff_Date — Effective approval date by the District SPC.
    eff_date        DATE,

    -- Spec field 16: LP_Name — Local plan name (Text, 100).
    lp_name         VARCHAR(100),

    -- Spec field 17: Locality — Community / area name (Text, 50).
    locality        VARCHAR(50),

    -- Spec field 18: MMDA — Metropolitan / Municipal / District Assembly
    --                       abbreviation (Text, 10), e.g. "LADMA".
    mmda            VARCHAR(10),

    -- Spec field 19: Last_Update — Last update on a parcel (e.g. change of
    -- use approved by SPC).
    last_update     DATE,

    -- Spec field 20: Remarks — Additional info (Text, 200).
    remarks         VARCHAR(200),

    -- ------------------------------------------------------------------
    -- Geometry — parcel polygon in WGS 84 (EPSG:4326).
    -- MultiPolygon allows parcels with islands or disjoint parts.
    -- ------------------------------------------------------------------
    geom            geometry(MultiPolygon, 4326),

    -- ------------------------------------------------------------------
    -- Audit columns (not in the spec, added for change tracking)
    -- ------------------------------------------------------------------
    created_at      TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    updated_at      TIMESTAMPTZ  NOT NULL DEFAULT NOW(),

    -- ------------------------------------------------------------------
    -- Constraints
    -- ------------------------------------------------------------------
    CONSTRAINT uq_landuse_parcels_upn        UNIQUE (upn),
    CONSTRAINT ck_landuse_parcels_style      CHECK (style       IS NULL OR style      >= 0),
    CONSTRAINT ck_landuse_parcels_min_height CHECK (min_height  IS NULL OR min_height >= 0),
    CONSTRAINT ck_landuse_parcels_max_height CHECK (max_height  IS NULL OR max_height >= 0),
    CONSTRAINT ck_landuse_parcels_height_order
        CHECK (min_height IS NULL OR max_height IS NULL OR min_height <= max_height)
);

-- ---------------------------------------------------------------------------
-- Column comments — preserve the source-document descriptions
-- ---------------------------------------------------------------------------
COMMENT ON TABLE  public.landuse_parcels IS 'Land use parcels — LUSPA spec, February 2026 (revised).';

COMMENT ON COLUMN public.landuse_parcels.upn         IS 'UPN — Unique Parcel Number (Integer, 10 digits).';
COMMENT ON COLUMN public.landuse_parcels.style       IS 'Style — Colour Assign ID per Revised Zoning Guidelines (2025).';
COMMENT ON COLUMN public.landuse_parcels.landuse     IS 'Broad land use, e.g. Residential, Commercial, Mixed.';
COMMENT ON COLUMN public.landuse_parcels.zone_code   IS 'Zone code (acronym), e.g. Re A.';
COMMENT ON COLUMN public.landuse_parcels.zone_name   IS 'Zone name, e.g. Residential Zone A.';
COMMENT ON COLUMN public.landuse_parcels.sector      IS 'Sector number of the plan area.';
COMMENT ON COLUMN public.landuse_parcels.block       IS 'Block name within the sector.';
COMMENT ON COLUMN public.landuse_parcels.parcel_no   IS 'Plot number for land registration.';
COMMENT ON COLUMN public.landuse_parcels.prop_no     IS 'Property number for street addressing.';
COMMENT ON COLUMN public.landuse_parcels.st_name     IS 'Street name (max 18 characters, per SNPAS).';
COMMENT ON COLUMN public.landuse_parcels.prop_add    IS 'Property address of parcel.';
COMMENT ON COLUMN public.landuse_parcels.fac_name    IS 'Facility name of property.';
COMMENT ON COLUMN public.landuse_parcels.min_height  IS 'Minimum building height (storeys).';
COMMENT ON COLUMN public.landuse_parcels.max_height  IS 'Maximum building height (storeys).';
COMMENT ON COLUMN public.landuse_parcels.eff_date    IS 'Effective approval date by the District Spatial Planning Committee.';
COMMENT ON COLUMN public.landuse_parcels.lp_name     IS 'Local plan name.';
COMMENT ON COLUMN public.landuse_parcels.locality    IS 'Name of community or area.';
COMMENT ON COLUMN public.landuse_parcels.mmda        IS 'Metropolitan/Municipal/District Assembly abbreviation, e.g. LADMA.';
COMMENT ON COLUMN public.landuse_parcels.last_update IS 'Last update on a parcel (e.g. change of use approved by SPC).';
COMMENT ON COLUMN public.landuse_parcels.remarks     IS 'Additional information on the parcel.';
COMMENT ON COLUMN public.landuse_parcels.geom        IS 'Parcel boundary geometry (MultiPolygon, EPSG:4326).';
COMMENT ON COLUMN public.landuse_parcels.created_at  IS 'Row-creation timestamp (audit).';
COMMENT ON COLUMN public.landuse_parcels.updated_at  IS 'Row last-modified timestamp (audit, maintained by trigger).';

-- ---------------------------------------------------------------------------
-- Indices
-- ---------------------------------------------------------------------------

-- Spatial index — required for any ST_Intersects / ST_Within / map-bbox query.
CREATE INDEX IF NOT EXISTS idx_landuse_parcels_geom
    ON public.landuse_parcels
    USING GIST (geom);

-- B-tree indices for common attribute lookups.
-- (uq_landuse_parcels_upn already creates an implicit index on upn.)
CREATE INDEX IF NOT EXISTS idx_landuse_parcels_zone_code
    ON public.landuse_parcels (zone_code);

CREATE INDEX IF NOT EXISTS idx_landuse_parcels_mmda
    ON public.landuse_parcels (mmda);

CREATE INDEX IF NOT EXISTS idx_landuse_parcels_locality
    ON public.landuse_parcels (locality);

CREATE INDEX IF NOT EXISTS idx_landuse_parcels_lp_name
    ON public.landuse_parcels (lp_name);

CREATE INDEX IF NOT EXISTS idx_landuse_parcels_eff_date
    ON public.landuse_parcels (eff_date);

CREATE INDEX IF NOT EXISTS idx_landuse_parcels_last_update
    ON public.landuse_parcels (last_update);

-- Composite index for the very common "find all parcels in MMDA X with zone Y" query.
CREATE INDEX IF NOT EXISTS idx_landuse_parcels_mmda_zone
    ON public.landuse_parcels (mmda, zone_code);

-- ---------------------------------------------------------------------------
-- Trigger — keep updated_at fresh on every UPDATE
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.fn_landuse_parcels_set_updated_at()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
    NEW.updated_at := NOW();
    RETURN NEW;
END;
$$;

DROP TRIGGER IF EXISTS trg_landuse_parcels_set_updated_at ON public.landuse_parcels;

CREATE TRIGGER trg_landuse_parcels_set_updated_at
BEFORE UPDATE ON public.landuse_parcels
FOR EACH ROW
EXECUTE FUNCTION public.fn_landuse_parcels_set_updated_at();

-- ============================================================================
-- End of script
-- ============================================================================
