set check_function_bodies = off;

CREATE OR REPLACE FUNCTION gis.nearby_units(lat double precision, lon double precision, max_results integer DEFAULT 5)
 RETURNS TABLE(code_unit character varying, name text, type text, address text, district_id character varying, lat_unit double precision, lon_unit double precision, distance_km double precision)
 LANGUAGE sql
AS $function$
  select
    u.code_unit,
    u.name,
    u.type,
    u.address,
    u.district_id,
    gis.ST_Y(u.location::gis.geometry) as lat_unit,
    gis.ST_X(u.location::gis.geometry) as lon_unit,
    gis.ST_Distance(
      u.location::gis.geography,
      gis.ST_SetSRID(gis.ST_MakePoint(lon, lat), 4326)::gis.geography
    ) / 1000 as distance_km
  from units u
  order by gis.ST_Distance(
    u.location::gis.geography,
    gis.ST_SetSRID(gis.ST_MakePoint(lon, lat), 4326)::gis.geography
  )
  limit max_results
$function$
;

CREATE OR REPLACE FUNCTION gis.update_location_distance_to_unit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    loc_lat FLOAT;
    loc_lng FLOAT;
    unit_lat FLOAT;
    unit_lng FLOAT;
    loc_point gis.GEOGRAPHY;
    unit_point gis.GEOGRAPHY;
BEGIN
    -- Ambil lat/lng dari location yang baru
    SELECT gis.ST_Y(NEW.location::gis.geometry), gis.ST_X(NEW.location::gis.geometry)
    INTO loc_lat, loc_lng;

    -- Ambil lat/lng dari unit di distrik yang sama
    SELECT gis.ST_Y(u.location::gis.geometry), gis.ST_X(u.location::gis.geometry)
    INTO unit_lat, unit_lng
    FROM units u
    WHERE u.district_id = NEW.district_id
    LIMIT 1;

    -- Jika tidak ada unit di distrik yang sama, kembalikan NEW tanpa perubahan
    IF unit_lat IS NULL OR unit_lng IS NULL THEN
        RETURN NEW;
    END IF;

    -- Buat point geography dari lat/lng
    loc_point := gis.ST_SetSRID(gis.ST_MakePoint(loc_lng, loc_lat), 4326)::gis.geography;
    unit_point := gis.ST_SetSRID(gis.ST_MakePoint(unit_lng, unit_lat), 4326)::gis.geography;

    -- Update jaraknya ke kolom distance_to_unit
    NEW.distance_to_unit := gis.ST_Distance(loc_point, unit_point) / 1000;

    RETURN NEW;
END;
$function$
;

CREATE OR REPLACE FUNCTION gis.update_land_area()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
BEGIN
  NEW.land_area := ROUND((ST_Area(NEW.geometry::geography) / 1000000.0)::numeric, 2);
  RETURN NEW;
END;
$function$
;


drop trigger if exists "trg_update_land_area" on "public"."geographics";

drop trigger if exists "location_distance_to_unit_trigger" on "public"."locations";

-- Drop the trigger first
drop trigger if exists location_distance_to_unit_trigger on public.locations;

-- Drop all triggers on public.locations that use public.update_location_distance_to_unit()
DO $$
DECLARE
  trig record;
BEGIN
  FOR trig IN
    SELECT tgname
    FROM pg_trigger
    WHERE tgrelid = 'public.locations'::regclass
      AND NOT tgisinternal
      AND tgfoid = 'public.update_location_distance_to_unit()'::regprocedure
  LOOP
    EXECUTE format('DROP TRIGGER IF EXISTS %I ON public.locations;', trig.tgname);
  END LOOP;
END
$$;

-- Now drop the trigger function
drop function if exists public.update_location_distance_to_unit();

-- Now we can safely alter the column type
alter table "public"."locations" alter column "location" set data type gis.geography using "location"::gis.geography;

-- Recreate the trigger function
CREATE OR REPLACE FUNCTION public.update_location_distance_to_unit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    loc_lat FLOAT;
    loc_lng FLOAT;
    unit_lat FLOAT;
    unit_lng FLOAT;
    loc_point gis.GEOGRAPHY;
    unit_point gis.GEOGRAPHY;
BEGIN
    -- Ambil lat/lng dari location yang baru
    SELECT gis.ST_Y(NEW.location::gis.geometry), gis.ST_X(NEW.location::gis.geometry)
    INTO loc_lat, loc_lng;

    -- Ambil lat/lng dari unit di distrik yang sama
    SELECT gis.ST_Y(u.location::gis.geometry), gis.ST_X(u.location::gis.geometry)
    INTO unit_lat, unit_lng
    FROM units u
    WHERE u.district_id = NEW.district_id
    LIMIT 1;

    -- Jika tidak ada unit di distrik yang sama, kembalikan NEW tanpa perubahan
    IF unit_lat IS NULL OR unit_lng IS NULL THEN
        RETURN NEW;
    END IF;

    -- Buat point geography dari lat/lng
    loc_point := gis.ST_SetSRID(gis.ST_MakePoint(loc_lng, loc_lat), 4326)::gis.geography;
    unit_point := gis.ST_SetSRID(gis.ST_MakePoint(unit_lng, unit_lat), 4326)::gis.geography;

    -- Update jaraknya ke kolom distance_to_unit
    NEW.distance_to_unit := gis.ST_Distance(loc_point, unit_point) / 1000;

    RETURN NEW;
