-- migrate:no-foreign-keys
--
-- §22 three-tier refactor — S1. Insert a *collection* grain beneath project.
--
--   (1) a `collections` table beneath `projects`;
--   (2) move the per-corpus fields (type, initial_state) down from `projects`
--       (projects keeps id, name, content_repo, visibility, config_json, …);
--   (3) one default collection per project (id='default' for the standard
--       single-project deployment, subfolder = repo root), inheriting the
--       project's type / initial_state / visibility;
--   (4) re-key the 13 entry-corpus tables (project_id, slug) -> (collection_id,
--       slug) via the migration-028 rebuild pattern, mapping each row to its
--       project's default collection by JOIN;
--   (5) generalise project_members -> memberships(scope_type ∈ {project,
--       collection}, scope_id, …), collapsing the role enum to {owner,
--       contributor} (§B.3).
--
-- SQLite can't ALTER a PK/UNIQUE in place, so each keyed table is rebuilt by the
-- official create-copy-drop-rename procedure. FK enforcement is OFF for the file
-- (the `migrate:no-foreign-keys` marker tells the runner to toggle it and run
-- foreign_key_check after). cached_rfcs is rebuilt FIRST so the child tables can
-- re-point their composite FK at its new (collection_id, slug) key.
--
-- The tables 026 tagged with project_id but 028 did NOT key (threads, changes,
-- notifications, actions, pr_resolution_branches, cached_prs) keep project_id —
-- they carry a project-grain tag, untouched in S1. See
-- docs/design/2026-06-05-three-tier-projects-collections.md §A.6 / Part E.

-- ── §22.13 repair: re-stamp stale satellite project_id before rekeying ──────
-- The §22.13 default→ohm re-stamp (v0.39.0, `projects.restamp_default_project`)
-- updated `cached_rfcs.project_id` but NOT the entry-satellite tables, leaving
-- rows with a stale `project_id` (e.g. 'default') that the per-project collection
-- backfill below cannot map — the subquery returns NULL and the NOT NULL rebuild
-- fails (`cached_branches__new.collection_id`). Before rebuilding, re-derive each
-- satellite's `project_id` from its entry (`cached_rfcs`, joined by slug — slugs
-- are unique per collection and, pre-rebuild, globally), and drop rows whose
-- entry no longer exists (stale cache; the `cached_*` tables are rebuildable from
-- gitea). On a clean/fresh deployment every satellite is empty or already
-- consistent, so this whole block is a no-op. (Discovered on the OHM data:
-- ~1.3k `cached_branches` rows stranded at project_id='default'.)
-- First drop stale rows that DUPLICATE an already-correctly-stamped row (the same
-- branch cached under both the stale and the real project_id) — re-stamping them
-- would collide on the (project_id, rfc_slug, branch_name) key. The correctly-
-- stamped copy is kept (it carries the current head_sha / visibility). Only the
-- branch-keyed tables can hold such a pair; the others key on (rfc_slug,user_id)
-- /(scope,pr_number) and have no stale data here, so they need no dedup.
DELETE FROM cached_branches WHERE project_id NOT IN (SELECT id FROM projects)
  AND EXISTS (SELECT 1 FROM cached_branches o WHERE o.rfc_slug = cached_branches.rfc_slug AND o.branch_name = cached_branches.branch_name AND o.project_id IN (SELECT id FROM projects));
DELETE FROM branch_visibility WHERE project_id NOT IN (SELECT id FROM projects)
  AND EXISTS (SELECT 1 FROM branch_visibility o WHERE o.rfc_slug = branch_visibility.rfc_slug AND o.branch_name = branch_visibility.branch_name AND o.project_id IN (SELECT id FROM projects));
