-- ============================================================================
-- PERSON / CLIENT DOMAIN
-- ============================================================================
-- Created: 2026-07-20
-- Source: docs/datamodel/deelmodellen/model-aanmelding.md §2.1-2.9, §2.14, §2.16
--   (PERSOON, ADRES, CONTACTGEGEVEN, CLIENT, CLIENTRELATIE, VERWIJZER,
--    PRAKTIJK_INSTELLING, CLIENT_HUISARTS, VERZEKERING, TOESTEMMING,
--    CLIENTPORTAAL_ACCOUNT) — the AANMELDING/VERWIJZING/ZORGEPISODE part of
--    that document is superseded by the referral/intake domains that follow
--    this migration, not translated here.
--
-- Naming: English identifiers per besluit B20. Dutch field names in the
-- source document are translated 1:1 in meaning; nothing renamed to a
-- different concept.
--
-- Privacy: raw BSN is never stored, only a SHA-256 hash (bsn_hash) — this
-- follows the platform-wide privacy rule (root CLAUDE.md), which is a
-- stricter requirement than the source document's "versleuteld (pgcrypto)"
-- wording. Reversible BSN storage for declaratie/Vecozo purposes, if ever
-- needed, is a separate, explicitly-scoped decision — not implemented here.
-- ============================================================================

CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- ============================================================================
-- person
-- ============================================================================
CREATE TABLE person (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  family_name       TEXT NOT NULL,
  name_prefix       TEXT,
  given_names       TEXT,
  initials          TEXT,
  preferred_name    TEXT,
  birth_date        DATE NOT NULL,
  gender            TEXT NOT NULL,           -- waardelijst geslacht
  gender_identity   TEXT,                    -- waardelijst genderidentiteit
  bsn_hash          TEXT,                    -- SHA-256, optioneel op PERSOON (rolgebonden eis via nudge)
  deceased          BOOLEAN NOT NULL DEFAULT false,
  deceased_at       DATE,

  created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at        TIMESTAMPTZ,

  CONSTRAINT person_family_name_not_empty CHECK (length(trim(family_name)) > 0),
  CONSTRAINT person_deceased_at_requires_flag CHECK (deceased_at IS NULL OR deceased)
);

CREATE UNIQUE INDEX idx_person_bsn_hash ON person(bsn_hash) WHERE bsn_hash IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_person_name ON person(family_name, given_names) WHERE deleted_at IS NULL;

COMMENT ON TABLE person IS 'Natuurlijke persoon, onafhankelijk van rollen (cliënt, contactpersoon, vertegenwoordiger, medewerker). model-aanmelding.md §2.1.';
COMMENT ON COLUMN person.bsn_hash IS 'SHA-256 hash van het BSN; geen raw BSN opgeslagen (platform-privacyregel). Naam+geboortedatum-duplicaten zijn een nudge, geen constraint.';

ALTER TABLE person ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON person FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_person_updated_at BEFORE UPDATE ON person FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- address
-- ============================================================================
CREATE TABLE address (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  person_id     UUID NOT NULL REFERENCES person(id) ON DELETE CASCADE,
  address_type  TEXT NOT NULL,               -- waardelijst adrestype
  street        TEXT NOT NULL,
  house_number  TEXT NOT NULL,
  house_number_addition TEXT,
  postal_code   TEXT NOT NULL,
  city          TEXT NOT NULL,
  country       TEXT NOT NULL DEFAULT 'NL',
  valid_from    DATE,
  valid_to      DATE,

  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at    TIMESTAMPTZ
);

-- max één actueel (valid_to leeg) adres per persoon per adrestype
CREATE UNIQUE INDEX idx_address_current_per_type ON address(person_id, address_type)
  WHERE valid_to IS NULL AND deleted_at IS NULL;
CREATE INDEX idx_address_person ON address(person_id) WHERE deleted_at IS NULL;

COMMENT ON TABLE address IS 'model-aanmelding.md §2.2 — 0..n adressen per persoon (zib Patient).';

ALTER TABLE address ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON address FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_address_updated_at BEFORE UPDATE ON address FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- contact_detail
-- ============================================================================
CREATE TABLE contact_detail (
  id               UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  person_id        UUID NOT NULL REFERENCES person(id) ON DELETE CASCADE,
  contact_type     TEXT NOT NULL,            -- waardelijst contactgegeven_type (telefoon/e-mail)
  contact_subtype  TEXT,                     -- waardelijst contactgegeven_soort
  value            TEXT NOT NULL,
  is_preferred     BOOLEAN NOT NULL DEFAULT false,

  created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at       TIMESTAMPTZ
);

-- max één voorkeursgegeven per persoon per contacttype
CREATE UNIQUE INDEX idx_contact_detail_preferred ON contact_detail(person_id, contact_type)
  WHERE is_preferred AND deleted_at IS NULL;
CREATE INDEX idx_contact_detail_person ON contact_detail(person_id) WHERE deleted_at IS NULL;

COMMENT ON TABLE contact_detail IS 'model-aanmelding.md §2.3 — telefoonnummers en e-mailadressen per persoon.';