END;
$function$
;

-- Recreate the trigger
CREATE TRIGGER location_distance_to_unit_trigger
BEFORE INSERT OR UPDATE OF location ON public.locations
FOR EACH ROW
EXECUTE FUNCTION public.update_location_distance_to_unit();

-- drop policy "give all access to users" on "public"."geographics";

-- Check if constraint exists before attempting to drop it
DO $$
BEGIN
  IF EXISTS (
    SELECT 1 
    FROM information_schema.table_constraints 
    WHERE constraint_name = 'unit_statistics_unit_id_fkey' 
    AND table_name = 'unit_statistics'
  ) THEN
    ALTER TABLE "public"."unit_statistics" DROP CONSTRAINT "unit_statistics_unit_id_fkey";
  END IF;
END
$$;

drop view if exists "public"."location_paths";

drop function if exists "public"."update_land_area"();

drop function if exists "public"."update_timestamp"();

alter table "public"."units" drop constraint "units_pkey";

drop index if exists "public"."unit_statistics_unit_id_month_year_key";

drop index if exists "public"."units_pkey";

-- Check if _prisma_migrations table exists before trying to create it
DO $$
BEGIN
  IF NOT EXISTS (
    SELECT FROM pg_tables 
    WHERE schemaname = 'public' 
    AND tablename = '_prisma_migrations'
  ) THEN
    CREATE TABLE "public"."_prisma_migrations" (
        "id" character varying(36) not null,
        "checksum" character varying(64) not null,
        "finished_at" timestamp with time zone,
        "migration_name" character varying(255) not null,
        "logs" text,
        "rolled_back_at" timestamp with time zone,
        "started_at" timestamp with time zone not null default now(),
        "applied_steps_count" integer not null default 0
    );
  END IF;
END
$$;

-- Also wrap other table creations with existence checks
DO $$
BEGIN
  IF NOT EXISTS (
    SELECT FROM pg_tables 
    WHERE schemaname = 'public' 
    AND tablename = 'evidence'
  ) THEN
    create table "public"."evidence" (
        "incident_id" uuid not null,
        "type" character varying(50) not null,
        "url" text not null,
        "uploaded_at" timestamp(6) with time zone default CURRENT_TIMESTAMP,
        "caption" character varying(255),
        "description" character varying(255),
        "metadata" jsonb,
        "id" character varying(20) not null
    );
  END IF;
END
$$;

DO $$
BEGIN
  IF NOT EXISTS (
    SELECT FROM pg_tables 
    WHERE schemaname = 'public' 
    AND tablename = 'officers'
  ) THEN
    create table "public"."officers" (
        "unit_id" character varying(20) not null,
        "role_id" uuid not null,
        "nrp" character varying(100) not null,
        "name" character varying(100) not null,
        "rank" character varying(100),
        "position" character varying(100),
        "phone" character varying(20),
        "email" character varying(255),
        "avatar" text,
        "valid_until" timestamp(3) without time zone,
        "qr_code" text,
        "created_at" timestamp(6) with time zone default CURRENT_TIMESTAMP,
        "updated_at" timestamp(6) with time zone default CURRENT_TIMESTAMP,
        "patrol_unit_id" character varying(100) not null,
        "id" uuid not null default gen_random_uuid(),
        "banned_reason" character varying(255),
        "banned_until" timestamp(3) without time zone,
        "is_banned" boolean not null default false,
        "panic_strike" integer not null default 0,
        "spoofing_attempts" integer not null default 0
    );
  END IF;
END
$$;

DO $$
BEGIN
  IF NOT EXISTS (
    SELECT FROM pg_tables 
    WHERE schemaname = 'public' 
    AND tablename = 'panic_button_logs'
  ) THEN
    create table "public"."panic_button_logs" (
        "id" uuid not null default gen_random_uuid(),
        "user_id" uuid not null,
        "officer_id" uuid,
        "incident_id" uuid not null,
        "timestamp" timestamp(6) with time zone not null
    );
  END IF;
END
$$;

DO $$
BEGIN
  IF NOT EXISTS (
    SELECT FROM pg_tables 
    WHERE schemaname = 'public' 
    AND tablename = 'patrol_units'
  ) THEN
    create table "public"."patrol_units" (
        "unit_id" character varying(20) not null,
        "location_id" uuid not null,
        "name" character varying(100) not null,
        "type" character varying(50) not null,
        "status" character varying(50) not null,
        "radius" double precision not null,
        "created_at" timestamp(6) with time zone not null default CURRENT_TIMESTAMP,
        "id" character varying(100) not null
    );
  END IF;
END
$$;

alter table "public"."crimes" add column "avg_crime" double precision not null default 0;

alter table "public"."crimes" add column "crime_cleared" integer not null default 0;

alter table "public"."crimes" add column "source_type" character varying(100);

alter table "public"."crimes" alter column "year" drop not null;

alter table "public"."geographics" alter column "location" set data type gis.geography using "location"::gis.geography;

alter table "public"."location_logs" alter column "created_at" set default CURRENT_TIMESTAMP;

alter table "public"."location_logs" alter column "created_at" set data type timestamp(6) with time zone using "created_at"::timestamp(6) with time zone;