UPDATE rfc_invitations         SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = rfc_invitations.rfc_slug)         WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM rfc_invitations          WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE cached_branches         SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = cached_branches.rfc_slug)         WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM cached_branches          WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE branch_visibility       SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = branch_visibility.rfc_slug)       WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM branch_visibility        WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE branch_contribute_grants SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = branch_contribute_grants.rfc_slug) WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM branch_contribute_grants WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE stars                   SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = stars.rfc_slug)                   WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM stars                    WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE watches                 SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = watches.rfc_slug)                 WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM watches                  WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE pr_seen                 SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = pr_seen.rfc_slug)                 WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM pr_seen                  WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE branch_chat_seen        SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = branch_chat_seen.rfc_slug)        WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM branch_chat_seen         WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE funder_consents         SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = funder_consents.rfc_slug)         WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM funder_consents          WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE rfc_collaborators       SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = rfc_collaborators.rfc_slug)       WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM rfc_collaborators        WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE contribution_requests   SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = contribution_requests.rfc_slug)   WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM contribution_requests    WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);
UPDATE proposed_use_cases      SET project_id = (SELECT r.project_id FROM cached_rfcs r WHERE r.slug = proposed_use_cases.rfc_slug)      WHERE rfc_slug IN (SELECT slug FROM cached_rfcs);
DELETE FROM proposed_use_cases       WHERE rfc_slug NOT IN (SELECT slug FROM cached_rfcs);

-- ── collections: the new typed-corpus grain beneath projects ───────────────
CREATE TABLE collections (
    id            TEXT NOT NULL,
    project_id    TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    type          TEXT NOT NULL DEFAULT 'document'
                      CHECK (type IN ('document', 'specification', 'bdd')),
    subfolder     TEXT NOT NULL DEFAULT '',
    initial_state TEXT NOT NULL DEFAULT 'super-draft'
                      CHECK (initial_state IN ('super-draft', 'active')),
    visibility    TEXT NOT NULL DEFAULT 'gated'
                      CHECK (visibility IN ('gated', 'public', 'unlisted')),
    name          TEXT,
    registry_sha  TEXT,
    created_at    TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at    TEXT NOT NULL DEFAULT (datetime('now')),
    PRIMARY KEY (id)
);
CREATE INDEX idx_collections_project ON collections(project_id);

-- One default collection per project. id='default' for the standard
-- single-project deployment (a stable literal across deploy histories); the
-- project_id is used as a unique fallback id only if a non-standard
-- multi-project deployment migrates (pre-S5; avoids a PK collision).
INSERT INTO collections (id, project_id, type, subfolder, initial_state, visibility, name)
SELECT
    CASE WHEN (SELECT COUNT(*) FROM projects) <= 1 THEN 'default' ELSE p.id END,
    p.id, p.type, '', p.initial_state, p.visibility, p.name
FROM projects p;