ALTER TABLE contact_detail ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON contact_detail FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_contact_detail_updated_at BEFORE UPDATE ON contact_detail FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- client (rol op person)
-- ============================================================================
CREATE TABLE client (
  id                 UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  person_id          UUID NOT NULL REFERENCES person(id) ON DELETE RESTRICT,
  client_number      BIGINT NOT NULL,
  client_since       DATE NOT NULL,
  gp_situation       TEXT NOT NULL DEFAULT 'onbekend', -- waardelijst huisarts_situatie

  created_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at         TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at         TIMESTAMPTZ
);

-- max één CLIENT-rol per PERSOON
CREATE UNIQUE INDEX idx_client_person ON client(person_id) WHERE deleted_at IS NULL;
CREATE UNIQUE INDEX idx_client_number ON client(client_number) WHERE deleted_at IS NULL;

COMMENT ON TABLE client IS 'model-aanmelding.md §2.4 — instellingsgebonden rol op PERSOON. Nudge (geen constraint): cliënt zonder bsn_hash (Wabvpz).';

ALTER TABLE client ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON client FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_client_updated_at BEFORE UPDATE ON client FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- client_relation (contactpersoon / wettelijk vertegenwoordiger)
-- ============================================================================
CREATE TABLE client_relation (
  id                        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  client_id                 UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
  person_id                 UUID NOT NULL REFERENCES person(id) ON DELETE RESTRICT,
  relation_type              TEXT,            -- waardelijst relatie (partner, ouder, kind, ...)
  relation_role              TEXT NOT NULL,   -- waardelijst relatierol
  representation_basis       TEXT,            -- waardelijst vertegenwoordigingsgrond (alleen bij rol wettelijk_vertegenwoordiger)
  valid_from                 DATE NOT NULL,
  valid_to                   DATE,
  notes                      TEXT,

  created_at                 TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at                 TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at                 TIMESTAMPTZ
);

-- max één actuele rij per (client, persoon, rol)
CREATE UNIQUE INDEX idx_client_relation_current ON client_relation(client_id, person_id, relation_role)
  WHERE valid_to IS NULL AND deleted_at IS NULL;
CREATE INDEX idx_client_relation_client ON client_relation(client_id) WHERE deleted_at IS NULL;

COMMENT ON TABLE client_relation IS 'model-aanmelding.md §2.5 — contactpersonen/vertegenwoordigers, twee assen relatie x rol (zib Contactpersoon). Leeftijdsafhankelijke rechten (12/16 WGBO) zijn autorisatie, geen schema.';

ALTER TABLE client_relation ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON client_relation FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_client_relation_updated_at BEFORE UPDATE ON client_relation FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- Zelfrelatie (persoon = cliënt-persoon) niet toegestaan. CHECK-constraints
-- kunnen geen subquery bevatten in Postgres, dus dit is een trigger i.p.v.
-- een CHECK — functioneel identiek aan wat model-aanmelding.md §2.5 vraagt.
CREATE OR REPLACE FUNCTION prevent_client_relation_self_reference()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.person_id = (SELECT person_id FROM client WHERE client.id = NEW.client_id) THEN
    RAISE EXCEPTION 'client_relation: person_id mag niet gelijk zijn aan de cliënt-persoon zelf';
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER client_relation_no_self_reference
  BEFORE INSERT OR UPDATE ON client_relation
  FOR EACH ROW EXECUTE FUNCTION prevent_client_relation_self_reference();

-- ============================================================================
-- practice_organization (moet vóór referrer bestaan i.v.m. FK)
-- ============================================================================
CREATE TABLE practice_organization (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name          TEXT NOT NULL,
  practice_type TEXT,                        -- waardelijst praktijk_soort
  agb_code      TEXT,
  address_text  TEXT,
  phone         TEXT,

  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at    TIMESTAMPTZ
);

CREATE UNIQUE INDEX idx_practice_organization_agb ON practice_organization(agb_code) WHERE agb_code IS NOT NULL AND deleted_at IS NULL;

COMMENT ON TABLE practice_organization IS 'model-aanmelding.md §2.7 — organisatie waaraan verwijzers/huisartsen verbonden zijn.';

ALTER TABLE practice_organization ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON practice_organization FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_practice_organization_updated_at BEFORE UPDATE ON practice_organization FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- referrer
-- ============================================================================
CREATE TABLE referrer (
  id                      UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name                    TEXT NOT NULL,
  referrer_type           TEXT NOT NULL,     -- waardelijst verwijzertype (extra.agb_verplicht bepaalt nudge)
  agb_code                TEXT,
  practice_organization_id UUID REFERENCES practice_organization(id) ON DELETE SET NULL,
  phone                   TEXT,
  email                   TEXT,

  created_at              TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at              TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at              TIMESTAMPTZ
);

CREATE UNIQUE INDEX idx_referrer_agb ON referrer(agb_code) WHERE agb_code IS NOT NULL AND deleted_at IS NULL;
CREATE INDEX idx_referrer_practice ON referrer(practice_organization_id) WHERE deleted_at IS NULL;