alter table "public"."location_logs" alter column "location" set data type gis.geography using "location"::gis.geography;

alter table "public"."location_logs" alter column "timestamp" set data type timestamp(6) with time zone using "timestamp"::timestamp(6) with time zone;

alter table "public"."location_logs" alter column "updated_at" set default CURRENT_TIMESTAMP;

alter table "public"."location_logs" alter column "updated_at" set data type timestamp(6) with time zone using "updated_at"::timestamp(6) with time zone;

alter table "public"."locations" add column "distance_to_unit" double precision;

-- First, drop any triggers on the locations table that might use the location column
DO $$
BEGIN
  IF EXISTS (
    SELECT 1 FROM pg_trigger 
    WHERE tgrelid = 'public.locations'::regclass 
    AND tgname = 'location_distance_to_unit_trigger'
  ) THEN
    DROP TRIGGER location_distance_to_unit_trigger ON public.locations;
  END IF;
END
$$;

-- Now we can safely alter the column type
alter table "public"."locations" alter column "location" set data type gis.geography using "location"::gis.geography;

alter table "public"."profiles" add column "nik" character varying(100) not null default ''::character varying;

-- Safely drop unit_id column if it exists
DO $$
BEGIN
  IF EXISTS (
    SELECT 1
    FROM information_schema.columns
    WHERE table_schema = 'public'
      AND table_name = 'unit_statistics'
      AND column_name = 'unit_id'
  ) THEN
    ALTER TABLE "public"."unit_statistics" DROP COLUMN "unit_id";
  END IF;
END
$$;

-- Safely add code_unit column if it does not exist
DO $$
BEGIN
  IF NOT EXISTS (
    SELECT 1
    FROM information_schema.columns
    WHERE table_schema = 'public'
      AND table_name = 'unit_statistics'
      AND column_name = 'code_unit'
  ) THEN
    ALTER TABLE "public"."unit_statistics" ADD COLUMN "code_unit" character varying(20) NOT NULL;
  END IF;
END
$$;

alter table "public"."units" drop column "id";

alter table "public"."units" add column "city_id" character varying(20) not null;

alter table "public"."units" add column "phone" character varying(20);

alter table "public"."units" alter column "district_id" drop not null;

alter table "public"."units" alter column "location" set data type gis.geography using "location"::gis.geography;

alter table "public"."users" add column "banned_reason" character varying(255);

alter table "public"."users" add column "is_banned" boolean not null default false;

alter table "public"."users" add column "panic_strike" integer not null default 0;

alter table "public"."users" add column "spoofing_attempts" integer not null default 0;

CREATE UNIQUE INDEX _prisma_migrations_pkey ON public._prisma_migrations USING btree (id);

CREATE UNIQUE INDEX evidence_id_key ON public.evidence USING btree (id);

CREATE UNIQUE INDEX evidence_pkey ON public.evidence USING btree (id);

CREATE INDEX idx_evidence_incident_id ON public.evidence USING btree (incident_id);

CREATE INDEX idx_officers_name ON public.officers USING btree (name);

CREATE INDEX idx_officers_nrp ON public.officers USING btree (nrp);

CREATE INDEX idx_officers_position ON public.officers USING btree ("position");

CREATE INDEX idx_officers_rank ON public.officers USING btree (rank);

CREATE INDEX idx_officers_unit_id ON public.officers USING btree (unit_id);

CREATE INDEX idx_panic_buttons_user_id ON public.panic_button_logs USING btree (user_id);

CREATE INDEX idx_patrol_units_location_id ON public.patrol_units USING btree (location_id);

CREATE INDEX idx_patrol_units_name ON public.patrol_units USING btree (name);

CREATE INDEX idx_patrol_units_status ON public.patrol_units USING btree (status);

CREATE INDEX idx_patrol_units_type ON public.patrol_units USING btree (type);

CREATE INDEX idx_patrol_units_unit_id ON public.patrol_units USING btree (unit_id);

CREATE INDEX idx_profiles_nik ON public.profiles USING btree (nik);

CREATE UNIQUE INDEX officers_nrp_key ON public.officers USING btree (nrp);

CREATE UNIQUE INDEX officers_pkey ON public.officers USING btree (id);

CREATE UNIQUE INDEX panic_button_logs_pkey ON public.panic_button_logs USING btree (id);

CREATE UNIQUE INDEX patrol_units_id_key ON public.patrol_units USING btree (id);

CREATE UNIQUE INDEX patrol_units_pkey ON public.patrol_units USING btree (id);

CREATE UNIQUE INDEX profiles_nik_key ON public.profiles USING btree (nik);

CREATE UNIQUE INDEX unit_statistics_code_unit_month_year_key ON public.unit_statistics USING btree (code_unit, month, year);

CREATE UNIQUE INDEX units_pkey ON public.units USING btree (code_unit);

alter table "public"."_prisma_migrations" add constraint "_prisma_migrations_pkey" PRIMARY KEY using index "_prisma_migrations_pkey";

alter table "public"."evidence" add constraint "evidence_pkey" PRIMARY KEY using index "evidence_pkey";

alter table "public"."officers" add constraint "officers_pkey" PRIMARY KEY using index "officers_pkey";