-- ── projects: rebuild to DROP the per-corpus fields (type, initial_state) ───
CREATE TABLE projects__new (
    id            TEXT PRIMARY KEY,
    name          TEXT NOT NULL,
    content_repo  TEXT,
    visibility    TEXT NOT NULL DEFAULT 'gated'
                      CHECK (visibility IN ('gated', 'public', 'unlisted')),
    config_json   TEXT,
    registry_sha  TEXT,
    created_at    TEXT NOT NULL DEFAULT (datetime('now')),
    updated_at    TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO projects__new (id, name, content_repo, visibility, config_json, registry_sha, created_at, updated_at)
SELECT id, name, content_repo, visibility, config_json, registry_sha, created_at, updated_at FROM projects;
DROP TABLE projects;
ALTER TABLE projects__new RENAME TO projects;

-- ── cached_rfcs: PRIMARY KEY (project_id, slug) -> (collection_id, slug) ────
CREATE TABLE cached_rfcs__new (
    slug                TEXT NOT NULL,
    title               TEXT NOT NULL,
    state               TEXT NOT NULL CHECK (state IN ('super-draft', 'active', 'withdrawn', 'retired')),
    rfc_id              TEXT,
    repo                TEXT,
    proposed_by         TEXT,
    proposed_at         TEXT,
    graduated_at        TEXT,
    graduated_by        TEXT,
    owners_json         TEXT NOT NULL DEFAULT '[]',
    arbiters_json       TEXT NOT NULL DEFAULT '[]',
    tags_json           TEXT NOT NULL DEFAULT '[]',
    body                TEXT,
    body_sha            TEXT,
    last_main_commit_at TEXT,
    last_entry_commit_at TEXT,
    updated_at          TEXT NOT NULL DEFAULT (datetime('now')),
    models_json         TEXT,
    funder_login        TEXT,
    proposed_use_case   TEXT,
    collection_id       TEXT NOT NULL DEFAULT 'default' REFERENCES collections(id),
    unreviewed          INTEGER NOT NULL DEFAULT 0,
    reviewed_at         TEXT,
    reviewed_by         TEXT,
    PRIMARY KEY (collection_id, slug)
);
INSERT INTO cached_rfcs__new
    (slug, title, state, rfc_id, repo, proposed_by, proposed_at, graduated_at,
     graduated_by, owners_json, arbiters_json, tags_json, body, body_sha,
     last_main_commit_at, last_entry_commit_at, updated_at, models_json,
     funder_login, proposed_use_case, collection_id, unreviewed, reviewed_at, reviewed_by)
SELECT
    r.slug, r.title, r.state, r.rfc_id, r.repo, r.proposed_by, r.proposed_at, r.graduated_at,
    r.graduated_by, r.owners_json, r.arbiters_json, r.tags_json, r.body, r.body_sha,
    r.last_main_commit_at, r.last_entry_commit_at, r.updated_at, r.models_json,
    r.funder_login, r.proposed_use_case,
    (SELECT c.id FROM collections c WHERE c.project_id = r.project_id LIMIT 1),
    r.unreviewed, r.reviewed_at, r.reviewed_by
FROM cached_rfcs r;
DROP TABLE cached_rfcs;
ALTER TABLE cached_rfcs__new RENAME TO cached_rfcs;
CREATE INDEX idx_cached_rfcs_state ON cached_rfcs (state);
CREATE INDEX idx_cached_rfcs_last_active ON cached_rfcs (
    COALESCE(last_main_commit_at, last_entry_commit_at) DESC
);
CREATE INDEX idx_cached_rfcs_collection ON cached_rfcs(collection_id);

-- ── rfc_invitations: single-col FK -> composite (collection_id, rfc_slug) ───
CREATE TABLE rfc_invitations__new (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    rfc_slug                TEXT NOT NULL,
    inviter_user_id         INTEGER REFERENCES users(id) ON DELETE SET NULL,
    invitee_email           TEXT NOT NULL,
    role_in_rfc             TEXT NOT NULL CHECK (role_in_rfc IN ('contributor', 'discussant')),
    status                  TEXT NOT NULL DEFAULT 'pending'
        CHECK (status IN ('pending', 'accepted', 'revoked', 'expired')),
    token                   TEXT NOT NULL,
    expires_at              TEXT NOT NULL,
    created_at              TEXT NOT NULL DEFAULT (datetime('now')),
    accepted_at             TEXT,
    accepted_by_user_id     INTEGER REFERENCES users(id) ON DELETE SET NULL,
    collection_id           TEXT NOT NULL DEFAULT 'default',
    FOREIGN KEY (collection_id, rfc_slug) REFERENCES cached_rfcs(collection_id, slug) ON DELETE CASCADE
);
INSERT INTO rfc_invitations__new
    (id, rfc_slug, inviter_user_id, invitee_email, role_in_rfc, status, token,
     expires_at, created_at, accepted_at, accepted_by_user_id, collection_id)
SELECT
    i.id, i.rfc_slug, i.inviter_user_id, i.invitee_email, i.role_in_rfc, i.status, i.token,
    i.expires_at, i.created_at, i.accepted_at, i.accepted_by_user_id,
    (SELECT c.id FROM collections c WHERE c.project_id = i.project_id LIMIT 1)
FROM rfc_invitations i;
DROP TABLE rfc_invitations;
ALTER TABLE rfc_invitations__new RENAME TO rfc_invitations;
CREATE UNIQUE INDEX idx_rfc_invitations_token ON rfc_invitations (token);
CREATE INDEX idx_rfc_invitations_rfc_status ON rfc_invitations (rfc_slug, status);
CREATE INDEX idx_rfc_invitations_email_status ON rfc_invitations (invitee_email, status);

-- ── cached_branches: UNIQUE (project_id, rfc_slug, branch_name) -> collection
CREATE TABLE cached_branches__new (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    rfc_slug        TEXT NOT NULL,
    branch_name     TEXT NOT NULL,
    head_sha        TEXT,
    state           TEXT NOT NULL DEFAULT 'open' CHECK (state IN ('open', 'closed', 'deleted')),
    pinned          INTEGER NOT NULL DEFAULT 0,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    last_commit_at  TEXT,
    closed_at       TEXT,
    collection_id   TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, rfc_slug, branch_name)
);
INSERT INTO cached_branches__new
    (id, rfc_slug, branch_name, head_sha, state, pinned, created_at, last_commit_at, closed_at, collection_id)
