CREATE TABLE IF NOT EXISTS users (
    id UUID PRIMARY KEY,
    email TEXT NOT NULL DEFAULT '',
    name TEXT NOT NULL DEFAULT '',
    status TEXT NOT NULL DEFAULT 'active',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

INSERT INTO users (id, email, name, status)
VALUES ('00000000-0000-0000-0000-000000000001', 'local@example.test', 'Local User', 'active')
ON CONFLICT (id) DO NOTHING;
ALTER TABLE users ALTER COLUMN id TYPE UUID USING NULLIF(id::text, '')::uuid;
ALTER TABLE users ADD COLUMN IF NOT EXISTS region TEXT NOT NULL DEFAULT 'china';
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone_country_code TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS avatar_url TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS wechat_open_id TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS wechat_union_id TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS google_subject TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone_verified_at TIMESTAMPTZ;
ALTER TABLE users ADD COLUMN IF NOT EXISTS email_verified_at TIMESTAMPTZ;
ALTER TABLE users ADD COLUMN IF NOT EXISTS last_login_at TIMESTAMPTZ;
CREATE UNIQUE INDEX IF NOT EXISTS users_phone_unique_idx ON users(phone_country_code, phone) WHERE phone <> '';
CREATE UNIQUE INDEX IF NOT EXISTS users_email_unique_idx ON users(LOWER(email)) WHERE email <> '';
CREATE UNIQUE INDEX IF NOT EXISTS users_google_subject_unique_idx ON users(google_subject) WHERE google_subject <> '';
CREATE UNIQUE INDEX IF NOT EXISTS users_wechat_union_id_unique_idx ON users(wechat_union_id) WHERE wechat_union_id <> '';
CREATE UNIQUE INDEX IF NOT EXISTS users_wechat_open_id_unique_idx ON users(wechat_open_id) WHERE wechat_open_id <> '' AND wechat_union_id = '';

CREATE TABLE IF NOT EXISTS auth_verification_codes (
    id TEXT PRIMARY KEY,
    region TEXT NOT NULL,
    channel TEXT NOT NULL,
    target TEXT NOT NULL,
    purpose TEXT NOT NULL,
    code_hash TEXT NOT NULL,
    attempts INTEGER NOT NULL DEFAULT 0,
    expires_at TIMESTAMPTZ NOT NULL,
    consumed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS auth_codes_target_idx ON auth_verification_codes(region, channel, target, purpose, created_at DESC);

CREATE TABLE IF NOT EXISTS auth_device_sessions (
    id TEXT PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    device_type TEXT NOT NULL DEFAULT 'desktop',
    system TEXT NOT NULL DEFAULT 'unknown',
    browser TEXT NOT NULL DEFAULT 'unknown',
    client TEXT NOT NULL DEFAULT 'PC-WEB',
    user_agent TEXT NOT NULL DEFAULT '',
    created_at TIMESTAMPTZ NOT NULL,
    last_seen_at TIMESTAMPTZ NOT NULL,
    expires_at TIMESTAMPTZ NOT NULL,
    revoked_at TIMESTAMPTZ
);

CREATE INDEX IF NOT EXISTS auth_device_sessions_user_active_idx
    ON auth_device_sessions(user_id, last_seen_at DESC)
    WHERE revoked_at IS NULL;
CREATE INDEX IF NOT EXISTS auth_device_sessions_expiry_idx
    ON auth_device_sessions(expires_at)
    WHERE revoked_at IS NULL;

CREATE TABLE IF NOT EXISTS brand_kits (
    id TEXT PRIMARY KEY,
    user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name TEXT NOT NULL,
    document JSONB NOT NULL CHECK (jsonb_typeof(document) = 'object'),
    is_default BOOLEAN NOT NULL DEFAULT FALSE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS brand_kits_user_updated_idx ON brand_kits(user_id, updated_at DESC);
CREATE UNIQUE INDEX IF NOT EXISTS brand_kits_user_default_idx ON brand_kits(user_id) WHERE is_default;

CREATE TABLE IF NOT EXISTS projects (
    id TEXT PRIMARY KEY,
    user_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000001'::uuid,
    title TEXT NOT NULL,
    brief TEXT NOT NULL,
    status TEXT NOT NULL,
    thumbnail TEXT NOT NULL,
    canvas TEXT NOT NULL DEFAULT '',
    version TEXT NOT NULL DEFAULT '',
    snapshot_id TEXT NOT NULL DEFAULT '',
    last_thread_id TEXT NOT NULL DEFAULT '',
    brand_kit_id TEXT REFERENCES brand_kits(id) ON DELETE SET NULL,
    viewport_x DOUBLE PRECISION NOT NULL DEFAULT 0,
    viewport_y DOUBLE PRECISION NOT NULL DEFAULT 0,
    viewport_k DOUBLE PRECISION NOT NULL DEFAULT 1,
    updated_at TIMESTAMPTZ NOT NULL
);

ALTER TABLE projects ADD COLUMN IF NOT EXISTS user_id UUID;
ALTER TABLE projects ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE projects ALTER COLUMN user_id TYPE UUID USING NULLIF(user_id::text, '')::uuid;
UPDATE projects SET user_id = '00000000-0000-0000-0000-000000000001'::uuid WHERE user_id IS NULL;
ALTER TABLE projects ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000001'::uuid;
ALTER TABLE projects ALTER COLUMN user_id SET NOT NULL;
ALTER TABLE projects ADD COLUMN IF NOT EXISTS canvas TEXT NOT NULL DEFAULT '';
ALTER TABLE projects ADD COLUMN IF NOT EXISTS version TEXT NOT NULL DEFAULT '';
ALTER TABLE projects ADD COLUMN IF NOT EXISTS snapshot_id TEXT NOT NULL DEFAULT '';
ALTER TABLE projects ADD COLUMN IF NOT EXISTS last_thread_id TEXT NOT NULL DEFAULT '';
ALTER TABLE projects ADD COLUMN IF NOT EXISTS brand_kit_id TEXT;
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1
        FROM pg_constraint
        WHERE conname = 'projects_brand_kit_id_fkey'
          AND conrelid = 'projects'::regclass
    ) THEN
        ALTER TABLE projects
            ADD CONSTRAINT projects_brand_kit_id_fkey
            FOREIGN KEY (brand_kit_id) REFERENCES brand_kits(id) ON DELETE SET NULL;
    END IF;
END $$;
CREATE INDEX IF NOT EXISTS projects_user_updated_idx ON projects(user_id, updated_at DESC);
CREATE INDEX IF NOT EXISTS projects_brand_kit_idx ON projects(user_id, brand_kit_id) WHERE brand_kit_id IS NOT NULL;

CREATE TABLE IF NOT EXISTS project_share_policies (
    project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE,
    owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash BYTEA NOT NULL UNIQUE,
    token_ciphertext BYTEA NOT NULL,
    link_permission TEXT NOT NULL DEFAULT 'private' CHECK (link_permission IN ('private', 'canvas', 'viewer', 'editor')),
    policy_version BIGINT NOT NULL DEFAULT 1,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS project_share_policies_owner_idx ON project_share_policies(owner_id, updated_at DESC);

CREATE TABLE IF NOT EXISTS project_share_members (
    id UUID PRIMARY KEY,
    project_id TEXT NOT NULL REFERENCES project_share_policies(project_id) ON DELETE CASCADE,
    identifier_type TEXT NOT NULL CHECK (identifier_type IN ('email', 'phone')),
    identifier_hash BYTEA NOT NULL,
    identifier_ciphertext BYTEA NOT NULL,
    permission TEXT NOT NULL CHECK (permission IN ('canvas', 'viewer', 'editor')),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(project_id, identifier_type, identifier_hash)
);

CREATE INDEX IF NOT EXISTS project_share_members_project_idx ON project_share_members(project_id, created_at ASC);

CREATE TABLE IF NOT EXISTS project_share_visits (
    id TEXT PRIMARY KEY,
    project_id TEXT NOT NULL REFERENCES project_share_policies(project_id) ON DELETE CASCADE,
    visitor_hash BYTEA NOT NULL,
    user_id UUID REFERENCES users(id) ON DELETE SET NULL,
    visit_count BIGINT NOT NULL DEFAULT 1,
    first_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    UNIQUE(project_id, visitor_hash)
);

CREATE INDEX IF NOT EXISTS project_share_visits_project_seen_idx ON project_share_visits(project_id, last_seen_at DESC);

CREATE TABLE IF NOT EXISTS canvas_nodes (
    id TEXT PRIMARY KEY,
    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    user_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000001'::uuid,
    node_type TEXT NOT NULL,
    title TEXT NOT NULL,
    x DOUBLE PRECISION NOT NULL,
    y DOUBLE PRECISION NOT NULL,
    width DOUBLE PRECISION NOT NULL,
    height DOUBLE PRECISION NOT NULL,
    content TEXT NOT NULL,
    tone TEXT NOT NULL,
    status TEXT NOT NULL,
    sort_order INTEGER NOT NULL DEFAULT 0,
    parent_id TEXT NOT NULL DEFAULT '',
    layer_role TEXT NOT NULL DEFAULT '',
    font_size DOUBLE PRECISION NOT NULL DEFAULT 0,
    font_family TEXT NOT NULL DEFAULT '',
    font_weight TEXT NOT NULL DEFAULT '',
    color TEXT NOT NULL DEFAULT '',
    text_align TEXT NOT NULL DEFAULT '',
    line_height DOUBLE PRECISION NOT NULL DEFAULT 0,
    letter_spacing DOUBLE PRECISION NOT NULL DEFAULT 0,
    text_decoration TEXT NOT NULL DEFAULT '',
    text_transform TEXT NOT NULL DEFAULT '',
    opacity DOUBLE PRECISION NOT NULL DEFAULT 0,
    fill_color TEXT NOT NULL DEFAULT '',
    stroke_color TEXT NOT NULL DEFAULT '',
    stroke_width DOUBLE PRECISION NOT NULL DEFAULT 0,
    stroke_style TEXT NOT NULL DEFAULT '',
    flip_x BOOLEAN NOT NULL DEFAULT FALSE,
    flip_y BOOLEAN NOT NULL DEFAULT FALSE,
    rotation DOUBLE PRECISION NOT NULL DEFAULT 0
);

ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS user_id UUID;
ALTER TABLE canvas_nodes ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE canvas_nodes ALTER COLUMN user_id TYPE UUID USING NULLIF(user_id::text, '')::uuid;
UPDATE canvas_nodes
SET user_id = COALESCE((SELECT p.user_id FROM projects p WHERE p.id = canvas_nodes.project_id), '00000000-0000-0000-0000-000000000001'::uuid)
WHERE user_id IS NULL;
ALTER TABLE canvas_nodes ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000001'::uuid;
ALTER TABLE canvas_nodes ALTER COLUMN user_id SET NOT NULL;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS sort_order INTEGER NOT NULL DEFAULT 0;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS parent_id TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS layer_role TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS font_size DOUBLE PRECISION NOT NULL DEFAULT 0;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS font_family TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS font_weight TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS color TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS text_align TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS line_height DOUBLE PRECISION NOT NULL DEFAULT 0;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS letter_spacing DOUBLE PRECISION NOT NULL DEFAULT 0;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS text_decoration TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS text_transform TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS opacity DOUBLE PRECISION NOT NULL DEFAULT 0;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS fill_color TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS stroke_color TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS stroke_width DOUBLE PRECISION NOT NULL DEFAULT 0;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS stroke_style TEXT NOT NULL DEFAULT '';
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS flip_x BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS flip_y BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE canvas_nodes ADD COLUMN IF NOT EXISTS rotation DOUBLE PRECISION NOT NULL DEFAULT 0;

CREATE INDEX IF NOT EXISTS canvas_nodes_project_id_idx ON canvas_nodes(project_id);
CREATE INDEX IF NOT EXISTS canvas_nodes_user_project_idx ON canvas_nodes(user_id, project_id);

CREATE TABLE IF NOT EXISTS mockup_blobs (
    data_id TEXT PRIMARY KEY,
    width INTEGER NOT NULL,
    height INTEGER NOT NULL,
    data BYTEA NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE TABLE IF NOT EXISTS canvas_connections (
    id TEXT PRIMARY KEY,
    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    user_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000001'::uuid,
    from_node_id TEXT NOT NULL,
    to_node_id TEXT NOT NULL
);

ALTER TABLE canvas_connections ADD COLUMN IF NOT EXISTS user_id UUID;
ALTER TABLE canvas_connections ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE canvas_connections ALTER COLUMN user_id TYPE UUID USING NULLIF(user_id::text, '')::uuid;
UPDATE canvas_connections
SET user_id = COALESCE((SELECT p.user_id FROM projects p WHERE p.id = canvas_connections.project_id), '00000000-0000-0000-0000-000000000001'::uuid)
WHERE user_id IS NULL;
ALTER TABLE canvas_connections ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000001'::uuid;
ALTER TABLE canvas_connections ALTER COLUMN user_id SET NOT NULL;
CREATE INDEX IF NOT EXISTS canvas_connections_project_id_idx ON canvas_connections(project_id);
CREATE INDEX IF NOT EXISTS canvas_connections_user_project_idx ON canvas_connections(user_id, project_id);

CREATE TABLE IF NOT EXISTS canvas_snapshots (
    id TEXT PRIMARY KEY,
    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    user_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000001'::uuid,
    version TEXT NOT NULL,
    canvas TEXT NOT NULL,
    viewport_json JSONB NOT NULL,
    nodes_json JSONB NOT NULL,
    connections_json JSONB NOT NULL,
    created_at TIMESTAMPTZ NOT NULL
);

ALTER TABLE canvas_snapshots ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE canvas_snapshots ALTER COLUMN user_id TYPE UUID USING NULLIF(user_id::text, '')::uuid;
UPDATE canvas_snapshots SET user_id = '00000000-0000-0000-0000-000000000001'::uuid WHERE user_id IS NULL;
ALTER TABLE canvas_snapshots ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000001'::uuid;
ALTER TABLE canvas_snapshots ALTER COLUMN user_id SET NOT NULL;
CREATE INDEX IF NOT EXISTS canvas_snapshots_project_version_idx ON canvas_snapshots(user_id, project_id, created_at DESC);

CREATE TABLE IF NOT EXISTS agent_messages (
    id TEXT PRIMARY KEY,
    project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
    user_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000001'::uuid,
    role TEXT NOT NULL,
    message_type TEXT NOT NULL DEFAULT '',
    title TEXT NOT NULL,
    content TEXT NOT NULL,
    thread_id TEXT NOT NULL DEFAULT '',
    action_id TEXT NOT NULL DEFAULT '',
    step_id TEXT NOT NULL DEFAULT '',
    tool_call_id TEXT NOT NULL DEFAULT '',
    name TEXT NOT NULL DEFAULT '',
    tool_hint TEXT NOT NULL DEFAULT '',
    status TEXT NOT NULL DEFAULT '',
    created_at TIMESTAMPTZ NOT NULL
);

ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS user_id UUID;
ALTER TABLE agent_messages ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE agent_messages ALTER COLUMN user_id TYPE UUID USING NULLIF(user_id::text, '')::uuid;
UPDATE agent_messages
SET user_id = COALESCE((SELECT p.user_id FROM projects p WHERE p.id = agent_messages.project_id), '00000000-0000-0000-0000-000000000001'::uuid)
WHERE user_id IS NULL;
ALTER TABLE agent_messages ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000001'::uuid;
ALTER TABLE agent_messages ALTER COLUMN user_id SET NOT NULL;
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS message_type TEXT NOT NULL DEFAULT '';
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS thread_id TEXT NOT NULL DEFAULT '';
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS action_id TEXT NOT NULL DEFAULT '';
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS step_id TEXT NOT NULL DEFAULT '';
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS tool_call_id TEXT NOT NULL DEFAULT '';
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS name TEXT NOT NULL DEFAULT '';
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS tool_hint TEXT NOT NULL DEFAULT '';
ALTER TABLE agent_messages ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT '';

CREATE INDEX IF NOT EXISTS agent_messages_project_id_idx ON agent_messages(project_id);
CREATE INDEX IF NOT EXISTS agent_messages_user_project_idx ON agent_messages(user_id, project_id);

CREATE TABLE IF NOT EXISTS outbox_events (
    id TEXT PRIMARY KEY,
    user_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000001'::uuid,
    aggregate_type TEXT NOT NULL,
    aggregate_id TEXT NOT NULL,
    event_type TEXT NOT NULL,
    thread_id TEXT NOT NULL DEFAULT '',
    payload_json JSONB NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    published_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

ALTER TABLE outbox_events ALTER COLUMN user_id DROP DEFAULT;
ALTER TABLE outbox_events ALTER COLUMN user_id TYPE UUID USING NULLIF(user_id::text, '')::uuid;
UPDATE outbox_events SET user_id = '00000000-0000-0000-0000-000000000001'::uuid WHERE user_id IS NULL;
ALTER TABLE outbox_events ALTER COLUMN user_id SET DEFAULT '00000000-0000-0000-0000-000000000001'::uuid;
ALTER TABLE outbox_events ALTER COLUMN user_id SET NOT NULL;
CREATE INDEX IF NOT EXISTS outbox_events_status_created_idx ON outbox_events(status, created_at);
CREATE INDEX IF NOT EXISTS outbox_events_project_idx ON outbox_events(aggregate_type, aggregate_id, created_at);