alter table "public"."panic_button_logs" add constraint "panic_button_logs_pkey" PRIMARY KEY using index "panic_button_logs_pkey";

alter table "public"."patrol_units" add constraint "patrol_units_pkey" PRIMARY KEY using index "patrol_units_pkey";

alter table "public"."units" add constraint "units_pkey" PRIMARY KEY using index "units_pkey";

alter table "public"."evidence" add constraint "evidence_incident_id_fkey" FOREIGN KEY (incident_id) REFERENCES incident_logs(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;

alter table "public"."evidence" validate constraint "evidence_incident_id_fkey";

alter table "public"."officers" add constraint "officers_patrol_unit_id_fkey" FOREIGN KEY (patrol_unit_id) REFERENCES patrol_units(id) ON UPDATE CASCADE ON DELETE RESTRICT not valid;

alter table "public"."officers" validate constraint "officers_patrol_unit_id_fkey";

alter table "public"."officers" add constraint "officers_role_id_fkey" FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE not valid;

alter table "public"."officers" validate constraint "officers_role_id_fkey";

alter table "public"."officers" add constraint "officers_unit_id_fkey" FOREIGN KEY (unit_id) REFERENCES units(code_unit) ON DELETE CASCADE not valid;

alter table "public"."officers" validate constraint "officers_unit_id_fkey";

alter table "public"."panic_button_logs" add constraint "panic_button_logs_incident_id_fkey" FOREIGN KEY (incident_id) REFERENCES incident_logs(id) ON DELETE CASCADE not valid;

alter table "public"."panic_button_logs" validate constraint "panic_button_logs_incident_id_fkey";

alter table "public"."panic_button_logs" add constraint "panic_button_logs_officer_id_fkey" FOREIGN KEY (officer_id) REFERENCES officers(id) ON DELETE CASCADE not valid;

alter table "public"."panic_button_logs" validate constraint "panic_button_logs_officer_id_fkey";

alter table "public"."panic_button_logs" add constraint "panic_button_logs_user_id_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE not valid;

alter table "public"."panic_button_logs" validate constraint "panic_button_logs_user_id_fkey";

alter table "public"."patrol_units" add constraint "patrol_units_location_id_fkey" FOREIGN KEY (location_id) REFERENCES locations(id) ON DELETE CASCADE not valid;

alter table "public"."patrol_units" validate constraint "patrol_units_location_id_fkey";

alter table "public"."patrol_units" add constraint "patrol_units_unit_id_fkey" FOREIGN KEY (unit_id) REFERENCES units(code_unit) ON DELETE CASCADE not valid;

alter table "public"."patrol_units" validate constraint "patrol_units_unit_id_fkey";

alter table "public"."unit_statistics" add constraint "unit_statistics_code_unit_fkey" FOREIGN KEY (code_unit) REFERENCES units(code_unit) ON DELETE CASCADE not valid;

alter table "public"."unit_statistics" validate constraint "unit_statistics_code_unit_fkey";

alter table "public"."units" add constraint "units_city_id_fkey" FOREIGN KEY (city_id) REFERENCES cities(id) ON DELETE CASCADE not valid;

alter table "public"."units" validate constraint "units_city_id_fkey";

set check_function_bodies = off;

CREATE OR REPLACE FUNCTION public.delete_user()
 RETURNS void
 LANGUAGE plpgsql
 SECURITY DEFINER
 SET search_path TO ''
AS $function$
begin
delete from auth.users where id = (select auth.uid());
end;
$function$
;

CREATE OR REPLACE FUNCTION public.generate_username(email text)
 RETURNS text
 LANGUAGE plpgsql
 SECURITY DEFINER
AS $function$
DECLARE
  result_username TEXT;
  username_base TEXT;
  random_number INTEGER;
  username_exists BOOLEAN;
BEGIN
  -- Extract the part before @ from email
  username_base := split_part(email, '@', 1);
  
  -- Remove any special characters and replace with underscore
  username_base := regexp_replace(username_base, '[^a-zA-Z0-9]', '_', 'g');
  
  -- Generate a random number between 100 and 9999
  random_number := floor(random() * 9900 + 100)::integer;
  
  -- Combine the base and random number
  result_username := username_base || random_number;
  
  -- Check if username already exists
  LOOP
    SELECT EXISTS(SELECT 1 FROM public.profiles WHERE username = result_username) INTO username_exists;
    
    IF NOT username_exists THEN
      EXIT;
    END IF;
    
    -- Generate a different random number
    random_number := floor(random() * 9900 + 100)::integer;
    result_username := username_base || random_number;
  END LOOP;
  
  RETURN result_username;
END;
$function$
;

CREATE OR REPLACE FUNCTION public.handle_new_user()
 RETURNS trigger
 LANGUAGE plpgsql
 SECURITY DEFINER
AS $function$
DECLARE
    role_id UUID;
    officer_role_id UUID;
    is_officer BOOLEAN;
    officer_data JSONB;
    unit_id VARCHAR;
BEGIN
    -- Check if the user is registering as an officer
    is_officer := FALSE;
    
    -- Check user_metadata for officer flag
    IF NEW.raw_user_meta_data ? 'is_officer' THEN
        is_officer := (NEW.raw_user_meta_data->>'is_officer')::boolean;
    END IF;
    
    IF is_officer THEN
        -- Get officer role ID
        SELECT id INTO officer_role_id FROM public.roles WHERE name = 'officer' LIMIT 1;
        IF officer_role_id IS NULL THEN
            RAISE EXCEPTION 'Officer role not found';
        END IF;
        
        -- Extract officer data from metadata
        officer_data := NEW.raw_user_meta_data->'officer_data';
        
        -- Get unit ID from metadata
        unit_id := officer_data->>'unit_id';
        IF unit_id IS NULL THEN
            RAISE EXCEPTION 'Unit ID is required for officer registration';
        END IF;
        
        -- Insert into officers table
        INSERT INTO public.officers (
            id,
            unit_id,
            role_id,
            nrp,
            name,
            rank,
            position,
            phone,
            email,
            created_at,
            updated_at
        ) VALUES (
            NEW.id,
            unit_id,
            officer_role_id,
            officer_data->>'nrp',
            COALESCE(officer_data->>'name', NEW.email),
            officer_data->>'rank',
            officer_data->>'position',
            COALESCE(NEW.phone, officer_data->>'phone'),
            NEW.email,
            NEW.created_at,
            NEW.updated_at
        );

        -- Return early since we've handled the officer case
        RETURN NEW;
    ELSE
        -- Standard user registration - Get viewer role ID
        SELECT id INTO role_id FROM public.roles WHERE name = 'viewer' LIMIT 1;
        IF role_id IS NULL THEN
            RAISE EXCEPTION 'Viewer role not found';
        END IF;
        
        -- Insert into users table
        INSERT INTO public.users (
            id,
            roles_id,
            email,
            phone,
            encrypted_password,
            invited_at,
            confirmed_at,
            email_confirmed_at,
            recovery_sent_at,
            last_sign_in_at,
            app_metadata,
            user_metadata,
            created_at,
            updated_at,
            banned_until,
            is_anonymous
        ) VALUES (
            NEW.id,
            role_id,
            NEW.email,
            NEW.phone,
            NEW.encrypted_password,
            NEW.invited_at,
            NEW.confirmed_at,
            NEW.email_confirmed_at,
            NEW.recovery_sent_at,
            NEW.last_sign_in_at,
            NEW.raw_app_meta_data,
            NEW.raw_user_meta_data,
            NEW.created_at,
            NEW.updated_at,
            NEW.banned_until,
            NEW.is_anonymous
        );
        
        -- Insert into profiles table
        INSERT INTO public.profiles (
            id,
            user_id,
            avatar,
            username,
            first_name,
            last_name,
            bio,
            address,
            birth_date
        ) VALUES (
            gen_random_uuid(),
            NEW.id,
            NULL,
            public.generate_username(NEW.email),
            NULL,
            NULL,
            NULL,
            NULL,
            NULL
        );
        
        RETURN NEW;
    END IF;
END;
$function$
;

CREATE OR REPLACE FUNCTION public.handle_user_delete()
 RETURNS trigger
 LANGUAGE plpgsql
 SECURITY DEFINER
AS $function$
DECLARE
    is_officer BOOLEAN;
BEGIN
    -- Check if the user is an officer
    is_officer := EXISTS (SELECT 1 FROM public.officers WHERE id = OLD.id);
    
    IF is_officer THEN
        -- Delete officer record
        DELETE FROM public.officers WHERE id = OLD.id;
    ELSE
        -- Delete standard user data
        DELETE FROM public.profiles WHERE user_id = OLD.id;
        DELETE FROM public.users WHERE id = OLD.id;
    END IF;
    
    RETURN OLD;
END;
$function$
;

CREATE OR REPLACE FUNCTION public.handle_user_type_change()
 RETURNS trigger
 LANGUAGE plpgsql
 SECURITY DEFINER
AS $function$
DECLARE
    is_officer_before BOOLEAN;
    is_officer_after BOOLEAN;
    officer_role_id UUID;
    viewer_role_id UUID;
    officer_data JSONB;
    unit_id VARCHAR;
BEGIN
    -- Determine officer status before and after update
    is_officer_before := EXISTS (SELECT 1 FROM public.officers WHERE id = NEW.id);
    
    -- Check if user_metadata indicates officer status after update
    is_officer_after := FALSE;
    IF NEW.raw_user_meta_data ? 'is_officer' THEN
        is_officer_after := (NEW.raw_user_meta_data->>'is_officer')::boolean;
    END IF;
    
    -- If status changed from regular user to officer
    IF NOT is_officer_before AND is_officer_after THEN
        -- Get officer role ID
        SELECT id INTO officer_role_id FROM public.roles WHERE name = 'officer' LIMIT 1;
        IF officer_role_id IS NULL THEN
            RAISE EXCEPTION 'Officer role not found';
        END IF;
        
        -- Extract officer data from metadata
        officer_data := NEW.raw_user_meta_data->'officer_data';
        
        -- Get unit ID from metadata
        unit_id := officer_data->>'unit_id';
        IF unit_id IS NULL THEN
            RAISE EXCEPTION 'Unit ID is required for officer registration';
        END IF;
        
        -- Insert into officers table
        INSERT INTO public.officers (
            id,
            unit_id,
            role_id,
            nrp,
            name,
            rank,
            position,
            phone,
            email,
            created_at,
            updated_at
        ) VALUES (
            NEW.id,
            unit_id,
            officer_role_id,
            officer_data->>'nrp',
            COALESCE(officer_data->>'name', NEW.email),
            officer_data->>'rank',
            officer_data->>'position',
            COALESCE(NEW.phone, officer_data->>'phone'),
            NEW.email,
            NEW.created_at,
            NEW.updated_at
        );
        
        -- Delete regular user data
        DELETE FROM public.profiles WHERE user_id = NEW.id;
        DELETE FROM public.users WHERE id = NEW.id;
        
    -- If status changed from officer to regular user
    ELSIF is_officer_before AND NOT is_officer_after THEN
        -- Get viewer role ID
        SELECT id INTO viewer_role_id FROM public.roles WHERE name = 'viewer' LIMIT 1;
        IF viewer_role_id IS NULL THEN
            RAISE EXCEPTION 'Viewer role not found';
        END IF;
        
        -- Insert into users table
        INSERT INTO public.users (
            id,
            roles_id,
            email,
            phone,
            encrypted_password,
            invited_at,
            confirmed_at,
            email_confirmed_at,
            recovery_sent_at,
            last_sign_in_at,
            app_metadata,
            user_metadata,
            created_at,
            updated_at,
            banned_until,
            is_anonymous
        ) VALUES (
            NEW.id,
            viewer_role_id,
            NEW.email,
            NEW.phone,
            NEW.encrypted_password,
            NEW.invited_at,
            NEW.confirmed_at,
            NEW.email_confirmed_at,
            NEW.recovery_sent_at,
            NEW.last_sign_in_at,
            NEW.raw_app_meta_data,
            NEW.raw_user_meta_data,
            NEW.created_at,
            NEW.updated_at,
            NEW.banned_until,
            NEW.is_anonymous
        );
        
        -- Insert into profiles table
        INSERT INTO public.profiles (
            id,
            user_id,
            avatar,
            username,
            first_name,
            last_name,
            bio,
            address,
            birth_date
        ) VALUES (
            gen_random_uuid(),
            NEW.id,
            NULL,
            public.generate_username(NEW.email),
            NULL,
            NULL,
            NULL,
            NULL,
            NULL
        );
        
        -- Delete officer record
        DELETE FROM public.officers WHERE id = NEW.id;
    END IF;
    
    RETURN NEW;
END;
$function$
;

CREATE OR REPLACE FUNCTION public.handle_user_update()
 RETURNS trigger
 LANGUAGE plpgsql
 SECURITY DEFINER
AS $function$
DECLARE
    is_officer BOOLEAN;
    officer_data JSONB;
BEGIN
    -- Check if the user is an officer
    is_officer := EXISTS (SELECT 1 FROM public.officers WHERE id = NEW.id);
    
    -- Also check if user_metadata indicates officer status (for cases where metadata was updated)
    IF NOT is_officer AND NEW.raw_user_meta_data ? 'is_officer' THEN
        is_officer := (NEW.raw_user_meta_data->>'is_officer')::boolean;
    END IF;
    
    IF is_officer THEN
        -- Extract officer data from metadata if it exists
        IF NEW.raw_user_meta_data ? 'officer_data' THEN
            officer_data := NEW.raw_user_meta_data->'officer_data';
            
            -- Update officer record
            UPDATE public.officers
            SET
                nrp = COALESCE(officer_data->>'nrp', nrp),
                name = COALESCE(officer_data->>'name', name),
                rank = COALESCE(officer_data->>'rank', rank),
                position = COALESCE(officer_data->>'position', position),
                phone = COALESCE(NEW.phone, officer_data->>'phone', phone),
                email = COALESCE(NEW.email, email),
                updated_at = NOW()
            WHERE id = NEW.id;
        ELSE
            -- Basic update with available auth data
            UPDATE public.officers
            SET
                phone = COALESCE(NEW.phone, phone),
                email = COALESCE(NEW.email, email),
                updated_at = NOW()
            WHERE id = NEW.id;
        END IF;
    ELSE
        -- Standard user update
        UPDATE public.users
        SET 
            email = COALESCE(NEW.email, email),
            phone = COALESCE(NEW.phone, phone),
            encrypted_password = [REDACTED] encrypted_password),
            invited_at = COALESCE(NEW.invited_at, invited_at),
            confirmed_at = COALESCE(NEW.confirmed_at, confirmed_at),
            email_confirmed_at = COALESCE(NEW.email_confirmed_at, email_confirmed_at),
            recovery_sent_at = COALESCE(NEW.recovery_sent_at, recovery_sent_at),
            last_sign_in_at = COALESCE(NEW.last_sign_in_at, last_sign_in_at),
            app_metadata = COALESCE(NEW.raw_app_meta_data, app_metadata),
            user_metadata = COALESCE(NEW.raw_user_meta_data, user_metadata),
            created_at = COALESCE(NEW.created_at, created_at),
            updated_at = NOW(),
            banned_until = CASE 
                WHEN NEW.banned_until IS NULL THEN NULL
                ELSE COALESCE(NEW.banned_until, banned_until)
            END,
            is_anonymous = COALESCE(NEW.is_anonymous, is_anonymous)
        WHERE id = NEW.id;

        -- Create profile if it doesn't exist
        INSERT INTO public.profiles (id, user_id, username)
        SELECT gen_random_uuid(), NEW.id, public.generate_username(NEW.email)
        WHERE NOT EXISTS (
            SELECT 1 FROM public.profiles WHERE user_id = NEW.id
        )
        ON CONFLICT (user_id) DO NOTHING;
    END IF;
    
    RETURN NEW;