SELECT
    b.id, b.rfc_slug, b.branch_name, b.head_sha, b.state, b.pinned, b.created_at, b.last_commit_at, b.closed_at,
    (SELECT c.id FROM collections c WHERE c.project_id = b.project_id LIMIT 1)
FROM cached_branches b;
DROP TABLE cached_branches;
ALTER TABLE cached_branches__new RENAME TO cached_branches;
CREATE INDEX idx_cached_branches_rfc ON cached_branches (rfc_slug, state);

-- ── branch_visibility: UNIQUE (project_id, rfc_slug, branch_name) -> collection
CREATE TABLE branch_visibility__new (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    rfc_slug        TEXT NOT NULL,
    branch_name     TEXT NOT NULL,
    read_public     INTEGER NOT NULL DEFAULT 1,
    contribute_mode TEXT NOT NULL DEFAULT 'just-me' CHECK (contribute_mode IN ('just-me', 'specific', 'any-contributor')),
    collection_id   TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, rfc_slug, branch_name)
);
INSERT INTO branch_visibility__new
    (id, rfc_slug, branch_name, read_public, contribute_mode, collection_id)
SELECT
    v.id, v.rfc_slug, v.branch_name, v.read_public, v.contribute_mode,
    (SELECT c.id FROM collections c WHERE c.project_id = v.project_id LIMIT 1)
FROM branch_visibility v;
DROP TABLE branch_visibility;
ALTER TABLE branch_visibility__new RENAME TO branch_visibility;

-- ── branch_contribute_grants: UNIQUE (..., grantee) -> +collection_id ───────
CREATE TABLE branch_contribute_grants__new (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    rfc_slug        TEXT NOT NULL,
    branch_name     TEXT NOT NULL,
    grantee_user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    granted_by      INTEGER NOT NULL REFERENCES users(id) ON DELETE SET NULL,
    granted_at      TEXT NOT NULL DEFAULT (datetime('now')),
    collection_id   TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, rfc_slug, branch_name, grantee_user_id)
);
INSERT INTO branch_contribute_grants__new
    (id, rfc_slug, branch_name, grantee_user_id, granted_by, granted_at, collection_id)
SELECT
    g.id, g.rfc_slug, g.branch_name, g.grantee_user_id, g.granted_by, g.granted_at,
    (SELECT c.id FROM collections c WHERE c.project_id = g.project_id LIMIT 1)
FROM branch_contribute_grants g;
DROP TABLE branch_contribute_grants;
ALTER TABLE branch_contribute_grants__new RENAME TO branch_contribute_grants;
CREATE INDEX idx_grants_lookup ON branch_contribute_grants (rfc_slug, branch_name);
CREATE INDEX idx_grants_grantee ON branch_contribute_grants (grantee_user_id);