COMMENT ON TABLE referrer IS 'model-aanmelding.md §2.6 — persoon/functionaris die verwijst, los van practice_organization. Kan zonder praktijk bestaan (gemeente-ambtenaar). Ook referent voor de vaste huisarts.';

ALTER TABLE referrer ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON referrer FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_referrer_updated_at BEFORE UPDATE ON referrer FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- client_general_practitioner (vaste huisarts)
-- ============================================================================
CREATE TABLE client_general_practitioner (
  id                        UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  client_id                 UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
  referrer_id                UUID REFERENCES referrer(id) ON DELETE SET NULL,
  practice_organization_id   UUID REFERENCES practice_organization(id) ON DELETE SET NULL,
  valid_from                 DATE NOT NULL,
  valid_to                   DATE,

  created_at                 TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at                 TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at                 TIMESTAMPTZ,

  CONSTRAINT client_gp_at_least_one_ref CHECK (referrer_id IS NOT NULL OR practice_organization_id IS NOT NULL)
);

-- max één actuele registratie per cliënt
CREATE UNIQUE INDEX idx_client_gp_current ON client_general_practitioner(client_id)
  WHERE valid_to IS NULL AND deleted_at IS NULL;

COMMENT ON TABLE client_general_practitioner IS 'model-aanmelding.md §2.8 — vaste huisarts, los van de incidentele verwijzer. "Geen huisarts"/"geen toestemming" staat op client.gp_situation, niet hier.';

ALTER TABLE client_general_practitioner ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON client_general_practitioner FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_client_gp_updated_at BEFORE UPDATE ON client_general_practitioner FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- insurance
-- ============================================================================
CREATE TABLE insurance (
  id                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  client_id             UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
  uzovi_code            TEXT NOT NULL,
  insurer_name          TEXT,
  policy_number         TEXT,
  valid_from            DATE NOT NULL,
  valid_to              DATE,
  cov_checked_at        DATE,

  created_at            TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at            TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at            TIMESTAMPTZ
);

-- max één actuele verzekering per cliënt
CREATE UNIQUE INDEX idx_insurance_current ON insurance(client_id) WHERE valid_to IS NULL AND deleted_at IS NULL;

COMMENT ON TABLE insurance IS 'model-aanmelding.md §2.9 — Zvw-verzekeringsgegevens. Nudge: Zvw-aanmelding zonder actuele COV-controle. Wlz/forensisch: declaratie-ronde.';

ALTER TABLE insurance ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON insurance FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_insurance_updated_at BEFORE UPDATE ON insurance FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- consent
-- ============================================================================
-- referral_case_id/clinical_care_episode_id FKs are added by later migrations
-- (create_referral_domain, create_intake_treatment_advice_domain) once those
-- tables exist — see ALTER TABLE at the bottom of create_referral_domain.
CREATE TABLE consent (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  client_id         UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,
  consent_type      TEXT NOT NULL,           -- waardelijst toestemming_type (extra.grondslag)
  status            TEXT NOT NULL,           -- waardelijst toestemming_status
  consent_date      DATE NOT NULL,
  method            TEXT,                    -- waardelijst toestemming_wijze
  recorded_by       UUID REFERENCES practitioners(id),
  valid_to          DATE,
  revoked_at        DATE,
  scope_referral_case_id UUID,               -- optioneel; default cliëntbreed
  notes             TEXT,

  created_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at        TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at        TIMESTAMPTZ
);

-- max één actuele (niet-ingetrokken) rij per (client, type, scope)
CREATE UNIQUE INDEX idx_consent_current ON consent(client_id, consent_type, COALESCE(scope_referral_case_id, '00000000-0000-0000-0000-000000000000'))
  WHERE status <> 'ingetrokken' AND deleted_at IS NULL;
CREATE INDEX idx_consent_client ON consent(client_id) WHERE deleted_at IS NULL;

COMMENT ON TABLE consent IS 'model-aanmelding.md §2.14 — generieke WGBO/AVG-toestemming. Cliëntakkoord op het behandelplan is een apart feit bij het behandelplan, geen consent-rij. scope_referral_case_id FK volgt in create_referral_domain.sql.';

ALTER TABLE consent ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON consent FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_consent_updated_at BEFORE UPDATE ON consent FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();

-- ============================================================================
-- client_portal_account
-- ============================================================================
CREATE TABLE client_portal_account (
  id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  client_id   UUID NOT NULL REFERENCES client(id) ON DELETE CASCADE,

  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  deleted_at  TIMESTAMPTZ
);

CREATE UNIQUE INDEX idx_client_portal_account_client ON client_portal_account(client_id) WHERE deleted_at IS NULL;

COMMENT ON TABLE client_portal_account IS 'model-aanmelding.md §2.16 — relatie-placeholder (Wabvpz elektronische inzage). Authenticatiedetails: auth/ADM-ronde.';

ALTER TABLE client_portal_account ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Enable all for authenticated users" ON client_portal_account FOR ALL USING (auth.role() = 'authenticated');
CREATE TRIGGER update_client_portal_account_updated_at BEFORE UPDATE ON client_portal_account FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