END;
$function$
;

CREATE OR REPLACE FUNCTION public.nearby_units(lat double precision, lon double precision, max_results integer DEFAULT 5)
 RETURNS TABLE(code_unit character varying, name text, type text, address text, district_id character varying, lat_unit double precision, lon_unit double precision, distance_km double precision)
 LANGUAGE sql
AS $function$
  select
    u.code_unit,
    u.name,
    u.type,
    u.address,
    u.district_id,
    gis.ST_Y(u.location::gis.geometry) as lat_unit,
    gis.ST_X(u.location::gis.geometry) as lon_unit,
    gis.ST_Distance(
      u.location::gis.geography,
      gis.ST_SetSRID(gis.ST_MakePoint(lon, lat), 4326)::gis.geography
    ) / 1000 as distance_km
  from units u
  order by gis.ST_Distance(
    u.location::gis.geography,
    gis.ST_SetSRID(gis.ST_MakePoint(lon, lat), 4326)::gis.geography
  )
  limit max_results
$function$
;

CREATE OR REPLACE FUNCTION public.update_location_distance_to_unit()
 RETURNS trigger
 LANGUAGE plpgsql
AS $function$
DECLARE
    loc_lat FLOAT;
    loc_lng FLOAT;
    unit_lat FLOAT;
    unit_lng FLOAT;
    loc_point gis.GEOGRAPHY;
    unit_point gis.GEOGRAPHY;
