CREATE TABLE IF NOT EXISTS h5_users (
  id CHAR(36) PRIMARY KEY,
  username VARCHAR(64) NOT NULL UNIQUE,
  slug VARCHAR(64) NULL,
  email VARCHAR(255) NULL,
  display_name VARCHAR(128) NOT NULL,
  salt VARCHAR(64) NOT NULL,
  password_hash VARCHAR(128) NOT NULL,
  password_algorithm VARCHAR(32) NOT NULL DEFAULT 'pbkdf2-sha512',
  role ENUM('user', 'admin') NOT NULL DEFAULT 'user',
  status ENUM('active', 'suspended', 'disabled') NOT NULL DEFAULT 'active',
  plan_type VARCHAR(32) NOT NULL DEFAULT 'free',
  workspace_root VARCHAR(512) NOT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_users_slug (slug),
  UNIQUE KEY uq_h5_users_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_spaces (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  space_name VARCHAR(128) NOT NULL DEFAULT '我的空间',
  quota_bytes BIGINT NOT NULL DEFAULT 5242880,
  used_bytes BIGINT NOT NULL DEFAULT 0,
  reserved_bytes BIGINT NOT NULL DEFAULT 0,
  status ENUM('active', 'locked', 'deleted') NOT NULL DEFAULT 'active',
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_user_space_user (user_id),
  CONSTRAINT fk_h5_space_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT chk_h5_space_quota CHECK (
    quota_bytes >= 0 AND used_bytes >= 0 AND reserved_bytes >= 0
  )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS mindspace_config (
  `key` VARCHAR(64) PRIMARY KEY,
  value TEXT NOT NULL,
  description VARCHAR(255) NULL,
  updated_at BIGINT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_space_categories (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  space_id CHAR(36) NOT NULL,
  category_code VARCHAR(32) NOT NULL,
  category_name VARCHAR(128) NOT NULL,
  visibility_policy VARCHAR(64) NOT NULL,
  ai_access_policy VARCHAR(64) NOT NULL,
  publish_policy VARCHAR(64) NOT NULL,
  is_system TINYINT(1) NOT NULL DEFAULT 0,
  sort_order INT NOT NULL DEFAULT 0,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_space_category_code (space_id, category_code),
  KEY idx_h5_space_category_user (user_id, sort_order),
  CONSTRAINT fk_h5_category_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_category_space FOREIGN KEY (space_id) REFERENCES h5_user_spaces(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_assets (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  space_id CHAR(36) NOT NULL,
  category_id CHAR(36) NOT NULL,
  parent_id CHAR(36) NULL,
  asset_type VARCHAR(32) NOT NULL,
  mime_type VARCHAR(128) NOT NULL,
  original_filename VARCHAR(255) NOT NULL,
  display_name VARCHAR(255) NOT NULL,
  current_version_id CHAR(36) NULL,
  size_bytes BIGINT NOT NULL DEFAULT 0,
  checksum CHAR(64) NOT NULL,
  risk_level ENUM('none', 'low', 'medium', 'high', 'critical') NOT NULL DEFAULT 'none',
  visibility ENUM('private', 'internal', 'public_candidate') NOT NULL DEFAULT 'private',
  status ENUM('uploaded', 'processing', 'ready', 'quarantined', 'archived', 'deleted') NOT NULL DEFAULT 'ready',
  source_type ENUM('upload', 'chat', 'agent', 'template', 'generated', 'workspace') NOT NULL DEFAULT 'upload',
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  deleted_at BIGINT NULL,
  KEY idx_h5_assets_category (user_id, category_id, updated_at),
  KEY idx_h5_assets_parent (user_id, parent_id),
  KEY idx_h5_assets_checksum (user_id, checksum),
  CONSTRAINT fk_h5_asset_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_asset_space FOREIGN KEY (space_id) REFERENCES h5_user_spaces(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_asset_category FOREIGN KEY (category_id) REFERENCES h5_space_categories(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_asset_parent FOREIGN KEY (parent_id) REFERENCES h5_assets(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_asset_versions (
  id CHAR(36) PRIMARY KEY,
  asset_id CHAR(36) NOT NULL,
  version_no INT NOT NULL,
  storage_key VARCHAR(512) NOT NULL,
  size_bytes BIGINT NOT NULL,
  checksum CHAR(64) NOT NULL,
  mime_type VARCHAR(128) NOT NULL,
  created_by CHAR(36) NOT NULL,
  change_note VARCHAR(512) NULL,
  scan_status ENUM('pending', 'passed', 'warned', 'blocked') NOT NULL DEFAULT 'pending',
  created_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_asset_version (asset_id, version_no),
  UNIQUE KEY uq_h5_asset_storage_key (storage_key),
  CONSTRAINT fk_h5_asset_version_asset FOREIGN KEY (asset_id) REFERENCES h5_assets(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_asset_version_user FOREIGN KEY (created_by) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_upload_sessions (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  space_id CHAR(36) NOT NULL,
  category_id CHAR(36) NOT NULL,
  filename VARCHAR(255) NOT NULL,
  expected_size BIGINT NOT NULL,
  actual_size BIGINT NULL,
  declared_mime_type VARCHAR(128) NULL,
  detected_mime_type VARCHAR(128) NULL,
  reserved_bytes BIGINT NOT NULL,
  temporary_storage_key VARCHAR(512) NOT NULL,
  checksum CHAR(64) NULL,
  completed_asset_id CHAR(36) NULL,
  status ENUM('reserved', 'uploaded', 'completed', 'cancelled', 'expired', 'failed') NOT NULL DEFAULT 'reserved',
  expires_at BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  completed_at BIGINT NULL,
  KEY idx_h5_upload_user_status (user_id, status, expires_at),
  CONSTRAINT fk_h5_upload_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_upload_space FOREIGN KEY (space_id) REFERENCES h5_user_spaces(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_upload_category FOREIGN KEY (category_id) REFERENCES h5_space_categories(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_page_records (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  space_id CHAR(36) NOT NULL,
  category_id CHAR(36) NOT NULL,
  source_session_id VARCHAR(128) NULL,
  source_message_id VARCHAR(128) NULL,
  source_asset_id CHAR(36) NULL,
  title VARCHAR(255) NOT NULL,
  summary VARCHAR(1000) NULL,
  cover_image_asset_id CHAR(36) NULL,
  page_type VARCHAR(32) NOT NULL DEFAULT 'article',
  template_id VARCHAR(64) NOT NULL DEFAULT 'editorial',
  draft_content_ref VARCHAR(512) NULL,
  current_version_id CHAR(36) NULL,
  current_publish_id CHAR(36) NULL,
  status ENUM('draft', 'reviewing', 'risk_found', 'ready', 'published', 'protected', 'expired', 'offline', 'deleted') NOT NULL DEFAULT 'draft',
  visibility ENUM('private', 'internal', 'public') NOT NULL DEFAULT 'private',
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  deleted_at BIGINT NULL,
  KEY idx_h5_pages_user_status (user_id, status, updated_at),
  KEY idx_h5_pages_category (user_id, category_id, updated_at),
  KEY idx_h5_pages_source_session (user_id, source_session_id),
  CONSTRAINT fk_h5_page_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_page_space FOREIGN KEY (space_id) REFERENCES h5_user_spaces(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_page_category FOREIGN KEY (category_id) REFERENCES h5_space_categories(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_page_source_asset FOREIGN KEY (source_asset_id) REFERENCES h5_assets(id) ON DELETE SET NULL,
  CONSTRAINT fk_h5_page_cover_asset FOREIGN KEY (cover_image_asset_id) REFERENCES h5_assets(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_page_versions (
  id CHAR(36) PRIMARY KEY,
  page_id CHAR(36) NOT NULL,
  version_no INT NOT NULL,
  content_asset_id CHAR(36) NOT NULL,
  bundle_asset_id CHAR(36) NULL,
  source_snapshot_json JSON NULL,
  security_scan_id CHAR(36) NULL,
  created_by CHAR(36) NOT NULL,
  change_note VARCHAR(512) NULL,
  immutable TINYINT(1) NOT NULL DEFAULT 0,
  created_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_page_version (page_id, version_no),
  CONSTRAINT fk_h5_page_version_page FOREIGN KEY (page_id) REFERENCES h5_page_records(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_page_version_content FOREIGN KEY (content_asset_id) REFERENCES h5_assets(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_page_version_bundle FOREIGN KEY (bundle_asset_id) REFERENCES h5_assets(id) ON DELETE SET NULL,
  CONSTRAINT fk_h5_page_version_user FOREIGN KEY (created_by) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_security_scans (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  target_type ENUM('asset', 'page_version', 'publication_bundle') NOT NULL,
  target_id CHAR(36) NOT NULL,
  scanner_version VARCHAR(64) NOT NULL,
  status ENUM('queued', 'running', 'passed', 'warned', 'blocked', 'failed') NOT NULL,
  risk_level ENUM('none', 'low', 'medium', 'high', 'critical') NOT NULL DEFAULT 'none',
  findings_count INT NOT NULL DEFAULT 0,
  summary_json JSON NULL,
  started_at BIGINT NOT NULL,
  completed_at BIGINT NULL,
  KEY idx_h5_security_scan_target (user_id, target_type, target_id, completed_at),
  CONSTRAINT fk_h5_security_scan_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_security_findings (
  id CHAR(36) PRIMARY KEY,
  scan_id CHAR(36) NOT NULL,
  finding_type VARCHAR(64) NOT NULL,
  risk_level ENUM('low', 'medium', 'high', 'critical') NOT NULL,
  occurrence_count INT NOT NULL DEFAULT 1,
  sample_masked VARCHAR(255) NULL,
  blocking TINYINT(1) NOT NULL DEFAULT 0,
  created_at BIGINT NOT NULL,
  KEY idx_h5_security_finding_scan (scan_id, risk_level),
  CONSTRAINT fk_h5_security_finding_scan FOREIGN KEY (scan_id) REFERENCES h5_security_scans(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_publish_records (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  page_id CHAR(36) NOT NULL,
  page_version_id CHAR(36) NOT NULL,
  publish_type ENUM('page', 'share') NOT NULL DEFAULT 'page',
  url_slug VARCHAR(64) NOT NULL,
  public_url VARCHAR(512) NOT NULL,
  access_mode ENUM('public', 'password', 'private_link', 'time_limited', 'login_required', 'owner_only') NOT NULL,
  password_hash VARCHAR(255) NULL,
  token_hash CHAR(64) NULL,
  token_prefix VARCHAR(16) NULL,
  expires_at BIGINT NULL,
  published_at BIGINT NOT NULL,
  offline_at BIGINT NULL,
  status ENUM('draft', 'online', 'expired', 'offline', 'blocked') NOT NULL DEFAULT 'online',
  view_count BIGINT NOT NULL DEFAULT 0,
  security_scan_id CHAR(36) NOT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  KEY idx_h5_publish_route (url_slug, status, published_at),
  KEY idx_h5_publish_page (user_id, page_id, published_at),
  UNIQUE KEY uq_h5_publish_token_hash (token_hash),
  CONSTRAINT fk_h5_publish_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_publish_page FOREIGN KEY (page_id) REFERENCES h5_page_records(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_publish_page_version FOREIGN KEY (page_version_id) REFERENCES h5_page_versions(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_publish_scan FOREIGN KEY (security_scan_id) REFERENCES h5_security_scans(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_publication_events (
  id CHAR(36) PRIMARY KEY,
  publish_id CHAR(36) NOT NULL,
  event_type ENUM('published', 'republished', 'settings_changed', 'expired', 'offlined', 'blocked') NOT NULL,
  actor_id CHAR(36) NOT NULL,
  old_page_version_id CHAR(36) NULL,
  new_page_version_id CHAR(36) NULL,
  access_mode VARCHAR(32) NOT NULL,
  detail_json JSON NULL,
  created_at BIGINT NOT NULL,
  KEY idx_h5_publication_event (publish_id, created_at),
  CONSTRAINT fk_h5_publication_event_publish FOREIGN KEY (publish_id) REFERENCES h5_publish_records(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_publication_event_actor FOREIGN KEY (actor_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_publication_views (
  id CHAR(36) PRIMARY KEY,
  publish_id CHAR(36) NOT NULL,
  viewer_user_id CHAR(36) NULL,
  referrer_host VARCHAR(255) NULL,
  device_type ENUM('desktop', 'mobile', 'tablet', 'bot', 'unknown') NOT NULL,
  viewed_at BIGINT NOT NULL,
  KEY idx_h5_publication_view_time (publish_id, viewed_at),
  KEY idx_h5_publication_viewer (viewer_user_id, viewed_at),
  CONSTRAINT fk_h5_publication_view_publish FOREIGN KEY (publish_id) REFERENCES h5_publish_records(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_publication_view_user FOREIGN KEY (viewer_user_id) REFERENCES h5_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_agent_jobs (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  session_id VARCHAR(128) NULL,
  job_type VARCHAR(64) NOT NULL,
  instruction TEXT NOT NULL,
  permission_scope JSON NULL,
  user_context_json JSON NULL,
  output_category_id CHAR(36) NOT NULL,
  output_type VARCHAR(32) NOT NULL,
  max_output_bytes BIGINT NOT NULL DEFAULT 2097152,
  status ENUM('queued', 'running', 'completed', 'failed', 'cancelled', 'timed_out') NOT NULL DEFAULT 'queued',
  idempotency_key VARCHAR(128) NULL,
  progress_json JSON NULL,
  job_token_hash CHAR(64) NULL,
  result_page_id CHAR(36) NULL,
  result_asset_id CHAR(36) NULL,
  error_code VARCHAR(64) NULL,
  error_message VARCHAR(1000) NULL,
  queued_at BIGINT NOT NULL,
  started_at BIGINT NULL,
  heartbeat_at BIGINT NULL,
  completed_at BIGINT NULL,
  expires_at BIGINT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_agent_job_idempotency (user_id, idempotency_key),
  KEY idx_h5_agent_job_user_status (user_id, status, queued_at),
  KEY idx_h5_agent_job_token (job_token_hash),
  CONSTRAINT fk_h5_agent_job_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_agent_job_output_category FOREIGN KEY (output_category_id) REFERENCES h5_space_categories(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_agent_job_result_page FOREIGN KEY (result_page_id) REFERENCES h5_page_records(id) ON DELETE SET NULL,
  CONSTRAINT fk_h5_agent_job_result_asset FOREIGN KEY (result_asset_id) REFERENCES h5_assets(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_agent_job_assets (
  id CHAR(36) PRIMARY KEY,
  job_id CHAR(36) NOT NULL,
  asset_id CHAR(36) NOT NULL,
  asset_version_id CHAR(36) NOT NULL,
  permission ENUM('read', 'write', 'create_derivative') NOT NULL DEFAULT 'read',
  created_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_agent_job_asset (job_id, asset_id, asset_version_id),
  KEY idx_h5_agent_job_asset_job (job_id, created_at),
  CONSTRAINT fk_h5_agent_job_asset_job FOREIGN KEY (job_id) REFERENCES h5_agent_jobs(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_agent_job_asset_asset FOREIGN KEY (asset_id) REFERENCES h5_assets(id) ON DELETE CASCADE,
  CONSTRAINT fk_h5_agent_job_asset_version FOREIGN KEY (asset_version_id) REFERENCES h5_asset_versions(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_wallets (
  user_id CHAR(36) PRIMARY KEY,
  balance_cents BIGINT NOT NULL DEFAULT 0,
  tokens_used BIGINT NOT NULL DEFAULT 0,
  updated_at BIGINT NOT NULL,
  CONSTRAINT fk_h5_wallet_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_path_grants (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  path VARCHAR(512) NOT NULL,
  mode ENUM('read', 'readwrite') NOT NULL DEFAULT 'readwrite',
  UNIQUE KEY uq_h5_user_path (user_id, path),
  CONSTRAINT fk_h5_path_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_sessions (
  agent_session_id VARCHAR(128) NOT NULL PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  created_at BIGINT NOT NULL,
  KEY idx_h5_user_sessions_user (user_id),
  CONSTRAINT fk_h5_session_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_session_billing_state (
  agent_session_id VARCHAR(128) NOT NULL PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  last_accumulated_cost DOUBLE NULL,
  last_input_tokens BIGINT NOT NULL DEFAULT 0,
  last_output_tokens BIGINT NOT NULL DEFAULT 0,
  updated_at BIGINT NOT NULL,
  CONSTRAINT fk_h5_billing_state_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_usage_records (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  agent_session_id VARCHAR(128) NOT NULL,
  request_id VARCHAR(128) NULL,
  input_tokens INT NOT NULL DEFAULT 0,
  output_tokens INT NOT NULL DEFAULT 0,
  cost_cents BIGINT NOT NULL,
  balance_after_cents BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  KEY idx_h5_usage_user_time (user_id, created_at),
  CONSTRAINT fk_h5_usage_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_policies (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  subject_type ENUM('role', 'user') NOT NULL,
  subject_id VARCHAR(64) NOT NULL,
  policy_key VARCHAR(64) NOT NULL,
  policy_value VARCHAR(512) NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_user_policy (subject_type, subject_id, policy_key),
  KEY idx_h5_policy_subject (subject_type, subject_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_skill_grants (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  subject_type ENUM('role', 'user') NOT NULL,
  subject_id VARCHAR(64) NOT NULL,
  skill_name VARCHAR(64) NOT NULL,
  enabled TINYINT(1) NOT NULL DEFAULT 0,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_user_skill_grant (subject_type, subject_id, skill_name),
  KEY idx_h5_skill_subject (subject_type, subject_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_capability_grants (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  subject_type ENUM('role', 'user') NOT NULL,
  subject_id VARCHAR(64) NOT NULL,
  capability_key VARCHAR(64) NOT NULL,
  allowed TINYINT(1) NOT NULL DEFAULT 0,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_capability_grant (subject_type, subject_id, capability_key),
  KEY idx_h5_capability_subject (subject_type, subject_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_login_sessions (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  token_hash CHAR(64) NOT NULL,
  expires_at BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  revoked_at BIGINT NULL,
  UNIQUE KEY uq_h5_login_token_hash (token_hash),
  KEY idx_h5_login_session_user (user_id),
  KEY idx_h5_login_session_expires (expires_at),
  CONSTRAINT fk_h5_login_session_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_mindspace_audit_logs (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  action VARCHAR(64) NOT NULL,
  object_type VARCHAR(32) NOT NULL,
  object_id CHAR(36) NOT NULL,
  ip VARCHAR(64) NULL,
  result VARCHAR(32) NOT NULL,
  risk_level VARCHAR(16) NULL,
  created_at BIGINT NOT NULL,
  KEY idx_h5_mindspace_audit_user_time (user_id, created_at),
  CONSTRAINT fk_h5_mindspace_audit_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_billing_ledger (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  type ENUM('recharge', 'deduct', 'refund', 'adjust') NOT NULL,
  amount_cents BIGINT NOT NULL,
  tokens BIGINT NOT NULL DEFAULT 0,
  session_id VARCHAR(128) NULL,
  note VARCHAR(512) NULL,
  operator_id CHAR(36) NULL,
  created_at BIGINT NOT NULL,
  KEY idx_h5_ledger_user (user_id, created_at),
  CONSTRAINT fk_h5_ledger_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_llm_provider_keys (
  id CHAR(36) PRIMARY KEY,
  provider_id VARCHAR(64) NOT NULL,
  provider_kind ENUM('builtin', 'custom') NOT NULL DEFAULT 'builtin',
  api_url VARCHAR(512) NULL,
  base_path VARCHAR(256) NULL,
  models_json TEXT NULL,
  goosed_provider_id VARCHAR(64) NULL,
  engine VARCHAR(32) NOT NULL DEFAULT 'openai',
  relay_provider VARCHAR(64) NULL,
  name VARCHAR(128) NOT NULL,
  api_key_ciphertext TEXT NOT NULL,
  api_key_iv VARCHAR(24) NOT NULL,
  api_key_tag VARCHAR(24) NOT NULL,
  default_model VARCHAR(128) NOT NULL,
  status ENUM('active', 'disabled') NOT NULL DEFAULT 'active',
  is_selected TINYINT(1) NOT NULL DEFAULT 0,
  is_vision_selected TINYINT(1) NOT NULL DEFAULT 0,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_llm_key_name (name),
  KEY idx_h5_llm_key_provider (provider_id),
  KEY idx_h5_llm_key_selected (is_selected),
  KEY idx_h5_llm_key_vision (is_vision_selected)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_llm_executor_bindings (
  id CHAR(36) PRIMARY KEY,
  executor ENUM('goose', 'aider', 'openhands') NOT NULL,
  purpose VARCHAR(32) NOT NULL DEFAULT 'default',
  provider_key_id CHAR(36) NULL,
  model VARCHAR(128) NOT NULL,
  enabled TINYINT(1) NOT NULL DEFAULT 1,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_llm_executor_binding (executor, purpose),
  KEY idx_h5_llm_executor_provider (provider_key_id),
  CONSTRAINT fk_h5_llm_executor_provider FOREIGN KEY (provider_key_id) REFERENCES h5_llm_provider_keys(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_payment_orders (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  amount_cents BIGINT NOT NULL,
  channel ENUM('wechat') NOT NULL DEFAULT 'wechat',
  status ENUM('pending', 'paid', 'failed', 'expired', 'refunded') NOT NULL DEFAULT 'pending',
  pay_mode ENUM('native', 'h5', 'jsapi') NOT NULL DEFAULT 'native',
  out_trade_no VARCHAR(64) NOT NULL,
  provider_txn VARCHAR(128) NULL,
  code_url VARCHAR(512) NULL,
  h5_url VARCHAR(1024) NULL,
  client_ip VARCHAR(64) NULL,
  expire_at BIGINT NOT NULL,
  paid_at BIGINT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_h5_payment_out_trade_no (out_trade_no),
  KEY idx_h5_payment_user_time (user_id, created_at),
  KEY idx_h5_payment_status_expire (status, expire_at),
  CONSTRAINT fk_h5_payment_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_categories (
  id CHAR(36) PRIMARY KEY,
  name VARCHAR(50) NOT NULL,
  slug VARCHAR(50) NOT NULL,
  icon VARCHAR(10) NOT NULL DEFAULT '',
  description VARCHAR(200) NOT NULL DEFAULT '',
  sort_order INT NOT NULL DEFAULT 0,
  is_active TINYINT(1) NOT NULL DEFAULT 1,
  created_at BIGINT NOT NULL,
  UNIQUE KEY uq_plaza_category_slug (slug),
  KEY idx_plaza_category_sort (is_active, sort_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_posts (
  id CHAR(36) PRIMARY KEY,
  publication_id CHAR(36) NOT NULL,
  user_id CHAR(36) NOT NULL,
  title VARCHAR(200) NOT NULL,
  summary VARCHAR(500) NOT NULL DEFAULT '',
  cover_url VARCHAR(500) NOT NULL DEFAULT '',
  user_slug VARCHAR(100) NOT NULL,
  user_display_name VARCHAR(100) NOT NULL,
  user_avatar_url VARCHAR(500) NOT NULL DEFAULT '',
  category_id CHAR(36) NOT NULL,
  tags JSON NOT NULL,
  status ENUM('pending_review', 'published', 'hidden', 'rejected') NOT NULL DEFAULT 'pending_review',
  view_count INT UNSIGNED NOT NULL DEFAULT 0,
  like_count INT UNSIGNED NOT NULL DEFAULT 0,
  collect_count INT UNSIGNED NOT NULL DEFAULT 0,
  comment_count INT UNSIGNED NOT NULL DEFAULT 0,
  share_count INT UNSIGNED NOT NULL DEFAULT 0,
  hot_score DOUBLE NOT NULL DEFAULT 0,
  hot_updated_at BIGINT NULL,
  allow_comment TINYINT(1) NOT NULL DEFAULT 1,
  published_at BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_plaza_post_publication (publication_id),
  KEY idx_plaza_post_user_status (user_id, status),
  KEY idx_plaza_post_category_hot (category_id, status, hot_score),
  KEY idx_plaza_post_published_at (published_at),
  KEY idx_plaza_post_status_hot (status, hot_score),
  CONSTRAINT fk_plaza_post_publication FOREIGN KEY (publication_id) REFERENCES h5_publish_records(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_post_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_post_category FOREIGN KEY (category_id) REFERENCES plaza_categories(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_reactions (
  id CHAR(36) PRIMARY KEY,
  post_id CHAR(36) NOT NULL,
  user_id CHAR(36) NOT NULL,
  type ENUM('like', 'collect', 'share') NOT NULL,
  created_at BIGINT NOT NULL,
  UNIQUE KEY uq_plaza_reaction (post_id, user_id, type),
  KEY idx_plaza_reaction_post (post_id, type),
  KEY idx_plaza_reaction_user (user_id, type),
  CONSTRAINT fk_plaza_reaction_post FOREIGN KEY (post_id) REFERENCES plaza_posts(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_reaction_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_comments (
  id CHAR(36) PRIMARY KEY,
  post_id CHAR(36) NOT NULL,
  user_id CHAR(36) NOT NULL,
  parent_id CHAR(36) NULL,
  content VARCHAR(500) NOT NULL,
  status ENUM('visible', 'deleted', 'flagged', 'hidden') NOT NULL DEFAULT 'visible',
  like_count INT UNSIGNED NOT NULL DEFAULT 0,
  reply_count INT UNSIGNED NOT NULL DEFAULT 0,
  deleted_by ENUM('user', 'ops') NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  KEY idx_plaza_comment_post (post_id, status, created_at),
  KEY idx_plaza_comment_parent (parent_id),
  KEY idx_plaza_comment_user (user_id, created_at),
  CONSTRAINT fk_plaza_comment_post FOREIGN KEY (post_id) REFERENCES plaza_posts(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_comment_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_comment_reactions (
  id CHAR(36) PRIMARY KEY,
  comment_id CHAR(36) NOT NULL,
  user_id CHAR(36) NOT NULL,
  created_at BIGINT NOT NULL,
  UNIQUE KEY uq_plaza_comment_reaction (comment_id, user_id),
  KEY idx_plaza_comment_reaction_comment (comment_id),
  CONSTRAINT fk_plaza_comment_reaction_comment FOREIGN KEY (comment_id) REFERENCES plaza_comments(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_comment_reaction_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_follows (
  follower_id CHAR(36) NOT NULL,
  followee_id CHAR(36) NOT NULL,
  created_at BIGINT NOT NULL,
  PRIMARY KEY (follower_id, followee_id),
  KEY idx_plaza_follow_followee (followee_id, created_at),
  CONSTRAINT fk_plaza_follow_follower FOREIGN KEY (follower_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_follow_followee FOREIGN KEY (followee_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_algorithm_config (
  `key` VARCHAR(100) NOT NULL,
  value DOUBLE NOT NULL,
  description VARCHAR(200) NOT NULL DEFAULT '',
  updated_at BIGINT NOT NULL,
  PRIMARY KEY (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_attribution_events (
  id CHAR(36) PRIMARY KEY,
  event_type ENUM('landing', 'signup') NOT NULL,
  utm_source VARCHAR(100) NOT NULL,
  utm_medium VARCHAR(100) NOT NULL DEFAULT '',
  utm_campaign VARCHAR(100) NOT NULL DEFAULT '',
  ref_id VARCHAR(200) NOT NULL DEFAULT '',
  user_id CHAR(36) NULL,
  ip_hash VARCHAR(64) NOT NULL DEFAULT '',
  created_at BIGINT NOT NULL,
  KEY idx_plaza_attr_created (created_at),
  KEY idx_plaza_attr_campaign (utm_source, utm_campaign, created_at),
  CONSTRAINT fk_plaza_attr_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_reports (
  id CHAR(36) PRIMARY KEY,
  target_type ENUM('post', 'comment') NOT NULL,
  target_id CHAR(36) NOT NULL,
  reporter_id CHAR(36) NOT NULL,
  reason ENUM('spam', 'violence', 'porn', 'political', 'privacy', 'other') NOT NULL,
  detail VARCHAR(500) NOT NULL DEFAULT '',
  status ENUM('pending', 'processed', 'dismissed') NOT NULL DEFAULT 'pending',
  processed_by CHAR(36) NULL,
  processed_at BIGINT NULL,
  action_taken VARCHAR(200) NOT NULL DEFAULT '',
  created_at BIGINT NOT NULL,
  KEY idx_plaza_report_status (status, target_type, created_at),
  KEY idx_plaza_report_target (target_type, target_id),
  CONSTRAINT fk_plaza_report_reporter FOREIGN KEY (reporter_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_report_processor FOREIGN KEY (processed_by) REFERENCES h5_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Session snapshot cache: stores userVisible messages for fast history reload.
-- Rollback: DROP TABLE h5_session_snapshots (data-only, no dependants).
CREATE TABLE IF NOT EXISTS h5_session_snapshots (
  agent_session_id VARCHAR(128) NOT NULL PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  name VARCHAR(512) NOT NULL DEFAULT '',
  working_dir VARCHAR(1024) NOT NULL DEFAULT '',
  created_at_str VARCHAR(64) NOT NULL DEFAULT '',
  updated_at_str VARCHAR(64) NOT NULL DEFAULT '',
  user_set_name TINYINT(1) NOT NULL DEFAULT 0,
  recipe_json TEXT NULL,
  synced_msg_count INT NOT NULL DEFAULT 0,
  source_updated_at VARCHAR(64) NOT NULL DEFAULT '',
  messages_json LONGTEXT NOT NULL,
  synced_at BIGINT NOT NULL,
  KEY idx_h5_snapshots_user (user_id),
  KEY idx_h5_snapshots_synced (synced_at),
  CONSTRAINT fk_h5_snapshot_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_featured (
  id CHAR(36) PRIMARY KEY,
  post_id CHAR(36) NOT NULL,
  position VARCHAR(50) NOT NULL,
  sort_order INT NOT NULL DEFAULT 0,
  starts_at BIGINT NOT NULL,
  expires_at BIGINT NULL,
  created_by CHAR(36) NOT NULL,
  created_at BIGINT NOT NULL,
  KEY idx_plaza_featured_position (position, starts_at, expires_at),
  CONSTRAINT fk_plaza_featured_post FOREIGN KEY (post_id) REFERENCES plaza_posts(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_featured_creator FOREIGN KEY (created_by) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS plaza_user_events (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NULL,
  session_id VARCHAR(64) NOT NULL,
  post_id CHAR(36) NOT NULL,
  event_type ENUM(
    'impression', 'click', 'view', 'dwell',
    'like', 'collect', 'comment', 'share',
    'dislike', 'hide'
  ) NOT NULL,
  dwell_ms INT UNSIGNED NULL,
  feed_sort VARCHAR(32) NOT NULL DEFAULT '',
  feed_category VARCHAR(64) NOT NULL DEFAULT '',
  position INT UNSIGNED NULL,
  created_at BIGINT NOT NULL,
  KEY idx_plaza_event_user_time (user_id, created_at),
  KEY idx_plaza_event_session_time (session_id, created_at),
  KEY idx_plaza_event_post (post_id, event_type, created_at),
  CONSTRAINT fk_plaza_event_post FOREIGN KEY (post_id) REFERENCES plaza_posts(id) ON DELETE CASCADE,
  CONSTRAINT fk_plaza_event_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS ops_audit_log (
  id CHAR(36) PRIMARY KEY,
  operator_id CHAR(36) NOT NULL,
  action VARCHAR(100) NOT NULL,
  target_type VARCHAR(50) NOT NULL,
  target_id CHAR(36) NOT NULL,
  detail JSON NOT NULL,
  created_at BIGINT NOT NULL,
  KEY idx_ops_audit_operator (operator_id, created_at),
  KEY idx_ops_audit_target (target_type, target_id, created_at),
  CONSTRAINT fk_ops_audit_operator FOREIGN KEY (operator_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_wechat_identities (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  app_id VARCHAR(32) NOT NULL,
  openid VARCHAR(64) NOT NULL,
  unionid VARCHAR(64) NULL,
  nickname VARCHAR(128) NULL,
  avatar_url VARCHAR(512) NULL,
  last_login_at BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_wechat_openid (app_id, openid),
  UNIQUE KEY uq_wechat_user (user_id, app_id),
  KEY idx_wechat_unionid (unionid),
  CONSTRAINT fk_wechat_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_wechat_oauth_states (
  state VARCHAR(64) PRIMARY KEY,
  return_to VARCHAR(512) NULL,
  utm_source VARCHAR(64) NULL,
  utm_medium VARCHAR(64) NULL,
  utm_campaign VARCHAR(64) NULL,
  intent VARCHAR(16) NOT NULL DEFAULT 'login',
  bind_user_id CHAR(36) NULL,
  auth_mode VARCHAR(8) NOT NULL DEFAULT 'mp',
  status VARCHAR(16) NOT NULL DEFAULT 'pending',
  result_kind VARCHAR(32) NULL,
  result_token VARCHAR(512) NULL,
  result_message VARCHAR(255) NULL,
  expires_at BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  KEY idx_wechat_oauth_state_expires (expires_at),
  KEY idx_wechat_oauth_state_status (status, expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_wechat_pending_binds (
  token VARCHAR(64) PRIMARY KEY,
  app_id VARCHAR(32) NOT NULL,
  openid VARCHAR(64) NOT NULL,
  unionid VARCHAR(64) NULL,
  nickname VARCHAR(128) NULL,
  avatar_url VARCHAR(512) NULL,
  return_to VARCHAR(512) NULL,
  utm_source VARCHAR(64) NULL,
  utm_medium VARCHAR(64) NULL,
  utm_campaign VARCHAR(64) NULL,
  expires_at BIGINT NOT NULL,
  created_at BIGINT NOT NULL,
  KEY idx_wechat_pending_expires (expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_wechat_agent_routes (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  app_id VARCHAR(32) NOT NULL,
  openid VARCHAR(64) NOT NULL,
  agent_session_id VARCHAR(128) NOT NULL,
  status ENUM('active', 'disabled') NOT NULL DEFAULT 'active',
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_wechat_agent_route (app_id, openid),
  KEY idx_wechat_agent_route_user (user_id, updated_at),
  CONSTRAINT fk_wechat_agent_route_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_wechat_mp_messages (
  app_id VARCHAR(32) NOT NULL,
  openid VARCHAR(64) NOT NULL,
  msg_id VARCHAR(128) NOT NULL,
  status ENUM('processing', 'done', 'failed') NOT NULL DEFAULT 'processing',
  agent_session_id VARCHAR(128) NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  PRIMARY KEY (app_id, openid, msg_id),
  KEY idx_wechat_mp_messages_updated (updated_at),
  KEY idx_wechat_mp_messages_session (agent_session_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_wechat_mp_message_details (
  id CHAR(36) PRIMARY KEY,
  app_id VARCHAR(32) NOT NULL,
  openid VARCHAR(64) NOT NULL,
  user_id CHAR(36) NULL,
  msg_id VARCHAR(128) NULL,
  msg_type VARCHAR(32) NOT NULL,
  display_text TEXT NULL,
  agent_text TEXT NULL,
  media_id VARCHAR(256) NULL,
  media_url TEXT NULL,
  media_public_url TEXT NULL,
  media_format VARCHAR(64) NULL,
  location_lat DECIMAL(10,7) NULL,
  location_lng DECIMAL(10,7) NULL,
  location_label VARCHAR(255) NULL,
  link_url TEXT NULL,
  link_title VARCHAR(255) NULL,
  raw_xml_hash CHAR(40) NULL,
  raw_json JSON NULL,
  created_at BIGINT NOT NULL,
  KEY idx_wechat_mp_message_details_user (app_id, openid, created_at),
  KEY idx_wechat_mp_message_details_msg (msg_id),
  KEY idx_wechat_mp_message_details_type (msg_type, created_at),
  CONSTRAINT fk_wechat_mp_message_details_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_schedule_items (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  kind ENUM('task', 'event') NOT NULL,
  title VARCHAR(255) NOT NULL,
  description TEXT NULL,
  status ENUM('active', 'completed', 'cancelled', 'deleted') NOT NULL DEFAULT 'active',
  start_at BIGINT NULL,
  end_at BIGINT NULL,
  due_at BIGINT NULL,
  all_day TINYINT(1) NOT NULL DEFAULT 0,
  timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai',
  location VARCHAR(255) NULL,
  source_channel ENUM('h5', 'wechat', 'agent', 'api') NOT NULL DEFAULT 'agent',
  source_session_id VARCHAR(128) NULL,
  source_message_id VARCHAR(128) NULL,
  source_text TEXT NULL,
  metadata_json JSON NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  deleted_at BIGINT NULL,
  KEY idx_schedule_user_time (user_id, status, start_at, due_at),
  KEY idx_schedule_user_updated (user_id, updated_at),
  CONSTRAINT fk_schedule_item_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_schedule_reminders (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  item_id CHAR(36) NOT NULL,
  remind_at BIGINT NOT NULL,
  offset_minutes INT NULL,
  channel ENUM('wechat', 'in_app') NOT NULL DEFAULT 'wechat',
  status ENUM('pending', 'locked', 'sent', 'failed', 'cancelled') NOT NULL DEFAULT 'pending',
  attempts INT NOT NULL DEFAULT 0,
  last_error VARCHAR(500) NULL,
  locked_until BIGINT NULL,
  sent_at BIGINT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_schedule_item_remind_at (item_id, remind_at, channel),
  KEY idx_reminder_due (status, remind_at),
  KEY idx_reminder_user (user_id, status, remind_at),
  CONSTRAINT fk_schedule_reminder_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE,
  CONSTRAINT fk_schedule_reminder_item FOREIGN KEY (item_id) REFERENCES h5_schedule_items(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_schedule_digest_subscriptions (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  digest_type ENUM('todo_day') NOT NULL DEFAULT 'todo_day',
  hour TINYINT UNSIGNED NOT NULL,
  minute TINYINT UNSIGNED NOT NULL DEFAULT 0,
  timezone VARCHAR(64) NOT NULL DEFAULT 'Asia/Shanghai',
  channel ENUM('wechat', 'in_app') NOT NULL DEFAULT 'wechat',
  status ENUM('active', 'locked', 'failed', 'cancelled') NOT NULL DEFAULT 'active',
  next_run_at BIGINT NOT NULL,
  last_run_at BIGINT NULL,
  attempts INT NOT NULL DEFAULT 0,
  locked_until BIGINT NULL,
  last_error VARCHAR(500) NULL,
  source_channel ENUM('h5', 'wechat', 'agent', 'api') NOT NULL DEFAULT 'agent',
  source_session_id VARCHAR(128) NULL,
  source_message_id VARCHAR(128) NULL,
  source_text TEXT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_schedule_digest_user_type_channel (user_id, digest_type, channel),
  KEY idx_schedule_digest_due (status, next_run_at),
  CONSTRAINT fk_schedule_digest_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_balance_alert_subscriptions (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  threshold_cents BIGINT NOT NULL,
  channel ENUM('wechat', 'in_app') NOT NULL DEFAULT 'wechat',
  status ENUM('active', 'locked', 'failed', 'cancelled') NOT NULL DEFAULT 'active',
  next_run_at BIGINT NOT NULL,
  last_run_at BIGINT NULL,
  last_notified_balance_cents BIGINT NULL,
  attempts INT NOT NULL DEFAULT 0,
  locked_until BIGINT NULL,
  last_error VARCHAR(500) NULL,
  source_channel ENUM('h5', 'wechat', 'agent', 'api') NOT NULL DEFAULT 'agent',
  source_session_id VARCHAR(128) NULL,
  source_message_id VARCHAR(128) NULL,
  source_text TEXT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uq_balance_alert_user_channel (user_id, channel),
  KEY idx_balance_alert_due (status, next_run_at),
  KEY idx_balance_alert_user (user_id, status, next_run_at),
  CONSTRAINT fk_balance_alert_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_schedule_delivery_logs (
  id CHAR(36) PRIMARY KEY,
  reminder_id CHAR(36) NULL,
  subscription_id CHAR(36) NULL,
  user_id CHAR(36) NOT NULL,
  channel ENUM('wechat', 'in_app') NOT NULL,
  status ENUM('success', 'failed') NOT NULL,
  provider_message_id VARCHAR(128) NULL,
  error_code VARCHAR(64) NULL,
  error_message VARCHAR(500) NULL,
  created_at BIGINT NOT NULL,
  KEY idx_delivery_reminder (reminder_id, created_at),
  KEY idx_delivery_subscription (subscription_id, created_at),
  KEY idx_delivery_user (user_id, created_at),
  CONSTRAINT fk_schedule_delivery_reminder FOREIGN KEY (reminder_id) REFERENCES h5_schedule_reminders(id) ON DELETE CASCADE,
  CONSTRAINT fk_schedule_delivery_subscription FOREIGN KEY (subscription_id) REFERENCES h5_schedule_digest_subscriptions(id) ON DELETE CASCADE,
  CONSTRAINT fk_schedule_delivery_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_user_notifications (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  channel ENUM('web', 'wechat') NOT NULL DEFAULT 'web',
  notification_type VARCHAR(64) NOT NULL,
  title VARCHAR(255) NOT NULL,
  body TEXT NOT NULL,
  data_json JSON NULL,
  status ENUM('unread', 'read') NOT NULL DEFAULT 'unread',
  read_at BIGINT NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  KEY idx_user_notifications_user_status_created (user_id, status, created_at),
  KEY idx_user_notifications_user_created (user_id, created_at),
  CONSTRAINT fk_user_notifications_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_plan_catalog (
  plan_type    VARCHAR(32) PRIMARY KEY,
  name         VARCHAR(64) NOT NULL,
  price_cents  INT NOT NULL DEFAULT 0,
  period_days  INT NOT NULL DEFAULT 30,
  period_tokens BIGINT NOT NULL DEFAULT 0,
  period_images INT NOT NULL DEFAULT 0,
  model_tier   VARCHAR(32) NOT NULL DEFAULT 'basic',
  overage_rate DECIMAL(4,2) NOT NULL DEFAULT 1.00,
  sort_order   INT NOT NULL DEFAULT 0,
  is_active    TINYINT(1) NOT NULL DEFAULT 1,
  description  VARCHAR(512) NULL,
  created_at   BIGINT NOT NULL,
  updated_at   BIGINT NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_subscriptions (
  id CHAR(36) PRIMARY KEY,
  user_id CHAR(36) NOT NULL,
  plan_type VARCHAR(32) NOT NULL DEFAULT 'free',
  status ENUM('active', 'expired', 'cancelled') NOT NULL DEFAULT 'active',
  period_tokens_limit BIGINT NOT NULL DEFAULT 0,
  period_tokens_used BIGINT NOT NULL DEFAULT 0,
  period_images_limit INT NOT NULL DEFAULT 0,
  period_images_used INT NOT NULL DEFAULT 0,
  period_start BIGINT NOT NULL,
  period_end BIGINT NOT NULL,
  expires_at BIGINT NOT NULL,
  overage_rate DECIMAL(4,2) NOT NULL DEFAULT 1.00,
  auto_renew TINYINT(1) NOT NULL DEFAULT 0,
  operator_id CHAR(36) NULL,
  note VARCHAR(512) NULL,
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  KEY idx_h5_sub_user_status (user_id, status),
  KEY idx_h5_sub_expires (expires_at, status),
  CONSTRAINT fk_h5_sub_user FOREIGN KEY (user_id) REFERENCES h5_users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE IF NOT EXISTS h5_blocked_words (
  id CHAR(36) PRIMARY KEY,
  word VARCHAR(200) NOT NULL,
  replacement VARCHAR(200) NOT NULL DEFAULT '***',
  note VARCHAR(500) NULL,
  status ENUM('active', 'disabled') NOT NULL DEFAULT 'active',
  created_at BIGINT NOT NULL,
  updated_at BIGINT NOT NULL,
  UNIQUE KEY uk_blocked_word (word)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
