-- Users. A magic-link email is the only way to sign in (see EmailVerifier /
-- AuthController::requestLoginLink) -- there is no password. Passkeys
-- (passkeys / webauthn_challenges below) are the only alternative.
CREATE TABLE IF NOT EXISTS users (
    id                          INTEGER PRIMARY KEY AUTOINCREMENT,
    email                       TEXT NOT NULL UNIQUE COLLATE NOCASE,
    -- Null until the address is verified -- opening a magic link both signs
    -- the user in and, the first time, sets this.
    email_verified_at           TEXT NULL,
    -- When the last verification / email-change link was sent, for the
    -- once-per-minute resend throttle.
    verification_email_sent_at TEXT NULL,
    created_at                  TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    updated_at                  TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);

-- Projects. Each belongs to exactly one owner.
CREATE TABLE IF NOT EXISTS projects (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    owner_id   INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
    title      TEXT NOT NULL,
    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_projects_owner ON projects (owner_id);

-- Project-specific card statuses ("To do" / "Doing" / "Done" by default --
-- see CardStatusRepository::seedDefaults -- then user-managed from there via
-- the project configuration view).
CREATE TABLE IF NOT EXISTS card_statuses (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    project_id INTEGER NOT NULL REFERENCES projects (id) ON DELETE CASCADE,
    name       TEXT NOT NULL,
    position   INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_card_statuses_project_position
    ON card_statuses (project_id, position);

-- Cards. A card either sits in its owner's global inbox (project_id AND
-- status_id both NULL) or belongs to exactly one project with a status in it
-- (both set) -- the CHECK below enforces that pairing, never one without the
-- other. `position` is a dense 0..n-1 rank within a "column": the cards
-- sharing an (owner_id, project_id, status_id).
CREATE TABLE IF NOT EXISTS cards (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    owner_id   INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
    project_id INTEGER NULL REFERENCES projects (id) ON DELETE CASCADE,
    -- RESTRICT, not SET NULL: a project card must always have a status (see
    -- the CHECK below), so a status can only be deleted once its cards are
    -- reassigned elsewhere first (see CardStatusController::destroy).
    status_id  INTEGER NULL REFERENCES card_statuses (id) ON DELETE RESTRICT,
    text       TEXT NOT NULL,
    complete   INTEGER NOT NULL DEFAULT 0 CHECK (complete IN (0, 1)),
    position   INTEGER NOT NULL DEFAULT 0,
    created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    CHECK ((project_id IS NULL) = (status_id IS NULL))
);

CREATE INDEX IF NOT EXISTS idx_cards_owner_project_status_position
    ON cards (owner_id, project_id, status_id, position);
CREATE INDEX IF NOT EXISTS idx_cards_status ON cards (status_id);

-- Magic-link tokens for verifying an email address. `new_email` is null for a
-- plain "verify your current address" link, or the requested address for a
-- deferred email change (applied only when the link is opened).
CREATE TABLE IF NOT EXISTS email_verifications (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id     INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
    token_hash  TEXT NOT NULL UNIQUE,
    new_email   TEXT NULL,
    expires_at  TEXT NOT NULL,
    consumed_at TEXT NULL,
    created_at  TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_email_verifications_user ON email_verifications (user_id);

-- Passkeys (WebAuthn discoverable credentials): an alternative to the email
-- magic link. A user may register several (one per device/authenticator).
CREATE TABLE IF NOT EXISTS passkeys (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id       INTEGER NOT NULL REFERENCES users (id) ON DELETE CASCADE,
    credential_id TEXT NOT NULL UNIQUE,
    public_key    TEXT NOT NULL,
    sign_count    INTEGER NOT NULL DEFAULT 0,
    label         TEXT NOT NULL DEFAULT '',
    created_at    TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
    last_used_at  TEXT NULL
);

CREATE INDEX IF NOT EXISTS idx_passkeys_user ON passkeys (user_id);

-- Short-lived, single-use WebAuthn challenges bridging the "options" and
-- "verify" calls of both the registration and login ceremonies. user_id is
-- set for a registration (tied to the signed-in caller) and NULL for a login
-- attempt, since who's logging in isn't known until the credential comes back.
CREATE TABLE IF NOT EXISTS webauthn_challenges (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id     INTEGER NULL REFERENCES users (id) ON DELETE CASCADE,
    purpose     TEXT NOT NULL CHECK (purpose IN ('register', 'login')),
    challenge   TEXT NOT NULL,
    expires_at  TEXT NOT NULL,
    consumed_at TEXT NULL,
    created_at  TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);

CREATE INDEX IF NOT EXISTS idx_webauthn_challenges_expiry ON webauthn_challenges (expires_at);