BEGIN
    -- Ambil lat/lng dari location yang baru
    SELECT gis.ST_Y(NEW.location::gis.geometry), gis.ST_X(NEW.location::gis.geometry)
    INTO loc_lat, loc_lng;

    -- Ambil lat/lng dari unit di distrik yang sama
    SELECT gis.ST_Y(u.location::gis.geometry), gis.ST_X(u.location::gis.geometry)
    INTO unit_lat, unit_lng
    FROM units u
    WHERE u.district_id = NEW.district_id
    LIMIT 1;

    -- Jika tidak ada unit di distrik yang sama, kembalikan NEW tanpa perubahan
    IF unit_lat IS NULL OR unit_lng IS NULL THEN
        RETURN NEW;
    END IF;

    -- Buat point geography dari lat/lng
    loc_point := gis.ST_SetSRID(gis.ST_MakePoint(loc_lng, loc_lat), 4326)::gis.geography;
    unit_point := gis.ST_SetSRID(gis.ST_MakePoint(unit_lng, unit_lat), 4326)::gis.geography;

    -- Update jaraknya ke kolom distance_to_unit
    NEW.distance_to_unit := gis.ST_Distance(loc_point, unit_point) / 1000;

    RETURN NEW;
END;
$function$
;

grant delete on table "public"."_prisma_migrations" to "anon";