-- ── stars: UNIQUE (project_id, user_id, rfc_slug) -> collection_id ──────────
CREATE TABLE stars__new (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id     INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    rfc_slug    TEXT NOT NULL,
    starred_at  TEXT NOT NULL DEFAULT (datetime('now')),
    collection_id  TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, user_id, rfc_slug)
);
INSERT INTO stars__new (id, user_id, rfc_slug, starred_at, collection_id)
SELECT s.id, s.user_id, s.rfc_slug, s.starred_at,
    (SELECT c.id FROM collections c WHERE c.project_id = s.project_id LIMIT 1)
FROM stars s;
DROP TABLE stars;
ALTER TABLE stars__new RENAME TO stars;
CREATE INDEX idx_stars_user ON stars (user_id);
CREATE INDEX idx_stars_rfc ON stars (rfc_slug);

-- ── watches: UNIQUE (project_id, user_id, rfc_slug) -> collection_id ────────
CREATE TABLE watches__new (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id                 INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    rfc_slug                TEXT NOT NULL,
    state                   TEXT NOT NULL CHECK (state IN ('watching', 'following', 'muted')),
    set_by                  TEXT NOT NULL CHECK (set_by IN ('auto', 'explicit')),
    set_at                  TEXT NOT NULL DEFAULT (datetime('now')),
    last_participation_at   TEXT,
    collection_id           TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, user_id, rfc_slug)
);
INSERT INTO watches__new
    (id, user_id, rfc_slug, state, set_by, set_at, last_participation_at, collection_id)
SELECT
    w.id, w.user_id, w.rfc_slug, w.state, w.set_by, w.set_at, w.last_participation_at,
    (SELECT c.id FROM collections c WHERE c.project_id = w.project_id LIMIT 1)
FROM watches w;
DROP TABLE watches;
ALTER TABLE watches__new RENAME TO watches;
CREATE INDEX idx_watches_user ON watches (user_id);
CREATE INDEX idx_watches_rfc ON watches (rfc_slug);
CREATE INDEX idx_watches_decay ON watches (state, last_participation_at);

-- ── pr_seen: UNIQUE (project_id, user_id, rfc_slug, pr_number) -> collection ─
CREATE TABLE pr_seen__new (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id                 INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    rfc_slug                TEXT NOT NULL,
    pr_number               INTEGER NOT NULL,
    last_seen_commit_sha    TEXT,
    last_seen_message_id    INTEGER REFERENCES thread_messages(id) ON DELETE SET NULL,
    seen_at                 TEXT NOT NULL DEFAULT (datetime('now')),
    collection_id           TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, user_id, rfc_slug, pr_number)
);
INSERT INTO pr_seen__new
    (id, user_id, rfc_slug, pr_number, last_seen_commit_sha, last_seen_message_id, seen_at, collection_id)
SELECT
    p.id, p.user_id, p.rfc_slug, p.pr_number, p.last_seen_commit_sha, p.last_seen_message_id, p.seen_at,
    (SELECT c.id FROM collections c WHERE c.project_id = p.project_id LIMIT 1)
FROM pr_seen p;
DROP TABLE pr_seen;
ALTER TABLE pr_seen__new RENAME TO pr_seen;

-- ── branch_chat_seen: UNIQUE (project_id, user_id, rfc_slug, branch) -> coll ─
CREATE TABLE branch_chat_seen__new (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id                 INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    rfc_slug                TEXT NOT NULL,
    branch_name             TEXT NOT NULL,
    last_seen_message_id    INTEGER REFERENCES thread_messages(id) ON DELETE SET NULL,
    seen_at                 TEXT NOT NULL DEFAULT (datetime('now')),
    collection_id           TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, user_id, rfc_slug, branch_name)
);
INSERT INTO branch_chat_seen__new
    (id, user_id, rfc_slug, branch_name, last_seen_message_id, seen_at, collection_id)