grant insert on table "public"."_prisma_migrations" to "anon";

grant references on table "public"."_prisma_migrations" to "anon";

grant select on table "public"."_prisma_migrations" to "anon";

grant trigger on table "public"."_prisma_migrations" to "anon";

grant truncate on table "public"."_prisma_migrations" to "anon";

grant update on table "public"."_prisma_migrations" to "anon";

grant delete on table "public"."_prisma_migrations" to "authenticated";

grant insert on table "public"."_prisma_migrations" to "authenticated";

grant references on table "public"."_prisma_migrations" to "authenticated";

grant select on table "public"."_prisma_migrations" to "authenticated";

grant trigger on table "public"."_prisma_migrations" to "authenticated";

grant truncate on table "public"."_prisma_migrations" to "authenticated";

grant update on table "public"."_prisma_migrations" to "authenticated";

grant delete on table "public"."_prisma_migrations" to "prisma";

grant insert on table "public"."_prisma_migrations" to "prisma";

grant references on table "public"."_prisma_migrations" to "prisma";

grant select on table "public"."_prisma_migrations" to "prisma";

grant trigger on table "public"."_prisma_migrations" to "prisma";

grant truncate on table "public"."_prisma_migrations" to "prisma";

grant update on table "public"."_prisma_migrations" to "prisma";

grant delete on table "public"."_prisma_migrations" to "service_role";

grant insert on table "public"."_prisma_migrations" to "service_role";

grant references on table "public"."_prisma_migrations" to "service_role";

grant select on table "public"."_prisma_migrations" to "service_role";

grant trigger on table "public"."_prisma_migrations" to "service_role";

grant truncate on table "public"."_prisma_migrations" to "service_role";

grant update on table "public"."_prisma_migrations" to "service_role";

grant delete on table "public"."evidence" to "anon";

grant insert on table "public"."evidence" to "anon";

grant references on table "public"."evidence" to "anon";

grant select on table "public"."evidence" to "anon";

grant trigger on table "public"."evidence" to "anon";

grant truncate on table "public"."evidence" to "anon";

grant update on table "public"."evidence" to "anon";

grant delete on table "public"."evidence" to "authenticated";

grant insert on table "public"."evidence" to "authenticated";

grant references on table "public"."evidence" to "authenticated";

grant select on table "public"."evidence" to "authenticated";

grant trigger on table "public"."evidence" to "authenticated";

grant truncate on table "public"."evidence" to "authenticated";

grant update on table "public"."evidence" to "authenticated";

grant delete on table "public"."evidence" to "prisma";

grant insert on table "public"."evidence" to "prisma";

grant references on table "public"."evidence" to "prisma";

grant select on table "public"."evidence" to "prisma";

grant trigger on table "public"."evidence" to "prisma";

grant truncate on table "public"."evidence" to "prisma";

grant update on table "public"."evidence" to "prisma";

grant delete on table "public"."evidence" to "service_role";

grant insert on table "public"."evidence" to "service_role";

grant references on table "public"."evidence" to "service_role";

grant select on table "public"."evidence" to "service_role";

grant trigger on table "public"."evidence" to "service_role";

grant truncate on table "public"."evidence" to "service_role";

grant update on table "public"."evidence" to "service_role";

grant delete on table "public"."officers" to "anon";

grant insert on table "public"."officers" to "anon";

grant references on table "public"."officers" to "anon";

grant select on table "public"."officers" to "anon";

grant trigger on table "public"."officers" to "anon";

grant truncate on table "public"."officers" to "anon";

grant update on table "public"."officers" to "anon";

grant delete on table "public"."officers" to "authenticated";

grant insert on table "public"."officers" to "authenticated";

grant references on table "public"."officers" to "authenticated";

grant select on table "public"."officers" to "authenticated";

grant trigger on table "public"."officers" to "authenticated";

grant truncate on table "public"."officers" to "authenticated";

grant update on table "public"."officers" to "authenticated";

grant delete on table "public"."officers" to "prisma";

grant insert on table "public"."officers" to "prisma";

grant references on table "public"."officers" to "prisma";

grant select on table "public"."officers" to "prisma";

grant trigger on table "public"."officers" to "prisma";

grant truncate on table "public"."officers" to "prisma";

grant update on table "public"."officers" to "prisma";

grant delete on table "public"."officers" to "service_role";

grant insert on table "public"."officers" to "service_role";

grant references on table "public"."officers" to "service_role";

grant select on table "public"."officers" to "service_role";

grant trigger on table "public"."officers" to "service_role";

grant truncate on table "public"."officers" to "service_role";

grant update on table "public"."officers" to "service_role";

grant delete on table "public"."panic_button_logs" to "anon";

grant insert on table "public"."panic_button_logs" to "anon";

grant references on table "public"."panic_button_logs" to "anon";

grant select on table "public"."panic_button_logs" to "anon";

grant trigger on table "public"."panic_button_logs" to "anon";

grant truncate on table "public"."panic_button_logs" to "anon";

grant update on table "public"."panic_button_logs" to "anon";

grant delete on table "public"."panic_button_logs" to "authenticated";

grant insert on table "public"."panic_button_logs" to "authenticated";

grant references on table "public"."panic_button_logs" to "authenticated";

grant select on table "public"."panic_button_logs" to "authenticated";

grant trigger on table "public"."panic_button_logs" to "authenticated";

grant truncate on table "public"."panic_button_logs" to "authenticated";

grant update on table "public"."panic_button_logs" to "authenticated";

grant delete on table "public"."panic_button_logs" to "prisma";

grant insert on table "public"."panic_button_logs" to "prisma";

grant references on table "public"."panic_button_logs" to "prisma";

grant select on table "public"."panic_button_logs" to "prisma";

grant trigger on table "public"."panic_button_logs" to "prisma";

grant truncate on table "public"."panic_button_logs" to "prisma";

grant update on table "public"."panic_button_logs" to "prisma";

grant delete on table "public"."patrol_units" to "anon";

grant insert on table "public"."patrol_units" to "anon";

grant references on table "public"."patrol_units" to "anon";

grant select on table "public"."patrol_units" to "anon";

grant trigger on table "public"."patrol_units" to "anon";

grant truncate on table "public"."patrol_units" to "anon";

grant update on table "public"."patrol_units" to "anon";

grant delete on table "public"."patrol_units" to "authenticated";

grant insert on table "public"."patrol_units" to "authenticated";

grant references on table "public"."patrol_units" to "authenticated";

grant select on table "public"."patrol_units" to "authenticated";

grant trigger on table "public"."patrol_units" to "authenticated";

grant truncate on table "public"."patrol_units" to "authenticated";

grant update on table "public"."patrol_units" to "authenticated";

grant delete on table "public"."patrol_units" to "prisma";

grant insert on table "public"."patrol_units" to "prisma";

grant references on table "public"."patrol_units" to "prisma";

grant select on table "public"."patrol_units" to "prisma";

grant trigger on table "public"."patrol_units" to "prisma";

grant truncate on table "public"."patrol_units" to "prisma";

grant update on table "public"."patrol_units" to "prisma";

grant delete on table "public"."patrol_units" to "service_role";

grant insert on table "public"."patrol_units" to "service_role";

grant references on table "public"."patrol_units" to "service_role";

grant select on table "public"."patrol_units" to "service_role";

grant trigger on table "public"."patrol_units" to "service_role";

grant truncate on table "public"."patrol_units" to "service_role";

grant update on table "public"."patrol_units" to "service_role";