SELECT
    s.id, s.user_id, s.rfc_slug, s.branch_name, s.last_seen_message_id, s.seen_at,
    (SELECT c.id FROM collections c WHERE c.project_id = s.project_id LIMIT 1)
FROM branch_chat_seen s;
DROP TABLE branch_chat_seen;
ALTER TABLE branch_chat_seen__new RENAME TO branch_chat_seen;

-- ── funder_consents: PRIMARY KEY (project_id, user_id, rfc_slug) -> collection
CREATE TABLE funder_consents__new (
    user_id     INTEGER NOT NULL,
    rfc_slug    TEXT    NOT NULL,
    created_at  TEXT    NOT NULL DEFAULT (datetime('now')),
    collection_id  TEXT NOT NULL DEFAULT 'default',
    PRIMARY KEY (collection_id, user_id, rfc_slug),
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
INSERT INTO funder_consents__new (user_id, rfc_slug, created_at, collection_id)
SELECT f.user_id, f.rfc_slug, f.created_at,
    (SELECT c.id FROM collections c WHERE c.project_id = f.project_id LIMIT 1)
FROM funder_consents f;
DROP TABLE funder_consents;
ALTER TABLE funder_consents__new RENAME TO funder_consents;
CREATE INDEX idx_funder_consents_slug ON funder_consents (rfc_slug);

-- ── rfc_collaborators: UNIQUE idx + composite FK -> collection_id ───────────
CREATE TABLE rfc_collaborators__new (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    rfc_slug        TEXT NOT NULL,
    user_id         INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    role_in_rfc     TEXT NOT NULL CHECK (role_in_rfc IN ('contributor', 'discussant')),
    invitation_id   INTEGER REFERENCES rfc_invitations(id) ON DELETE SET NULL,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    collection_id   TEXT NOT NULL DEFAULT 'default',
    FOREIGN KEY (collection_id, rfc_slug) REFERENCES cached_rfcs(collection_id, slug) ON DELETE CASCADE
);
INSERT INTO rfc_collaborators__new
    (id, rfc_slug, user_id, role_in_rfc, invitation_id, created_at, collection_id)
SELECT
    rc.id, rc.rfc_slug, rc.user_id, rc.role_in_rfc, rc.invitation_id, rc.created_at,
    (SELECT c.id FROM collections c WHERE c.project_id = rc.project_id LIMIT 1)
FROM rfc_collaborators rc;
DROP TABLE rfc_collaborators;
ALTER TABLE rfc_collaborators__new RENAME TO rfc_collaborators;
CREATE UNIQUE INDEX idx_rfc_collaborators_unique ON rfc_collaborators (collection_id, rfc_slug, user_id);
CREATE INDEX idx_rfc_collaborators_user ON rfc_collaborators (user_id);

-- ── contribution_requests: UNIQUE idx (pending) + composite FK -> collection ─
CREATE TABLE contribution_requests__new (
    id                  INTEGER PRIMARY KEY AUTOINCREMENT,
    rfc_slug            TEXT NOT NULL,
    requester_user_id   INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    matched_term        TEXT NOT NULL,
    who_i_am            TEXT NOT NULL,
    why                 TEXT NOT NULL,
    use_case            TEXT,
    status              TEXT NOT NULL DEFAULT 'pending'
                            CHECK (status IN ('pending', 'accepted', 'declined')),
    created_at          TEXT NOT NULL DEFAULT (datetime('now')),
    decided_at          TEXT,
    decided_by_user_id  INTEGER REFERENCES users(id) ON DELETE SET NULL,
    invitation_id       INTEGER REFERENCES rfc_invitations(id) ON DELETE SET NULL,
    notification_id     INTEGER REFERENCES notifications(id) ON DELETE SET NULL,
    collection_id       TEXT NOT NULL DEFAULT 'default',
    FOREIGN KEY (collection_id, rfc_slug) REFERENCES cached_rfcs(collection_id, slug) ON DELETE CASCADE
);
INSERT INTO contribution_requests__new
    (id, rfc_slug, requester_user_id, matched_term, who_i_am, why, use_case, status,
     created_at, decided_at, decided_by_user_id, invitation_id, notification_id, collection_id)
SELECT
    cr.id, cr.rfc_slug, cr.requester_user_id, cr.matched_term, cr.who_i_am, cr.why, cr.use_case, cr.status,
    cr.created_at, cr.decided_at, cr.decided_by_user_id, cr.invitation_id, cr.notification_id,
    (SELECT c.id FROM collections c WHERE c.project_id = cr.project_id LIMIT 1)
FROM contribution_requests cr;
DROP TABLE contribution_requests;
ALTER TABLE contribution_requests__new RENAME TO contribution_requests;
CREATE INDEX idx_contribution_requests_rfc ON contribution_requests(rfc_slug, status);
CREATE INDEX idx_contribution_requests_requester ON contribution_requests(requester_user_id, status);
CREATE UNIQUE INDEX idx_contribution_requests_one_open
    ON contribution_requests(collection_id, rfc_slug, requester_user_id)
    WHERE status = 'pending';

-- ── proposed_use_cases: UNIQUE (project_id, scope, pr_number) -> collection ──
CREATE TABLE proposed_use_cases__new (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    scope           TEXT NOT NULL CHECK (scope IN ('rfc', 'pr')),
    rfc_slug        TEXT NOT NULL,
    pr_number       INTEGER NOT NULL,
    use_case        TEXT NOT NULL,
    created_at      TEXT NOT NULL DEFAULT (datetime('now')),
    collection_id   TEXT NOT NULL DEFAULT 'default',
    UNIQUE (collection_id, scope, pr_number)
);
INSERT INTO proposed_use_cases__new
    (id, scope, rfc_slug, pr_number, use_case, created_at, collection_id)
SELECT
    u.id, u.scope, u.rfc_slug, u.pr_number, u.use_case, u.created_at,
    (SELECT c.id FROM collections c WHERE c.project_id = u.project_id LIMIT 1)
FROM proposed_use_cases u;
DROP TABLE proposed_use_cases;
ALTER TABLE proposed_use_cases__new RENAME TO proposed_use_cases;
CREATE INDEX idx_proposed_use_cases_lookup ON proposed_use_cases (scope, pr_number);
CREATE INDEX idx_proposed_use_cases_slug ON proposed_use_cases (scope, rfc_slug);

-- ── project_members -> memberships(scope_type, scope_id, …); roles collapsed ─
CREATE TABLE memberships (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    scope_type  TEXT NOT NULL CHECK (scope_type IN ('project', 'collection')),
    scope_id    TEXT NOT NULL,
    user_id     INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    role        TEXT NOT NULL CHECK (role IN ('owner', 'contributor')),
    granted_by  INTEGER REFERENCES users(id) ON DELETE SET NULL,
    granted_at  TEXT NOT NULL DEFAULT (datetime('now')),
    UNIQUE (scope_type, scope_id, user_id)
);
CREATE INDEX idx_memberships_user ON memberships(user_id);
CREATE INDEX idx_memberships_scope ON memberships(scope_type, scope_id);

-- M2 project_members rows attached at what is now the *collection*; collapse the
-- role enum (project_admin -> owner, project_contributor -> contributor;
-- project_viewer dropped this pass, §B.3) and migrate onto the default
-- collection of each project.
INSERT INTO memberships (scope_type, scope_id, user_id, role, granted_by, granted_at)
SELECT 'collection',
       (SELECT c.id FROM collections c WHERE c.project_id = pm.project_id LIMIT 1),
       pm.user_id,
       CASE pm.role WHEN 'project_admin' THEN 'owner'
                    WHEN 'project_contributor' THEN 'contributor'
                    ELSE 'contributor' END,
       pm.granted_by, pm.granted_at
FROM project_members pm
WHERE pm.role IN ('project_admin', 'project_contributor');
DROP TABLE project_members;
